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 if 'gzip' in response.headers['content-encoding']: 

386 import gzip 

387 from io import BytesIO 

388 

389 # assume utf-8 if encoding is not specified 

390 with gzip.open( 

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

392 ) as fp: 

393 encoding = None 

394 data = fp.read() 

395 

396 # decode data, if an encoding was specified 

397 # We only touch self.data once since 

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

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

400 if encoding: 

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

402 else: 

403 self.data = data 

404 

405 

406class TextDisplayObject(DisplayObject): 

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

408 

409 Parameters 

410 ---------- 

411 data : str or unicode 

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

413 url : unicode 

414 A URL to download the data from. 

415 filename : unicode 

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

417 metadata : dict 

418 Dict of metadata associated to be the object when displayed 

419 """ 

420 def _check_data(self): 

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

422 raise TypeError("%s expects text, not %r" % (self.__class__.__name__, self.data)) 

423 

424class Pretty(TextDisplayObject): 

425 

426 def _repr_pretty_(self, pp, cycle): 

427 return pp.text(self.data) 

428 

429 

430class HTML(TextDisplayObject): 

431 

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

433 def warn(): 

434 if not data: 

435 return False 

436 

437 # 

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

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

440 # 

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

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

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

444 

445 if warn(): 

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

447 super(HTML, self).__init__(data=data, url=url, filename=filename, metadata=metadata) 

448 

449 def _repr_html_(self): 

450 return self._data_and_metadata() 

451 

452 def __html__(self): 

453 """ 

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

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

456 special characters (<>&) escaped. 

457 """ 

458 return self._repr_html_() 

459 

460 

461class Markdown(TextDisplayObject): 

462 

463 def _repr_markdown_(self): 

464 return self._data_and_metadata() 

465 

466 

467class Math(TextDisplayObject): 

468 

469 def _repr_latex_(self): 

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

471 if self.metadata: 

472 return s, deepcopy(self.metadata) 

473 else: 

474 return s 

475 

476 

477class Latex(TextDisplayObject): 

478 

479 def _repr_latex_(self): 

480 return self._data_and_metadata() 

481 

482 

483class SVG(DisplayObject): 

484 """Embed an SVG into the display. 

485 

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

487 a url=URL keyword argument. 

488 """ 

489 

490 _read_flags = 'rb' 

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

492 # document headers 

493 _data: Optional[str] = None 

494 

495 @property 

496 def data(self): 

497 return self._data 

498 

499 @data.setter 

500 def data(self, svg): 

501 if svg is None: 

502 self._data = None 

503 return 

504 # parse into dom object 

505 from xml.dom import minidom 

506 x = minidom.parseString(svg) 

507 # get svg tag (should be 1) 

508 found_svg = x.getElementsByTagName('svg') 

509 if found_svg: 

510 svg = found_svg[0].toxml() 

511 else: 

512 # fallback on the input, trust the user 

513 # but this is probably an error. 

514 pass 

515 if isinstance(svg, bytes): 

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

517 else: 

518 self._data = svg 

519 

520 def _repr_svg_(self): 

521 return self._data_and_metadata() 

522 

523class ProgressBar(DisplayObject): 

524 """Progressbar supports displaying a progressbar like element 

525 """ 

526 def __init__(self, total): 

527 """Creates a new progressbar 

528 

529 Parameters 

530 ---------- 

531 total : int 

532 maximum size of the progressbar 

533 """ 

534 self.total = total 

535 self._progress = 0 

536 self.html_width = '60ex' 

537 self.text_width = 60 

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

539 

540 def __repr__(self): 

541 fraction = self.progress / self.total 

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

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

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

545 filled, rest, 

546 self.progress, self.total, 

547 ) 

548 

549 def _repr_html_(self): 

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

551 self.html_width, self.total, self.progress) 

552 

553 def display(self): 

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

555 

556 def update(self): 

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

558 

559 @property 

560 def progress(self): 

561 return self._progress 

562 

563 @progress.setter 

564 def progress(self, value): 

565 self._progress = value 

566 self.update() 

567 

568 def __iter__(self): 

569 self.display() 

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

571 return self 

572 

573 def __next__(self): 

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

575 self.progress += 1 

576 if self.progress < self.total: 

577 return self.progress 

578 else: 

579 raise StopIteration() 

580 

581class JSON(DisplayObject): 

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

583 

584 not an already-serialized JSON string. 

585 

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

587 """ 

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

589 _data = None 

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

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

592 

593 Parameters 

594 ---------- 

595 data : dict or list 

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

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

598 or list containers. 

599 url : unicode 

600 A URL to download the data from. 

601 filename : unicode 

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

603 expanded : boolean 

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

605 metadata : dict 

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

607 root : str 

608 The name of the root element of the JSON tree 

