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 {extras} 

275 ></iframe> 

276 """ 

277 

278 def __init__( 

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

280 ): 

281 if extras is None: 

282 extras = [] 

283 

284 self.src = src 

285 self.width = width 

286 self.height = height 

287 self.extras = extras 

288 self.params = kwargs 

289 

290 def _repr_html_(self): 

291 """return the embed iframe""" 

292 from html import escape as html_escape 

293 

294 if self.params: 

295 from urllib.parse import urlencode 

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

297 else: 

298 params = "" 

299 return self.iframe.format( 

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

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

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

303 params=params, 

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

305 ) 

306 

307 

308class YouTubeVideo(IFrame): 

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

310 

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

312 do:: 

313 

314 vid = YouTubeVideo("foo") 

315 display(vid) 

316 

317 To start from 30 seconds:: 

318 

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

320 display(vid) 

321 

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

323 :class:`datetime.timedelta`:: 

324 

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

326 

327 Other parameters can be provided as documented at 

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

329 

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

331 will be inserted in the document. 

332 """ 

333 

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

335 self.id=id 

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

337 if allow_autoplay: 

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

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

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

341 

342 def _repr_jpeg_(self): 

343 # Deferred import 

344 from urllib.request import urlopen 

345 

346 try: 

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

348 except OSError: 

349 return None 

350 

351class VimeoVideo(IFrame): 

352 """ 

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

354 """ 

355 

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

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

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

359 

360class ScribdDocument(IFrame): 

361 """ 

362 Class for embedding a Scribd document in an IPython session 

363 

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

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

366 

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

368 

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

370 """ 

371 

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

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

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

375 

376class FileLink: 

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

378 

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

380 

381 you would do:: 

382 

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

384 display(local_file) 

385 

386 or in the HTML notebook, just:: 

387 

388 FileLink("my/data.txt") 

389 """ 

390 

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

392 

393 def __init__(self, 

394 path, 

395 url_prefix='', 

396 result_html_prefix='', 

397 result_html_suffix='<br>'): 

398 """ 

399 Parameters 

400 ---------- 

401 path : str 

402 path to the file or directory that should be formatted 

403 url_prefix : str 

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

405 ''] 

406 result_html_prefix : str 

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

408 result_html_suffix : str 

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

410 """ 

411 if isdir(path): 

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

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

414 self.path = fsdecode(path) 

415 self.url_prefix = url_prefix 

416 self.result_html_prefix = result_html_prefix 

417 self.result_html_suffix = result_html_suffix 

418 

419 def _format_path(self): 

420 from html import escape as html_escape 

421 

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

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

424 self.html_link_str % \ 

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

426 self.result_html_suffix]) 

427 

428 def _repr_html_(self): 

429 """return html link to file 

430 """ 

431 if not exists(self.path): 

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

433 "It may still be in the process of " 

434 "being generated, or you may have the " 

435 "incorrect path." % self.path) 

436 

437 return self._format_path() 

438 

439 def __repr__(self): 

440 """return absolute path to file 

441 """ 

442 return abspath(self.path) 

443 

444class FileLinks(FileLink): 

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

446 

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

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

449 

450 local_files = FileLinks("my/data") 

451 display(local_files) 

452 

453 or in the HTML notebook, just:: 

454 

455 FileLinks("my/data") 

456 """ 

457 def __init__(self, 

458 path, 

459 url_prefix='', 

460 included_suffixes=None, 

461 result_html_prefix='', 

462 result_html_suffix='<br>', 

463 notebook_display_formatter=None, 

464 terminal_display_formatter=None, 

465 recursive=True): 

466 """ 

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

468 ``result_html_prefix`` and ``result_html_suffix`` parameters. 

469 

470 included_suffixes : list 

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

472 all files] 

473 

474 notebook_display_formatter : function 

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

476 formatter functions below. 

477 

478 terminal_display_formatter : function 

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

480 formatter functions below. 

481 

482 Formatter functions must be of the form:: 

483 

484 f(dirname, fnames, included_suffixes) 

485 

486 dirname : str 

487 The name of a directory 

488 fnames : list 

489 The files in that directory 

490 included_suffixes : list 

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

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

493 recursive : boolean 

494 Whether to recurse into subdirectories. Default is True. 

495 

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

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

498 passing terminal_display_formatter). This function is iterated over for 

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

500 passed here to support alternative formatting. 

501 

502 """ 

503 if isfile(path): 

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

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

506 self.included_suffixes = included_suffixes 

507 # remove trailing slashes for more consistent output formatting 

508 path = path.rstrip('/') 

509 

510 self.path = path 

511 self.url_prefix = url_prefix 

