Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/IPython/core/display.py: 28%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

467 statements  

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 

6from __future__ import annotations 

7 

8from enum import Enum 

9from dataclasses import dataclass, KW_ONLY 

10from binascii import b2a_base64, hexlify 

11import html 

12import json 

13import mimetypes 

14import os 

15import struct 

16import warnings 

17from copy import deepcopy 

18from os.path import splitext 

19from pathlib import Path, PurePath 

20 

21from typing import TYPE_CHECKING, Self 

22 

23from IPython.testing.skipdoctest import skip_doctest 

24from . import display_functions 

25 

26if TYPE_CHECKING: 

27 from collections.abc import Callable 

28 

29 

30__all__ = [ 

31 "display_pretty", 

32 "display_html", 

33 "display_markdown", 

34 "display_svg", 

35 "display_png", 

36 "display_jpeg", 

37 "display_webp", 

38 "display_latex", 

39 "display_json", 

40 "display_javascript", 

41 "display_pdf", 

42 "DisplayObject", 

43 "TextDisplayObject", 

44 "Pretty", 

45 "HTML", 

46 "Markdown", 

47 "Math", 

48 "Latex", 

49 "SVG", 

50 "ProgressBar", 

51 "JSON", 

52 "GeoJSON", 

53 "Javascript", 

54 "Image", 

55 "Video", 

56] 

57 

58#----------------------------------------------------------------------------- 

59# utility functions 

60#----------------------------------------------------------------------------- 

61 

62def _safe_exists(path): 

63 """Check path, but don't let exceptions raise""" 

64 try: 

65 return os.path.exists(path) 

66 except Exception: 

67 return False 

68 

69 

70def _display_mimetype(mimetype, objs, raw=False, metadata=None): 

71 """internal implementation of all display_foo methods 

72 

73 Parameters 

74 ---------- 

75 mimetype : str 

76 The mimetype to be published (e.g. 'image/png') 

77 *objs : object 

78 The Python objects to display, or if raw=True raw text data to 

79 display. 

80 raw : bool 

81 Are the data objects raw data or Python objects that need to be 

82 formatted before display? [default: False] 

83 metadata : dict (optional) 

84 Metadata to be associated with the specific mimetype output. 

85 """ 

86 if metadata: 

87 metadata = {mimetype: metadata} 

88 if raw: 

89 # turn list of pngdata into list of { 'image/png': pngdata } 

90 objs = [ {mimetype: obj} for obj in objs ] 

91 display_functions.display(*objs, raw=raw, metadata=metadata, include=[mimetype]) 

92 

93#----------------------------------------------------------------------------- 

94# Main functions 

95#----------------------------------------------------------------------------- 

96 

97 

98def display_pretty(*objs, **kwargs): 

99 """Display the pretty (default) representation of an object. 

100 

101 Parameters 

102 ---------- 

103 *objs : object 

104 The Python objects to display, or if raw=True raw text data to 

105 display. 

106 raw : bool 

107 Are the data objects raw data or Python objects that need to be 

108 formatted before display? [default: False] 

109 metadata : dict (optional) 

110 Metadata to be associated with the specific mimetype output. 

111 """ 

112 _display_mimetype('text/plain', objs, **kwargs) 

113 

114 

115def display_html(*objs, **kwargs): 

116 """Display the HTML representation of an object. 

117 

118 Note: If raw=False and the object does not have a HTML 

119 representation, no HTML will be shown. 

120 

121 Parameters 

122 ---------- 

123 *objs : object 

124 The Python objects to display, or if raw=True raw HTML data to 

125 display. 

126 raw : bool 

127 Are the data objects raw data or Python objects that need to be 

128 formatted before display? [default: False] 

129 metadata : dict (optional) 

130 Metadata to be associated with the specific mimetype output. 

131 """ 

132 _display_mimetype('text/html', objs, **kwargs) 

133 

134 

135def display_markdown(*objs, **kwargs): 

136 """Displays the Markdown representation of an object. 

137 

138 Parameters 

139 ---------- 

140 *objs : object 

141 The Python objects to display, or if raw=True raw markdown data to 

142 display. 

143 raw : bool 

144 Are the data objects raw data or Python objects that need to be 

145 formatted before display? [default: False] 

146 metadata : dict (optional) 

147 Metadata to be associated with the specific mimetype output. 

148 """ 

149 

150 _display_mimetype('text/markdown', objs, **kwargs) 

151 

152 

153def display_svg(*objs, **kwargs): 

154 """Display the SVG representation of an object. 

155 

156 Parameters 

157 ---------- 

158 *objs : object 

159 The Python objects to display, or if raw=True raw svg data to 

160 display. 

161 raw : bool 

162 Are the data objects raw data or Python objects that need to be 

163 formatted before display? [default: False] 

164 metadata : dict (optional) 

165 Metadata to be associated with the specific mimetype output. 

166 """ 

167 _display_mimetype('image/svg+xml', objs, **kwargs) 

168 

169 

170def display_png(*objs, **kwargs): 

