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

258 statements  

« prev     ^ index     » next       coverage.py v7.2.2, created at 2023-03-26 06:07 +0000

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

256 else: 

257 return '' 

258 

259class IFrame(object): 

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__(self, src, width, height, extras: Iterable[str] = None, **kwargs): 

276 if extras is None: 

277 extras = [] 

278 

279 self.src = src 

280 self.width = width 

281 self.height = height 

282 self.extras = extras 

283 self.params = kwargs 

284 

285 def _repr_html_(self): 

286 """return the embed iframe""" 

287 if self.params: 

288 from urllib.parse import urlencode 

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

290 else: 

291 params = "" 

292 return self.iframe.format( 

293 src=self.src, 

294 width=self.width, 

295 height=self.height, 

296 params=params, 

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

298 ) 

299 

300 

301class YouTubeVideo(IFrame): 

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

303 

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

305 do:: 

306 

307 vid = YouTubeVideo("foo") 

308 display(vid) 

309 

310 To start from 30 seconds:: 

311 

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

313 display(vid) 

314 

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

316 :class:`datetime.timedelta`:: 

317 

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

319 

320 Other parameters can be provided as documented at 

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

322  

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

324 will be inserted in the document. 

325 """ 

326 

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

328 self.id=id 

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

330 if allow_autoplay: 

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

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

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

334 

335 def _repr_jpeg_(self): 

336 # Deferred import 

337 from urllib.request import urlopen 

338 

339 try: 

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

341 except IOError: 

342 return None 

343 

344class VimeoVideo(IFrame): 

345 """ 

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

347 """ 

348 

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

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

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

352 

353class ScribdDocument(IFrame): 

354 """ 

355 Class for embedding a Scribd document in an IPython session 

356 

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

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

359 

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

361 

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

363 """ 

364 

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

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

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

368 

369class FileLink(object): 

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

371 

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

373 

374 you would do:: 

375 

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

377 display(local_file) 

378 

379 or in the HTML notebook, just:: 

380 

381 FileLink("my/data.txt") 

382 """ 

383 

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

385 

386 def __init__(self, 

387 path, 

388 url_prefix='', 

389 result_html_prefix='', 

390 result_html_suffix='<br>'): 

391 """ 

392 Parameters 

393 ---------- 

394 path : str 

395 path to the file or directory that should be formatted 

396 url_prefix : str 

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

398 ''] 

399 result_html_prefix : str 

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

401 result_html_suffix : str 

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

403 """ 

404 if isdir(path): 

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

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

407 self.path = fsdecode(path) 

408 self.url_prefix = url_prefix 

409 self.result_html_prefix = result_html_prefix 

410 self.result_html_suffix = result_html_suffix 

411 

412 def _format_path(self): 

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

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

415 self.html_link_str % \ 

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

417 self.result_html_suffix]) 

418 

419 def _repr_html_(self): 

420 """return html link to file 

421 """ 

422 if not exists(self.path): 

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

424 "It may still be in the process of " 

425 "being generated, or you may have the " 

426 "incorrect path." % self.path) 

427 

428 return self._format_path() 

429 

430 def __repr__(self): 

431 """return absolute path to file 

432 """ 

433 return abspath(self.path) 

434 

435class FileLinks(FileLink): 

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

437 

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

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

440 

441 local_files = FileLinks("my/data") 

442 display(local_files) 

443 

444 or in the HTML notebook, just:: 

445 

446 FileLinks("my/data") 

447 """ 

448 def __init__(self, 

449 path, 

450 url_prefix='', 

451 included_suffixes=None, 

452 result_html_prefix='', 

453 result_html_suffix='<br>', 

454 notebook_display_formatter=None, 

455 terminal_display_formatter=None, 

456 recursive=True): 

457 """ 

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

459 ``result_html_prefix`` and ``result_html_suffix`` parameters. 

460 

461 included_suffixes : list 

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

463 all files] 

464 

465 notebook_display_formatter : function 

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

467 formatter functions below. 

468 

469 terminal_display_formatter : function 

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

471 formatter functions below. 

472 

473 Formatter functions must be of the form:: 

474 

475 f(dirname, fnames, included_suffixes) 

476 

477 dirname : str 

478 The name of a directory 

479 fnames : list 

480 The files in that directory 

481 included_suffixes : list 

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

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

484 recursive : boolean 

485 Whether to recurse into subdirectories. Default is True. 

486 

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

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

489 passing terminal_display_formatter). This function is iterated over for 

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

491 passed here to support alternative formatting. 

492 