512 self.result_html_prefix = result_html_prefix 

513 self.result_html_suffix = result_html_suffix 

514 

515 self.notebook_display_formatter = \ 

516 notebook_display_formatter or self._get_notebook_display_formatter() 

517 self.terminal_display_formatter = \ 

518 terminal_display_formatter or self._get_terminal_display_formatter() 

519 

520 self.recursive = recursive 

521 

522 def _get_display_formatter( 

523 self, 

524 dirname_output_format, 

525 fname_output_format, 

526 fp_format, 

527 fp_cleaner=None, 

528 escape_names=False, 

529 ): 

530 """generate built-in formatter function 

531 

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

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

534 

535 dirname_output_format: string to use for formatting directory 

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

537 must appear in this string 

538 fname_output_format: string to use for formatting file names, 

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

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

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

542 second 

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

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

545 and fname will be substituted for the second 

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

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

548 """ 

549 if escape_names: 

550 from html import escape as html_escape 

551 

552 escape = html_escape 

553 else: 

554 escape = str 

555 

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

557 result = [] 

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

559 # are going to be displayed 

560 display_fnames = [] 

561 for fname in fnames: 

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

563 (included_suffixes is None or 

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

565 display_fnames.append(fname) 

566 

567 if len(display_fnames) == 0: 

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

569 # (not even the directory name) 

570 pass 

571 else: 

572 # otherwise print the formatted directory name followed by 

573 # the formatted filenames 

574 dirname_output_line = dirname_output_format % escape(dirname) 

575 result.append(dirname_output_line) 

576 for fname in display_fnames: 

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

578 if fp_cleaner is not None: 

579 fp = fp_cleaner(fp) 

580 try: 

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

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

583 except TypeError: 

584 # ... or just a single filepath 

585 fname_output_line = fname_output_format % escape(fname) 

586 result.append(fname_output_line) 

587 return result 

588 return f 

589 

590 def _get_notebook_display_formatter(self, 

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

592 """ generate function to use for notebook formatting 

593 """ 

594 dirname_output_format = \ 

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

596 fname_output_format = \ 

597 self.result_html_prefix + spacer + self.html_link_str + self.result_html_suffix 

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

599 if sep == "\\": 

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

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

602 def fp_cleaner(fp): 

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

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

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

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

607 else: 

608 fp_cleaner = None 

609 

610 return self._get_display_formatter( 

611 dirname_output_format, 

612 fname_output_format, 

613 fp_format, 

614 fp_cleaner, 

615 escape_names=True, 

616 ) 

617 

618 def _get_terminal_display_formatter(self, 

619 spacer=" "): 

620 """ generate function to use for terminal formatting 

621 """ 

622 dirname_output_format = "%s/" 

623 fname_output_format = spacer + "%s" 

624 fp_format = '%s/%s' 

625 

626 return self._get_display_formatter(dirname_output_format, 

627 fname_output_format, 

628 fp_format) 

629 

630 def _format_path(self): 

631 result_lines = [] 

632 if self.recursive: 

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

634 else: 

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

636 walked_dir.sort() 

637 for dirname, subdirs, fnames in walked_dir: 

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

639 return '\n'.join(result_lines) 

640 

641 def __repr__(self): 

642 """return newline-separated absolute paths 

643 """ 

644 result_lines = [] 

645 if self.recursive: 

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

647 else: 

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

649 walked_dir.sort() 

650 for dirname, subdirs, fnames in walked_dir: 

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

652 return '\n'.join(result_lines) 

653 

654 

655class Code(TextDisplayObject): 

656 """Display syntax-highlighted source code. 

657 

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

659 

660 Parameters 

661 ---------- 

662 data : str 

663 The code as a string 

664 url : str 

665 A URL to fetch the code from 

666 filename : str 

667 A local filename to load the code from 

668 language : str 

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

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

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

672 """ 

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

674 self.language = language 

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

676 

677 def _get_lexer(self): 

678 if self.language: 

679 from pygments.lexers import get_lexer_by_name 

680 return get_lexer_by_name(self.language) 

681 elif self.filename: 

682 from pygments.lexers import get_lexer_for_filename 

683 return get_lexer_for_filename(self.filename) 

684 else: 

685 from pygments.lexers import guess_lexer 

686 return guess_lexer(self.data) 

687 

688 def __repr__(self): 

689 return self.data 

690 

691 def _repr_html_(self): 

692 from pygments import highlight 

693 from pygments.formatters import HtmlFormatter 

694 fmt = HtmlFormatter() 

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

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

697 

698 def _repr_latex_(self): 

699 from pygments import highlight 

700 from pygments.formatters import LatexFormatter 

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