Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/IPython/core/display.py: 27%
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
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 width, height = struct.unpack("<HH", data[24:30])
836 width = width & 0x3FFF
837 height = height & 0x3FFF
838 return (width, height)
839 elif data[12:16] == b"VP8L":
840 size_info = struct.unpack("<I", data[21:25])[0]
841 width = 1 + ((size_info & 0x3F) << 8) | (size_info >> 24)
842 height = 1 + (
843 (((size_info >> 8) & 0xF) << 10)
844 | (((size_info >> 14) & 0x3FC) << 2)
845 | ((size_info >> 22) & 0x3)
846 )
847 return (width, height)
848 else:
849 raise ValueError("Not a valid WEBP header")
852@dataclass
853class _ImageFormat:
854 magics: tuple[bytes, ...]
855 """Constants for identifying image data."""
857 shape: Callable[[bytes], tuple[int, int]]
858 """Reads (width, height) from image data."""
861class ImageFormat(_ImageFormat, Enum):
862 png = (b"\x89PNG\r\n\x1a\n",), _pngxy
863 jpeg = (b"\xff\xd8",), _jpegxy
864 jpg = jpeg # alias, has `.name == "jpeg"`
865 gif = (b"GIF87a", b"GIF89a"), _gifxy
866 webp = (b"WEBP",), _webpxy
868 @property
869 def mime_type(self):
870 return f"image/{self.name}"
872 @classmethod
873 def from_data(cls, data: bytes) -> Self | None:
874 for fmt in cls:
875 for magic in fmt.magics:
876 if data.startswith(magic):
877 return fmt
878 return None
881class Image(DisplayObject):
883 _read_flags = "rb"
885 def __init__(
886 self,
887 data=None,
888 url=None,
889 filename=None,
890 format=None,
891 embed=None,
892 width=None,
893 height=None,
894 retina=False,
895 unconfined=False,
896 metadata=None,
897 alt=None,
898 ):
899 """Create a PNG/JPEG/GIF/WEBP image object given raw data.
901 When this object is returned by an input cell or passed to the
902 display function, it will result in the image being displayed
903 in the frontend.
905 Parameters
906 ----------
907 data : unicode, str or bytes
908 The raw image data or a URL or filename to load the data from.
909 This always results in embedded image data.
911 url : unicode
912 A URL to download the data from. If you specify `url=`,
913 the image data will not be embedded unless you also specify `embed=True`.
915 filename : unicode
916 Path to a local file to load the data from.
917 Images from a file are always embedded.
919 format : unicode
920 The format of the image data (png/jpeg/jpg/gif/webp). If a filename or URL is given
921 for format will be inferred from the filename extension.
923 embed : bool
924 Should the image data be embedded using a data URI (True) or be
925 loaded using an <img> tag. Set this to True if you want the image
926 to be viewable later with no internet connection in the notebook.
928 Default is `True`, unless the keyword argument `url` is set, then
929 default value is `False`.
931 Note that QtConsole is not able to display images if `embed` is set to `False`
933 width : int
934 Width in pixels to which to constrain the image in html
936 height : int
937 Height in pixels to which to constrain the image in html
939 retina : bool
940 Automatically set the width and height to half of the measured
941 width and height.
942 This only works for embedded images because it reads the width/height
943 from image data.
944 For non-embedded images, you can just set the desired display width
945 and height directly.
947 unconfined : bool
948 Set unconfined=True to disable max-width confinement of the image.
950 metadata : dict
951 Specify extra metadata to attach to the image.
953 alt : unicode
954 Alternative text for the image, for use by screen readers.
956 Examples
957 --------
958 embedded image data, works in qtconsole and notebook
959 when passed positionally, the first arg can be any of raw image data,
960 a URL, or a filename from which to load image data.
961 The result is always embedding image data for inline images.
963 >>> Image('https://www.google.fr/images/srpr/logo3w.png') # doctest: +SKIP
964 <IPython.core.display.Image object>
966 >>> Image('/path/to/image.jpg')
967 <IPython.core.display.Image object>
969 >>> Image(b'RAW_PNG_DATA...')
970 <IPython.core.display.Image object>
972 Specifying Image(url=...) does not embed the image data,
973 it only generates ``<img>`` tag with a link to the source.
974 This will not work in the qtconsole or offline.
976 >>> Image(url='https://www.google.fr/images/srpr/logo3w.png')
977 <IPython.core.display.Image object>
979 """
980 if isinstance(data, (Path, PurePath)):
981 data = str(data)
983 if filename is not None:
984 ext = self._find_ext(filename)
985 elif url is not None:
986 ext = self._find_ext(url)
987 elif data is None:
988 raise ValueError("No image data found. Expecting filename, url, or data.")
989 elif isinstance(data, str) and (
990 data.startswith('http') or _safe_exists(data)
991 ):
992 ext = self._find_ext(data)
993 else:
994 ext = None
996 if format is None:
997 if ext is not None:
998 format = ext.lower()
999 elif isinstance(data, bytes) and (
1000 image_format := ImageFormat.from_data(data)
1001 ):
1002 format = image_format.name
1003 else: # failed to detect format, default png
1004 format = ImageFormat.png.name
1005 else:
1006 format = format.lower()
1007 # normalize e.g. `jpg` -> `jpeg`, `UNKNOWN` → `unknown`
1008 self.format = (
1009 ImageFormat[format].name if format in ImageFormat.__members__ else format
1010 )
1012 self.embed = embed if embed is not None else (url is None)
1013 if self.embed:
1014 if self.format not in ImageFormat.__members__:
1015 raise ValueError("Cannot embed the '%s' image format" % (self.format))
1016 self._mimetype = ImageFormat[self.format].mime_type
1018 self.width = width
1019 self.height = height
1020 self.retina = retina
1021 self.unconfined = unconfined
1022 self.alt = alt
1023 super().__init__(data=data, url=url, filename=filename,
1024 metadata=metadata)
1026 if self.width is None and self.metadata.get('width', {}):
1027 self.width = metadata['width']
1029 if self.height is None and self.metadata.get('height', {}):
1030 self.height = metadata['height']
1032 if self.alt is None and self.metadata.get("alt", {}):
1033 self.alt = metadata["alt"]
1035 if retina:
1036 self._retina_shape()
1039 def _retina_shape(self):
1040 """load pixel-doubled width and height from image data"""
1041 if not self.embed:
1042 return
1043 if self.format in ImageFormat.__members__:
1044 w, h = ImageFormat[self.format].shape(self.data)
1045 else:
1046 return
1047 self.width = w // 2
1048 self.height = h // 2
1050 def reload(self):
1051 """Reload the raw data from file or URL."""
1052 if self.embed:
1053 super().reload()
1054 if self.retina:
1055 self._retina_shape()
1057 def _repr_html_(self):
1058 if not self.embed:
1059 import html
1060 width = height = klass = alt = ""
1061 if self.width:
1062 width = ' width="%d"' % self.width
1063 if self.height:
1064 height = ' height="%d"' % self.height
1065 if self.unconfined:
1066 klass = ' class="unconfined"'
1067 if self.alt:
1068 alt = ' alt="%s"' % html.escape(self.alt)
1069 return '<img src="{url}"{width}{height}{klass}{alt}/>'.format(
1070 url=html.escape(self.url or ""),
1071 width=width,
1072 height=height,
1073 klass=klass,
1074 alt=alt,
1075 )
1077 def _repr_mimebundle_(self, include=None, exclude=None):
1078 """Return the image as a mimebundle
1080 Any new mimetype support should be implemented here.
1081 """
1082 if self.embed:
1083 mimetype = self._mimetype
1084 data, metadata = self._data_and_metadata(always_both=True)
1085 if metadata:
1086 metadata = {mimetype: metadata}
1087 return {mimetype: data}, metadata
1088 else:
1089 return {'text/html': self._repr_html_()}
1091 def _data_and_metadata(self, always_both=False):
1092 """shortcut for returning metadata with shape information, if defined"""
1093 try:
1094 b64_data = b2a_base64(self.data, newline=False).decode("ascii")
1095 except TypeError as e:
1096 raise FileNotFoundError(
1097 "No such file or directory: '%s'" % (self.data)) from e
1098 md = {}
1099 if self.metadata:
1100 md.update(self.metadata)
1101 if self.width:
1102 md['width'] = self.width
1103 if self.height:
1104 md['height'] = self.height
1105 if self.unconfined:
1106 md['unconfined'] = self.unconfined
1107 if self.alt:
1108 md["alt"] = self.alt
1109 if md or always_both:
1110 return b64_data, md
1111 else:
1112 return b64_data
1114 def _repr_png_(self):
1115 if self.embed and self.format == ImageFormat.png.name:
1116 return self._data_and_metadata()
1118 def _repr_jpeg_(self):
1119 if self.embed and self.format == ImageFormat.jpeg.name:
1120 return self._data_and_metadata()
1122 def _find_ext(self, s: str) -> str:
1123 base, ext = splitext(s)
1125 if not ext:
1126 return base
1128 # `splitext` includes leading period, so we skip it
1129 return ext[1:].lower()
1132class Video(DisplayObject):
1134 def __init__(self, data=None, url=None, filename=None, embed=False,
1135 mimetype=None, width=None, height=None, html_attributes="controls"):
1136 """Create a video object given raw data or an URL.
1138 When this object is returned by an input cell or passed to the
1139 display function, it will result in the video being displayed
1140 in the frontend.
1142 Parameters
1143 ----------
1144 data : unicode, str or bytes
1145 The raw video data or a URL or filename to load the data from.
1146 Raw data will require passing ``embed=True``.
1148 url : unicode
1149 A URL for the video. If you specify ``url=``,
1150 the image data will not be embedded.
1152 filename : unicode
1153 Path to a local file containing the video.
1154 Will be interpreted as a local URL unless ``embed=True``.
1156 embed : bool
1157 Should the video be embedded using a data URI (True) or be
1158 loaded using a <video> tag (False).
1160 Since videos are large, embedding them should be avoided, if possible.
1161 You must confirm embedding as your intention by passing ``embed=True``.
1163 Local files can be displayed with URLs without embedding the content, via::
1165 Video('./video.mp4')
1167 mimetype : unicode
1168 Specify the mimetype for embedded videos.
1169 Default will be guessed from file extension, if available.
1171 width : int
1172 Width in pixels to which to constrain the video in HTML.
1173 If not supplied, defaults to the width of the video.
1175 height : int
1176 Height in pixels to which to constrain the video in html.
1177 If not supplied, defaults to the height of the video.
1179 html_attributes : str
1180 Attributes for the HTML ``<video>`` block.
1181 Default: ``"controls"`` to get video controls.
1182 Other examples: ``"controls muted"`` for muted video with controls,
1183 ``"loop autoplay"`` for looping autoplaying video without controls.
1185 Examples
1186 --------
1187 ::
1189 Video('https://archive.org/download/Sita_Sings_the_Blues/Sita_Sings_the_Blues_small.mp4')
1190 Video('path/to/video.mp4')
1191 Video('path/to/video.mp4', embed=True)
1192 Video('path/to/video.mp4', embed=True, html_attributes="controls muted autoplay")
1193 Video(b'raw-videodata', embed=True)
1194 """
1195 if isinstance(data, (Path, PurePath)):
1196 data = str(data)
1198 if url is None and isinstance(data, str) and data.startswith(('http:', 'https:')):
1199 url = data
1200 data = None
1201 elif data is not None and os.path.exists(data):
1202 filename = data
1203 data = None
1205 if data and not embed:
1206 msg = ''.join([
1207 "To embed videos, you must pass embed=True ",
1208 "(this may make your notebook files huge)\n",
1209 "Consider passing Video(url='...')",
1210 ])
1211 raise ValueError(msg)
1213 self.mimetype = mimetype
1214 self.embed = embed
1215 self.width = width
1216 self.height = height
1217 self.html_attributes = html_attributes
1218 super().__init__(data=data, url=url, filename=filename)
1220 def _repr_html_(self):
1221 width = height = ''
1222 if self.width:
1223 width = ' width="%d"' % self.width
1224 if self.height:
1225 height = ' height="%d"' % self.height
1227 # External URLs and potentially local files are not embedded into the
1228 # notebook output.
1229 if not self.embed:
1230 import html
1231 url = self.url if self.url is not None else self.filename
1232 output = """<video src="{}" {} {} {}>
1233 Your browser does not support the <code>video</code> element.
1234 </video>""".format(html.escape(url or ""), self.html_attributes, width, height)
1235 return output
1237 # Embedded videos are base64-encoded.
1238 mimetype = self.mimetype
1239 if self.filename is not None:
1240 if not mimetype:
1241 import mimetypes
1243 mimetype, _ = mimetypes.guess_type(self.filename)
1245 with open(self.filename, 'rb') as f:
1246 video = f.read()
1247 else:
1248 video = self.data
1249 if isinstance(video, str):
1250 # unicode input is already b64-encoded
1251 b64_video = video
1252 else:
1253 b64_video = b2a_base64(video, newline=False).decode("ascii").rstrip()
1255 output = """<video {0} {1} {2}>
1256 <source src="data:{3};base64,{4}" type="{3}">
1257 Your browser does not support the video tag.
1258 </video>""".format(self.html_attributes, width, height, mimetype, b64_video)
1259 return output
1261 def reload(self):
1262 # TODO
1263 pass