171 """Display the PNG representation of an object. 

172 

173 Parameters 

174 ---------- 

175 *objs : object 

176 The Python objects to display, or if raw=True raw png data to 

177 display. 

178 raw : bool 

179 Are the data objects raw data or Python objects that need to be 

180 formatted before display? [default: False] 

181 metadata : dict (optional) 

182 Metadata to be associated with the specific mimetype output. 

183 """ 

184 _display_mimetype('image/png', objs, **kwargs) 

185 

186 

187def display_jpeg(*objs, **kwargs): 

188 """Display the JPEG representation of an object. 

189 

190 Parameters 

191 ---------- 

192 *objs : object 

193 The Python objects to display, or if raw=True raw JPEG data to 

194 display. 

195 raw : bool 

196 Are the data objects raw data or Python objects that need to be 

197 formatted before display? [default: False] 

198 metadata : dict (optional) 

199 Metadata to be associated with the specific mimetype output. 

200 """ 

201 _display_mimetype('image/jpeg', objs, **kwargs) 

202 

203 

204def display_webp(*objs, **kwargs): 

205 """Display the WEBP representation of an object. 

206 

207 Parameters 

208 ---------- 

209 *objs : object 

210 The Python objects to display, or if raw=True raw JPEG data to 

211 display. 

212 raw : bool 

213 Are the data objects raw data or Python objects that need to be 

214 formatted before display? [default: False] 

215 metadata : dict (optional) 

216 Metadata to be associated with the specific mimetype output. 

217 """ 

218 _display_mimetype("image/webp", objs, **kwargs) 

219 

220 

221def display_latex(*objs, **kwargs): 

222 """Display the LaTeX representation of an object. 

223 

224 Parameters 

225 ---------- 

226 *objs : object 

227 The Python objects to display, or if raw=True raw latex data to 

228 display. 

229 raw : bool 

230 Are the data objects raw data or Python objects that need to be 

231 formatted before display? [default: False] 

232 metadata : dict (optional) 

233 Metadata to be associated with the specific mimetype output. 

234 """ 

235 _display_mimetype('text/latex', objs, **kwargs) 

236 

237 

238def display_json(*objs, **kwargs): 

239 """Display the JSON representation of an object. 

240 

241 Note that not many frontends support displaying JSON. 

242 

243 Parameters 

244 ---------- 

245 *objs : object 

246 The Python objects to display, or if raw=True raw json data to 

247 display. 

248 raw : bool 

249 Are the data objects raw data or Python objects that need to be 

250 formatted before display? [default: False] 

251 metadata : dict (optional) 

252 Metadata to be associated with the specific mimetype output. 

253 """ 

254 _display_mimetype('application/json', objs, **kwargs) 

255 

256 

257def display_javascript(*objs, **kwargs): 

258 """Display the Javascript representation of an object. 

259 

260 Parameters 

261 ---------- 

262 *objs : object 

263 The Python objects to display, or if raw=True raw javascript data to 

264 display. 

265 raw : bool 

266 Are the data objects raw data or Python objects that need to be 

267 formatted before display? [default: False] 

268 metadata : dict (optional) 

269 Metadata to be associated with the specific mimetype output. 

270 """ 

271 _display_mimetype('application/javascript', objs, **kwargs) 

272 

273 

274def display_pdf(*objs, **kwargs): 

275 """Display the PDF representation of an object. 

276 

277 Parameters 

278 ---------- 

279 *objs : object 

280 The Python objects to display, or if raw=True raw javascript data to 

281 display. 

282 raw : bool 

283 Are the data objects raw data or Python objects that need to be 

284 formatted before display? [default: False] 

285 metadata : dict (optional) 

286 Metadata to be associated with the specific mimetype output. 

287 """ 

288 _display_mimetype('application/pdf', objs, **kwargs) 

289 

290 

291#----------------------------------------------------------------------------- 

292# Smart classes 

293#----------------------------------------------------------------------------- 

294 

295 

296class DisplayObject: 

297 """An object that wraps data to be displayed.""" 

298 

299 _read_flags = 'r' 

300 _show_mem_addr = False 

301 metadata = None 

302 

303 def __init__(self, data=None, url=None, filename=None, metadata=None): 

304 """Create a display object given raw data. 

305 

306 When this object is returned by an expression or passed to the 

307 display function, it will result in the data being displayed 

308 in the frontend. The MIME type of the data should match the 

309 subclasses used, so the Png subclass should be used for 'image/png' 

310 data. If the data is a URL, the data will first be downloaded 

311 and then displayed. 

312 

313 Parameters 

314 ---------- 

315 data : unicode, str or bytes 

316 The raw data or a URL or file to load the data from 

317 url : unicode 

318 A URL to download the data from. 

319 filename : unicode 

320 Path to a local file to load the data from. 

321 metadata : dict 

322 Dict of metadata associated to be the object when displayed 

323 """ 

324 if isinstance(data, (Path, PurePath)): 

325 data = str(data) 

326 

327 if data is not None and isinstance(data, str): 

328 if data.startswith('http') and url is None: 

329 url = data 

330 filename = None 

331 data = None 

332 elif _safe_exists(data) and filename is None: 

333 url = None 

334 filename = data 

335 data = None 

336 

337 self.url = url 

338 self.filename = filename 

339 # because of @data.setter methods in 

340 # subclasses ensure url and filename are set 

