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
« prev ^ index » next coverage.py v7.2.2, created at 2023-03-26 06:07 +0000
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, Iterable
13__all__ = ['Audio', 'IFrame', 'YouTubeVideo', 'VimeoVideo', 'ScribdDocument',
14 'FileLink', 'FileLinks', 'Code']
17class Audio(DisplayObject):
18 """Create an audio object.
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).
24 Parameters
25 ----------
26 data : numpy array, list, unicode, str or bytes
27 Can be one of
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.
38 If the array option is used, the waveform will be normalized.
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.
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.
66 Examples
67 --------
69 >>> import pytest
70 >>> np = pytest.importorskip("numpy")
72 Generate a sound
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>
81 Can also do stereo or more channels
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>
88 From URL:
90 >>> Audio("http://www.nch.com.au/acm/8k16bitpcm.wav") # doctest: +SKIP
91 >>> Audio(url="http://www.w3schools.com/html/horse.ogg") # doctest: +SKIP
93 From a File:
95 >>> Audio('IPython/lib/tests/test.wav') # doctest: +SKIP
96 >>> Audio(filename='IPython/lib/tests/test.wav') # doctest: +SKIP
98 From Bytes:
100 >>> Audio(b'RAW_WAV_DATA..') # doctest: +SKIP
101 >>> Audio(data=b'RAW_WAV_DATA..') # doctest: +SKIP
103 See Also
104 --------
105 ipywidgets.Audio
107 Audio widget with more more flexibility and options.
109 """
110 _read_flags = 'rb'
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")
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)
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)
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()
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"
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
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)
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()
166 return val
168 @staticmethod
169 def _validate_and_normalize_with_numpy(data, normalize) -> Tuple[bytes, int]:
170 import numpy as np
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')
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
190 @staticmethod
191 def _validate_and_normalize_without_numpy(data, normalize):
192 import array
193 import sys
195 data = array.array('f', data)
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
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
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
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
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())
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 ""
247 def autoplay_attr(self):
248 if(self.autoplay):
249 return 'autoplay="autoplay"'
250 else:
251 return ''
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 ''
259class IFrame(object):
260 """
261 Generic class to embed an iframe in an IPython notebook
262 """
264 iframe = """
265 <iframe
266 width="{width}"
267 height="{height}"
268 src="{src}{params}"
269 frameborder="0"
270 allowfullscreen
271 {extras}
272 ></iframe>
273 """
275 def __init__(self, src, width, height, extras: Iterable[str] = None, **kwargs):
276 if extras is None:
277 extras = []
279 self.src = src
280 self.width = width
281 self.height = height
282 self.extras = extras
283 self.params = kwargs
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 )
301class YouTubeVideo(IFrame):
302 """Class for embedding a YouTube Video in an IPython session, based on its video id.
304 e.g. to embed the video from https://www.youtube.com/watch?v=foo , you would
305 do::
307 vid = YouTubeVideo("foo")
308 display(vid)
310 To start from 30 seconds::
312 vid = YouTubeVideo("abc", start=30)
313 display(vid)
315 To calculate seconds from time as hours, minutes, seconds use
316 :class:`datetime.timedelta`::
318 start=int(timedelta(hours=1, minutes=46, seconds=40).total_seconds())
320 Other parameters can be provided as documented at
321 https://developers.google.com/youtube/player_parameters#Parameters
323 When converting the notebook using nbconvert, a jpeg representation of the video
324 will be inserted in the document.
325 """
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)
335 def _repr_jpeg_(self):
336 # Deferred import
337 from urllib.request import urlopen
339 try:
340 return urlopen("https://img.youtube.com/vi/{id}/hqdefault.jpg".format(id=self.id)).read()
341 except IOError:
342 return None
344class VimeoVideo(IFrame):
345 """
346 Class for embedding a Vimeo video in an IPython session, based on its video id.
347 """
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)
353class ScribdDocument(IFrame):
354 """
355 Class for embedding a Scribd document in an IPython session
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
360 e.g to Display Wes' foundational paper about PANDAS in book mode from page 3
362 ScribdDocument(71048089, width=800, height=400, start_page=3, view_mode="book")
363 """
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)
369class FileLink(object):
370 """Class for embedding a local file link in an IPython session, based on path
372 e.g. to embed a link that was generated in the IPython notebook as my/data.txt
374 you would do::
376 local_file = FileLink("my/data.txt")
377 display(local_file)
379 or in the HTML notebook, just::
381 FileLink("my/data.txt")
382 """
384 html_link_str = "<a href='%s' target='_blank'>%s</a>"
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
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])
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)
428 return self._format_path()
430 def __repr__(self):
431 """return absolute path to file
432 """
433 return abspath(self.path)
435class FileLinks(FileLink):
436 """Class for embedding local file links in an IPython session, based on path
438 e.g. to embed links to files that were generated in the IPython notebook
439 under ``my/data``, you would do::
441 local_files = FileLinks("my/data")
442 display(local_files)
444 or in the HTML notebook, just::
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.
461 included_suffixes : list
462 Filename suffixes to include when formatting output [default: include
463 all files]
465 notebook_display_formatter : function
466 Used to format links for display in the notebook. See discussion of
467 formatter functions below.
469 terminal_display_formatter : function
470 Used to format links for display in the terminal. See discussion of
471 formatter functions below.
473 Formatter functions must be of the form::
475 f(dirname, fnames, included_suffixes)
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.
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.
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('/')
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
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()
511 self.recursive = recursive
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
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
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)
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
567 def _get_notebook_display_formatter(self,
568 spacer=" "):
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
587 return self._get_display_formatter(dirname_output_format,
588 fname_output_format,
589 fp_format,
590 fp_cleaner)
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'
600 return self._get_display_formatter(dirname_output_format,
601 fname_output_format,
602 fp_format)
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)
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)
629class Code(TextDisplayObject):
630 """Display syntax-highlighted source code.
632 This uses Pygments to highlight the code for HTML and Latex output.
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)
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)
662 def __repr__(self):
663 return self.data
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)
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())