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

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

266 statements  

1"""Various display related classes. 

2 

3Authors : MinRK, gregcaporaso, dannystaple 

4""" 

5from os.path import exists, isfile, splitext, abspath, join, isdir 

6from os import walk, sep, fsdecode 

7 

8from IPython.core.display import DisplayObject, TextDisplayObject 

9 

10from collections.abc import Iterable 

11 

12__all__ = ['Audio', 'IFrame', 'YouTubeVideo', 'VimeoVideo', 'ScribdDocument', 

13 'FileLink', 'FileLinks', 'Code'] 

14 

15 

16class Audio(DisplayObject): 

17 """Create an audio object. 

18 

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

20 display function, it will result in Audio controls being displayed 

21 in the frontend (only works in the notebook). 

22 

23 Parameters 

24 ---------- 

25 data : numpy array, list, unicode, str or bytes 

26 Can be one of 

27 

28 * Numpy 1d array containing the desired waveform (mono) 

29 * Numpy 2d array containing waveforms for each channel. 

30 Shape=(NCHAN, NSAMPLES). For the standard channel order, see 

31 http://msdn.microsoft.com/en-us/library/windows/hardware/dn653308(v=vs.85).aspx 

32 * List of float or integer representing the waveform (mono) 

33 * String containing the filename 

34 * Bytestring containing raw PCM data or 

35 * URL pointing to a file on the web. 

36 

37 If the array option is used, the waveform will be normalized. 

38 

39 If a filename or url is used, the format support will be browser 

40 dependent. 

41 url : unicode 

42 A URL to download the data from. 

43 filename : unicode 

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

45 embed : boolean 

46 Should the audio data be embedded using a data URI (True) or should 

47 the original source be referenced. Set this to True if you want the 

48 audio to playable later with no internet connection in the notebook. 

49 

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

51 default value is `False`. 

52 rate : integer 

53 The sampling rate of the raw data. 

54 Only required when data parameter is being used as an array 

55 autoplay : bool 

56 Set to True if the audio should immediately start playing. 

57 Default is `False`. 

58 normalize : bool 

59 Whether audio should be normalized (rescaled) to the maximum possible 

60 range. Default is `True`. When set to `False`, `data` must be between 

61 -1 and 1 (inclusive), otherwise an error is raised. 

62 Applies only when `data` is a list or array of samples; other types of 

63 audio are never normalized. 

64 

65 Examples 

66 -------- 

67 

68 >>> import pytest 

69 >>> np = pytest.importorskip("numpy") 

70 

71 Generate a sound 

72 

73 >>> import numpy as np 

74 >>> framerate = 44100 

75 >>> t = np.linspace(0,5,framerate*5) 

76 >>> data = np.sin(2*np.pi*220*t) + np.sin(2*np.pi*224*t) 

77 >>> Audio(data, rate=framerate) 

78 <IPython.lib.display.Audio object> 

79 

80 Can also do stereo or more channels 

81 

82 >>> dataleft = np.sin(2*np.pi*220*t) 

83 >>> dataright = np.sin(2*np.pi*224*t) 

84 >>> Audio([dataleft, dataright], rate=framerate) 

85 <IPython.lib.display.Audio object> 

86 

87 From URL: 

88 

89 >>> Audio("http://www.nch.com.au/acm/8k16bitpcm.wav") # doctest: +SKIP 

90 >>> Audio(url="http://www.w3schools.com/html/horse.ogg") # doctest: +SKIP 

91 

92 From a File: 

93 

94 >>> Audio('IPython/lib/tests/test.wav') # doctest: +SKIP 

95 >>> Audio(filename='IPython/lib/tests/test.wav') # doctest: +SKIP 

96 

97 From Bytes: 

98 

99 >>> Audio(b'RAW_WAV_DATA..') # doctest: +SKIP 

100 >>> Audio(data=b'RAW_WAV_DATA..') # doctest: +SKIP 

101 

102 See Also 

103 -------- 

104 ipywidgets.Audio 

105 

106 Audio widget with more more flexibility and options. 

107 

108 """ 

109 _read_flags = 'rb' 

110 