609 """ 

610 self.metadata = { 

611 'expanded': expanded, 

612 'root': root, 

613 } 

614 if metadata: 

615 self.metadata.update(metadata) 

616 if kwargs: 

617 self.metadata.update(kwargs) 

618 super(JSON, self).__init__(data=data, url=url, filename=filename) 

619 

620 def _check_data(self): 

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

622 raise TypeError("%s expects JSONable dict or list, not %r" % (self.__class__.__name__, self.data)) 

623 

624 @property 

625 def data(self): 

626 return self._data 

627 

628 @data.setter 

629 def data(self, data): 

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

631 data = str(data) 

632 

633 if isinstance(data, str): 

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

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

636 data = json.loads(data) 

637 self._data = data 

638 

639 def _data_and_metadata(self): 

640 return self.data, self.metadata 

641 

642 def _repr_json_(self): 

643 return self._data_and_metadata() 

644 

645 

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

647 link.rel = "stylesheet"; 

648 link.type = "text/css"; 

649 link.href = "%s"; 

650 document.head.appendChild(link); 

651""" 

652 

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

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

655 script.onload = resolve; 

656 script.onerror = reject; 

657 script.src = "%s"; 

658 document.head.appendChild(script); 

659}).then(() => { 

660""" 

661 

662_lib_t2 = """ 

663});""" 

664 

665class GeoJSON(JSON): 

666 """GeoJSON expects JSON-able dict 

667 

668 not an already-serialized JSON string. 

669 

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

671 """ 

672 

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

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

675 

676 Parameters 

677 ---------- 

678 data : dict or list 

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

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

681 or list containers. 

682 url_template : string 

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

684 layer_options : dict 

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

686 url : unicode 

687 A URL to download the data from. 

688 filename : unicode 

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

690 metadata : dict 

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

692 

693 Examples 

694 -------- 

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

696 interest on frontend that do support GeoJSON display. 

697 

698 >>> from IPython.display import GeoJSON 

699 

700 >>> GeoJSON(data={ 

701 ... "type": "Feature", 

702 ... "geometry": { 

703 ... "type": "Point", 

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

705 ... } 

706 ... }, 

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

708 ... layer_options={ 

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

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

711 ... "minZoom" : 0, 

712 ... "maxZoom" : 18, 

713 ... }) 

714 <IPython.core.display.GeoJSON object> 

715 

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

717 the GeoJSON object. 

718 

719 """ 

720 

721 super(GeoJSON, self).__init__(*args, **kwargs) 

722 

723 

724 def _ipython_display_(self): 

725 bundle = { 

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

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

728 } 

729 metadata = { 

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

731 } 

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

733 

734class Javascript(TextDisplayObject): 

735 

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

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

738 

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

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

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

742 downloaded and then displayed. 

743 

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

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

746 visible in the output area. 

747 

748 Parameters 

749 ---------- 

750 data : unicode, str or bytes 

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

752 url : unicode 

753 A URL to download the data from. 

754 filename : unicode 

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

756 lib : list or str 

757 A sequence of Javascript library URLs to load asynchronously before 

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

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

760 string. 

761 css : list or str 

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

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

764 can also be given as a string. 