341 # before assigning to self.data 

342 self.data = data 

343 

344 if metadata is not None: 

345 self.metadata = metadata 

346 elif self.metadata is None: 

347 self.metadata = {} 

348 

349 self.reload() 

350 self._check_data() 

351 

352 def __repr__(self): 

353 if not self._show_mem_addr: 

354 cls = self.__class__ 

355 r = "<{}.{} object>".format(cls.__module__, cls.__name__) 

356 else: 

357 r = super().__repr__() 

358 return r 

359 

360 def _check_data(self): 

361 """Override in subclasses if there's something to check.""" 

362 pass 

363 

364 def _data_and_metadata(self): 

365 """shortcut for returning metadata with shape information, if defined""" 

366 if self.metadata: 

367 return self.data, deepcopy(self.metadata) 

368 else: 

369 return self.data 

370 

371 def reload(self): 

372 """Reload the raw data from file or URL.""" 

373 if self.filename is not None: 

374 encoding = None if "b" in self._read_flags else "utf-8" 

375 with open(self.filename, self._read_flags, encoding=encoding) as f: 

376 self.data = f.read() 

377 elif self.url is not None: 

378 # Deferred import 

379 from urllib.request import urlopen 

380 response = urlopen(self.url) 

381 data = response.read() 

382 # extract encoding from header, if there is one: 

383 encoding = None 

384 if 'content-type' in response.headers: 

385 for sub in response.headers['content-type'].split(';'): 

386 sub = sub.strip() 

387 if sub.startswith('charset'): 

388 encoding = sub.split('=')[-1].strip() 

389 break 

390 if 'content-encoding' in response.headers: 

391 if 'gzip' in response.headers['content-encoding']: 

392 import gzip 

393 from io import BytesIO 

394 

395 # assume utf-8 if encoding is not specified 

396 with gzip.open( 

397 BytesIO(data), "rt", encoding=encoding or "utf-8" 

398 ) as fp: 

399 encoding = None 

400 data = fp.read() 

401 

402 # decode data, if an encoding was specified 

403 # We only touch self.data once since 

404 # subclasses such as SVG have @data.setter methods 

405 # that transform self.data into ... well svg. 

406 if encoding: 

407 self.data = data.decode(encoding, 'replace') 

408 else: 

409 self.data = data 

410 

411 

412class TextDisplayObject(DisplayObject): 

413 """Create a text display object given raw data. 

414 

415 Parameters 

416 ---------- 

417 data : str or unicode 

418 The raw data or a URL or file to load the data from. 

419 url : unicode 

420 A URL to download the data from. 

421 filename : unicode 

422 Path to a local file to load the data from. 

423 metadata : dict 

424 Dict of metadata associated to be the object when displayed 

425 """ 

426 def _check_data(self): 

427 if self.data is not None and not isinstance(self.data, str): 

428 raise TypeError("{} expects text, not {!r}".format(self.__class__.__name__, self.data)) 

429 

430class Pretty(TextDisplayObject): 

431 

432 def _repr_pretty_(self, pp, cycle): 

433 return pp.text(self.data) 

434 

435 

436class HTML(TextDisplayObject): 

437 

438 def __init__(self, data=None, url=None, filename=None, metadata=None): 

439 def warn(): 

440 if not data: 

441 return False 

442 

443 # 

444 # Avoid calling lower() on the entire data, because it could be a 

445 # long string and we're only interested in its beginning and end. 

446 # 

447 prefix = data[:10].lower() 

448 suffix = data[-10:].lower() 

449 return prefix.startswith("<iframe ") and suffix.endswith("</iframe>") 

450 

451 if warn(): 

452 warnings.warn("Consider using IPython.display.IFrame instead") 

453 super().__init__(data=data, url=url, filename=filename, metadata=metadata) 

454 

455 def _repr_html_(self): 

456 return self._data_and_metadata() 

457 

458 def __html__(self): 

459 """ 

460 This method exists to inform other HTML-using modules (e.g. Markupsafe, 

461 htmltag, etc) that this object is HTML and does not need things like 

462 special characters (<>&) escaped. 

463 """ 

464 return self._repr_html_() 

465 

466 

467class Markdown(TextDisplayObject): 

468 

469 def _repr_markdown_(self): 

470 return self._data_and_metadata() 

471 

472 

473class Math(TextDisplayObject): 

474 

475 def _repr_latex_(self): 

476 s = r"$\displaystyle %s$" % self.data.strip('$') 

477 if self.metadata: 

478 return s, deepcopy(self.metadata) 

479 else: 

480 return s 

481 

482 

483class Latex(TextDisplayObject): 

484 

485 def _repr_latex_(self): 

486 return self._data_and_metadata() 

487 

488 

489class SVG(DisplayObject): 

490 """Embed an SVG into the display. 

491 

492 Note if you just want to view a svg image via a URL use `:class:Image` with 

493 a url=URL keyword argument. 

494 """ 

495 

496 _read_flags = 'rb' 

497 # wrap data in a property, which extracts the <svg> tag, discarding 

498 # document headers 

499 _data: str | None = None 

500 

501 @property 

502 def data(self): 

503 return self._data 

504 

505 @data.setter 