111 def __init__(self, data=None, filename=None, url=None, embed=None, rate=None, autoplay=False, normalize=True, *, 

112 element_id=None): 

113 if filename is None and url is None and data is None: 

114 raise ValueError("No audio data found. Expecting filename, url, or data.") 

115 if embed is False and url is None: 

116 raise ValueError("No url found. Expecting url when embed=False") 

117 

118 if url is not None and embed is not True: 

119 self.embed = False 

120 else: 

121 self.embed = True 

122 self.autoplay = autoplay 

123 self.element_id = element_id 

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

125 

126 if self.data is not None and not isinstance(self.data, bytes): 

127 if rate is None: 

128 raise ValueError("rate must be specified when data is a numpy array or list of audio samples.") 

129 self.data = Audio._make_wav(data, rate, normalize) 

130 

131 def reload(self): 

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

133 import mimetypes 

134 if self.embed: 

135 super().reload() 

136 

137 if self.filename is not None: 

138 self.mimetype = mimetypes.guess_type(self.filename)[0] 

139 elif self.url is not None: 

140 self.mimetype = mimetypes.guess_type(self.url)[0] 

141 else: 

142 self.mimetype = "audio/wav" 

143 

144 @staticmethod 

145 def _make_wav(data, rate, normalize): 

146 """ Transform a numpy array to a PCM bytestring """ 

147 from io import BytesIO 

148 import wave 

149 

150 try: 

151 scaled, nchan = Audio._validate_and_normalize_with_numpy(data, normalize) 

152 except ImportError: 

153 scaled, nchan = Audio._validate_and_normalize_without_numpy(data, normalize) 

154 

155 fp = BytesIO() 

156 waveobj = wave.open(fp,mode='wb') 

157 waveobj.setnchannels(nchan) 

158 waveobj.setframerate(rate) 

159 waveobj.setsampwidth(2) 

160 waveobj.setcomptype('NONE','NONE') 

161 waveobj.writeframes(scaled) 

162 val = fp.getvalue() 

163 waveobj.close() 

164 

165 return val 

166 

167 @staticmethod 

168 def _validate_and_normalize_with_numpy(data, normalize) -> tuple[bytes, int]: 

169 import numpy as np 

170 

171 data = np.array(data, dtype=float) 

172 if len(data.shape) == 1: 

173 nchan = 1 

174 elif len(data.shape) == 2: 

175 # In wave files,channels are interleaved. E.g., 

176 # "L1R1L2R2..." for stereo. See 

177 # http://msdn.microsoft.com/en-us/library/windows/hardware/dn653308(v=vs.85).aspx 

178 # for channel ordering 

179 nchan = data.shape[0] 

180 data = data.T.ravel() 

181 else: 

182 raise ValueError('Array audio input must be a 1D or 2D array') 

183 

184 max_abs_value = np.max(np.abs(data)) 

185 normalization_factor = Audio._get_normalization_factor(max_abs_value, normalize) 

186 scaled = data / normalization_factor * 32767 

187 return scaled.astype("<h").tobytes(), nchan 

188 

189 @staticmethod 

190 def _validate_and_normalize_without_numpy(data, normalize): 

191 import array 

192 import sys 

193 

194 data = array.array('f', data) 

195 

196 try: 

197 max_abs_value = float(max([abs(x) for x in data])) 

198 except TypeError as e: 

199 raise TypeError('Only lists of mono audio are ' 

200 'supported if numpy is not installed') from e 

201 

202 normalization_factor = Audio._get_normalization_factor(max_abs_value, normalize) 

203 scaled = array.array('h', [int(x / normalization_factor * 32767) for x in data]) 

204 if sys.byteorder == 'big': 

205 scaled.byteswap() 

206 nchan = 1 

207 return scaled.tobytes(), nchan 

208 

209 @staticmethod 

210 def _get_normalization_factor(max_abs_value, normalize): 

211 if not normalize and max_abs_value > 1: 

212 raise ValueError('Audio data must be between -1 and 1 when normalize=False.') 

213 return max_abs_value if normalize else 1 

214 

215 def _data_and_metadata(self): 

216 """shortcut for returning metadata with url information, if defined""" 

217 md = {} 

218 if self.url: 

219 md['url'] = self.url 

