display.py 40 KB

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