506 def data(self, svg): 

507 if svg is None: 

508 self._data = None 

509 return 

510 # parse into dom object 

511 from xml.dom import minidom 

512 x = minidom.parseString(svg) 

513 # get svg tag (should be 1) 

514 found_svg = x.getElementsByTagName('svg') 

515 if found_svg: 

516 svg = found_svg[0].toxml() 

517 else: 

518 # fallback on the input, trust the user 

519 # but this is probably an error. 

520 pass 

521 if isinstance(svg, bytes): 

522 self._data = svg.decode(errors="replace") 

523 else: 

524 self._data = svg 

525 

526 def _repr_svg_(self): 

527 return self._data_and_metadata() 

528 

529class ProgressBar(DisplayObject): 

530 """Progressbar supports displaying a progressbar like element 

531 """ 

532 def __init__(self, total): 

533 """Creates a new progressbar 

534 

535 Parameters 

536 ---------- 

537 total : int 

538 maximum size of the progressbar 

539 """ 

540 self.total = total 

541 self._progress = 0 

542 self.html_width = '60ex' 

543 self.text_width = 60 

544 self._display_id = hexlify(os.urandom(8)).decode('ascii') 

545 

546 def __repr__(self): 

547 fraction = self.progress / self.total 

548 filled = '=' * int(fraction * self.text_width) 

549 rest = ' ' * (self.text_width - len(filled)) 

550 return '[{}{}] {}/{}'.format( 

551 filled, rest, 

552 self.progress, self.total, 

553 ) 

554 

555 def _repr_html_(self): 

556 return "<progress style='width:{}' max='{}' value='{}'></progress>".format( 

557 self.html_width, self.total, self.progress) 

558 

559 def display(self): 

560 display_functions.display(self, display_id=self._display_id) 

561 

562 def update(self): 

563 display_functions.display(self, display_id=self._display_id, update=True) 

564 

565 @property 

566 def progress(self): 

567 return self._progress 

568 

569 @progress.setter 

570 def progress(self, value): 

571 self._progress = value 

572 self.update() 

573 

574 def __iter__(self): 

575 self.display() 

576 self._progress = -1 # First iteration is 0 

577 return self 

578 

579 def __next__(self): 

580 """Returns current value and increments display by one.""" 

581 self.progress += 1 

582 if self.progress < self.total: 

583 return self.progress 

584 else: 

585 raise StopIteration() 

586 

587class JSON(DisplayObject): 

588 """JSON expects a JSON-able dict or list 

589 

590 not an already-serialized JSON string. 

591 

592 Scalar types (None, number, string) are not allowed, only dict or list containers. 

593 """ 

594 # wrap data in a property, which warns about passing already-serialized JSON 

595 _data = None 

596 def __init__(self, data=None, url=None, filename=None, expanded=False, metadata=None, root='root', **kwargs): 

597 """Create a JSON display object given raw data. 

598 

599 Parameters 

600 ---------- 

601 data : dict or list 

602 JSON data to display. Not an already-serialized JSON string. 

603 Scalar types (None, number, string) are not allowed, only dict 

604 or list containers. 

605 url : unicode 

606 A URL to download the data from. 

607 filename : unicode 

608 Path to a local file to load the data from. 

609 expanded : boolean 

610 Metadata to control whether a JSON display component is expanded. 

611 metadata : dict 

612 Specify extra metadata to attach to the json display object. 

613 root : str 

614 The name of the root element of the JSON tree 

615 """ 

616 self.metadata = { 

617 'expanded': expanded, 

618 'root': root, 

619 } 

620 if metadata: 

621 self.metadata.update(metadata) 

622 if kwargs: 

623 self.metadata.update(kwargs) 

624 super().__init__(data=data, url=url, filename=filename) 

625 

626 def _check_data(self): 

627 if self.data is not None and not isinstance(self.data, (dict, list)): 

628 raise TypeError("{} expects JSONable dict or list, not {!r}".format(self.__class__.__name__, self.data)) 

629 

630 @property 

631 def data(self): 

632 return self._data 

633 

634 @data.setter 

635 def data(self, data): 

636 if isinstance(data, (Path, PurePath)): 

637 data = str(data) 

638 

639 if isinstance(data, str): 

640 if self.filename is None and self.url is None: 

641 warnings.warn("JSON expects JSONable dict or list, not JSON strings") 

642 data = json.loads(data) 

643 self._data = data 

644 

645 def _data_and_metadata(self): 

646 return self.data, self.metadata 

647 

648 def _repr_json_(self): 

649 return self._data_and_metadata() 

650 

651 

652_css_t = """var link = document.createElement("link"); 

653 link.rel = "stylesheet"; 

654 link.type = "text/css"; 

655 link.href = "%s"; 

656 document.head.appendChild(link); 

657""" 

658 

659_lib_t1 = """new Promise(function(resolve, reject) { 

660 var script = document.createElement("script"); 

661 script.onload = resolve; 

662 script.onerror = reject; 

663 script.src = "%s"; 

664 document.head.appendChild(script); 

665}).then(() => { 

666""" 

667 

668_lib_t2 = """ 

669});""" 

670 

671class GeoJSON(JSON): 

