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

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

472 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 

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