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