672 """GeoJSON expects JSON-able dict 

673 

674 not an already-serialized JSON string. 

675 

676 Scalar types (None, number, string) are not allowed, only dict containers. 

677 """ 

678 

679 def __init__(self, *args, **kwargs): 

680 """Create a GeoJSON display object given raw data. 

681 

682 Parameters 

683 ---------- 

684 data : dict or list 

685 VegaLite data. Not an already-serialized JSON string. 

686 Scalar types (None, number, string) are not allowed, only dict 

687 or list containers. 

688 url_template : string 

689 Leaflet TileLayer URL template: http://leafletjs.com/reference.html#url-template 

690 layer_options : dict 

691 Leaflet TileLayer options: http://leafletjs.com/reference.html#tilelayer-options 

692 url : unicode 

693 A URL to download the data from. 

694 filename : unicode 

695 Path to a local file to load the data from. 

696 metadata : dict 

697 Specify extra metadata to attach to the json display object. 

698 

699 Examples 

700 -------- 

701 The following will display an interactive map of Mars with a point of 

702 interest on frontend that do support GeoJSON display. 

703 

704 >>> from IPython.display import GeoJSON 

705 

706 >>> GeoJSON(data={ 

707 ... "type": "Feature", 

708 ... "geometry": { 

709 ... "type": "Point", 

710 ... "coordinates": [-81.327, 296.038] 

711 ... } 

712 ... }, 

713 ... url_template="http://s3-eu-west-1.amazonaws.com/whereonmars.cartodb.net/{basemap_id}/{z}/{x}/{y}.png", 

714 ... layer_options={ 

715 ... "basemap_id": "celestia_mars-shaded-16k_global", 

716 ... "attribution" : "Celestia/praesepe", 

717 ... "minZoom" : 0, 

718 ... "maxZoom" : 18, 

719 ... }) 

720 <IPython.core.display.GeoJSON object> 

721 

722 In the terminal IPython, you will only see the text representation of 

723 the GeoJSON object. 

724 

725 """ 

726 

727 super().__init__(*args, **kwargs) 

728 

729 

730 def _ipython_display_(self): 

731 bundle = { 

732 'application/geo+json': self.data, 

733 'text/plain': '<IPython.display.GeoJSON object>' 

734 } 

735 metadata = { 

736 'application/geo+json': self.metadata 

737 } 

738 display_functions.display(bundle, metadata=metadata, raw=True) 

739 

740class Javascript(TextDisplayObject): 

741 

742 def __init__(self, data=None, url=None, filename=None, lib=None, css=None): 

743 """Create a Javascript display object given raw data. 

744 

745 When this object is returned by an expression or passed to the 

746 display function, it will result in the data being displayed 

747 in the frontend. If the data is a URL, the data will first be 

748 downloaded and then displayed. 

749 

750 In the Notebook, the containing element will be available as `element`, 

751 and jQuery will be available. Content appended to `element` will be 

752 visible in the output area. 

753 

754 Parameters 

755 ---------- 

756 data : unicode, str or bytes 

757 The Javascript source code or a URL to download it from. 

758 url : unicode 

759 A URL to download the data from. 

760 filename : unicode 

761 Path to a local file to load the data from. 

762 lib : list or str 

763 A sequence of Javascript library URLs to load asynchronously before 

764 running the source code. The full URLs of the libraries should 

765 be given. A single Javascript library URL can also be given as a 

766 string. 

767 css : list or str 

768 A sequence of css files to load before running the source code. 

769 The full URLs of the css files should be given. A single css URL 

770 can also be given as a string. 

771 """ 

772 if isinstance(lib, str): 

773 lib = [lib] 

774 elif lib is None: 

775 lib = [] 

776 if isinstance(css, str): 

777 css = [css] 

778 elif css is None: 

779 css = [] 

780 if not isinstance(lib, (list,tuple)): 

781 raise TypeError('expected sequence, got: %r' % lib) 

782 if not isinstance(css, (list,tuple)): 

783 raise TypeError('expected sequence, got: %r' % css) 

784 self.lib = lib 

785 self.css = css 

786 super().__init__(data=data, url=url, filename=filename) 

787 

788 def _repr_javascript_(self): 

789 r = '' 

790 for c in self.css: 

791 r += _css_t % c 

792 for l in self.lib: 

793 r += _lib_t1 % l 

794 r += self.data 

795 r += _lib_t2*len(self.lib) 

796 return r 

797 

798 

799def _pngxy(data): 

800 """read the (width, height) from a PNG header""" 

801 ihdr = data.index(b'IHDR') 

802 # next 8 bytes are width/height 

803 return struct.unpack('>ii', data[ihdr+4:ihdr+12]) 

804 

805 

806def _jpegxy(data): 

807 """read the (width, height) from a JPEG header""" 

808 # adapted from http://www.64lines.com/jpeg-width-height 

809 

810 idx = 4 

811 while True: 

812 block_size = struct.unpack('>H', data[idx:idx+2])[0] 

813 idx = idx + block_size 

814 if data[idx:idx+2] == b'\xFF\xC0': 

815 # found Start of Frame 

816 iSOF = idx 

817 break 

818 else: 

819 # read another block 

820 idx += 2 

