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

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

260 statements  

1"""Various display related classes. 

2 

3Authors : MinRK, gregcaporaso, dannystaple 

4""" 

5from html import escape as html_escape 

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

7from os import walk, sep, fsdecode 

8 

9from IPython.core.display import DisplayObject, TextDisplayObject 

10 

11from collections.abc import Iterable 

12 

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

14 'FileLink', 'FileLinks', 'Code'] 

15 

16 

17class Audio(DisplayObject): 

18 """Create an audio object. 

19 

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

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

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

23 

24 Parameters 

25 ---------- 

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

27 Can be one of 

28 

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

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

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

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

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

34 * String containing the filename 

35 * Bytestring containing raw PCM data or 

36 * URL pointing to a file on the web. 

37 

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

39 

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

41 dependent. 

42 url : unicode 

43 A URL to download the data from. 

44 filename : unicode 

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

46 embed : boolean 

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

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

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

50 

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

52 default value is `False`. 

53 rate : integer 

54 The sampling rate of the raw data. 

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

56 autoplay : bool 

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

58 Default is `False`. 

59 normalize : bool 

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

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

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

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

64 audio are never normalized. 

65 

66 Examples 

67 -------- 

68 

69 >>> import pytest 

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

71 

72 Generate a sound 

73 

74 >>> import numpy as np 

75 >>> framerate = 44100 

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

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

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

79 <IPython.lib.display.Audio object> 

80 

81 Can also do stereo or more channels 

82 

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

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

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

86 <IPython.lib.display.Audio object> 

87 

88 From URL: 

89 

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

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

92 

93 From a File: 

94 

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

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

97 

98 From Bytes: 

99 

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

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

102 

103 See Also 

104 -------- 

105 ipywidgets.Audio 

106 

107 Audio widget with more more flexibility and options. 

108 

109 """ 

110 _read_flags = 'rb' 

111 

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

113 element_id=None): 

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

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

116 if embed is False and url is None: 

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

118 

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

120 self.embed = False 

121 else: 

122 self.embed = True 

123 self.autoplay = autoplay 

124 self.element_id = element_id 

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

126 

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

128 if rate is None: 

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

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

131 

132 def reload(self): 

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

134 import mimetypes 

135 if self.embed: 

136 super().reload() 

137 

138 if self.filename is not None: 

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

140 elif self.url is not None: 

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

142 else: 

143 self.mimetype = "audio/wav" 

144 

145 @staticmethod 

146 def _make_wav(data, rate, normalize): 

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

148 from io import BytesIO 

149 import wave 

150 

151 try: 

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

153 except ImportError: 

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

155 

156 fp = BytesIO() 

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

158 waveobj.setnchannels(nchan) 

159 waveobj.setframerate(rate) 

160 waveobj.setsampwidth(2) 

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

162 waveobj.writeframes(scaled) 

163 val = fp.getvalue() 

164 waveobj.close() 

165 

166 return val 

167 

168 @staticmethod 

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

170 import numpy as np 

171 

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

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

174 nchan = 1 

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

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

177 # "L1R1L2R2..." for stereo. See 

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

179 # for channel ordering 

180 nchan = data.shape[0] 

181 data = data.T.ravel() 

182 else: 

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

184 

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

186 normalization_factor = Audio._get_normalization_factor(max_abs_value, normalize) 

187 scaled = data / normalization_factor * 32767 

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

189 

190 @staticmethod 

191 def _validate_and_normalize_without_numpy(data, normalize): 

192 import array 

193 import sys 

194 

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

196 

197 try: 

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

199 except TypeError as e: 

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

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

202 

203 normalization_factor = Audio._get_normalization_factor(max_abs_value, normalize) 

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

205 if sys.byteorder == 'big': 

206 scaled.byteswap() 

207 nchan = 1 

208 return scaled.tobytes(), nchan 

209 

210 @staticmethod 

211 def _get_normalization_factor(max_abs_value, normalize): 

212 if not normalize and max_abs_value > 1: 

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

214 return max_abs_value if normalize else 1 

215 

216 def _data_and_metadata(self): 

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

218 md = {} 

219 if self.url: 

220 md['url'] = self.url 

221 if md: 

222 return self.data, md 

223 else: 

224 return self.data 

225 

226 def _repr_html_(self): 

227 src = """ 

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

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

230 Your browser does not support the audio element. 

231 </audio> 

232 """ 

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

234 element_id=self.element_id_attr()) 

235 

236 def src_attr(self): 

237 import base64 

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

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

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

241 base64=data) 

242 elif self.url is not None: 

243 return html_escape(self.url) 

244 else: 

245 return "" 

246 

247 def autoplay_attr(self): 

248 if(self.autoplay): 

249 return 'autoplay="autoplay"' 

