Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/IPython/core/display.py: 28%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1"""Top-level display functions for displaying object in different formats."""
3# Copyright (c) IPython Development Team.
4# Distributed under the terms of the Modified BSD License.
6from __future__ import annotations
8from enum import Enum
9from dataclasses import dataclass, KW_ONLY
10from binascii import b2a_base64, hexlify
11import html
12import json
13import mimetypes
14import os
15import struct
16import warnings
17from copy import deepcopy
18from os.path import splitext
19from pathlib import Path, PurePath
21from typing import TYPE_CHECKING, Self
23from IPython.testing.skipdoctest import skip_doctest
24from . import display_functions
26if TYPE_CHECKING:
27 from collections.abc import Callable
30__all__ = [
31 "display_pretty",
32 "display_html",
33 "display_markdown",
34 "display_svg",
35 "display_png",
36 "display_jpeg",
37 "display_webp",
38 "display_latex",
39 "display_json",
40 "display_javascript",
41 "display_pdf",
42 "DisplayObject",
43 "TextDisplayObject",
44 "Pretty",
45 "HTML",
46 "Markdown",
47 "Math",
48 "Latex",
49 "SVG",
50 "ProgressBar",
51 "JSON",
52 "GeoJSON",
53 "Javascript",
54 "Image",
55 "Video",
56]
58#-----------------------------------------------------------------------------
59# utility functions
60#-----------------------------------------------------------------------------
62def _safe_exists(path):
63 """Check path, but don't let exceptions raise"""
64 try:
65 return os.path.exists(path)
66 except Exception:
67 return False
70def _display_mimetype(mimetype, objs, raw=False, metadata=None):
71 """internal implementation of all display_foo methods
73 Parameters
74 ----------
75 mimetype : str
76 The mimetype to be published (e.g. 'image/png')
77 *objs : object
78 The Python objects to display, or if raw=True raw text data to
79 display.
80 raw : bool
81 Are the data objects raw data or Python objects that need to be
82 formatted before display? [default: False]
83 metadata : dict (optional)
84 Metadata to be associated with the specific mimetype output.
85 """
86 if metadata:
87 metadata = {mimetype: metadata}
88 if raw:
89 # turn list of pngdata into list of { 'image/png': pngdata }
90 objs = [ {mimetype: obj} for obj in objs ]
91 display_functions.display(*objs, raw=raw, metadata=metadata, include=[mimetype])
93#-----------------------------------------------------------------------------
94# Main functions
95#-----------------------------------------------------------------------------
98def display_pretty(*objs, **kwargs):
99 """Display the pretty (default) representation of an object.
101 Parameters
102 ----------
103 *objs : object
104 The Python objects to display, or if raw=True raw text data to
105 display.
106 raw : bool
107 Are the data objects raw data or Python objects that need to be
108 formatted before display? [default: False]
109 metadata : dict (optional)
110 Metadata to be associated with the specific mimetype output.
111 """
112 _display_mimetype('text/plain', objs, **kwargs)
115def display_html(*objs, **kwargs):
116 """Display the HTML representation of an object.
118 Note: If raw=False and the object does not have a HTML
119 representation, no HTML will be shown.
121 Parameters
122 ----------
123 *objs : object
124 The Python objects to display, or if raw=True raw HTML data to
125 display.
126 raw : bool
127 Are the data objects raw data or Python objects that need to be
128 formatted before display? [default: False]
129 metadata : dict (optional)
130 Metadata to be associated with the specific mimetype output.
131 """
132 _display_mimetype('text/html', objs, **kwargs)
135def display_markdown(*objs, **kwargs):
136 """Displays the Markdown representation of an object.
138 Parameters
139 ----------
140 *objs : object
141 The Python objects to display, or if raw=True raw markdown data to
142 display.
143 raw : bool
144 Are the data objects raw data or Python objects that need to be
145 formatted before display? [default: False]
146 metadata : dict (optional)
147 Metadata to be associated with the specific mimetype output.
148 """
150 _display_mimetype('text/markdown', objs, **kwargs)
153def display_svg(*objs, **kwargs):
154 """Display the SVG representation of an object.
156 Parameters
157 ----------
158 *objs : object
159 The Python objects to display, or if raw=True raw svg data to
160 display.
161 raw : bool
162 Are the data objects raw data or Python objects that need to be
163 formatted before display? [default: False]
164 metadata : dict (optional)
165 Metadata to be associated with the specific mimetype output.
166 """
167 _display_mimetype('image/svg+xml', objs, **kwargs)
170def display_png(*objs, **kwargs):
171 """Display the PNG representation of an object.
173 Parameters
174 ----------
175 *objs : object
176 The Python objects to display, or if raw=True raw png data to
177 display.
178 raw : bool
179 Are the data objects raw data or Python objects that need to be
180 formatted before display? [default: False]
181 metadata : dict (optional)
182 Metadata to be associated with the specific mimetype output.
183 """
184 _display_mimetype('image/png', objs, **kwargs)
187def display_jpeg(*objs, **kwargs):
188 """Display the JPEG representation of an object.
190 Parameters
191 ----------
192 *objs : object
193 The Python objects to display, or if raw=True raw JPEG data to
194 display.
195 raw : bool
196 Are the data objects raw data or Python objects that need to be
197 formatted before display? [default: False]
198 metadata : dict (optional)
199 Metadata to be associated with the specific mimetype output.
200 """
201 _display_mimetype('image/jpeg', objs, **kwargs)
204def display_webp(*objs, **kwargs):
205 """Display the WEBP representation of an object.
207 Parameters
208 ----------
209 *objs : object
210 The Python objects to display, or if raw=True raw JPEG data to
211 display.
212 raw : bool
213 Are the data objects raw data or Python objects that need to be
214 formatted before display? [default: False]
215 metadata : dict (optional)
216 Metadata to be associated with the specific mimetype output.
217 """
218 _display_mimetype("image/webp", objs, **kwargs)
221def display_latex(*objs, **kwargs):
222 """Display the LaTeX representation of an object.
224 Parameters
225 ----------
226 *objs : object
227 The Python objects to display, or if raw=True raw latex data to
228 display.
229 raw : bool
230 Are the data objects raw data or Python objects that need to be
231 formatted before display? [default: False]
232 metadata : dict (optional)
233 Metadata to be associated with the specific mimetype output.
234 """
235 _display_mimetype('text/latex', objs, **kwargs)
238def display_json(*objs, **kwargs):
239 """Display the JSON representation of an object.
241 Note that not many frontends support displaying JSON.
243 Parameters
244 ----------
245 *objs : object
246 The Python objects to display, or if raw=True raw json data to
247 display.
248 raw : bool
249 Are the data objects raw data or Python objects that need to be
250 formatted before display? [default: False]
251 metadata : dict (optional)
252 Metadata to be associated with the specific mimetype output.
253 """
254 _display_mimetype('application/json', objs, **kwargs)
257def display_javascript(*objs, **kwargs):
258 """Display the Javascript representation of an object.
260 Parameters
261 ----------
262 *objs : object
263 The Python objects to display, or if raw=True raw javascript data to
264 display.
265 raw : bool
266 Are the data objects raw data or Python objects that need to be
267 formatted before display? [default: False]
268 metadata : dict (optional)
269 Metadata to be associated with the specific mimetype output.
270 """
271 _display_mimetype('application/javascript', objs, **kwargs)
274def display_pdf(*objs, **kwargs):
275 """Display the PDF representation of an object.
277 Parameters
278 ----------
279 *objs : object
280 The Python objects to display, or if raw=True raw javascript data to
281 display.
282 raw : bool
283 Are the data objects raw data or Python objects that need to be
284 formatted before display? [default: False]
285 metadata : dict (optional)
286 Metadata to be associated with the specific mimetype output.
287 """
288 _display_mimetype('application/pdf', objs, **kwargs)
291#-----------------------------------------------------------------------------
292# Smart classes
293#-----------------------------------------------------------------------------
296class DisplayObject:
297 """An object that wraps data to be displayed."""
299 _read_flags = 'r'
300 _show_mem_addr = False
301 metadata = None
303 def __init__(self, data=None, url=None, filename=None, metadata=None):
304 """Create a display object given raw data.
306 When this object is returned by an expression or passed to the
307 display function, it will result in the data being displayed
308 in the frontend. The MIME type of the data should match the
309 subclasses used, so the Png subclass should be used for 'image/png'
310 data. If the data is a URL, the data will first be downloaded
311 and then displayed.
313 Parameters
314 ----------
315 data : unicode, str or bytes
316 The raw data or a URL or file to load the data from
317 url : unicode
318 A URL to download the data from.
319 filename : unicode
320 Path to a local file to load the data from.
321 metadata : dict
322 Dict of metadata associated to be the object when displayed
323 """
324 if isinstance(data, (Path, PurePath)):
325 data = str(data)
327 if data is not None and isinstance(data, str):
328 if data.startswith('http') and url is None:
329 url = data
330 filename = None
331 data = None
332 elif _safe_exists(data) and filename is None:
333 url = None
334 filename = data
335 data = None
337 self.url = url
338 self.filename = filename
339 # because of @data.setter methods in
340 # subclasses ensure url and filename are set
341 # before assigning to self.data
342 self.data = data
344 if metadata is not None:
345 self.metadata = metadata
346 elif self.metadata is None:
347 self.metadata = {}
349 self.reload()
350 self._check_data()
352 def __repr__(self):
353 if not self._show_mem_addr:
354 cls = self.__class__
355 r = "<{}.{} object>".format(cls.__module__, cls.__name__)
356 else:
357 r = super().__repr__()
358 return r
360 def _check_data(self):
361 """Override in subclasses if there's something to check."""
362 pass
364 def _data_and_metadata(self):
365 """shortcut for returning metadata with shape information, if defined"""
366 if self.metadata:
367 return self.data, deepcopy(self.metadata)
368 else:
369 return self.data
371 def reload(self):
372 """Reload the raw data from file or URL."""
373 if self.filename is not None:
374 encoding = None if "b" in self._read_flags else "utf-8"
375 with open(self.filename, self._read_flags, encoding=encoding) as f:
376 self.data = f.read()
377 elif self.url is not None:
378 # Deferred import
379 from urllib.request import urlopen
380 response = urlopen(self.url)
381 data = response.read()
382 # extract encoding from header, if there is one:
383 encoding = None
384 if 'content-type' in response.headers:
385 for sub in response.headers['content-type'].split(';'):
386 sub = sub.strip()
387 if sub.startswith('charset'):
388 encoding = sub.split('=')[-1].strip()
389 break
390 if 'content-encoding' in response.headers:
391 if 'gzip' in response.headers['content-encoding']:
392 import gzip
393 from io import BytesIO
395 # assume utf-8 if encoding is not specified
396 with gzip.open(
397 BytesIO(data), "rt", encoding=encoding or "utf-8"
398 ) as fp:
399 encoding = None
400 data = fp.read()
402 # decode data, if an encoding was specified
403 # We only touch self.data once since
404 # subclasses such as SVG have @data.setter methods
405 # that transform self.data into ... well svg.
406 if encoding:
407 self.data = data.decode(encoding, 'replace')
408 else:
409 self.data = data
412class TextDisplayObject(DisplayObject):
413 """Create a text display object given raw data.
415 Parameters
416 ----------
417 data : str or unicode
418 The raw data or a URL or file to load the data from.
419 url : unicode
420 A URL to download the data from.
421 filename : unicode
422 Path to a local file to load the data from.
423 metadata : dict
424 Dict of metadata associated to be the object when displayed
425 """
426 def _check_data(self):
427 if self.data is not None and not isinstance(self.data, str):
428 raise TypeError("{} expects text, not {!r}".format(self.__class__.__name__, self.data))
430class Pretty(TextDisplayObject):
432 def _repr_pretty_(self, pp, cycle):
433 return pp.text(self.data)
436class HTML(TextDisplayObject):
438 def __init__(self, data=None, url=None, filename=None, metadata=None):
439 def warn():
440 if not data:
441 return False
443 #
444 # Avoid calling lower() on the entire data, because it could be a
445 # long string and we're only interested in its beginning and end.
446 #
447 prefix = data[:10].lower()
448 suffix = data[-10:].lower()
449 return prefix.startswith("<iframe ") and suffix.endswith("</iframe>")
451 if warn():
452 warnings.warn("Consider using IPython.display.IFrame instead")
453 super().__init__(data=data, url=url, filename=filename, metadata=metadata)
455 def _repr_html_(self):
456 return self._data_and_metadata()
458 def __html__(self):
459 """
460 This method exists to inform other HTML-using modules (e.g. Markupsafe,
461 htmltag, etc) that this object is HTML and does not need things like
462 special characters (<>&) escaped.
463 """
464 return self._repr_html_()
467class Markdown(TextDisplayObject):
469 def _repr_markdown_(self):
470 return self._data_and_metadata()
473class Math(TextDisplayObject):
475 def _repr_latex_(self):
476 s = r"$\displaystyle %s$" % self.data.strip('$')
477 if self.metadata:
478 return s, deepcopy(self.metadata)
479 else:
480 return s
483class Latex(TextDisplayObject):
485 def _repr_latex_(self):
486 return self._data_and_metadata()
489class SVG(DisplayObject):
490 """Embed an SVG into the display.
492 Note if you just want to view a svg image via a URL use `:class:Image` with
493 a url=URL keyword argument.
494 """
496 _read_flags = 'rb'
497 # wrap data in a property, which extracts the <svg> tag, discarding
498 # document headers
499 _data: str | None = None
501 @property
502 def data(self):
503 return self._data
505 @data.setter
506 def data(self, svg):
507 if svg is None:
508 self._data = None
509 return
510 # parse into dom object
511 from xml.dom import minidom
512 x = minidom.parseString(svg)
513 # get svg tag (should be 1)
514 found_svg = x.getElementsByTagName('svg')
515 if found_svg:
516 svg = found_svg[0].toxml()
517 else:
518 # fallback on the input, trust the user
519 # but this is probably an error.
520 pass
521 if isinstance(svg, bytes):
522 self._data = svg.decode(errors="replace")
523 else:
524 self._data = svg
526 def _repr_svg_(self):
527 return self._data_and_metadata()
529class ProgressBar(DisplayObject):
530 """Progressbar supports displaying a progressbar like element
531 """
532 def __init__(self, total):
533 """Creates a new progressbar
535 Parameters
536 ----------
537 total : int
538 maximum size of the progressbar
539 """
540 self.total = total
541 self._progress = 0
542 self.html_width = '60ex'
543 self.text_width = 60
544 self._display_id = hexlify(os.urandom(8)).decode('ascii')
546 def __repr__(self):
547 fraction = self.progress / self.total
548 filled = '=' * int(fraction * self.text_width)
549 rest = ' ' * (self.text_width - len(filled))
550 return '[{}{}] {}/{}'.format(
551 filled, rest,
552 self.progress, self.total,
553 )
555 def _repr_html_(self):
556 return "<progress style='width:{}' max='{}' value='{}'></progress>".format(
557 self.html_width, self.total, self.progress)
559 def display(self):
560 display_functions.display(self, display_id=self._display_id)
562 def update(self):
563 display_functions.display(self, display_id=self._display_id, update=True)
565 @property
566 def progress(self):
567 return self._progress
569 @progress.setter
570 def progress(self, value):
571 self._progress = value
572 self.update()
574 def __iter__(self):
575 self.display()
576 self._progress = -1 # First iteration is 0
577 return self
579 def __next__(self):
580 """Returns current value and increments display by one."""
581 self.progress += 1
582 if self.progress < self.total:
583 return self.progress
584 else:
585 raise StopIteration()
587class JSON(DisplayObject):
588 """JSON expects a JSON-able dict or list
590 not an already-serialized JSON string.
592 Scalar types (None, number, string) are not allowed, only dict or list containers.
593 """
594 # wrap data in a property, which warns about passing already-serialized JSON
595 _data = None
596 def __init__(self, data=None, url=None, filename=None, expanded=False, metadata=None, root='root', **kwargs):
597 """Create a JSON display object given raw data.
599 Parameters
600 ----------
601 data : dict or list
602 JSON data to display. Not an already-serialized JSON string.
603 Scalar types (None, number, string) are not allowed, only dict
604 or list containers.
605 url : unicode
606 A URL to download the data from.
607 filename : unicode
608 Path to a local file to load the data from.
609 expanded : boolean
610 Metadata to control whether a JSON display component is expanded.
611 metadata : dict
612 Specify extra metadata to attach to the json display object.
613 root : str
614 The name of the root element of the JSON tree
615 """
616 self.metadata = {
617 'expanded': expanded,
618 'root': root,
619 }
620 if metadata:
621 self.metadata.update(metadata)
622 if kwargs:
623 self.metadata.update(kwargs)
624 super().__init__(data=data, url=url, filename=filename)
626 def _check_data(self):
627 if self.data is not None and not isinstance(self.data, (dict, list)):
628 raise TypeError("{} expects JSONable dict or list, not {!r}".format(self.__class__.__name__, self.data))
630 @property
631 def data(self):
632 return self._data
634 @data.setter
635 def data(self, data):
636 if isinstance(data, (Path, PurePath)):
637 data = str(data)
639 if isinstance(data, str):
640 if self.filename is None and self.url is None:
641 warnings.warn("JSON expects JSONable dict or list, not JSON strings")
642 data = json.loads(data)
643 self._data = data
645 def _data_and_metadata(self):
646 return self.data, self.metadata
648 def _repr_json_(self):
649 return self._data_and_metadata()
652_css_t = """var link = document.createElement("link");
653 link.rel = "stylesheet";
654 link.type = "text/css";
655 link.href = "%s";
656 document.head.appendChild(link);
657"""
659_lib_t1 = """new Promise(function(resolve, reject) {
660 var script = document.createElement("script");
661 script.onload = resolve;
662 script.onerror = reject;
663 script.src = "%s";
664 document.head.appendChild(script);
665}).then(() => {
666"""
668_lib_t2 = """
669});"""
671class GeoJSON(JSON):
672 """GeoJSON expects JSON-able dict
674 not an already-serialized JSON string.
676 Scalar types (None, number, string) are not allowed, only dict containers.
677 """
679 def __init__(self, *args, **kwargs):
680 """Create a GeoJSON display object given raw data.
682 Parameters
683 ----------
684 data : dict or list
685 VegaLite data. Not an already-serialized JSON string.
686 Scalar types (None, number, string) are not allowed, only dict
687 or list containers.
688 url_template : string
689 Leaflet TileLayer URL template: http://leafletjs.com/reference.html#url-template
690 layer_options : dict
691 Leaflet TileLayer options: http://leafletjs.com/reference.html#tilelayer-options
692 url : unicode
693 A URL to download the data from.
694 filename : unicode
695 Path to a local file to load the data from.
696 metadata : dict
697 Specify extra metadata to attach to the json display object.
699 Examples
700 --------
701 The following will display an interactive map of Mars with a point of
702 interest on frontend that do support GeoJSON display.
704 >>> from IPython.display import GeoJSON
706 >>> GeoJSON(data={
707 ... "type": "Feature",
708 ... "geometry": {
709 ... "type": "Point",
710 ... "coordinates": [-81.327, 296.038]
711 ... }
712 ... },
713 ... url_template="http://s3-eu-west-1.amazonaws.com/whereonmars.cartodb.net/{basemap_id}/{z}/{x}/{y}.png",
714 ... layer_options={
715 ... "basemap_id": "celestia_mars-shaded-16k_global",
716 ... "attribution" : "Celestia/praesepe",
717 ... "minZoom" : 0,
718 ... "maxZoom" : 18,
719 ... })
720 <IPython.core.display.GeoJSON object>
722 In the terminal IPython, you will only see the text representation of
723 the GeoJSON object.
725 """
727 super().__init__(*args, **kwargs)
730 def _ipython_display_(self):
731 bundle = {
732 'application/geo+json': self.data,
733 'text/plain': '<IPython.display.GeoJSON object>'
734 }
735 metadata = {
736 'application/geo+json': self.metadata
737 }
738 display_functions.display(bundle, metadata=metadata, raw=True)
740class Javascript(TextDisplayObject):
742 def __init__(self, data=None, url=None, filename=None, lib=None, css=None):
743 """Create a Javascript display object given raw data.
745 When this object is returned by an expression or passed to the
746 display function, it will result in the data being displayed
747 in the frontend. If the data is a URL, the data will first be
748 downloaded and then displayed.
750 In the Notebook, the containing element will be available as `element`,
751 and jQuery will be available. Content appended to `element` will be
752 visible in the output area.
754 Parameters
755 ----------
756 data : unicode, str or bytes
757 The Javascript source code or a URL to download it from.
758 url : unicode
759 A URL to download the data from.
760 filename : unicode
761 Path to a local file to load the data from.
762 lib : list or str
763 A sequence of Javascript library URLs to load asynchronously before
764 running the source code. The full URLs of the libraries should
765 be given. A single Javascript library URL can also be given as a
766 string.
767 css : list or str
768 A sequence of css files to load before running the source code.
769 The full URLs of the css files should be given. A single css URL
770 can also be given as a string.
771 """
772 if isinstance(lib, str):
773 lib = [lib]
774 elif lib is None:
775 lib = []
776 if isinstance(css, str):
777 css = [css]
778 elif css is None:
779 css = []
780 if not isinstance(lib, (list,tuple)):
781 raise TypeError('expected sequence, got: %r' % lib)
782 if not isinstance(css, (list,tuple)):
783 raise TypeError('expected sequence, got: %r' % css)
784 self.lib = lib
785 self.css = css
786 super().__init__(data=data, url=url, filename=filename)
788 def _repr_javascript_(self):
789 r = ''
790 for c in self.css:
791 r += _css_t % c
792 for l in self.lib:
793 r += _lib_t1 % l
794 r += self.data
795 r += _lib_t2*len(self.lib)
796 return r
799def _pngxy(data):
800 """read the (width, height) from a PNG header"""
801 ihdr = data.index(b'IHDR')
802 # next 8 bytes are width/height
803 return struct.unpack('>ii', data[ihdr+4:ihdr+12])
806def _jpegxy(data):
807 """read the (width, height) from a JPEG header"""
808 # adapted from http://www.64lines.com/jpeg-width-height
810 idx = 4
811 while True:
812 block_size = struct.unpack('>H', data[idx:idx+2])[0]
813 idx = idx + block_size
814 if data[idx:idx+2] == b'\xFF\xC0':
815 # found Start of Frame
816 iSOF = idx
817 break
818 else:
819 # read another block
820 idx += 2
822 h, w = struct.unpack('>HH', data[iSOF+5:iSOF+9])
823 return w, h
826def _gifxy(data):
827 """read the (width, height) from a GIF header"""
828 return struct.unpack('<HH', data[6:10])
831def _webpxy(data):
832 """read the (width, height) from a WEBP header"""
833 if data[12:16] == b"VP8 ":
834 width, height = struct.unpack("<HH", data[24:30])
835 width = width & 0x3FFF
836 height = height & 0x3FFF
837 return (width, height)
838 elif data[12:16] == b"VP8L":
839 size_info = struct.unpack("<I", data[21:25])[0]
840 width = 1 + ((size_info & 0x3F) << 8) | (size_info >> 24)
841 height = 1 + (
842 (((size_info >> 8) & 0xF) << 10)
843 | (((size_info >> 14) & 0x3FC) << 2)
844 | ((size_info >> 22) & 0x3)
845 )
846 return (width, height)
847 else:
848 raise ValueError("Not a valid WEBP header")
851@dataclass
852class _ImageFormat:
853 magics: tuple[bytes, ...]
854 """Constants for identifying image data."""
856 shape: Callable[[bytes], tuple[int, int]]
857 """Reads (width, height) from image data."""
860class ImageFormat(_ImageFormat, Enum):
861 png = (b"\x89PNG\r\n\x1a\n",), _pngxy
862 jpeg = (b"\xff\xd8",), _jpegxy
863 jpg = jpeg # alias, has `.name == "jpeg"`
864 gif = (b"GIF87a", b"GIF89a"), _gifxy
865 webp = (b"WEBP",), _webpxy
867 @property
868 def mime_type(self):
869 return f"image/{self.name}"
871 @classmethod
872 def from_data(cls, data: bytes) -> Self | None:
873 for fmt in cls:
874 for magic in fmt.magics:
875 if data.startswith(magic):
876 return fmt
877 return None
880class Image(DisplayObject):
882 _read_flags = "rb"
884 def __init__(
885 self,
886 data=None,
887 url=None,
888 filename=None,
889 format=None,
890 embed=None,
891 width=None,
892 height=None,
893 retina=False,
894 unconfined=False,
895 metadata=None,
896 alt=None,
897 ):
898 """Create a PNG/JPEG/GIF/WEBP image object given raw data.
900 When this object is returned by an input cell or passed to the
901 display function, it will result in the image being displayed
902 in the frontend.
904 Parameters
905 ----------
906 data : unicode, str or bytes
907 The raw image data or a URL or filename to load the data from.
908 This always results in embedded image data.
910 url : unicode
911 A URL to download the data from. If you specify `url=`,
912 the image data will not be embedded unless you also specify `embed=True`.
914 filename : unicode
915 Path to a local file to load the data from.
916 Images from a file are always embedded.
918 format : unicode
919 The format of the image data (png/jpeg/jpg/gif/webp). If a filename or URL is given
920 for format will be inferred from the filename extension.
922 embed : bool
923 Should the image data be embedded using a data URI (True) or be
924 loaded using an <img> tag. Set this to True if you want the image
925 to be viewable later with no internet connection in the notebook.
927 Default is `True`, unless the keyword argument `url` is set, then
928 default value is `False`.
930 Note that QtConsole is not able to display images if `embed` is set to `False`
932 width : int
933 Width in pixels to which to constrain the image in html
935 height : int
936 Height in pixels to which to constrain the image in html
938 retina : bool
939 Automatically set the width and height to half of the measured
940 width and height.
941 This only works for embedded images because it reads the width/height
942 from image data.
943 For non-embedded images, you can just set the desired display width
944 and height directly.
946 unconfined : bool
947 Set unconfined=True to disable max-width confinement of the image.
949 metadata : dict
950 Specify extra metadata to attach to the image.
952 alt : unicode
953 Alternative text for the image, for use by screen readers.
955 Examples
956 --------
957 embedded image data, works in qtconsole and notebook
958 when passed positionally, the first arg can be any of raw image data,
959 a URL, or a filename from which to load image data.
960 The result is always embedding image data for inline images.
962 >>> Image('https://www.google.fr/images/srpr/logo3w.png') # doctest: +SKIP
963 <IPython.core.display.Image object>
965 >>> Image('/path/to/image.jpg')
966 <IPython.core.display.Image object>
968 >>> Image(b'RAW_PNG_DATA...')
969 <IPython.core.display.Image object>
971 Specifying Image(url=...) does not embed the image data,
972 it only generates ``<img>`` tag with a link to the source.
973 This will not work in the qtconsole or offline.
975 >>> Image(url='https://www.google.fr/images/srpr/logo3w.png')
976 <IPython.core.display.Image object>
978 """
979 if isinstance(data, (Path, PurePath)):
980 data = str(data)
982 if filename is not None:
983 ext = self._find_ext(filename)
984 elif url is not None:
985 ext = self._find_ext(url)
986 elif data is None:
987 raise ValueError("No image data found. Expecting filename, url, or data.")
988 elif isinstance(data, str) and (
989 data.startswith('http') or _safe_exists(data)
990 ):
991 ext = self._find_ext(data)
992 else:
993 ext = None
995 if format is None:
996 if ext is not None:
997 format = ext.lower()
998 elif isinstance(data, bytes) and (
999 image_format := ImageFormat.from_data(data)
1000 ):
1001 format = image_format.name
1002 else: # failed to detect format, default png
1003 format = ImageFormat.png.name
1004 else:
1005 format = format.lower()
1006 # normalize e.g. `jpg` -> `jpeg`, `UNKNOWN` → `unknown`
1007 self.format = (
1008 ImageFormat[format].name if format in ImageFormat.__members__ else format
1009 )
1011 self.embed = embed if embed is not None else (url is None)
1012 if self.embed:
1013 if self.format not in ImageFormat.__members__:
1014 raise ValueError("Cannot embed the '%s' image format" % (self.format))
1015 self._mimetype = ImageFormat[self.format].mime_type
1017 self.width = width
1018 self.height = height
1019 self.retina = retina
1020 self.unconfined = unconfined
1021 self.alt = alt
1022 super().__init__(data=data, url=url, filename=filename,
1023 metadata=metadata)
1025 if self.width is None and self.metadata.get('width', {}):
1026 self.width = metadata['width']
1028 if self.height is None and self.metadata.get('height', {}):
1029 self.height = metadata['height']
1031 if self.alt is None and self.metadata.get("alt", {}):
1032 self.alt = metadata["alt"]
1034 if retina:
1035 self._retina_shape()
1038 def _retina_shape(self):
1039 """load pixel-doubled width and height from image data"""
1040 if not self.embed:
1041 return
1042 if self.format in ImageFormat.__members__:
1043 w, h = ImageFormat[self.format].shape(self.data)
1044 else:
1045 return
1046 self.width = w // 2
1047 self.height = h // 2
1049 def reload(self):
1050 """Reload the raw data from file or URL."""
1051 if self.embed:
1052 super().reload()
1053 if self.retina:
1054 self._retina_shape()
1056 def _repr_html_(self):
1057 if not self.embed:
1058 width = height = klass = alt = ""
1059 if self.width:
1060 width = ' width="%d"' % self.width
1061 if self.height:
1062 height = ' height="%d"' % self.height
1063 if self.unconfined:
1064 klass = ' class="unconfined"'
1065 if self.alt:
1066 alt = ' alt="%s"' % html.escape(self.alt)
1067 return '<img src="{url}"{width}{height}{klass}{alt}/>'.format(
1068 url=html.escape(self.url or ""),
1069 width=width,
1070 height=height,
1071 klass=klass,
1072 alt=alt,
1073 )
1075 def _repr_mimebundle_(self, include=None, exclude=None):
1076 """Return the image as a mimebundle
1078 Any new mimetype support should be implemented here.
1079 """
1080 if self.embed:
1081 mimetype = self._mimetype
1082 data, metadata = self._data_and_metadata(always_both=True)
1083 if metadata:
1084 metadata = {mimetype: metadata}
1085 return {mimetype: data}, metadata
1086 else:
1087 return {'text/html': self._repr_html_()}
1089 def _data_and_metadata(self, always_both=False):
1090 """shortcut for returning metadata with shape information, if defined"""
1091 try:
1092 b64_data = b2a_base64(self.data, newline=False).decode("ascii")
1093 except TypeError as e:
1094 raise FileNotFoundError(
1095 "No such file or directory: '%s'" % (self.data)) from e
1096 md = {}
1097 if self.metadata:
1098 md.update(self.metadata)
1099 if self.width:
1100 md['width'] = self.width
1101 if self.height:
1102 md['height'] = self.height
1103 if self.unconfined:
1104 md['unconfined'] = self.unconfined
1105 if self.alt:
1106 md["alt"] = self.alt
1107 if md or always_both:
1108 return b64_data, md
1109 else:
1110 return b64_data
1112 def _repr_png_(self):
1113 if self.embed and self.format == ImageFormat.png.name:
1114 return self._data_and_metadata()
1116 def _repr_jpeg_(self):
1117 if self.embed and self.format == ImageFormat.jpeg.name:
1118 return self._data_and_metadata()
1120 def _find_ext(self, s: str) -> str:
1121 base, ext = splitext(s)
1123 if not ext:
1124 return base
1126 # `splitext` includes leading period, so we skip it
1127 return ext[1:].lower()
1130class Video(DisplayObject):
1132 def __init__(self, data=None, url=None, filename=None, embed=False,
1133 mimetype=None, width=None, height=None, html_attributes="controls"):
1134 """Create a video object given raw data or an URL.
1136 When this object is returned by an input cell or passed to the
1137 display function, it will result in the video being displayed
1138 in the frontend.
1140 Parameters
1141 ----------
1142 data : unicode, str or bytes
1143 The raw video data or a URL or filename to load the data from.
1144 Raw data will require passing ``embed=True``.
1146 url : unicode
1147 A URL for the video. If you specify ``url=``,
1148 the image data will not be embedded.
1150 filename : unicode
1151 Path to a local file containing the video.
1152 Will be interpreted as a local URL unless ``embed=True``.
1154 embed : bool
1155 Should the video be embedded using a data URI (True) or be
1156 loaded using a <video> tag (False).
1158 Since videos are large, embedding them should be avoided, if possible.
1159 You must confirm embedding as your intention by passing ``embed=True``.
1161 Local files can be displayed with URLs without embedding the content, via::
1163 Video('./video.mp4')
1165 mimetype : unicode
1166 Specify the mimetype for embedded videos.
1167 Default will be guessed from file extension, if available.
1169 width : int
1170 Width in pixels to which to constrain the video in HTML.
1171 If not supplied, defaults to the width of the video.
1173 height : int
1174 Height in pixels to which to constrain the video in html.
1175 If not supplied, defaults to the height of the video.
1177 html_attributes : str
1178 Attributes for the HTML ``<video>`` block.
1179 Default: ``"controls"`` to get video controls.
1180 Other examples: ``"controls muted"`` for muted video with controls,
1181 ``"loop autoplay"`` for looping autoplaying video without controls.
1183 Examples
1184 --------
1185 ::
1187 Video('https://archive.org/download/Sita_Sings_the_Blues/Sita_Sings_the_Blues_small.mp4')
1188 Video('path/to/video.mp4')
1189 Video('path/to/video.mp4', embed=True)
1190 Video('path/to/video.mp4', embed=True, html_attributes="controls muted autoplay")
1191 Video(b'raw-videodata', embed=True)
1192 """
1193 if isinstance(data, (Path, PurePath)):
1194 data = str(data)
1196 if url is None and isinstance(data, str) and data.startswith(('http:', 'https:')):
1197 url = data
1198 data = None
1199 elif data is not None and os.path.exists(data):
1200 filename = data
1201 data = None
1203 if data and not embed:
1204 msg = ''.join([
1205 "To embed videos, you must pass embed=True ",
1206 "(this may make your notebook files huge)\n",
1207 "Consider passing Video(url='...')",
1208 ])
1209 raise ValueError(msg)
1211 self.mimetype = mimetype
1212 self.embed = embed
1213 self.width = width
1214 self.height = height
1215 self.html_attributes = html_attributes
1216 super().__init__(data=data, url=url, filename=filename)
1218 def _repr_html_(self):
1219 width = height = ''
1220 if self.width:
1221 width = ' width="%d"' % self.width
1222 if self.height:
1223 height = ' height="%d"' % self.height
1225 # External URLs and potentially local files are not embedded into the
1226 # notebook output.
1227 if not self.embed:
1228 url = self.url if self.url is not None else self.filename
1229 output = """<video src="{}" {} {} {}>
1230 Your browser does not support the <code>video</code> element.
1231 </video>""".format(html.escape(url or ""), self.html_attributes, width, height)
1232 return output
1234 # Embedded videos are base64-encoded.
1235 mimetype = self.mimetype
1236 if self.filename is not None:
1237 if not mimetype:
1238 mimetype, _ = mimetypes.guess_type(self.filename)
1240 with open(self.filename, 'rb') as f:
1241 video = f.read()
1242 else:
1243 video = self.data
1244 if isinstance(video, str):
1245 # unicode input is already b64-encoded
1246 b64_video = video
1247 else:
1248 b64_video = b2a_base64(video, newline=False).decode("ascii").rstrip()
1250 output = """<video {0} {1} {2}>
1251 <source src="data:{3};base64,{4}" type="{3}">
1252 Your browser does not support the video tag.
1253 </video>""".format(self.html_attributes, width, height, mimetype, b64_video)
1254 return output
1256 def reload(self):
1257 # TODO
1258 pass