Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/IPython/lib/display.py: 20%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
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 os.path import exists, isfile, splitext, abspath, join, isdir
6from os import walk, sep, fsdecode
8from IPython.core.display import DisplayObject, TextDisplayObject
10from collections.abc import Iterable
12__all__ = ['Audio', 'IFrame', 'YouTubeVideo', 'VimeoVideo', 'ScribdDocument',
13 'FileLink', 'FileLinks', 'Code']
16class Audio(DisplayObject):
17 """Create an audio object.
19 When this object is returned by an input cell or passed to the
20 display function, it will result in Audio controls being displayed
21 in the frontend (only works in the notebook).
23 Parameters
24 ----------
25 data : numpy array, list, unicode, str or bytes
26 Can be one of
28 * Numpy 1d array containing the desired waveform (mono)
29 * Numpy 2d array containing waveforms for each channel.
30 Shape=(NCHAN, NSAMPLES). For the standard channel order, see
31 http://msdn.microsoft.com/en-us/library/windows/hardware/dn653308(v=vs.85).aspx
32 * List of float or integer representing the waveform (mono)
33 * String containing the filename
34 * Bytestring containing raw PCM data or
35 * URL pointing to a file on the web.
37 If the array option is used, the waveform will be normalized.
39 If a filename or url is used, the format support will be browser
40 dependent.
41 url : unicode
42 A URL to download the data from.
43 filename : unicode
44 Path to a local file to load the data from.
45 embed : boolean
46 Should the audio data be embedded using a data URI (True) or should
47 the original source be referenced. Set this to True if you want the
48 audio to playable later with no internet connection in the notebook.
50 Default is `True`, unless the keyword argument `url` is set, then
51 default value is `False`.
52 rate : integer
53 The sampling rate of the raw data.
54 Only required when data parameter is being used as an array
55 autoplay : bool
56 Set to True if the audio should immediately start playing.
57 Default is `False`.
58 normalize : bool
59 Whether audio should be normalized (rescaled) to the maximum possible
60 range. Default is `True`. When set to `False`, `data` must be between
61 -1 and 1 (inclusive), otherwise an error is raised.
62 Applies only when `data` is a list or array of samples; other types of
63 audio are never normalized.
65 Examples
66 --------
68 >>> import pytest
69 >>> np = pytest.importorskip("numpy")
71 Generate a sound
73 >>> import numpy as np
74 >>> framerate = 44100
75 >>> t = np.linspace(0,5,framerate*5)
76 >>> data = np.sin(2*np.pi*220*t) + np.sin(2*np.pi*224*t)
77 >>> Audio(data, rate=framerate)
78 <IPython.lib.display.Audio object>
80 Can also do stereo or more channels
82 >>> dataleft = np.sin(2*np.pi*220*t)
83 >>> dataright = np.sin(2*np.pi*224*t)
84 >>> Audio([dataleft, dataright], rate=framerate)
85 <IPython.lib.display.Audio object>
87 From URL:
89 >>> Audio("http://www.nch.com.au/acm/8k16bitpcm.wav") # doctest: +SKIP
90 >>> Audio(url="http://www.w3schools.com/html/horse.ogg") # doctest: +SKIP
92 From a File:
94 >>> Audio('IPython/lib/tests/test.wav') # doctest: +SKIP
95 >>> Audio(filename='IPython/lib/tests/test.wav') # doctest: +SKIP
97 From Bytes:
99 >>> Audio(b'RAW_WAV_DATA..') # doctest: +SKIP
100 >>> Audio(data=b'RAW_WAV_DATA..') # doctest: +SKIP
102 See Also
103 --------
104 ipywidgets.Audio
106 Audio widget with more more flexibility and options.
108 """
109 _read_flags = 'rb'
111 def __init__(self, data=None, filename=None, url=None, embed=None, rate=None, autoplay=False, normalize=True, *,
112 element_id=None):
113 if filename is None and url is None and data is None:
114 raise ValueError("No audio data found. Expecting filename, url, or data.")
115 if embed is False and url is None:
116 raise ValueError("No url found. Expecting url when embed=False")
118 if url is not None and embed is not True:
119 self.embed = False
120 else:
121 self.embed = True
122 self.autoplay = autoplay
123 self.element_id = element_id
124 super().__init__(data=data, url=url, filename=filename)
126 if self.data is not None and not isinstance(self.data, bytes):
127 if rate is None:
128 raise ValueError("rate must be specified when data is a numpy array or list of audio samples.")
129 self.data = Audio._make_wav(data, rate, normalize)
131 def reload(self):
132 """Reload the raw data from file or URL."""
133 import mimetypes
134 if self.embed:
135 super().reload()
137 if self.filename is not None:
138 self.mimetype = mimetypes.guess_type(self.filename)[0]
139 elif self.url is not None:
140 self.mimetype = mimetypes.guess_type(self.url)[0]
141 else:
142 self.mimetype = "audio/wav"
144 @staticmethod
145 def _make_wav(data, rate, normalize):
146 """ Transform a numpy array to a PCM bytestring """
147 from io import BytesIO
148 import wave
150 try:
151 scaled, nchan = Audio._validate_and_normalize_with_numpy(data, normalize)
152 except ImportError:
153 scaled, nchan = Audio._validate_and_normalize_without_numpy(data, normalize)
155 fp = BytesIO()
156 waveobj = wave.open(fp,mode='wb')
157 waveobj.setnchannels(nchan)
158 waveobj.setframerate(rate)
159 waveobj.setsampwidth(2)
160 waveobj.setcomptype('NONE','NONE')
161 waveobj.writeframes(scaled)
162 val = fp.getvalue()
163 waveobj.close()
165 return val
167 @staticmethod
168 def _validate_and_normalize_with_numpy(data, normalize) -> tuple[bytes, int]:
169 import numpy as np
171 data = np.array(data, dtype=float)
172 if len(data.shape) == 1:
173 nchan = 1
174 elif len(data.shape) == 2:
175 # In wave files,channels are interleaved. E.g.,
176 # "L1R1L2R2..." for stereo. See
177 # http://msdn.microsoft.com/en-us/library/windows/hardware/dn653308(v=vs.85).aspx
178 # for channel ordering
179 nchan = data.shape[0]
180 data = data.T.ravel()
181 else:
182 raise ValueError('Array audio input must be a 1D or 2D array')
184 max_abs_value = np.max(np.abs(data))
185 normalization_factor = Audio._get_normalization_factor(max_abs_value, normalize)
186 scaled = data / normalization_factor * 32767
187 return scaled.astype("<h").tobytes(), nchan
189 @staticmethod
190 def _validate_and_normalize_without_numpy(data, normalize):
191 import array
192 import sys
194 data = array.array('f', data)
196 try:
197 max_abs_value = float(max([abs(x) for x in data]))
198 except TypeError as e:
199 raise TypeError('Only lists of mono audio are '
200 'supported if numpy is not installed') from e
202 normalization_factor = Audio._get_normalization_factor(max_abs_value, normalize)
203 scaled = array.array('h', [int(x / normalization_factor * 32767) for x in data])
204 if sys.byteorder == 'big':
205 scaled.byteswap()
206 nchan = 1
207 return scaled.tobytes(), nchan
209 @staticmethod
210 def _get_normalization_factor(max_abs_value, normalize):
211 if not normalize and max_abs_value > 1:
212 raise ValueError('Audio data must be between -1 and 1 when normalize=False.')
213 return max_abs_value if normalize else 1
215 def _data_and_metadata(self):
216 """shortcut for returning metadata with url information, if defined"""
217 md = {}
218 if self.url:
219 md['url'] = self.url
220 if md:
221 return self.data, md
222 else:
223 return self.data
225 def _repr_html_(self):
226 src = """
227 <audio {element_id} controls="controls" {autoplay}>
228 <source src="{src}" type="{type}" />
229 Your browser does not support the audio element.
230 </audio>
231 """
232 return src.format(src=self.src_attr(), type=self.mimetype, autoplay=self.autoplay_attr(),
233 element_id=self.element_id_attr())
235 def src_attr(self):
236 import base64
237 if self.embed and (self.data is not None):
238 data = base64=base64.b64encode(self.data).decode('ascii')
239 return """data:{type};base64,{base64}""".format(type=self.mimetype,
240 base64=data)
241 elif self.url is not None:
242 from html import escape as html_escape
244 return html_escape(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 from html import escape as html_escape
258 return f'id="{html_escape(self.element_id)}"'
259 else:
260 return ''
262class IFrame:
263 """
264 Generic class to embed an iframe in an IPython notebook
265 """
267 iframe = """
268 <iframe
269 width="{width}"
270 height="{height}"
271 src="{src}{params}"
272 frameborder="0"
273 allowfullscreen
274 {extras}
275 ></iframe>
276 """
278 def __init__(
279 self, src, width, height, extras: Iterable[str] | None = None, **kwargs
280 ):
281 if extras is None:
282 extras = []
284 self.src = src
285 self.width = width
286 self.height = height
287 self.extras = extras
288 self.params = kwargs
290 def _repr_html_(self):
291 """return the embed iframe"""
292 from html import escape as html_escape
294 if self.params:
295 from urllib.parse import urlencode
296 params = "?" + urlencode(self.params)
297 else:
298 params = ""
299 return self.iframe.format(
300 src=html_escape(str(self.src)),
301 width=html_escape(str(self.width)),
302 height=html_escape(str(self.height)),
303 params=params,
304 extras=" ".join(self.extras),
305 )
308class YouTubeVideo(IFrame):
309 """Class for embedding a YouTube Video in an IPython session, based on its video id.
311 e.g. to embed the video from https://www.youtube.com/watch?v=foo , you would
312 do::
314 vid = YouTubeVideo("foo")
315 display(vid)
317 To start from 30 seconds::
319 vid = YouTubeVideo("abc", start=30)
320 display(vid)
322 To calculate seconds from time as hours, minutes, seconds use
323 :class:`datetime.timedelta`::
325 start=int(timedelta(hours=1, minutes=46, seconds=40).total_seconds())
327 Other parameters can be provided as documented at
328 https://developers.google.com/youtube/player_parameters#Parameters
330 When converting the notebook using nbconvert, a jpeg representation of the video
331 will be inserted in the document.
332 """
334 def __init__(self, id, width=400, height=300, allow_autoplay=False, **kwargs):
335 self.id=id
336 src = f"https://www.youtube.com/embed/{id}"
337 if allow_autoplay:
338 extras = list(kwargs.get("extras", [])) + ['allow="autoplay"']
339 kwargs.update(autoplay=1, extras=extras)
340 super().__init__(src, width, height, **kwargs)
342 def _repr_jpeg_(self):
343 # Deferred import
344 from urllib.request import urlopen
346 try:
347 return urlopen(f"https://img.youtube.com/vi/{self.id}/hqdefault.jpg").read()
348 except OSError:
349 return None
351class VimeoVideo(IFrame):
352 """
353 Class for embedding a Vimeo video in an IPython session, based on its video id.
354 """
356 def __init__(self, id, width=400, height=300, **kwargs):
357 src=f"https://player.vimeo.com/video/{id}"
358 super().__init__(src, width, height, **kwargs)
360class ScribdDocument(IFrame):
361 """
362 Class for embedding a Scribd document in an IPython session
364 Use the start_page params to specify a starting point in the document
365 Use the view_mode params to specify display type one off scroll | slideshow | book
367 e.g to Display Wes' foundational paper about PANDAS in book mode from page 3
369 ScribdDocument(71048089, width=800, height=400, start_page=3, view_mode="book")
370 """
372 def __init__(self, id, width=400, height=300, **kwargs):
373 src=f"https://www.scribd.com/embeds/{id}/content"
374 super().__init__(src, width, height, **kwargs)
376class FileLink:
377 """Class for embedding a local file link in an IPython session, based on path
379 e.g. to embed a link that was generated in the IPython notebook as my/data.txt
381 you would do::
383 local_file = FileLink("my/data.txt")
384 display(local_file)
386 or in the HTML notebook, just::
388 FileLink("my/data.txt")
389 """
391 html_link_str = "<a href='%s' target='_blank'>%s</a>"
393 def __init__(self,
394 path,
395 url_prefix='',
396 result_html_prefix='',
397 result_html_suffix='<br>'):
398 """
399 Parameters
400 ----------
401 path : str
402 path to the file or directory that should be formatted
403 url_prefix : str
404 prefix to be prepended to all files to form a working link [default:
405 '']
406 result_html_prefix : str
407 text to append to beginning to link [default: '']
408 result_html_suffix : str
409 text to append at the end of link [default: '<br>']
410 """
411 if isdir(path):
412 raise ValueError("Cannot display a directory using FileLink. "
413 "Use FileLinks to display '%s'." % path)
414 self.path = fsdecode(path)
415 self.url_prefix = url_prefix
416 self.result_html_prefix = result_html_prefix
417 self.result_html_suffix = result_html_suffix
419 def _format_path(self):
420 from html import escape as html_escape
422 fp = ''.join([self.url_prefix, html_escape(self.path)])
423 return ''.join([self.result_html_prefix,
424 self.html_link_str % \
425 (fp, html_escape(self.path, quote=False)),
426 self.result_html_suffix])
428 def _repr_html_(self):
429 """return html link to file
430 """
431 if not exists(self.path):
432 return ("Path (<tt>%s</tt>) doesn't exist. "
433 "It may still be in the process of "
434 "being generated, or you may have the "
435 "incorrect path." % self.path)
437 return self._format_path()
439 def __repr__(self):
440 """return absolute path to file
441 """
442 return abspath(self.path)
444class FileLinks(FileLink):
445 """Class for embedding local file links in an IPython session, based on path
447 e.g. to embed links to files that were generated in the IPython notebook
448 under ``my/data``, you would do::
450 local_files = FileLinks("my/data")
451 display(local_files)
453 or in the HTML notebook, just::
455 FileLinks("my/data")
456 """
457 def __init__(self,
458 path,
459 url_prefix='',
460 included_suffixes=None,
461 result_html_prefix='',
462 result_html_suffix='<br>',
463 notebook_display_formatter=None,
464 terminal_display_formatter=None,
465 recursive=True):
466 """
467 See :class:`FileLink` for the ``path``, ``url_prefix``,
468 ``result_html_prefix`` and ``result_html_suffix`` parameters.
470 included_suffixes : list
471 Filename suffixes to include when formatting output [default: include
472 all files]
474 notebook_display_formatter : function
475 Used to format links for display in the notebook. See discussion of
476 formatter functions below.
478 terminal_display_formatter : function
479 Used to format links for display in the terminal. See discussion of
480 formatter functions below.
482 Formatter functions must be of the form::
484 f(dirname, fnames, included_suffixes)
486 dirname : str
487 The name of a directory
488 fnames : list
489 The files in that directory
490 included_suffixes : list
491 The file suffixes that should be included in the output (passing None
492 meansto include all suffixes in the output in the built-in formatters)
493 recursive : boolean
494 Whether to recurse into subdirectories. Default is True.
496 The function should return a list of lines that will be printed in the
497 notebook (if passing notebook_display_formatter) or the terminal (if
498 passing terminal_display_formatter). This function is iterated over for
499 each directory in self.path. Default formatters are in place, can be
500 passed here to support alternative formatting.
502 """
503 if isfile(path):
504 raise ValueError("Cannot display a file using FileLinks. "
505 "Use FileLink to display '%s'." % path)
506 self.included_suffixes = included_suffixes
507 # remove trailing slashes for more consistent output formatting
508 path = path.rstrip('/')
510 self.path = path
511 self.url_prefix = url_prefix
512 self.result_html_prefix = result_html_prefix
513 self.result_html_suffix = result_html_suffix
515 self.notebook_display_formatter = \
516 notebook_display_formatter or self._get_notebook_display_formatter()
517 self.terminal_display_formatter = \
518 terminal_display_formatter or self._get_terminal_display_formatter()
520 self.recursive = recursive
522 def _get_display_formatter(
523 self,
524 dirname_output_format,
525 fname_output_format,
526 fp_format,
527 fp_cleaner=None,
528 escape_names=False,
529 ):
530 """generate built-in formatter function
532 this is used to define both the notebook and terminal built-in
533 formatters as they only differ by some wrapper text for each entry
535 dirname_output_format: string to use for formatting directory
536 names, dirname will be substituted for a single "%s" which
537 must appear in this string
538 fname_output_format: string to use for formatting file names,
539 if a single "%s" appears in the string, fname will be substituted
540 if two "%s" appear in the string, the path to fname will be
541 substituted for the first and fname will be substituted for the
542 second
543 fp_format: string to use for formatting filepaths, must contain
544 exactly two "%s" and the dirname will be substituted for the first
545 and fname will be substituted for the second
546 escape_names: whether directory and file names must be HTML-escaped
547 before being substituted, as they are for the notebook formatter
548 """
549 if escape_names:
550 from html import escape as html_escape
552 escape = html_escape
553 else:
554 escape = str
556 def f(dirname, fnames, included_suffixes=None):
557 result = []
558 # begin by figuring out which filenames, if any,
559 # are going to be displayed
560 display_fnames = []
561 for fname in fnames:
562 if (isfile(join(dirname,fname)) and
563 (included_suffixes is None or
564 splitext(fname)[1] in included_suffixes)):
565 display_fnames.append(fname)
567 if len(display_fnames) == 0:
568 # if there are no filenames to display, don't print anything
569 # (not even the directory name)
570 pass
571 else:
572 # otherwise print the formatted directory name followed by
573 # the formatted filenames
574 dirname_output_line = dirname_output_format % escape(dirname)
575 result.append(dirname_output_line)
576 for fname in display_fnames:
577 fp = fp_format % (escape(dirname), escape(fname))
578 if fp_cleaner is not None:
579 fp = fp_cleaner(fp)
580 try:
581 # output can include both a filepath and a filename...
582 fname_output_line = fname_output_format % (fp, escape(fname))
583 except TypeError:
584 # ... or just a single filepath
585 fname_output_line = fname_output_format % escape(fname)
586 result.append(fname_output_line)
587 return result
588 return f
590 def _get_notebook_display_formatter(self,
591 spacer=" "):
592 """ generate function to use for notebook formatting
593 """
594 dirname_output_format = \
595 self.result_html_prefix + "%s/" + self.result_html_suffix
596 fname_output_format = \
597 self.result_html_prefix + spacer + self.html_link_str + self.result_html_suffix
598 fp_format = self.url_prefix + '%s/%s'
599 if sep == "\\":
600 # Working on a platform where the path separator is "\", so
601 # must convert these to "/" for generating a URI
602 def fp_cleaner(fp):
603 # Replace all occurrences of backslash ("\") with a forward
604 # slash ("/") - this is necessary on windows when a path is
605 # provided as input, but we must link to a URI
606 return fp.replace('\\','/')
607 else:
608 fp_cleaner = None
610 return self._get_display_formatter(
611 dirname_output_format,
612 fname_output_format,
613 fp_format,
614 fp_cleaner,
615 escape_names=True,
616 )
618 def _get_terminal_display_formatter(self,
619 spacer=" "):
620 """ generate function to use for terminal formatting
621 """
622 dirname_output_format = "%s/"
623 fname_output_format = spacer + "%s"
624 fp_format = '%s/%s'
626 return self._get_display_formatter(dirname_output_format,
627 fname_output_format,
628 fp_format)
630 def _format_path(self):
631 result_lines = []
632 if self.recursive:
633 walked_dir = list(walk(self.path))
634 else:
635 walked_dir = [next(walk(self.path))]
636 walked_dir.sort()
637 for dirname, subdirs, fnames in walked_dir:
638 result_lines += self.notebook_display_formatter(dirname, fnames, self.included_suffixes)
639 return '\n'.join(result_lines)
641 def __repr__(self):
642 """return newline-separated absolute paths
643 """
644 result_lines = []
645 if self.recursive:
646 walked_dir = list(walk(self.path))
647 else:
648 walked_dir = [next(walk(self.path))]
649 walked_dir.sort()
650 for dirname, subdirs, fnames in walked_dir:
651 result_lines += self.terminal_display_formatter(dirname, fnames, self.included_suffixes)
652 return '\n'.join(result_lines)
655class Code(TextDisplayObject):
656 """Display syntax-highlighted source code.
658 This uses Pygments to highlight the code for HTML and Latex output.
660 Parameters
661 ----------
662 data : str
663 The code as a string
664 url : str
665 A URL to fetch the code from
666 filename : str
667 A local filename to load the code from
668 language : str
669 The short name of a Pygments lexer to use for highlighting.
670 If not specified, it will guess the lexer based on the filename
671 or the code. Available lexers: http://pygments.org/docs/lexers/
672 """
673 def __init__(self, data=None, url=None, filename=None, language=None):
674 self.language = language
675 super().__init__(data=data, url=url, filename=filename)
677 def _get_lexer(self):
678 if self.language:
679 from pygments.lexers import get_lexer_by_name
680 return get_lexer_by_name(self.language)
681 elif self.filename:
682 from pygments.lexers import get_lexer_for_filename
683 return get_lexer_for_filename(self.filename)
684 else:
685 from pygments.lexers import guess_lexer
686 return guess_lexer(self.data)
688 def __repr__(self):
689 return self.data
691 def _repr_html_(self):
692 from pygments import highlight
693 from pygments.formatters import HtmlFormatter
694 fmt = HtmlFormatter()
695 style = '<style>{}</style>'.format(fmt.get_style_defs('.output_html'))
696 return style + highlight(self.data, self._get_lexer(), fmt)
698 def _repr_latex_(self):
699 from pygments import highlight
700 from pygments.formatters import LatexFormatter
701 return highlight(self.data, self._get_lexer(), LatexFormatter())