250 else: 

251 return '' 

252 

253 def element_id_attr(self): 

254 if (self.element_id): 

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

256 else: 

257 return '' 

258 

259class IFrame: 

260 """ 

261 Generic class to embed an iframe in an IPython notebook 

262 """ 

263 

264 iframe = """ 

265 <iframe 

266 width="{width}" 

267 height="{height}" 

268 src="{src}{params}" 

269 frameborder="0" 

270 allowfullscreen 

271 {extras} 

272 ></iframe> 

273 """ 

274 

275 def __init__( 

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

277 ): 

278 if extras is None: 

279 extras = [] 

280 

281 self.src = src 

282 self.width = width 

283 self.height = height 

284 self.extras = extras 

285 self.params = kwargs 

286 

287 def _repr_html_(self): 

288 """return the embed iframe""" 

289 if self.params: 

290 from urllib.parse import urlencode 

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

292 else: 

293 params = "" 

294 return self.iframe.format( 

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

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

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

298 params=params, 

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

300 ) 

301 

302 

303class YouTubeVideo(IFrame): 

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

305 

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

307 do:: 

308 

309 vid = YouTubeVideo("foo") 

310 display(vid) 

311 

312 To start from 30 seconds:: 

313 

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

315 display(vid) 

316 

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

318 :class:`datetime.timedelta`:: 

319 

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

321 

322 Other parameters can be provided as documented at 

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

324 

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

326 will be inserted in the document. 

327 """ 

328 

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

330 self.id=id 

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

332 if allow_autoplay: 

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

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

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

336 

337 def _repr_jpeg_(self): 

338 # Deferred import 

339 from urllib.request import urlopen 

340 

341 try: 

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

343 except OSError: 

344 return None 

345 

346class VimeoVideo(IFrame): 

347 """ 

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

349 """ 

350 

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

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

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

354 

355class ScribdDocument(IFrame): 

356 """ 

357 Class for embedding a Scribd document in an IPython session 

358 

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

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

361 

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

363 

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

365 """ 

366 

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

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

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

370 

371class FileLink: 

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

373 

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

375 

376 you would do:: 

377 

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

379 display(local_file) 

380 

381 or in the HTML notebook, just:: 

382 

383 FileLink("my/data.txt") 

384 """ 

385 

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

387 

388 def __init__(self, 

389 path, 

390 url_prefix='', 

391 result_html_prefix='', 

392 result_html_suffix='<br>'): 

393 """ 

394 Parameters 

395 ---------- 

396 path : str 

397 path to the file or directory that should be formatted 

398 url_prefix : str 

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

400 ''] 

401 result_html_prefix : str 

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

403 result_html_suffix : str 

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

405 """ 

406 if isdir(path): 

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

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

409 self.path = fsdecode(path) 

410 self.url_prefix = url_prefix 

411 self.result_html_prefix = result_html_prefix 

412 self.result_html_suffix = result_html_suffix 

413 

414 def _format_path(self): 

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

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

417 self.html_link_str % \ 

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

419 self.result_html_suffix]) 

420 

421 def _repr_html_(self): 

422 """return html link to file 

423 """ 

424 if not exists(self.path): 

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

426 "It may still be in the process of " 

427 "being generated, or you may have the " 

428 "incorrect path." % self.path) 

429 

430 return self._format_path() 

431 

432 def __repr__(self): 

433 """return absolute path to file 

434 """ 

435 return abspath(self.path) 

436 

437class FileLinks(FileLink): 

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

439 

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

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

442 

443 local_files = FileLinks("my/data") 

444 display(local_files) 

445 

446 or in the HTML notebook, just:: 

447 

448 FileLinks("my/data") 

449 """ 

450 def __init__(self, 

451 path, 

452 url_prefix='', 

453 included_suffixes=None, 

454 result_html_prefix='', 

455 result_html_suffix='<br>', 

456 notebook_display_formatter=None, 

457 terminal_display_formatter=None, 

458 recursive=True): 

459 """ 

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

461 ``result_html_prefix`` and ``result_html_suffix`` parameters. 

462 

463 included_suffixes : list 

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

465 all files] 

466 

467 notebook_display_formatter : function 

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

469 formatter functions below. 

470 

471 terminal_display_formatter : function 

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

473 formatter functions below. 

474 

475 Formatter functions must be of the form:: 

476 

477 f(dirname, fnames, included_suffixes) 

478 

479 dirname : str 

480 The name of a directory 

481 fnames : list 

482 The files in that directory 

483 included_suffixes : list 

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

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

486 recursive : boolean 

487 Whether to recurse into subdirectories. Default is True. 

488 

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

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

491 passing terminal_display_formatter). This function is iterated over for 

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

493 passed here to support alternative formatting. 