765 """ 

766 if isinstance(lib, str): 

767 lib = [lib] 

768 elif lib is None: 

769 lib = [] 

770 if isinstance(css, str): 

771 css = [css] 

772 elif css is None: 

773 css = [] 

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

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

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

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

778 self.lib = lib 

779 self.css = css 

780 super(Javascript, self).__init__(data=data, url=url, filename=filename) 

781 

782 def _repr_javascript_(self): 

783 r = '' 

784 for c in self.css: 

785 r += _css_t % c 

786 for l in self.lib: 

787 r += _lib_t1 % l 

788 r += self.data 

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

790 return r 

791 

792 

793# constants for identifying png/jpeg/gif/webp data 

794_PNG = b"\x89PNG\r\n\x1a\n" 

795_JPEG = b"\xff\xd8" 

796_GIF1 = b"GIF87a" 

797_GIF2 = b"GIF89a" 

798_WEBP = b"WEBP" 

799 

800 

801def _pngxy(data): 

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

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

804 # next 8 bytes are width/height 

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

806 

807 

808def _jpegxy(data): 

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

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

811 

812 idx = 4 

813 while True: 

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

815 idx = idx + block_size 

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

817 # found Start of Frame 

818 iSOF = idx 

819 break 

820 else: 

821 # read another block 

822 idx += 2 

823 

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

825 return w, h 

826 

827 

828def _gifxy(data): 

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

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

831 

832 

833def _webpxy(data): 

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

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

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

837 width = width & 0x3FFF 

838 height = height & 0x3FFF 

839 return (width, height) 

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

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

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

843 height = 1 + ( 

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

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

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

847 ) 

848 return (width, height) 

849 else: 

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

851 

852 

853class Image(DisplayObject): 

854 

855 _read_flags = "rb" 

856 _FMT_JPEG = "jpeg" 

857 _FMT_PNG = "png" 

858 _FMT_GIF = "gif" 

859 _FMT_WEBP = "webp" 

860 _ACCEPTABLE_EMBEDDINGS = [_FMT_JPEG, _FMT_PNG, _FMT_GIF, _FMT_WEBP] 

861 _MIMETYPES = { 

862 _FMT_PNG: "image/png", 

863 _FMT_JPEG: "image/jpeg", 

864 _FMT_GIF: "image/gif", 

865 _FMT_WEBP: "image/webp", 

866 } 

867 

868 def __init__( 

869 self, 

870 data=None, 

871 url=None, 

872 filename=None, 

873 format=None, 

874 embed=None, 

875 width=None, 

876 height=None, 

877 retina=False, 

878 unconfined=False, 

879 metadata=None, 

880 alt=None, 

881 ): 

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

883 

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

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

886 in the frontend. 

887 

888 Parameters 

889 ---------- 

890 data : unicode, str or bytes 

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

892 This always results in embedded image data. 

893 

894 url : unicode 

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

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

897 

898 filename : unicode 

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

900 Images from a file are always embedded. 

901 

902 format : unicode 

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

904 for format will be inferred from the filename extension. 

905 

906 embed : bool 

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

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

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

910 

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

912 default value is `False`. 

913 

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

915 

916 width : int 

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

918 

919 height : int 

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

921 

922 retina : bool 

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

924 width and height. 

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

926 from image data. 

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

928 and height directly. 

929 

930 unconfined : bool 

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

932 

933 metadata : dict 

934 Specify extra metadata to attach to the image. 

935 

936 alt : unicode 

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

938 

939 Examples 

940 -------- 

941 embedded image data, works in qtconsole and notebook 

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

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

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

945 

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

947 <IPython.core.display.Image object> 

948 

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

950 <IPython.core.display.Image object> 

951 

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

953 <IPython.core.display.Image object> 

954 

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

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

957 This will not work in the qtconsole or offline. 

958 

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

960 <IPython.core.display.Image object> 

961 

962 """ 

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

964 data = str(data) 

965 

966 if filename is not None: 

967 ext = self._find_ext(filename) 

968 elif url is not None: 

969 ext = self._find_ext(url) 

970 elif data is None: 

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

972 elif isinstance(data, str) and ( 

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

974 ): 

975 ext = self._find_ext(data) 

976 else: 

977 ext = None 

978 

979 if format is None: 

980 if ext is not None: 

981 if ext == u'jpg' or ext == u'jpeg': 

982 format = self._FMT_JPEG 

983 elif ext == u'png': 

984 format = self._FMT_PNG 

985 elif ext == u'gif': 

986 format = self._FMT_GIF 

987 elif ext == "webp": 

988 format = self._FMT_WEBP 

989 else: 

990 format = ext.lower() 

991 elif isinstance(data, bytes): 

992 # infer image type from image data header, 

993 # only if format has not been specified. 

994 if data[:2] == _JPEG: 

995 format = self._FMT_JPEG 

996 elif data[:8] == _PNG: 

997 format = self._FMT_PNG 

998 elif data[8:12] == _WEBP: 

999 format = self._FMT_WEBP 

1000 elif data[:6] == _GIF1 or data[:6] == _GIF2: 

1001 format = self._FMT_GIF 

1002 

1003 # failed to detect format, default png 

1004 if format is None: 

1005 format = self._FMT_PNG 

1006 

1007 if format.lower() == 'jpg': 

1008 # jpg->jpeg 

1009 format = self._FMT_JPEG 

1010 

1011 self.format = format.lower() 

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

1013 

1014 if self.embed and self.format not in self._ACCEPTABLE_EMBEDDINGS: 

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

1016 if self.embed: 

1017 self._mimetype = self._MIMETYPES.get(self.format) 

1018 

1019 self.width = width 

1020 self.height = height 

1021 self.retina = retina 

1022 self.unconfined = unconfined 

1023 self.alt = alt 

1024 super(Image, self).__init__(data=data, url=url, filename=filename, 

1025 metadata=metadata) 

1026 

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

1028 self.width = metadata['width'] 

1029 

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

1031 self.height = metadata['height'] 

1032 

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

1034 self.alt = metadata["alt"] 

1035 

1036 if retina: 

1037 self._retina_shape() 

1038 

1039 

1040 def _retina_shape(self): 

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

1042 if not self.embed: 

1043 return 

1044 if self.format == self._FMT_PNG: 

1045 w, h = _pngxy(self.data) 