493 """ 

494 if isfile(path): 

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

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

497 self.included_suffixes = included_suffixes 

498 # remove trailing slashes for more consistent output formatting 

499 path = path.rstrip('/') 

500 

501 self.path = path 

502 self.url_prefix = url_prefix 

503 self.result_html_prefix = result_html_prefix 

504 self.result_html_suffix = result_html_suffix 

505 

506 self.notebook_display_formatter = \ 

507 notebook_display_formatter or self._get_notebook_display_formatter() 

508 self.terminal_display_formatter = \ 

509 terminal_display_formatter or self._get_terminal_display_formatter() 

510 

511 self.recursive = recursive 

512 

513 def _get_display_formatter( 

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

515 ): 

516 """generate built-in formatter function 

517 

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

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

520 

521 dirname_output_format: string to use for formatting directory 

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

523 must appear in this string 

524 fname_output_format: string to use for formatting file names, 

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

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

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

528 second 

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

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

531 and fname will be substituted for the second 

532 """ 

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

534 result = [] 

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

536 # are going to be displayed 

537 display_fnames = [] 

538 for fname in fnames: 

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

540 (included_suffixes is None or 

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

542 display_fnames.append(fname) 

543 

544 if len(display_fnames) == 0: 

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

546 # (not even the directory name) 

547 pass 

548 else: 

549 # otherwise print the formatted directory name followed by 

550 # the formatted filenames 

551 dirname_output_line = dirname_output_format % dirname 

552 result.append(dirname_output_line) 

553 for fname in display_fnames: 

554 fp = fp_format % (dirname,fname) 

555 if fp_cleaner is not None: 

556 fp = fp_cleaner(fp) 

557 try: 

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

559 fname_output_line = fname_output_format % (fp, fname) 

560 except TypeError: 

561 # ... or just a single filepath 

562 fname_output_line = fname_output_format % fname 

563 result.append(fname_output_line) 

564 return result 

565 return f 

566 

567 def _get_notebook_display_formatter(self, 

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

569 """ generate function to use for notebook formatting 

570 """ 

571 dirname_output_format = \ 

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

573 fname_output_format = \ 

574 self.result_html_prefix + spacer + self.html_link_str + self.result_html_suffix 

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

576 if sep == "\\": 

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

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

579 def fp_cleaner(fp): 

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

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

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

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

584 else: 

585 fp_cleaner = None 

586 

587 return self._get_display_formatter(dirname_output_format, 

588 fname_output_format, 

589 fp_format, 

590 fp_cleaner) 

591 

592 def _get_terminal_display_formatter(self, 

593 spacer=" "): 

594 """ generate function to use for terminal formatting 

595 """ 

596 dirname_output_format = "%s/" 

597 fname_output_format = spacer + "%s" 

598 fp_format = '%s/%s' 

599 

600 return self._get_display_formatter(dirname_output_format, 

601 fname_output_format, 

602 fp_format) 

603 

604 def _format_path(self): 

605 result_lines = [] 

606 if self.recursive: 

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

608 else: 

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

610 walked_dir.sort() 

611 for dirname, subdirs, fnames in walked_dir: 

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

613 return '\n'.join(result_lines) 

614 

615 def __repr__(self): 

616 """return newline-separated absolute paths 

617 """ 

618 result_lines = [] 

619 if self.recursive: 

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

621 else: 

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

623 walked_dir.sort() 

624 for dirname, subdirs, fnames in walked_dir: 

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

626 return '\n'.join(result_lines) 

627 

628 

629class Code(TextDisplayObject): 

630 """Display syntax-highlighted source code. 

631 

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

633 

634 Parameters 

635 ---------- 

636 data : str 

637 The code as a string 

638 url : str 

639 A URL to fetch the code from 

640 filename : str 

641 A local filename to load the code from 

642 language : str 

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

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

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

646 """ 

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

648 self.language = language 

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

650 

651 def _get_lexer(self): 

652 if self.language: 

653 from pygments.lexers import get_lexer_by_name 

654 return get_lexer_by_name(self.language) 

655 elif self.filename: 

656 from pygments.lexers import get_lexer_for_filename 

657 return get_lexer_for_filename(self.filename) 

658 else: 

659 from pygments.lexers import guess_lexer 

660 return guess_lexer(self.data) 

661 

662 def __repr__(self): 

663 return self.data 

664 

665 def _repr_html_(self): 

666 from pygments import highlight 

667 from pygments.formatters import HtmlFormatter 

668 fmt = HtmlFormatter() 

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

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

671 

672 def _repr_latex_(self): 

673 from pygments import highlight 

674 from pygments.formatters import LatexFormatter 

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