821 

822 h, w = struct.unpack('>HH', data[iSOF+5:iSOF+9]) 

823 return w, h 

824 

825 

826def _gifxy(data): 

827 """read the (width, height) from a GIF header""" 

828 return struct.unpack('<HH', data[6:10]) 

829 

830 

831def _webpxy(data): 

832 """read the (width, height) from a WEBP header""" 

833 if data[12:16] == b"VP8 ": 

834 width, height = struct.unpack("<HH", data[24:30]) 

835 width = width & 0x3FFF 

836 height = height & 0x3FFF 

837 return (width, height) 

838 elif data[12:16] == b"VP8L": 

839 size_info = struct.unpack("<I", data[21:25])[0] 

840 width = 1 + ((size_info & 0x3F) << 8) | (size_info >> 24) 

841 height = 1 + ( 

842 (((size_info >> 8) & 0xF) << 10) 

843 | (((size_info >> 14) & 0x3FC) << 2) 

844 | ((size_info >> 22) & 0x3) 

845 ) 

846 return (width, height) 

847 else: 

848 raise ValueError("Not a valid WEBP header") 

849 

850 

851@dataclass 

852class _ImageFormat: 

853 magics: tuple[bytes, ...] 

854 """Constants for identifying image data.""" 

855 

856 shape: Callable[[bytes], tuple[int, int]] 

857 """Reads (width, height) from image data.""" 

858 

859 

860class ImageFormat(_ImageFormat, Enum): 

861 png = (b"\x89PNG\r\n\x1a\n",), _pngxy 

862 jpeg = (b"\xff\xd8",), _jpegxy 

863 jpg = jpeg # alias, has `.name == "jpeg"` 

864 gif = (b"GIF87a", b"GIF89a"), _gifxy 

865 webp = (b"WEBP",), _webpxy 

866 

867 @property 

868 def mime_type(self): 

869 return f"image/{self.name}" 

870 

871 @classmethod 

872 def from_data(cls, data: bytes) -> Self | None: 

873 for fmt in cls: 

874 for magic in fmt.magics: 

875 if data.startswith(magic): 

876 return fmt 

877 return None 

878 

879 

880class Image(DisplayObject): 

881 

882 _read_flags = "rb" 

883 

884 def __init__( 

885 self, 

886 data=None, 

887 url=None, 

888 filename=None, 

889 format=None, 

890 embed=None, 

891 width=None, 

892 height=None, 

893 retina=False, 

894 unconfined=False, 

895 metadata=None, 

896 alt=None, 

897 ): 

898 """Create a PNG/JPEG/GIF/WEBP image object given raw data. 

899 

900 When this object is returned by an input cell or passed to the 

901 display function, it will result in the image being displayed 

902 in the frontend. 

903 

904 Parameters 

905 ---------- 

906 data : unicode, str or bytes 

907 The raw image data or a URL or filename to load the data from. 

908 This always results in embedded image data. 

909 

910 url : unicode 

911 A URL to download the data from. If you specify `url=`, 

912 the image data will not be embedded unless you also specify `embed=True`. 

913 

914 filename : unicode 

915 Path to a local file to load the data from. 

916 Images from a file are always embedded. 

917 

918 format : unicode 

919 The format of the image data (png/jpeg/jpg/gif/webp). If a filename or URL is given 

920 for format will be inferred from the filename extension. 

921 

922 embed : bool 

923 Should the image data be embedded using a data URI (True) or be 

924 loaded using an <img> tag. Set this to True if you want the image 

925 to be viewable later with no internet connection in the notebook. 

926 

927 Default is `True`, unless the keyword argument `url` is set, then 

928 default value is `False`. 

929 

930 Note that QtConsole is not able to display images if `embed` is set to `False` 

931 

932 width : int 

933 Width in pixels to which to constrain the image in html 

934 

935 height : int 

936 Height in pixels to which to constrain the image in html 

937 

938 retina : bool 

939 Automatically set the width and height to half of the measured 

940 width and height. 

941 This only works for embedded images because it reads the width/height 

942 from image data. 

943 For non-embedded images, you can just set the desired display width 

944 and height directly. 

945 

946 unconfined : bool 

947 Set unconfined=True to disable max-width confinement of the image. 

948 

949 metadata : dict 

950 Specify extra metadata to attach to the image. 

951 

952 alt : unicode 

953 Alternative text for the image, for use by screen readers. 

954 

955 Examples 

956 -------- 

957 embedded image data, works in qtconsole and notebook 

958 when passed positionally, the first arg can be any of raw image data, 

959 a URL, or a filename from which to load image data. 

960 The result is always embedding image data for inline images. 

961 

962 >>> Image('https://www.google.fr/images/srpr/logo3w.png') # doctest: +SKIP 

963 <IPython.core.display.Image object> 

964 

965 >>> Image('/path/to/image.jpg') 

966 <IPython.core.display.Image object> 

967 

968 >>> Image(b'RAW_PNG_DATA...') 

969 <IPython.core.display.Image object> 

970 

971 Specifying Image(url=...) does not embed the image data, 

972 it only generates ``<img>`` tag with a link to the source. 

973 This will not work in the qtconsole or offline. 

974 

975 >>> Image(url='https://www.google.fr/images/srpr/logo3w.png') 

976 <IPython.core.display.Image object> 

977 

978 """ 

