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 typing import Tuple, Optional 

12from collections.abc import Iterable 

13 

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

15 'FileLink', 'FileLinks', 'Code'] 

16 

17 

18class Audio(DisplayObject): 

19 """Create an audio object. 

20 

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

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

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

24 

25 Parameters 

26 ---------- 

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

28 Can be one of 

29 

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

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

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

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

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

35 * String containing the filename 

36 * Bytestring containing raw PCM data or 

37 * URL pointing to a file on the web. 

38 

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

40 

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

42 dependent. 

43 url : unicode 

44 A URL to download the data from. 

45 filename : unicode 

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

47 embed : boolean 

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

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

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

51 

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

53 default value is `False`. 

54 rate : integer 

55 The sampling rate of the raw data. 

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

57 autoplay : bool 

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

59 Default is `False`. 

60 normalize : bool 

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

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

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

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

65 audio are never normalized. 

66 

67 Examples 

68 -------- 

69 

70 >>> import pytest 

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

72 

73 Generate a sound 

74 

75 >>> import numpy as np 

76 >>> framerate = 44100 

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

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

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

80 <IPython.lib.display.Audio object> 

81 

82 Can also do stereo or more channels 

83 

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

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

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

87 <IPython.lib.display.Audio object> 

88 

89 From URL: 

90 

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

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

93 

94 From a File: 

95 

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

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

98 

99 From Bytes: 

100 

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

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

103 

104 See Also 

105 -------- 

106 ipywidgets.Audio 

107 

108 Audio widget with more more flexibility and options. 

109 

110 """ 

111 _read_flags = 'rb' 

112 

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

114 element_id=None): 

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

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

117 if embed is False and url is None: 

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

119 

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

121 self.embed = False 

122 else: 

123 self.embed = True 

124 self.autoplay = autoplay 

125 self.element_id = element_id 

126 super(Audio, self).__init__(data=data, url=url, filename=filename) 

127 

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

129 if rate is None: 

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

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

132 

133 def reload(self): 

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

135 import mimetypes 

136 if self.embed: 

137 super(Audio, self).reload() 

138 

139 if self.filename is not None: 

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

141 elif self.url is not None: 

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

143 else: 

144 self.mimetype = "audio/wav" 

145 

146 @staticmethod 

147 def _make_wav(data, rate, normalize): 

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

149 from io import BytesIO 

150 import wave 

151 

152 try: 

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

154 except ImportError: 

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

156 

157 fp = BytesIO() 

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

159 waveobj.setnchannels(nchan) 

160 waveobj.setframerate(rate) 

161 waveobj.setsampwidth(2) 

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

163 waveobj.writeframes(scaled) 

164 val = fp.getvalue() 

165 waveobj.close() 

166 

167 return val 

168 

169 @staticmethod 

170 def _validate_and_normalize_with_numpy(data, normalize) -> Tuple[bytes, int]: 

171 import numpy as np 

172 

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

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

175 nchan = 1 

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

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

178 # "L1R1L2R2..." for stereo. See 

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

180 # for channel ordering 

181 nchan = data.shape[0] 

182 data = data.T.ravel() 

183 else: 

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

185 

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

187 normalization_factor = Audio._get_normalization_factor(max_abs_value, normalize) 

188 scaled = data / normalization_factor * 32767 

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

190 

191 @staticmethod 

192 def _validate_and_normalize_without_numpy(data, normalize): 

193 import array 

194 import sys 

195 

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

197 

198 try: 

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

200 except TypeError as e: 

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

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

203 

204 normalization_factor = Audio._get_normalization_factor(max_abs_value, normalize) 

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

206 if sys.byteorder == 'big': 

207 scaled.byteswap() 

208 nchan = 1 

209 return scaled.tobytes(), nchan 

210 

211 @staticmethod 

212 def _get_normalization_factor(max_abs_value, normalize): 

213 if not normalize and max_abs_value > 1: 

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

215 return max_abs_value if normalize else 1 

216 

217 def _data_and_metadata(self): 

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

219 md = {} 

220 if self.url: 

221 md['url'] = self.url 

222 if md: 

223 return self.data, md 

224 else: 

225 return self.data 

226 

227 def _repr_html_(self): 

228 src = """ 

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

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

231 Your browser does not support the audio element. 

232 </audio> 

233 """ 

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

235 element_id=self.element_id_attr()) 

236 

237 def src_attr(self): 

238 import base64 

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

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

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

242 base64=data) 

243 elif self.url is not None: 

244 return 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 return 'id="{element_id}"'.format(element_id=self.element_id) 

257 else: 

258 return '' 

259 

260class IFrame: 

261 """ 

262 Generic class to embed an iframe in an IPython notebook 

263 """ 

264 