220 if md: 

221 return self.data, md 

222 else: 

223 return self.data 

224 

225 def _repr_html_(self): 

226 src = """ 

227 <audio {element_id} controls="controls" {autoplay}> 

228 <source src="{src}" type="{type}" /> 

229 Your browser does not support the audio element. 

230 </audio> 

231 """ 

232 return src.format(src=self.src_attr(), type=self.mimetype, autoplay=self.autoplay_attr(), 

233 element_id=self.element_id_attr()) 

234 

235 def src_attr(self): 

236 import base64 

237 if self.embed and (self.data is not None): 

238 data = base64=base64.b64encode(self.data).decode('ascii') 

239 return """data:{type};base64,{base64}""".format(type=self.mimetype, 

240 base64=data) 

241 elif self.url is not None: 

242 from html import escape as html_escape 

243 

244 return html_escape(self.url) 

245 else: 

246 return "" 

247 

248 def autoplay_attr(self): 

249 if(self.autoplay): 

250 return 'autoplay="autoplay"' 

251 else: 

252 return '' 

253 

254 def element_id_attr(self): 

255 if (self.element_id): 

256 from html import escape as html_escape 

257 

258 return f'id="{html_escape(self.element_id)}"' 

259 else: 

260 return '' 

261 

262class IFrame: 

263 """ 

264 Generic class to embed an iframe in an IPython notebook 

265 """ 

266 

267 iframe = """ 

268 <iframe 

269 width="{width}" 

270 height="{height}" 

271 src="{src}{params}" 

272 frameborder="0" 

273 allowfullscreen 

274 referrerpolicy="strict-origin-when-cross-origin" 

275 {extras} 

276 ></iframe> 

277 """ 

278 

279 def __init__( 

280 self, src, width, height, extras: Iterable[str] | None = None, **kwargs 

281 ): 

282 if extras is None: 

283 extras = [] 

284 

285 self.src = src 

286 self.width = width 

287 self.height = height 

288 self.extras = extras 

289 self.params = kwargs 

290 

291 def _repr_html_(self): 

292 """return the embed iframe""" 

293 from html import escape as html_escape 

294 

295 if self.params: 

296 from urllib.parse import urlencode 

297 params = "?" + urlencode(self.params) 

298 else: 

299 params = "" 

300 return self.iframe.format( 

301 src=html_escape(str(self.src)), 

302 width=html_escape(str(self.width)), 

303 height=html_escape(str(self.height)), 

304 params=params, 

305 extras=" ".join(self.extras), 

306 ) 

307 

308 

309class YouTubeVideo(IFrame): 

310 """Class for embedding a YouTube Video in an IPython session, based on its video id. 

311 

312 e.g. to embed the video from https://www.youtube.com/watch?v=foo , you would 

313 do:: 

314 

315 vid = YouTubeVideo("foo") 

316 display(vid) 

317 

318 To start from 30 seconds:: 

319 

320 vid = YouTubeVideo("abc", start=30) 

321 display(vid) 

322 

323 To calculate seconds from time as hours, minutes, seconds use 

324 :class:`datetime.timedelta`:: 

325 

326 start=int(timedelta(hours=1, minutes=46, seconds=40).total_seconds()) 

327 

328 Other parameters can be provided as documented at 

329 https://developers.google.com/youtube/player_parameters#Parameters 

330 

331 When converting the notebook using nbconvert, a jpeg representation of the video 

332 will be inserted in the document. 

333 """ 

334 

335 def __init__(self, id, width=400, height=300, allow_autoplay=False, **kwargs): 

336 self.id=id 

337 src = f"https://www.youtube.com/embed/{id}" 

338 if allow_autoplay: 

339 extras = list(kwargs.get("extras", [])) + ['allow="autoplay"'] 

340 kwargs.update(autoplay=1, extras=extras) 

341 super().__init__(src, width, height, **kwargs) 

342 

343 def _repr_jpeg_(self): 

344 # Deferred import 

345 from urllib.request import urlopen 

346 

347 try: 

348 return urlopen(f"https://img.youtube.com/vi/{self.id}/hqdefault.jpg").read() 

349 except OSError: 

350 return None 

351 