979 if isinstance(data, (Path, PurePath)): 

980 data = str(data) 

981 

982 if filename is not None: 

983 ext = self._find_ext(filename) 

984 elif url is not None: 

985 ext = self._find_ext(url) 

986 elif data is None: 

987 raise ValueError("No image data found. Expecting filename, url, or data.") 

988 elif isinstance(data, str) and ( 

989 data.startswith('http') or _safe_exists(data) 

990 ): 

991 ext = self._find_ext(data) 

992 else: 

993 ext = None 

994 

995 if format is None: 

996 if ext is not None: 

997 format = ext.lower() 

998 elif isinstance(data, bytes) and ( 

999 image_format := ImageFormat.from_data(data) 

1000 ): 

1001 format = image_format.name 

1002 else: # failed to detect format, default png 

1003 format = ImageFormat.png.name 

1004 else: 

1005 format = format.lower() 

1006 # normalize e.g. `jpg` -> `jpeg`, `UNKNOWN` → `unknown` 

1007 self.format = ( 

1008 ImageFormat[format].name if format in ImageFormat.__members__ else format 

1009 ) 

1010 

1011 self.embed = embed if embed is not None else (url is None) 

1012 if self.embed: 

1013 if self.format not in ImageFormat.__members__: 

1014 raise ValueError("Cannot embed the '%s' image format" % (self.format)) 

1015 self._mimetype = ImageFormat[self.format].mime_type 

1016 

1017 self.width = width 

1018 self.height = height 

1019 self.retina = retina 

1020 self.unconfined = unconfined 

1021 self.alt = alt 

1022 super().__init__(data=data, url=url, filename=filename, 

1023 metadata=metadata) 

1024 

1025 if self.width is None and self.metadata.get('width', {}): 

1026 self.width = metadata['width'] 

1027 

1028 if self.height is None and self.metadata.get('height', {}): 

1029 self.height = metadata['height'] 

1030 

1031 if self.alt is None and self.metadata.get("alt", {}): 

1032 self.alt = metadata["alt"] 

1033 

1034 if retina: 

1035 self._retina_shape() 

1036 

1037 

1038 def _retina_shape(self): 

1039 """load pixel-doubled width and height from image data""" 

1040 if not self.embed: 

1041 return 

1042 if self.format in ImageFormat.__members__: 

1043 w, h = ImageFormat[self.format].shape(self.data) 

1044 else: 

1045 return 

1046 self.width = w // 2 

1047 self.height = h // 2 

1048 

1049 def reload(self): 

1050 """Reload the raw data from file or URL.""" 

1051 if self.embed: 

1052 super().reload() 

1053 if self.retina: 

1054 self._retina_shape() 

1055 

1056 def _repr_html_(self): 

1057 if not self.embed: 

1058 width = height = klass = alt = "" 

1059 if self.width: 

1060 width = ' width="%d"' % self.width 

1061 if self.height: 

1062 height = ' height="%d"' % self.height 

1063 if self.unconfined: 

1064 klass = ' class="unconfined"' 

1065 if self.alt: 

1066 alt = ' alt="%s"' % html.escape(self.alt) 

1067 return '<img src="{url}"{width}{height}{klass}{alt}/>'.format( 

1068 url=html.escape(self.url or ""), 

1069 width=width, 

1070 height=height, 

1071 klass=klass, 

1072 alt=alt, 

1073 ) 

1074 

1075 def _repr_mimebundle_(self, include=None, exclude=None): 

1076 """Return the image as a mimebundle 

1077 

1078 Any new mimetype support should be implemented here. 

1079 """ 

1080 if self.embed: 

1081 mimetype = self._mimetype 

1082 data, metadata = self._data_and_metadata(always_both=True) 

1083 if metadata: 

1084 metadata = {mimetype: metadata} 

1085 return {mimetype: data}, metadata 

1086 else: 

1087 return {'text/html': self._repr_html_()} 

1088 

1089 def _data_and_metadata(self, always_both=False): 

1090 """shortcut for returning metadata with shape information, if defined""" 

1091 try: 

1092 b64_data = b2a_base64(self.data, newline=False).decode("ascii") 

1093 except TypeError as e: 

1094 raise FileNotFoundError( 

1095 "No such file or directory: '%s'" % (self.data)) from e 

1096 md = {} 

1097 if self.metadata: 

1098 md.update(self.metadata) 

1099 if self.width: 

1100 md['width'] = self.width 

1101 if self.height: 

1102 md['height'] = self.height 

1103 if self.unconfined: 

1104 md['unconfined'] = self.unconfined 

1105 if self.alt: 

1106 md["alt"] = self.alt 

1107 if md or always_both: 

1108 return b64_data, md 

1109 else: 

1110 return b64_data 

1111 

1112 def _repr_png_(self): 

1113 if self.embed and self.format == ImageFormat.png.name: 

1114 return self._data_and_metadata() 

1115 

1116 def _repr_jpeg_(self): 

1117 if self.embed and self.format == ImageFormat.jpeg.name: 