265 iframe = """ 

266 <iframe 

267 width="{width}" 

268 height="{height}" 

269 src="{src}{params}" 

270 frameborder="0" 

271 allowfullscreen 

272 {extras} 

273 ></iframe> 

274 """ 

275 

276 def __init__( 

277 self, src, width, height, extras: Optional[Iterable[str]] = None, **kwargs 

278 ): 

279 if extras is None: 

280 extras = [] 

281 

282 self.src = src 

283 self.width = width 

284 self.height = height 

285 self.extras = extras 

286 self.params = kwargs 

287 

288 def _repr_html_(self): 

289 """return the embed iframe""" 

290 if self.params: 

291 from urllib.parse import urlencode 

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

293 else: 

294 params = "" 

295 return self.iframe.format( 

296 src=self.src, 

297 width=self.width, 

298 height=self.height, 

299 params=params, 

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

301 ) 

302 

303 

304class YouTubeVideo(IFrame): 

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

306 

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

308 do:: 

309 

310 vid = YouTubeVideo("foo") 

311 display(vid) 

312 

313 To start from 30 seconds:: 

314 

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

316 display(vid) 

317 

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

319 :class:`datetime.timedelta`:: 

320 

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

322 

323 Other parameters can be provided as documented at 

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

325  

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

327 will be inserted in the document. 

328 """ 

329 

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

331 self.id=id 

332 src = "https://www.youtube.com/embed/{0}".format(id) 

333 if allow_autoplay: 

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

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

336 super(YouTubeVideo, self).__init__(src, width, height, **kwargs) 

337 

338 def _repr_jpeg_(self): 

339 # Deferred import 

340 from urllib.request import urlopen 

341 

342 try: 

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

344 except IOError: 

345 return None 

346 

347class VimeoVideo(IFrame): 

348 """ 

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

350 """ 

351 

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

353 src="https://player.vimeo.com/video/{0}".format(id) 

354 super(VimeoVideo, self).__init__(src, width, height, **kwargs) 

355 

356class ScribdDocument(IFrame): 

357 """ 

358 Class for embedding a Scribd document in an IPython session 

359 

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

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

362 

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

364 

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

366 """ 

367 

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

369 src="https://www.scribd.com/embeds/{0}/content".format(id) 

370 super(ScribdDocument, self).__init__(src, width, height, **kwargs) 

371 

372class FileLink: 

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

374 

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

376 

377 you would do:: 

378 

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

380 display(local_file) 

381 

382 or in the HTML notebook, just:: 

383 

384 FileLink("my/data.txt") 

385 """ 

386 

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

388 

389 def __init__(self, 

390 path, 

391 url_prefix='', 

392 result_html_prefix='', 

393 result_html_suffix='<br>'): 

394 """ 

395 Parameters 

396 ---------- 

397 path : str 

398 path to the file or directory that should be formatted 

399 url_prefix : str 

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

401 ''] 

402 result_html_prefix : str 

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

404 result_html_suffix : str 

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

406 """ 

407 if isdir(path): 

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

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

410 self.path = fsdecode(path) 

411 self.url_prefix = url_prefix 

412 self.result_html_prefix = result_html_prefix 

413 self.result_html_suffix = result_html_suffix 

414 

415 def _format_path(self): 

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

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

418 self.html_link_str % \ 

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

420 self.result_html_suffix]) 

421 

422 def _repr_html_(self): 

423 """return html link to file 

424 """ 

425 if not exists(self.path): 

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

427 "It may still be in the process of " 

428 "being generated, or you may have the " 

429 "incorrect path." % self.path) 

430 

431 return self._format_path() 

432 

433 def __repr__(self): 

434 """return absolute path to file 

435 """ 

436 return abspath(self.path) 

437 

438class FileLinks(FileLink): 

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

440 

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

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

443 

444 local_files = FileLinks("my/data") 

445 display(local_files) 

446 

447 or in the HTML notebook, just:: 

448 

449 FileLinks("my/data") 

450 """ 

451 def __init__(self, 

452 path, 

453 url_prefix='', 

454 included_suffixes=None, 

455 result_html_prefix='', 

456 result_html_suffix='<br>', 

457 notebook_display_formatter=None, 

458 terminal_display_formatter=None, 

459 recursive=True): 

460 """ 

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

462 ``result_html_prefix`` and ``result_html_suffix`` parameters. 

463 

464 included_suffixes : list 

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

466 all files] 

467 

468 notebook_display_formatter : function 

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

470 formatter functions below. 

471 

472 terminal_display_formatter : function 

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

474 formatter functions below. 

475 

476 Formatter functions must be of the form:: 

477 

478 f(dirname, fnames, included_suffixes) 

479 

480 dirname : str 

481 The name of a directory 

482 fnames : list 

483 The files in that directory 

484 included_suffixes : list 

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

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

487 recursive : boolean 

488 Whether to recurse into subdirectories. Default is True. 

489 

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

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