352class VimeoVideo(IFrame): 

353 """ 

354 Class for embedding a Vimeo video in an IPython session, based on its video id. 

355 """ 

356 

357 def __init__(self, id, width=400, height=300, **kwargs): 

358 src=f"https://player.vimeo.com/video/{id}" 

359 super().__init__(src, width, height, **kwargs) 

360 

361class ScribdDocument(IFrame): 

362 """ 

363 Class for embedding a Scribd document in an IPython session 

364 

365 Use the start_page params to specify a starting point in the document 

366 Use the view_mode params to specify display type one off scroll | slideshow | book 

367 

368 e.g to Display Wes' foundational paper about PANDAS in book mode from page 3 

369 

370 ScribdDocument(71048089, width=800, height=400, start_page=3, view_mode="book") 

371 """ 

372 

373 def __init__(self, id, width=400, height=300, **kwargs): 

374 src=f"https://www.scribd.com/embeds/{id}/content" 

375 super().__init__(src, width, height, **kwargs) 

376 

377class FileLink: 

378 """Class for embedding a local file link in an IPython session, based on path 

379 

380 e.g. to embed a link that was generated in the IPython notebook as my/data.txt 

381 

382 you would do:: 

383 

384 local_file = FileLink("my/data.txt") 

385 display(local_file) 

386 

387 or in the HTML notebook, just:: 

388 

389 FileLink("my/data.txt") 

390 """ 

391 

392 html_link_str = "<a href='%s' target='_blank'>%s</a>" 

393 

394 def __init__(self, 

395 path, 

396 url_prefix='', 

397 result_html_prefix='', 

398 result_html_suffix='<br>'): 

399 """ 

400 Parameters 

401 ---------- 

402 path : str or os.PathLike 

403 path to the file or directory that should be formatted 

404 url_prefix : str 

405 prefix to be prepended to all files to form a working link [default: 

406 ''] 

407 result_html_prefix : str 

408 text to append to beginning to link [default: ''] 

409 result_html_suffix : str 

410 text to append at the end of link [default: '<br>'] 

411 """ 

412 if isdir(path): 

413 raise ValueError("Cannot display a directory using FileLink. " 

414 "Use FileLinks to display '%s'." % path) 

415 self.path = fsdecode(path) 

416 self.url_prefix = url_prefix 

417 self.result_html_prefix = result_html_prefix 

418 self.result_html_suffix = result_html_suffix 

419 

420 def _format_path(self): 

421 from html import escape as html_escape 

422 

423 fp = ''.join([self.url_prefix, html_escape(self.path)]) 

424 return ''.join([self.result_html_prefix, 

425 self.html_link_str % \ 

426 (fp, html_escape(self.path, quote=False)), 

427 self.result_html_suffix]) 

428 

429 def _repr_html_(self): 

430 """return html link to file 

431 """ 

432 if not exists(self.path): 

433 return ("Path (<tt>%s</tt>) doesn't exist. " 

434 "It may still be in the process of " 

435 "being generated, or you may have the " 

436 "incorrect path." % self.path) 

437 

438 return self._format_path() 

439 

440 def __repr__(self): 

441 """return absolute path to file 

442 """ 

443 return abspath(self.path) 

444 

445class FileLinks(FileLink): 

446 """Class for embedding local file links in an IPython session, based on path 

447 

448 e.g. to embed links to files that were generated in the IPython notebook 

449 under ``my/data``, you would do:: 

450 

451 local_files = FileLinks("my/data") 

452 display(local_files) 

453 

454 or in the HTML notebook, just:: 

455 

456 FileLinks("my/data") 

457 """ 

458 def __init__(self, 

459 path, 

460 url_prefix='', 

461 included_suffixes=None, 

462 result_html_prefix='', 

463 result_html_suffix='<br>', 

464 notebook_display_formatter=None, 

465 terminal_display_formatter=None, 

466 recursive=True): 