1118 return self._data_and_metadata() 

1119 

1120 def _find_ext(self, s: str) -> str: 

1121 base, ext = splitext(s) 

1122 

1123 if not ext: 

1124 return base 

1125 

1126 # `splitext` includes leading period, so we skip it 

1127 return ext[1:].lower() 

1128 

1129 

1130class Video(DisplayObject): 

1131 

1132 def __init__(self, data=None, url=None, filename=None, embed=False, 

1133 mimetype=None, width=None, height=None, html_attributes="controls"): 

1134 """Create a video object given raw data or an URL. 

1135 

1136 When this object is returned by an input cell or passed to the 

1137 display function, it will result in the video being displayed 

1138 in the frontend. 

1139 

1140 Parameters 

1141 ---------- 

1142 data : unicode, str or bytes 

1143 The raw video data or a URL or filename to load the data from. 

1144 Raw data will require passing ``embed=True``. 

1145 

1146 url : unicode 

1147 A URL for the video. If you specify ``url=``, 

1148 the image data will not be embedded. 

1149 

1150 filename : unicode 

1151 Path to a local file containing the video. 

1152 Will be interpreted as a local URL unless ``embed=True``. 

1153 

1154 embed : bool 

1155 Should the video be embedded using a data URI (True) or be 

1156 loaded using a <video> tag (False). 

1157 

1158 Since videos are large, embedding them should be avoided, if possible. 

1159 You must confirm embedding as your intention by passing ``embed=True``. 

1160 

1161 Local files can be displayed with URLs without embedding the content, via:: 

1162 

1163 Video('./video.mp4') 

1164 

1165 mimetype : unicode 

1166 Specify the mimetype for embedded videos. 

1167 Default will be guessed from file extension, if available. 

1168 

1169 width : int 

1170 Width in pixels to which to constrain the video in HTML. 

1171 If not supplied, defaults to the width of the video. 

1172 

1173 height : int 

1174 Height in pixels to which to constrain the video in html. 

1175 If not supplied, defaults to the height of the video. 

1176 

1177 html_attributes : str 

1178 Attributes for the HTML ``<video>`` block. 

1179 Default: ``"controls"`` to get video controls. 

1180 Other examples: ``"controls muted"`` for muted video with controls, 

1181 ``"loop autoplay"`` for looping autoplaying video without controls. 

1182 

1183 Examples 

1184 -------- 

1185 :: 

1186 

1187 Video('https://archive.org/download/Sita_Sings_the_Blues/Sita_Sings_the_Blues_small.mp4') 

1188 Video('path/to/video.mp4') 

1189 Video('path/to/video.mp4', embed=True) 

1190 Video('path/to/video.mp4', embed=True, html_attributes="controls muted autoplay") 

1191 Video(b'raw-videodata', embed=True) 

1192 """ 

1193 if isinstance(data, (Path, PurePath)): 

1194 data = str(data) 

1195 

1196 if url is None and isinstance(data, str) and data.startswith(('http:', 'https:')): 

1197 url = data 

1198 data = None 

1199 elif data is not None and os.path.exists(data): 

1200 filename = data 

1201 data = None 

1202 

1203 if data and not embed: 

1204 msg = ''.join([ 

1205 "To embed videos, you must pass embed=True ", 

1206 "(this may make your notebook files huge)\n", 

1207 "Consider passing Video(url='...')", 

1208 ]) 

1209 raise ValueError(msg) 

1210 

1211 self.mimetype = mimetype 

1212 self.embed = embed 

1213 self.width = width 

1214 self.height = height 

1215 self.html_attributes = html_attributes 

1216 super().__init__(data=data, url=url, filename=filename) 

1217 

1218 def _repr_html_(self): 

1219 width = height = '' 

1220 if self.width: 

1221 width = ' width="%d"' % self.width 

1222 if self.height: 

1223 height = ' height="%d"' % self.height 

1224 

1225 # External URLs and potentially local files are not embedded into the 

1226 # notebook output. 

1227 if not self.embed: 

1228 url = self.url if self.url is not None else self.filename 

1229 output = """<video src="{}" {} {} {}> 

1230 Your browser does not support the <code>video</code> element. 

1231 </video>""".format(html.escape(url or ""), self.html_attributes, width, height) 

1232 return output 

1233 

1234 # Embedded videos are base64-encoded. 

1235 mimetype = self.mimetype 

1236 if self.filename is not None: 

1237 if not mimetype: 

1238 mimetype, _ = mimetypes.guess_type(self.filename) 

1239 

1240 with open(self.filename, 'rb') as f: 

1241 video = f.read() 

1242 else: 

1243 video = self.data 

1244 if isinstance(video, str): 

1245 # unicode input is already b64-encoded 

1246 b64_video = video 

1247 else: 

1248 b64_video = b2a_base64(video, newline=False).decode("ascii").rstrip() 

1249 

1250 output = """<video {0} {1} {2}> 

1251 <source src="data:{3};base64,{4}" type="{3}"> 

1252 Your browser does not support the video tag. 

1253 </video>""".format(self.html_attributes, width, height, mimetype, b64_video) 

1254 return output 

1255 

1256 def reload(self): 

1257 # TODO 

1258 pass