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
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
1"""Various display related classes.
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
9from IPython.core.display import DisplayObject, TextDisplayObject
11from typing import Tuple, Optional
12from collections.abc import Iterable
14__all__ = ['Audio', 'IFrame', 'YouTubeVideo', 'VimeoVideo', 'ScribdDocument',
15 'FileLink', 'FileLinks', 'Code']
18class Audio(DisplayObject):
19 """Create an audio object.
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).
25 Parameters
26 ----------
27 data : numpy array, list, unicode, str or bytes
28 Can be one of
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.
39 If the array option is used, the waveform will be normalized.
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.
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.
67 Examples
68 --------
70 >>> import pytest
71 >>> np = pytest.importorskip("numpy")
73 Generate a sound
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>
82 Can also do stereo or more channels
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>
89 From URL:
91 >>> Audio("http://www.nch.com.au/acm/8k16bitpcm.wav") # doctest: +SKIP
92 >>> Audio(url="http://www.w3schools.com/html/horse.ogg") # doctest: +SKIP
94 From a File:
96 >>> Audio('IPython/lib/tests/test.wav') # doctest: +SKIP
97 >>> Audio(filename='IPython/lib/tests/test.wav') # doctest: +SKIP
99 From Bytes:
101 >>> Audio(b'RAW_WAV_DATA..') # doctest: +SKIP
102 >>> Audio(data=b'RAW_WAV_DATA..') # doctest: +SKIP
104 See Also
105 --------
106 ipywidgets.Audio
108 Audio widget with more more flexibility and options.
110 """
111 _read_flags = 'rb'
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")
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)
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)
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()
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"
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
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)
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()
167 return val
169 @staticmethod
170 def _validate_and_normalize_with_numpy(data, normalize) -> Tuple[bytes, int]:
171 import numpy as np
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')
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
191 @staticmethod
192 def _validate_and_normalize_without_numpy(data, normalize):
193 import array
194 import sys
196 data = array.array('f', data)
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
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
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
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
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())
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 ""
248 def autoplay_attr(self):
249 if(self.autoplay):
250 return 'autoplay="autoplay"'
251 else:
252 return ''
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 ''
260class IFrame:
261 """
262 Generic class to embed an iframe in an IPython notebook
263 """
265 iframe = """
266 <iframe
267 width="{width}"
268 height="{height}"
269 src="{src}{params}"
270 frameborder="0"
271 allowfullscreen
272 {extras}
273 ></iframe>
274 """
276 def __init__(
277 self, src, width, height, extras: Optional[Iterable[str]] = None, **kwargs
278 ):
279 if extras is None:
280 extras = []
282 self.src = src
283 self.width = width
284 self.height = height
285 self.extras = extras
286 self.params = kwargs
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 )
304class YouTubeVideo(IFrame):
305 """Class for embedding a YouTube Video in an IPython session, based on its video id.
307 e.g. to embed the video from https://www.youtube.com/watch?v=foo , you would
308 do::
310 vid = YouTubeVideo("foo")
311 display(vid)
313 To start from 30 seconds::
315 vid = YouTubeVideo("abc", start=30)
316 display(vid)
318 To calculate seconds from time as hours, minutes, seconds use
319 :class:`datetime.timedelta`::
321 start=int(timedelta(hours=1, minutes=46, seconds=40).total_seconds())
323 Other parameters can be provided as documented at
324 https://developers.google.com/youtube/player_parameters#Parameters
326 When converting the notebook using nbconvert, a jpeg representation of the video
327 will be inserted in the document.
328 """
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)
338 def _repr_jpeg_(self):
339 # Deferred import
340 from urllib.request import urlopen
342 try:
343 return urlopen("https://img.youtube.com/vi/{id}/hqdefault.jpg".format(id=self.id)).read()
344 except IOError:
345 return None
347class VimeoVideo(IFrame):
348 """
349 Class for embedding a Vimeo video in an IPython session, based on its video id.
350 """
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)
356class ScribdDocument(IFrame):
357 """
358 Class for embedding a Scribd document in an IPython session
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
363 e.g to Display Wes' foundational paper about PANDAS in book mode from page 3
365 ScribdDocument(71048089, width=800, height=400, start_page=3, view_mode="book")
366 """
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)
372class FileLink:
373 """Class for embedding a local file link in an IPython session, based on path
375 e.g. to embed a link that was generated in the IPython notebook as my/data.txt
377 you would do::
379 local_file = FileLink("my/data.txt")
380 display(local_file)
382 or in the HTML notebook, just::
384 FileLink("my/data.txt")
385 """
387 html_link_str = "<a href='%s' target='_blank'>%s</a>"
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
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])
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)
431 return self._format_path()
433 def __repr__(self):
434 """return absolute path to file
435 """
436 return abspath(self.path)
438class FileLinks(FileLink):
439 """Class for embedding local file links in an IPython session, based on path
441 e.g. to embed links to files that were generated in the IPython notebook
442 under ``my/data``, you would do::
444 local_files = FileLinks("my/data")
445 display(local_files)
447 or in the HTML notebook, just::
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.
464 included_suffixes : list
465 Filename suffixes to include when formatting output [default: include
466 all files]
468 notebook_display_formatter : function
469 Used to format links for display in the notebook. See discussion of
470 formatter functions below.
472 terminal_display_formatter : function
473 Used to format links for display in the terminal. See discussion of
474 formatter functions below.
476 Formatter functions must be of the form::
478 f(dirname, fnames, included_suffixes)
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.
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.
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('/')
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
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()
514 self.recursive = recursive
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
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
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)
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
570 def _get_notebook_display_formatter(self,
571 spacer=" "):
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
590 return self._get_display_formatter(dirname_output_format,
591 fname_output_format,
592 fp_format,
593 fp_cleaner)
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'
603 return self._get_display_formatter(dirname_output_format,
604 fname_output_format,
605 fp_format)
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)
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)
632class Code(TextDisplayObject):
633 """Display syntax-highlighted source code.
635 This uses Pygments to highlight the code for HTML and Latex output.
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)
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)
665 def __repr__(self):
666 return self.data
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)
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())