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