467 """ 

468 See :class:`FileLink` for the ``path``, ``url_prefix``, 

469 ``result_html_prefix`` and ``result_html_suffix`` parameters. 

470 

471 included_suffixes : list 

472 Filename suffixes to include when formatting output [default: include 

473 all files] 

474 

475 notebook_display_formatter : function 

476 Used to format links for display in the notebook. See discussion of 

477 formatter functions below. 

478 

479 terminal_display_formatter : function 

480 Used to format links for display in the terminal. See discussion of 

481 formatter functions below. 

482 

483 Formatter functions must be of the form:: 

484 

485 f(dirname, fnames, included_suffixes) 

486 

487 dirname : str 

488 The name of a directory 

489 fnames : list 

490 The files in that directory 

491 included_suffixes : list 

492 The file suffixes that should be included in the output (passing None 

493 meansto include all suffixes in the output in the built-in formatters) 

494 recursive : boolean 

495 Whether to recurse into subdirectories. Default is True. 

496 

497 The function should return a list of lines that will be printed in the 

498 notebook (if passing notebook_display_formatter) or the terminal (if 

499 passing terminal_display_formatter). This function is iterated over for 

500 each directory in self.path. Default formatters are in place, can be 

501 passed here to support alternative formatting. 

502 

503 """ 

504 if isfile(path): 

505 raise ValueError("Cannot display a file using FileLinks. " 

506 "Use FileLink to display '%s'." % path) 

507 self.included_suffixes = included_suffixes 

508 # remove trailing slashes for more consistent output formatting 

509 path = fsdecode(path).rstrip("/") 

510 

511 self.path = path 

512 self.url_prefix = url_prefix 

513 self.result_html_prefix = result_html_prefix 

514 self.result_html_suffix = result_html_suffix 

515 

516 self.notebook_display_formatter = \ 

517 notebook_display_formatter or self._get_notebook_display_formatter() 

518 self.terminal_display_formatter = \ 

519 terminal_display_formatter or self._get_terminal_display_formatter() 

520 

521 self.recursive = recursive 

522 

523 def _get_display_formatter( 

524 self, 

525 dirname_output_format, 

526 fname_output_format, 

527 fp_format, 

528 fp_cleaner=None, 

529 escape_names=False, 

530 ): 

531 """generate built-in formatter function 

532 

533 this is used to define both the notebook and terminal built-in 

534 formatters as they only differ by some wrapper text for each entry 

535 

536 dirname_output_format: string to use for formatting directory 

537 names, dirname will be substituted for a single "%s" which 

538 must appear in this string 

539 fname_output_format: string to use for formatting file names, 

540 if a single "%s" appears in the string, fname will be substituted 

541 if two "%s" appear in the string, the path to fname will be 

542 substituted for the first and fname will be substituted for the 

543 second 

544 fp_format: string to use for formatting filepaths, must contain 

545 exactly two "%s" and the dirname will be substituted for the first 

546 and fname will be substituted for the second 

547 escape_names: whether directory and file names must be HTML-escaped 

548 before being substituted, as they are for the notebook formatter 

549 """ 

550 if escape_names: 

551 from html import escape as html_escape 

552 

553 escape = html_escape 

554 else: 

555 escape = str 

556 

557 def f(dirname, fnames, included_suffixes=None): 

558 result = [] 

559 # begin by figuring out which filenames, if any, 

560 # are going to be displayed 

561 display_fnames = [] 

562 for fname in fnames: 

563 if (isfile(join(dirname,fname)) and 

564 (included_suffixes is None or 

565 splitext(fname)[1] in included_suffixes)): 

566 display_fnames.append(fname) 

567 

568 if len(display_fnames) == 0: 

569 # if there are no filenames to display, don't print anything 

570 # (not even the directory name) 

571 pass 

572 else: 

573 # otherwise print the formatted directory name followed by 

574 # the formatted filenames 

575 dirname_output_line = dirname_output_format % escape(dirname) 

576 result.append(dirname_output_line) 

577 for fname in display_fnames: 

578 fp = fp_format % (escape(dirname), escape(fname)) 

579 if fp_cleaner is not None: 

580 fp = fp_cleaner(fp) 

581 try: 

582 # output can include both a filepath and a filename... 

583 fname_output_line = fname_output_format % (fp, escape(fname)) 

584 except TypeError: 

585 # ... or just a single filepath 

586 fname_output_line = fname_output_format % escape(fname) 