492 passing terminal_display_formatter). This function is iterated over for 

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

494 passed here to support alternative formatting. 

495 

496 """ 

497 if isfile(path): 

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

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

500 self.included_suffixes = included_suffixes 

501 # remove trailing slashes for more consistent output formatting 

502 path = path.rstrip('/') 

503 

504 self.path = path 

505 self.url_prefix = url_prefix 

506 self.result_html_prefix = result_html_prefix 

507 self.result_html_suffix = result_html_suffix 

508 

509 self.notebook_display_formatter = \ 

510 notebook_display_formatter or self._get_notebook_display_formatter() 

511 self.terminal_display_formatter = \ 

512 terminal_display_formatter or self._get_terminal_display_formatter() 

513 

514 self.recursive = recursive 

515 

516 def _get_display_formatter( 

517 self, dirname_output_format, fname_output_format, fp_format, fp_cleaner=None 

518 ): 

519 """generate built-in formatter function 

520 

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

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

523 

524 dirname_output_format: string to use for formatting directory 

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

526 must appear in this string 

527 fname_output_format: string to use for formatting file names, 

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

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

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

531 second 

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

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

534 and fname will be substituted for the second 

535 """ 

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

537 result = [] 

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

539 # are going to be displayed 

540 display_fnames = [] 

541 for fname in fnames: 

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

543 (included_suffixes is None or 

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

545 display_fnames.append(fname) 

546 

547 if len(display_fnames) == 0: 

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

549 # (not even the directory name) 

550 pass 

551 else: 

552 # otherwise print the formatted directory name followed by 

553 # the formatted filenames 

554 dirname_output_line = dirname_output_format % dirname 

555 result.append(dirname_output_line) 

556 for fname in display_fnames: 

557 fp = fp_format % (dirname,fname) 

558 if fp_cleaner is not None: 

559 fp = fp_cleaner(fp) 

560 try: 

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

562 fname_output_line = fname_output_format % (fp, fname) 

563 except TypeError: 

564 # ... or just a single filepath 

565 fname_output_line = fname_output_format % fname 

566 result.append(fname_output_line) 

567 return result 

568 return f 

569 

570 def _get_notebook_display_formatter(self, 

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

572 """ generate function to use for notebook formatting 

573 """ 

574 dirname_output_format = \ 

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

576 fname_output_format = \ 

577 self.result_html_prefix + spacer + self.html_link_str + self.result_html_suffix 

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

579 if sep == "\\": 

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

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

582 def fp_cleaner(fp): 

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

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

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

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

587 else: 

588 fp_cleaner = None 

589 

590 return self._get_display_formatter(dirname_output_format, 

591 fname_output_format, 

592 fp_format, 

593 fp_cleaner) 

594 

595 def _get_terminal_display_formatter(self, 

596 spacer=" "): 

597 """ generate function to use for terminal formatting 

598 """ 

599 dirname_output_format = "%s/" 

600 fname_output_format = spacer + "%s" 

601 fp_format = '%s/%s' 

602 

603 return self._get_display_formatter(dirname_output_format, 

604 fname_output_format, 

605 fp_format) 

606 

607 def _format_path(self): 

608 result_lines = [] 

609 if self.recursive: 

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

611 else: 

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

613 walked_dir.sort() 

614 for dirname, subdirs, fnames in walked_dir: 

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

616 return '\n'.join(result_lines) 

617 

618 def __repr__(self): 

619 """return newline-separated absolute paths 

620 """ 

621 result_lines = [] 

622 if self.recursive: 

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

624 else: 

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

626 walked_dir.sort() 

627 for dirname, subdirs, fnames in walked_dir: 

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

629 return '\n'.join(result_lines) 

630 

631 

632class Code(TextDisplayObject): 

633 """Display syntax-highlighted source code. 

634 

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

636 

637 Parameters 

638 ---------- 

639 data : str 

640 The code as a string 

641 url : str 

642 A URL to fetch the code from 

643 filename : str 

644 A local filename to load the code from 

645 language : str 

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

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

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

649 """ 

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

651 self.language = language 

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

653 

654 def _get_lexer(self): 

655 if self.language: 

656 from pygments.lexers import get_lexer_by_name 

657 return get_lexer_by_name(self.language) 

658 elif self.filename: 

659 from pygments.lexers import get_lexer_for_filename 

660 return get_lexer_for_filename(self.filename) 

661 else: 

662 from pygments.lexers import guess_lexer 

663 return guess_lexer(self.data) 

664 

665 def __repr__(self): 

666 return self.data 

667 

668 def _repr_html_(self): 

669 from pygments import highlight 

670 from pygments.formatters import HtmlFormatter 

671 fmt = HtmlFormatter() 

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

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

674 

675 def _repr_latex_(self): 

676 from pygments import highlight 

677 from pygments.formatters import LatexFormatter 

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