494 

495 """ 

496 if isfile(path): 

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

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

499 self.included_suffixes = included_suffixes 

500 # remove trailing slashes for more consistent output formatting 

501 path = path.rstrip('/') 

502 

503 self.path = path 

504 self.url_prefix = url_prefix 

505 self.result_html_prefix = result_html_prefix 

506 self.result_html_suffix = result_html_suffix 

507 

508 self.notebook_display_formatter = \ 

509 notebook_display_formatter or self._get_notebook_display_formatter() 

510 self.terminal_display_formatter = \ 

511 terminal_display_formatter or self._get_terminal_display_formatter() 

512 

513 self.recursive = recursive 

514 

515 def _get_display_formatter( 

516 self, 

517 dirname_output_format, 

518 fname_output_format, 

519 fp_format, 

520 fp_cleaner=None, 

521 escape_names=False, 

522 ): 

523 """generate built-in formatter function 

524 

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

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

527 

528 dirname_output_format: string to use for formatting directory 

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

530 must appear in this string 

531 fname_output_format: string to use for formatting file names, 

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

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

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

535 second 

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

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

538 and fname will be substituted for the second 

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

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

541 """ 

542 escape = html_escape if escape_names else str 

543 

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

545 result = [] 

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

547 # are going to be displayed 

548 display_fnames = [] 

549 for fname in fnames: 

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

551 (included_suffixes is None or 

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

553 display_fnames.append(fname) 

554 

555 if len(display_fnames) == 0: 

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

557 # (not even the directory name) 

558 pass 

559 else: 

560 # otherwise print the formatted directory name followed by 

561 # the formatted filenames 

562 dirname_output_line = dirname_output_format % escape(dirname) 

563 result.append(dirname_output_line) 

564 for fname in display_fnames: 

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

566 if fp_cleaner is not None: 

567 fp = fp_cleaner(fp) 

568 try: 

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

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

571 except TypeError: 

572 # ... or just a single filepath 

573 fname_output_line = fname_output_format % escape(fname) 

574 result.append(fname_output_line) 

575 return result 

576 return f 

577 

578 def _get_notebook_display_formatter(self, 

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

580 """ generate function to use for notebook formatting 

581 """ 

582 dirname_output_format = \ 

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

584 fname_output_format = \ 

585 self.result_html_prefix + spacer + self.html_link_str + self.result_html_suffix 

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

587 if sep == "\\": 

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

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

590 def fp_cleaner(fp): 

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

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

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

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

595 else: 

596 fp_cleaner = None 

597 

598 return self._get_display_formatter( 

599 dirname_output_format, 

600 fname_output_format, 

601 fp_format, 

602 fp_cleaner, 

603 escape_names=True, 

604 ) 

605 

606 def _get_terminal_display_formatter(self, 

607 spacer=" "): 

608 """ generate function to use for terminal formatting 

609 """ 

610 dirname_output_format = "%s/" 

611 fname_output_format = spacer + "%s" 

612 fp_format = '%s/%s' 

613 

614 return self._get_display_formatter(dirname_output_format, 

615 fname_output_format, 

616 fp_format) 

617 

618 def _format_path(self): 

619 result_lines = [] 

620 if self.recursive: 

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

622 else: 

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

624 walked_dir.sort() 

625 for dirname, subdirs, fnames in walked_dir: 

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

627 return '\n'.join(result_lines) 

628 

629 def __repr__(self): 

630 """return newline-separated absolute paths 

631 """ 

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.terminal_display_formatter(dirname, fnames, self.included_suffixes) 

640 return '\n'.join(result_lines) 

641 

642 

643class Code(TextDisplayObject): 

644 """Display syntax-highlighted source code. 

645 

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

647 

648 Parameters 

649 ---------- 

650 data : str 

651 The code as a string 

652 url : str 

653 A URL to fetch the code from 

654 filename : str 

655 A local filename to load the code from 

656 language : str 

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

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

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

660 """ 

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

662 self.language = language 

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

664 

665 def _get_lexer(self): 

666 if self.language: 

667 from pygments.lexers import get_lexer_by_name 

668 return get_lexer_by_name(self.language) 

669 elif self.filename: 

670 from pygments.lexers import get_lexer_for_filename 

671 return get_lexer_for_filename(self.filename) 

672 else: 

673 from pygments.lexers import guess_lexer 

674 return guess_lexer(self.data) 

675 

676 def __repr__(self): 

677 return self.data 

678 

679 def _repr_html_(self): 

680 from pygments import highlight 

681 from pygments.formatters import HtmlFormatter 

682 fmt = HtmlFormatter() 

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

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

685 

686 def _repr_latex_(self): 

687 from pygments import highlight 

688 from pygments.formatters import LatexFormatter 

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