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 referrerpolicy="strict-origin-when-cross-origin"
275 {extras}
276 ></iframe>
277 """
279 def __init__(
280 self, src, width, height, extras: Iterable[str] | None = None, **kwargs
281 ):
282 if extras is None:
283 extras = []
285 self.src = src
286 self.width = width
287 self.height = height
288 self.extras = extras
289 self.params = kwargs
291 def _repr_html_(self):
292 """return the embed iframe"""
293 from html import escape as html_escape
295 if self.params:
296 from urllib.parse import urlencode
297 params = "?" + urlencode(self.params)
298 else:
299 params = ""
300 return self.iframe.format(
301 src=html_escape(str(self.src)),
302 width=html_escape(str(self.width)),
303 height=html_escape(str(self.height)),
304 params=params,
305 extras=" ".join(self.extras),
306 )
309class YouTubeVideo(IFrame):
310 """Class for embedding a YouTube Video in an IPython session, based on its video id.
312 e.g. to embed the video from https://www.youtube.com/watch?v=foo , you would
313 do::
315 vid = YouTubeVideo("foo")
316 display(vid)
318 To start from 30 seconds::
320 vid = YouTubeVideo("abc", start=30)
321 display(vid)
323 To calculate seconds from time as hours, minutes, seconds use
324 :class:`datetime.timedelta`::
326 start=int(timedelta(hours=1, minutes=46, seconds=40).total_seconds())
328 Other parameters can be provided as documented at
329 https://developers.google.com/youtube/player_parameters#Parameters
331 When converting the notebook using nbconvert, a jpeg representation of the video
332 will be inserted in the document.
333 """
335 def __init__(self, id, width=400, height=300, allow_autoplay=False, **kwargs):
336 self.id=id
337 src = f"https://www.youtube.com/embed/{id}"
338 if allow_autoplay:
339 extras = list(kwargs.get("extras", [])) + ['allow="autoplay"']
340 kwargs.update(autoplay=1, extras=extras)
341 super().__init__(src, width, height, **kwargs)
343 def _repr_jpeg_(self):
344 # Deferred import
345 from urllib.request import urlopen
347 try:
348 return urlopen(f"https://img.youtube.com/vi/{self.id}/hqdefault.jpg").read()
349 except OSError:
350 return None
352class VimeoVideo(IFrame):
353 """
354 Class for embedding a Vimeo video in an IPython session, based on its video id.
355 """
357 def __init__(self, id, width=400, height=300, **kwargs):
358 src=f"https://player.vimeo.com/video/{id}"
359 super().__init__(src, width, height, **kwargs)
361class ScribdDocument(IFrame):
362 """
363 Class for embedding a Scribd document in an IPython session
365 Use the start_page params to specify a starting point in the document
366 Use the view_mode params to specify display type one off scroll | slideshow | book
368 e.g to Display Wes' foundational paper about PANDAS in book mode from page 3
370 ScribdDocument(71048089, width=800, height=400, start_page=3, view_mode="book")
371 """
373 def __init__(self, id, width=400, height=300, **kwargs):
374 src=f"https://www.scribd.com/embeds/{id}/content"
375 super().__init__(src, width, height, **kwargs)
377class FileLink:
378 """Class for embedding a local file link in an IPython session, based on path
380 e.g. to embed a link that was generated in the IPython notebook as my/data.txt
382 you would do::
384 local_file = FileLink("my/data.txt")
385 display(local_file)
387 or in the HTML notebook, just::
389 FileLink("my/data.txt")
390 """
392 html_link_str = "<a href='%s' target='_blank'>%s</a>"
394 def __init__(self,
395 path,
396 url_prefix='',
397 result_html_prefix='',
398 result_html_suffix='<br>'):
399 """
400 Parameters
401 ----------
402 path : str or os.PathLike
403 path to the file or directory that should be formatted
404 url_prefix : str
405 prefix to be prepended to all files to form a working link [default:
406 '']
407 result_html_prefix : str
408 text to append to beginning to link [default: '']
409 result_html_suffix : str
410 text to append at the end of link [default: '<br>']
411 """
412 if isdir(path):
413 raise ValueError("Cannot display a directory using FileLink. "
414 "Use FileLinks to display '%s'." % path)
415 self.path = fsdecode(path)
416 self.url_prefix = url_prefix
417 self.result_html_prefix = result_html_prefix
418 self.result_html_suffix = result_html_suffix
420 def _format_path(self):
421 from html import escape as html_escape
423 fp = ''.join([self.url_prefix, html_escape(self.path)])
424 return ''.join([self.result_html_prefix,
425 self.html_link_str % \
426 (fp, html_escape(self.path, quote=False)),
427 self.result_html_suffix])
429 def _repr_html_(self):
430 """return html link to file
431 """
432 if not exists(self.path):
433 return ("Path (<tt>%s</tt>) doesn't exist. "
434 "It may still be in the process of "
435 "being generated, or you may have the "
436 "incorrect path." % self.path)
438 return self._format_path()
440 def __repr__(self):
441 """return absolute path to file
442 """
443 return abspath(self.path)
445class FileLinks(FileLink):
446 """Class for embedding local file links in an IPython session, based on path
448 e.g. to embed links to files that were generated in the IPython notebook
449 under ``my/data``, you would do::
451 local_files = FileLinks("my/data")
452 display(local_files)
454 or in the HTML notebook, just::
456 FileLinks("my/data")
457 """
458 def __init__(self,
459 path,
460 url_prefix='',
461 included_suffixes=None,
462 result_html_prefix='',
463 result_html_suffix='<br>',
464 notebook_display_formatter=None,
465 terminal_display_formatter=None,
466 recursive=True):
467 """
468 See :class:`FileLink` for the ``path``, ``url_prefix``,
469 ``result_html_prefix`` and ``result_html_suffix`` parameters.
471 included_suffixes : list
472 Filename suffixes to include when formatting output [default: include
473 all files]
475 notebook_display_formatter : function
476 Used to format links for display in the notebook. See discussion of
477 formatter functions below.
479 terminal_display_formatter : function
480 Used to format links for display in the terminal. See discussion of
481 formatter functions below.
483 Formatter functions must be of the form::
485 f(dirname, fnames, included_suffixes)
487 dirname : str
488 The name of a directory
489 fnames : list
490 The files in that directory
491 included_suffixes : list
492 The file suffixes that should be included in the output (passing None
493 meansto include all suffixes in the output in the built-in formatters)
494 recursive : boolean
495 Whether to recurse into subdirectories. Default is True.
497 The function should return a list of lines that will be printed in the
498 notebook (if passing notebook_display_formatter) or the terminal (if
499 passing terminal_display_formatter). This function is iterated over for
500 each directory in self.path. Default formatters are in place, can be
501 passed here to support alternative formatting.
503 """
504 if isfile(path):
505 raise ValueError("Cannot display a file using FileLinks. "
506 "Use FileLink to display '%s'." % path)
507 self.included_suffixes = included_suffixes
508 # remove trailing slashes for more consistent output formatting
509 path = fsdecode(path).rstrip("/")
511 self.path = path
512 self.url_prefix = url_prefix
513 self.result_html_prefix = result_html_prefix
514 self.result_html_suffix = result_html_suffix
516 self.notebook_display_formatter = \
517 notebook_display_formatter or self._get_notebook_display_formatter()
518 self.terminal_display_formatter = \
519 terminal_display_formatter or self._get_terminal_display_formatter()
521 self.recursive = recursive
523 def _get_display_formatter(
524 self,
525 dirname_output_format,
526 fname_output_format,
527 fp_format,
528 fp_cleaner=None,
529 escape_names=False,
530 ):
531 """generate built-in formatter function
533 this is used to define both the notebook and terminal built-in
534 formatters as they only differ by some wrapper text for each entry
536 dirname_output_format: string to use for formatting directory
537 names, dirname will be substituted for a single "%s" which
538 must appear in this string
539 fname_output_format: string to use for formatting file names,
540 if a single "%s" appears in the string, fname will be substituted
541 if two "%s" appear in the string, the path to fname will be
542 substituted for the first and fname will be substituted for the
543 second
544 fp_format: string to use for formatting filepaths, must contain
545 exactly two "%s" and the dirname will be substituted for the first
546 and fname will be substituted for the second
547 escape_names: whether directory and file names must be HTML-escaped
548 before being substituted, as they are for the notebook formatter
549 """
550 if escape_names:
551 from html import escape as html_escape
553 escape = html_escape
554 else:
555 escape = str
557 def f(dirname, fnames, included_suffixes=None):
558 result = []
559 # begin by figuring out which filenames, if any,
560 # are going to be displayed
561 display_fnames = []
562 for fname in fnames:
563 if (isfile(join(dirname,fname)) and
564 (included_suffixes is None or
565 splitext(fname)[1] in included_suffixes)):
566 display_fnames.append(fname)
568 if len(display_fnames) == 0:
569 # if there are no filenames to display, don't print anything
570 # (not even the directory name)
571 pass
572 else:
573 # otherwise print the formatted directory name followed by
574 # the formatted filenames
575 dirname_output_line = dirname_output_format % escape(dirname)
576 result.append(dirname_output_line)
577 for fname in display_fnames:
578 fp = fp_format % (escape(dirname), escape(fname))
579 if fp_cleaner is not None:
580 fp = fp_cleaner(fp)
581 try:
582 # output can include both a filepath and a filename...
583 fname_output_line = fname_output_format % (fp, escape(fname))
584 except TypeError:
585 # ... or just a single filepath
586 fname_output_line = fname_output_format % escape(fname)
587 result.append(fname_output_line)
588 return result
589 return f
591 def _get_notebook_display_formatter(self,
592 spacer=" "):
593 """ generate function to use for notebook formatting
594 """
595 dirname_output_format = \
596 self.result_html_prefix + "%s/" + self.result_html_suffix
597 fname_output_format = \
598 self.result_html_prefix + spacer + self.html_link_str + self.result_html_suffix
599 fp_format = self.url_prefix + '%s/%s'
600 if sep == "\\":
601 # Working on a platform where the path separator is "\", so
602 # must convert these to "/" for generating a URI
603 def fp_cleaner(fp):
604 # Replace all occurrences of backslash ("\") with a forward
605 # slash ("/") - this is necessary on windows when a path is
606 # provided as input, but we must link to a URI
607 return fp.replace('\\','/')
608 else:
609 fp_cleaner = None
611 return self._get_display_formatter(
612 dirname_output_format,
613 fname_output_format,
614 fp_format,
615 fp_cleaner,
616 escape_names=True,
617 )
619 def _get_terminal_display_formatter(self,
620 spacer=" "):
621 """ generate function to use for terminal formatting
622 """
623 dirname_output_format = "%s/"
624 fname_output_format = spacer + "%s"
625 fp_format = '%s/%s'
627 return self._get_display_formatter(dirname_output_format,
628 fname_output_format,
629 fp_format)
631 def _format_path(self):
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.notebook_display_formatter(dirname, fnames, self.included_suffixes)
640 return '\n'.join(result_lines)
642 def __repr__(self):
643 """return newline-separated absolute paths
644 """
645 result_lines = []
646 if self.recursive:
647 walked_dir = list(walk(self.path))
648 else:
649 walked_dir = [next(walk(self.path))]
650 walked_dir.sort()
651 for dirname, subdirs, fnames in walked_dir:
652 result_lines += self.terminal_display_formatter(dirname, fnames, self.included_suffixes)
653 return '\n'.join(result_lines)
656class Code(TextDisplayObject):
657 """Display syntax-highlighted source code.
659 This uses Pygments to highlight the code for HTML and Latex output.
661 Parameters
662 ----------
663 data : str
664 The code as a string
665 url : str
666 A URL to fetch the code from
667 filename : str
668 A local filename to load the code from
669 language : str
670 The short name of a Pygments lexer to use for highlighting.
671 If not specified, it will guess the lexer based on the filename
672 or the code. Available lexers: http://pygments.org/docs/lexers/
673 """
674 def __init__(self, data=None, url=None, filename=None, language=None):
675 self.language = language
676 super().__init__(data=data, url=url, filename=filename)
678 def _get_lexer(self):
679 if self.language:
680 from pygments.lexers import get_lexer_by_name
681 return get_lexer_by_name(self.language)
682 elif self.filename:
683 from pygments.lexers import get_lexer_for_filename
684 return get_lexer_for_filename(self.filename)
685 else:
686 from pygments.lexers import guess_lexer
687 return guess_lexer(self.data)
689 def __repr__(self):
690 return self.data
692 def _repr_html_(self):
693 from pygments import highlight
694 from pygments.formatters import HtmlFormatter
695 fmt = HtmlFormatter()
696 style = '<style>{}</style>'.format(fmt.get_style_defs('.output_html'))
697 return style + highlight(self.data, self._get_lexer(), fmt)
699 def _repr_latex_(self):
700 from pygments import highlight
701 from pygments.formatters import LatexFormatter
702 return highlight(self.data, self._get_lexer(), LatexFormatter())