1046 elif self.format == self._FMT_JPEG: 

1047 w, h = _jpegxy(self.data) 

1048 elif self.format == self._FMT_GIF: 

1049 w, h = _gifxy(self.data) 

1050 else: 

1051 # retina only supports png 

1052 return 

1053 self.width = w // 2 

1054 self.height = h // 2 

1055 

1056 def reload(self): 

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

1058 if self.embed: 

1059 super(Image,self).reload() 

1060 if self.retina: 

1061 self._retina_shape() 

1062 

1063 def _repr_html_(self): 

1064 if not self.embed: 

1065 width = height = klass = alt = "" 

1066 if self.width: 

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

1068 if self.height: 

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

1070 if self.unconfined: 

1071 klass = ' class="unconfined"' 

1072 if self.alt: 

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

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

1075 url=self.url, 

1076 width=width, 

1077 height=height, 

1078 klass=klass, 

1079 alt=alt, 

1080 ) 

1081 

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

1083 """Return the image as a mimebundle 

1084 

1085 Any new mimetype support should be implemented here. 

1086 """ 

1087 if self.embed: 

1088 mimetype = self._mimetype 

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

1090 if metadata: 

1091 metadata = {mimetype: metadata} 

1092 return {mimetype: data}, metadata 

1093 else: 

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

1095 

1096 def _data_and_metadata(self, always_both=False): 

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

1098 try: 

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

1100 except TypeError as e: 

1101 raise FileNotFoundError( 

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

1103 md = {} 

1104 if self.metadata: 

1105 md.update(self.metadata) 

1106 if self.width: 

1107 md['width'] = self.width 

1108 if self.height: 

1109 md['height'] = self.height 

1110 if self.unconfined: 

1111 md['unconfined'] = self.unconfined 

1112 if self.alt: 

1113 md["alt"] = self.alt 

1114 if md or always_both: 

1115 return b64_data, md 

1116 else: 

1117 return b64_data 

1118 

1119 def _repr_png_(self): 

1120 if self.embed and self.format == self._FMT_PNG: 

1121 return self._data_and_metadata() 

1122 

1123 def _repr_jpeg_(self): 

1124 if self.embed and self.format == self._FMT_JPEG: 

1125 return self._data_and_metadata() 

1126 

1127 def _find_ext(self, s): 

1128 base, ext = splitext(s) 

1129 

1130 if not ext: 

1131 return base 

1132 

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

1134 return ext[1:].lower() 

1135 

1136 

1137class Video(DisplayObject): 

1138 

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

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

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

1142 

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

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

1145 in the frontend. 

1146 

1147 Parameters 

1148 ---------- 

1149 data : unicode, str or bytes 

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

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

1152 

1153 url : unicode 

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

1155 the image data will not be embedded. 

1156 

1157 filename : unicode 

1158 Path to a local file containing the video. 

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

1160 

1161 embed : bool 

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

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

1164 

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

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

1167 

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

1169 

1170 Video('./video.mp4') 

1171 

1172 mimetype : unicode 

1173 Specify the mimetype for embedded videos. 

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

1175 

1176 width : int 

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

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

1179 

1180 height : int 

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

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

1183 

1184 html_attributes : str 

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

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

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

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

1189 

1190 Examples 

1191 -------- 

1192 :: 

1193 

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

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

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

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

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

1199 """ 

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

1201 data = str(data) 

1202 

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

1204 url = data 

1205 data = None 

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

1207 filename = data 

1208 data = None 

1209 

1210 if data and not embed: 

1211 msg = ''.join([ 

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

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

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

1215 ]) 

1216 raise ValueError(msg) 

1217 

1218 self.mimetype = mimetype 

1219 self.embed = embed 

1220 self.width = width 

1221 self.height = height 

1222 self.html_attributes = html_attributes 

1223 super(Video, self).__init__(data=data, url=url, filename=filename) 

1224 

1225 def _repr_html_(self): 

1226 width = height = '' 

1227 if self.width: 

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

1229 if self.height: 

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

1231 

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

1233 # notebook output. 

1234 if not self.embed: 

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

1236 output = """<video src="{0}" {1} {2} {3}> 

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

1238 </video>""".format(url, self.html_attributes, width, height) 

1239 return output 

1240 

1241 # Embedded videos are base64-encoded. 

1242 mimetype = self.mimetype 

1243 if self.filename is not None: 

1244 if not mimetype: 

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

1246 

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

1248 video = f.read() 

1249 else: 

1250 video = self.data 

1251 if isinstance(video, str): 

1252 # unicode input is already b64-encoded 

1253 b64_video = video 

1254 else: 

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

1256 

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

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

1259 Your browser does not support the video tag. 

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

1261 return output 

1262 

1263 def reload(self): 

1264 # TODO 

1265 pass