587 result.append(fname_output_line) 

588 return result 

589 return f 

590 

591 def _get_notebook_display_formatter(self, 

592 spacer="&nbsp;&nbsp;"): 

593 """ generate function to use for notebook formatting 

594 """ 

595 dirname_output_format = \ 

596 self.result_html_prefix + "%s/" + self.result_html_suffix 

597 fname_output_format = \ 

598 self.result_html_prefix + spacer + self.html_link_str + self.result_html_suffix 

599 fp_format = self.url_prefix + '%s/%s' 

600 if sep == "\\": 

601 # Working on a platform where the path separator is "\", so 

602 # must convert these to "/" for generating a URI 

603 def fp_cleaner(fp): 

604 # Replace all occurrences of backslash ("\") with a forward 

605 # slash ("/") - this is necessary on windows when a path is 

606 # provided as input, but we must link to a URI 

607 return fp.replace('\\','/') 

608 else: 

609 fp_cleaner = None 

610 

611 return self._get_display_formatter( 

612 dirname_output_format, 

613 fname_output_format, 

614 fp_format, 

615 fp_cleaner, 

616 escape_names=True, 

617 ) 

618 

619 def _get_terminal_display_formatter(self, 

620 spacer=" "): 

621 """ generate function to use for terminal formatting 

622 """ 

623 dirname_output_format = "%s/" 

624 fname_output_format = spacer + "%s" 

625 fp_format = '%s/%s' 

626 

627 return self._get_display_formatter(dirname_output_format, 

628 fname_output_format, 

629 fp_format) 

630 

631 def _format_path(self): 

632 result_lines = [] 

633 if self.recursive: 

634 walked_dir = list(walk(self.path)) 

635 else: 

636 walked_dir = [next(walk(self.path))] 

637 walked_dir.sort() 

638 for dirname, subdirs, fnames in walked_dir: 

639 result_lines += self.notebook_display_formatter(dirname, fnames, self.included_suffixes) 

640 return '\n'.join(result_lines) 

641 

642 def __repr__(self): 

643 """return newline-separated absolute paths 

644 """ 

645 result_lines = [] 

646 if self.recursive: 

647 walked_dir = list(walk(self.path)) 

648 else: 

649 walked_dir = [next(walk(self.path))] 

650 walked_dir.sort() 

651 for dirname, subdirs, fnames in walked_dir: 

652 result_lines += self.terminal_display_formatter(dirname, fnames, self.included_suffixes) 

653 return '\n'.join(result_lines) 

654 

655 

656class Code(TextDisplayObject): 

657 """Display syntax-highlighted source code. 

658 

659 This uses Pygments to highlight the code for HTML and Latex output. 

660 

661 Parameters 

662 ---------- 

663 data : str 

664 The code as a string 

665 url : str 

666 A URL to fetch the code from 

667 filename : str 

668 A local filename to load the code from 

669 language : str 

670 The short name of a Pygments lexer to use for highlighting. 

671 If not specified, it will guess the lexer based on the filename 

672 or the code. Available lexers: http://pygments.org/docs/lexers/ 

673 """ 

674 def __init__(self, data=None, url=None, filename=None, language=None): 

675 self.language = language 

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

677 

678 def _get_lexer(self): 

679 if self.language: 

680 from pygments.lexers import get_lexer_by_name 

681 return get_lexer_by_name(self.language) 

682 elif self.filename: 

683 from pygments.lexers import get_lexer_for_filename 

684 return get_lexer_for_filename(self.filename) 

685 else: 

686 from pygments.lexers import guess_lexer 

687 return guess_lexer(self.data) 

688 

689 def __repr__(self): 

690 return self.data 

691 

692 def _repr_html_(self): 

693 from pygments import highlight 

694 from pygments.formatters import HtmlFormatter 

695 fmt = HtmlFormatter() 

696 style = '<style>{}</style>'.format(fmt.get_style_defs('.output_html')) 

697 return style + highlight(self.data, self._get_lexer(), fmt) 

698 

699 def _repr_latex_(self): 

700 from pygments import highlight 

701 from pygments.formatters import LatexFormatter 

702 return highlight(self.data, self._get_lexer(), LatexFormatter())