display.py
675 lines
| 24.0 KiB
| text/x-python
|
PythonLexer
Brian E. Granger
|
r4408 | """Various display related classes. | ||
Greg Caporaso
|
r8798 | Authors : MinRK, gregcaporaso, dannystaple | ||
Brian E. Granger
|
r4408 | """ | ||
Cristian Ciupitu
|
r24230 | from html import escape as html_escape | ||
Greg Caporaso
|
r8800 | from os.path import exists, isfile, splitext, abspath, join, isdir | ||
Cristian Ciupitu
|
r24227 | from os import walk, sep, fsdecode | ||
Greg Caporaso
|
r8272 | |||
Thomas Kluyver
|
r24130 | from IPython.core.display import DisplayObject, TextDisplayObject | ||
David Österberg
|
r12829 | |||
yuji96
|
r26778 | from typing import Tuple, Iterable | ||
Matthias Bussonnier
|
r26390 | |||
Thomas Kluyver
|
r17121 | __all__ = ['Audio', 'IFrame', 'YouTubeVideo', 'VimeoVideo', 'ScribdDocument', | ||
Thomas Kluyver
|
r24130 | 'FileLink', 'FileLinks', 'Code'] | ||
Thomas Kluyver
|
r17121 | |||
David Österberg
|
r12829 | |||
class Audio(DisplayObject): | ||||
"""Create an audio object. | ||||
When this object is returned by an input cell or passed to the | ||||
display function, it will result in Audio controls being displayed | ||||
in the frontend (only works in the notebook). | ||||
Erik Tollerud
|
r16550 | |||
David Österberg
|
r12829 | Parameters | ||
---------- | ||||
David Österberg
|
r12968 | data : numpy array, list, unicode, str or bytes | ||
Erik Tollerud
|
r16551 | Can be one of | ||
* Numpy 1d array containing the desired waveform (mono) | ||||
* Numpy 2d array containing waveforms for each channel. | ||||
Shape=(NCHAN, NSAMPLES). For the standard channel order, see | ||||
http://msdn.microsoft.com/en-us/library/windows/hardware/dn653308(v=vs.85).aspx | ||||
* List of float or integer representing the waveform (mono) | ||||
* String containing the filename | ||||
* Bytestring containing raw PCM data or | ||||
* URL pointing to a file on the web. | ||||
Erik Tollerud
|
r16550 | |||
Jan S. (Milania1)
|
r24871 | If the array option is used, the waveform will be normalized. | ||
Erik Tollerud
|
r16550 | |||
Jan S. (Milania1)
|
r24871 | If a filename or url is used, the format support will be browser | ||
Erik Tollerud
|
r16550 | dependent. | ||
David Österberg
|
r12829 | url : unicode | ||
A URL to download the data from. | ||||
filename : unicode | ||||
Path to a local file to load the data from. | ||||
embed : boolean | ||||
Jonas Rauber
|
r22201 | Should the audio data be embedded using a data URI (True) or should | ||
Erik Tollerud
|
r16550 | the original source be referenced. Set this to True if you want the | ||
David Österberg
|
r12834 | audio to playable later with no internet connection in the notebook. | ||
David Österberg
|
r12829 | |||
Default is `True`, unless the keyword argument `url` is set, then | ||||
default value is `False`. | ||||
rate : integer | ||||
The sampling rate of the raw data. | ||||
Only required when data parameter is being used as an array | ||||
autoplay : bool | ||||
Set to True if the audio should immediately start playing. | ||||
Default is `False`. | ||||
Matan Gover
|
r24973 | normalize : bool | ||
Whether audio should be normalized (rescaled) to the maximum possible | ||||
range. Default is `True`. When set to `False`, `data` must be between | ||||
-1 and 1 (inclusive), otherwise an error is raised. | ||||
Applies only when `data` is a list or array of samples; other types of | ||||
audio are never normalized. | ||||
David Österberg
|
r12829 | |||
Examples | ||||
-------- | ||||
Thomas Kluyver
|
r13587 | |||
Nikita Kniazev
|
r27104 | >>> import pytest | ||
>>> np = pytest.importorskip("numpy") | ||||
Matthias Bussonnier
|
r25897 | Generate a sound | ||
Thomas Kluyver
|
r13587 | |||
Matthias Bussonnier
|
r25897 | >>> import numpy as np | ||
Nikita Kniazev
|
r26937 | >>> framerate = 44100 | ||
>>> t = np.linspace(0,5,framerate*5) | ||||
>>> data = np.sin(2*np.pi*220*t) + np.sin(2*np.pi*224*t) | ||||
>>> Audio(data, rate=framerate) | ||||
<IPython.lib.display.Audio object> | ||||
Erik Tollerud
|
r16551 | |||
Matthias Bussonnier
|
r25897 | Can also do stereo or more channels | ||
Thomas Kluyver
|
r13587 | |||
Matthias Bussonnier
|
r25897 | >>> dataleft = np.sin(2*np.pi*220*t) | ||
Nikita Kniazev
|
r26937 | >>> dataright = np.sin(2*np.pi*224*t) | ||
>>> Audio([dataleft, dataright], rate=framerate) | ||||
<IPython.lib.display.Audio object> | ||||
Thomas Kluyver
|
r13587 | |||
Matthias Bussonnier
|
r25897 | From URL: | ||
Nikita Kniazev
|
r26937 | >>> Audio("http://www.nch.com.au/acm/8k16bitpcm.wav") # doctest: +SKIP | ||
>>> Audio(url="http://www.w3schools.com/html/horse.ogg") # doctest: +SKIP | ||||
Matthias Bussonnier
|
r25897 | |||
From a File: | ||||
Matthias Bussonnier
|
r27616 | >>> Audio('IPython/lib/tests/test.wav') # doctest: +SKIP | ||
>>> Audio(filename='IPython/lib/tests/test.wav') # doctest: +SKIP | ||||
Matthias Bussonnier
|
r25897 | |||
From Bytes: | ||||
Nikita Kniazev
|
r26937 | >>> Audio(b'RAW_WAV_DATA..') # doctest: +SKIP | ||
>>> Audio(data=b'RAW_WAV_DATA..') # doctest: +SKIP | ||||
David Österberg
|
r12829 | |||
Matthias Bussonnier
|
r25005 | See Also | ||
-------- | ||||
Matthias Bussonnier
|
r25924 | ipywidgets.Audio | ||
Matthias Bussonnier
|
r27639 | |||
Audio widget with more more flexibility and options. | ||||
David Österberg
|
r12829 | """ | ||
Thomas Kluyver
|
r13549 | _read_flags = 'rb' | ||
Matthias Bussonnier
|
r25003 | def __init__(self, data=None, filename=None, url=None, embed=None, rate=None, autoplay=False, normalize=True, *, | ||
stef-ubuntu
|
r24993 | element_id=None): | ||
David Österberg
|
r12829 | if filename is None and url is None and data is None: | ||
Matan Gover
|
r24969 | raise ValueError("No audio data found. Expecting filename, url, or data.") | ||
David Österberg
|
r12829 | if embed is False and url is None: | ||
raise ValueError("No url found. Expecting url when embed=False") | ||||
Erik Tollerud
|
r16550 | |||
David Österberg
|
r12829 | if url is not None and embed is not True: | ||
self.embed = False | ||||
else: | ||||
self.embed = True | ||||
self.autoplay = autoplay | ||||
stef-ubuntu
|
r24993 | self.element_id = element_id | ||
David Österberg
|
r12829 | super(Audio, self).__init__(data=data, url=url, filename=filename) | ||
Erik Tollerud
|
r16550 | |||
David Österberg
|
r12829 | if self.data is not None and not isinstance(self.data, bytes): | ||
Matan Gover
|
r24970 | if rate is None: | ||
raise ValueError("rate must be specified when data is a numpy array or list of audio samples.") | ||||
Matan Gover
|
r24973 | self.data = Audio._make_wav(data, rate, normalize) | ||
Erik Tollerud
|
r16550 | |||
David Österberg
|
r12829 | def reload(self): | ||
"""Reload the raw data from file or URL.""" | ||||
David Österberg
|
r12833 | import mimetypes | ||
David Österberg
|
r12829 | if self.embed: | ||
super(Audio, self).reload() | ||||
if self.filename is not None: | ||||
self.mimetype = mimetypes.guess_type(self.filename)[0] | ||||
elif self.url is not None: | ||||
self.mimetype = mimetypes.guess_type(self.url)[0] | ||||
else: | ||||
self.mimetype = "audio/wav" | ||||
Erik Tollerud
|
r16550 | |||
Matan Gover
|
r24968 | @staticmethod | ||
Matan Gover
|
r24973 | def _make_wav(data, rate, normalize): | ||
David Österberg
|
r12829 | """ Transform a numpy array to a PCM bytestring """ | ||
David Österberg
|
r12833 | from io import BytesIO | ||
import wave | ||||
Erik Tollerud
|
r16550 | |||
David Österberg
|
r12869 | try: | ||
Matan Gover
|
r24973 | scaled, nchan = Audio._validate_and_normalize_with_numpy(data, normalize) | ||
David Österberg
|
r12869 | except ImportError: | ||
Matan Gover
|
r24973 | scaled, nchan = Audio._validate_and_normalize_without_numpy(data, normalize) | ||
Erik Tollerud
|
r16550 | |||
David Österberg
|
r12829 | fp = BytesIO() | ||
waveobj = wave.open(fp,mode='wb') | ||||
Erik Tollerud
|
r16550 | waveobj.setnchannels(nchan) | ||
David Österberg
|
r12829 | waveobj.setframerate(rate) | ||
waveobj.setsampwidth(2) | ||||
waveobj.setcomptype('NONE','NONE') | ||||
Jonas Haag
|
r25457 | waveobj.writeframes(scaled) | ||
David Österberg
|
r12829 | val = fp.getvalue() | ||
waveobj.close() | ||||
Erik Tollerud
|
r16550 | |||
return val | ||||
Matan Gover
|
r24968 | @staticmethod | ||
Matthias Bussonnier
|
r26390 | def _validate_and_normalize_with_numpy(data, normalize) -> Tuple[bytes, int]: | ||
Matan Gover
|
r24968 | import numpy as np | ||
data = np.array(data, dtype=float) | ||||
if len(data.shape) == 1: | ||||
nchan = 1 | ||||
elif len(data.shape) == 2: | ||||
# In wave files,channels are interleaved. E.g., | ||||
# "L1R1L2R2..." for stereo. See | ||||
# http://msdn.microsoft.com/en-us/library/windows/hardware/dn653308(v=vs.85).aspx | ||||
# for channel ordering | ||||
nchan = data.shape[0] | ||||
data = data.T.ravel() | ||||
else: | ||||
raise ValueError('Array audio input must be a 1D or 2D array') | ||||
Matan Gover
|
r24973 | |||
max_abs_value = np.max(np.abs(data)) | ||||
normalization_factor = Audio._get_normalization_factor(max_abs_value, normalize) | ||||
Jonas Haag
|
r25457 | scaled = data / normalization_factor * 32767 | ||
Matthias Bussonnier
|
r26390 | return scaled.astype("<h").tobytes(), nchan | ||
Matan Gover
|
r24973 | |||
Matan Gover
|
r24968 | @staticmethod | ||
Matan Gover
|
r24973 | def _validate_and_normalize_without_numpy(data, normalize): | ||
Jonas Haag
|
r25457 | import array | ||
import sys | ||||
data = array.array('f', data) | ||||
Matan Gover
|
r24968 | try: | ||
Matan Gover
|
r24973 | max_abs_value = float(max([abs(x) for x in data])) | ||
Ram Rachum
|
r25833 | except TypeError as e: | ||
Matan Gover
|
r24968 | raise TypeError('Only lists of mono audio are ' | ||
Ram Rachum
|
r25833 | 'supported if numpy is not installed') from e | ||
Matan Gover
|
r24972 | |||
Matan Gover
|
r24973 | normalization_factor = Audio._get_normalization_factor(max_abs_value, normalize) | ||
Jonas Haag
|
r25457 | scaled = array.array('h', [int(x / normalization_factor * 32767) for x in data]) | ||
if sys.byteorder == 'big': | ||||
scaled.byteswap() | ||||
Matan Gover
|
r24968 | nchan = 1 | ||
Jonas Haag
|
r25457 | return scaled.tobytes(), nchan | ||
Matan Gover
|
r24968 | |||
Matan Gover
|
r24973 | @staticmethod | ||
def _get_normalization_factor(max_abs_value, normalize): | ||||
if not normalize and max_abs_value > 1: | ||||
raise ValueError('Audio data must be between -1 and 1 when normalize=False.') | ||||
return max_abs_value if normalize else 1 | ||||
David Österberg
|
r12829 | def _data_and_metadata(self): | ||
"""shortcut for returning metadata with url information, if defined""" | ||||
md = {} | ||||
if self.url: | ||||
md['url'] = self.url | ||||
if md: | ||||
return self.data, md | ||||
else: | ||||
return self.data | ||||
Erik Tollerud
|
r16550 | |||
David Österberg
|
r12829 | def _repr_html_(self): | ||
src = """ | ||||
stef-ubuntu
|
r24993 | <audio {element_id} controls="controls" {autoplay}> | ||
David Österberg
|
r12829 | <source src="{src}" type="{type}" /> | ||
Your browser does not support the audio element. | ||||
</audio> | ||||
""" | ||||
stef-ubuntu
|
r24993 | return src.format(src=self.src_attr(), type=self.mimetype, autoplay=self.autoplay_attr(), | ||
element_id=self.element_id_attr()) | ||||
David Österberg
|
r12829 | |||
def src_attr(self): | ||||
David Österberg
|
r12833 | import base64 | ||
David Österberg
|
r12829 | if self.embed and (self.data is not None): | ||
David Österberg
|
r12840 | data = base64=base64.b64encode(self.data).decode('ascii') | ||
David Österberg
|
r12839 | return """data:{type};base64,{base64}""".format(type=self.mimetype, | ||
base64=data) | ||||
David Österberg
|
r12829 | elif self.url is not None: | ||
return self.url | ||||
else: | ||||
return "" | ||||
def autoplay_attr(self): | ||||
if(self.autoplay): | ||||
return 'autoplay="autoplay"' | ||||
else: | ||||
return '' | ||||
stef-ubuntu
|
r24993 | |||
def element_id_attr(self): | ||||
if (self.element_id): | ||||
return 'id="{element_id}"'.format(element_id=self.element_id) | ||||
else: | ||||
return '' | ||||
Greg Caporaso
|
r8272 | |||
Eugene Van den Bulke
|
r10260 | class IFrame(object): | ||
Eugene Van den Bulke
|
r10249 | """ | ||
Eugene Van den Bulke
|
r10260 | Generic class to embed an iframe in an IPython notebook | ||
Eugene Van den Bulke
|
r10249 | """ | ||
Eugene Van den Bulke
|
r10262 | iframe = """ | ||
<iframe | ||||
width="{width}" | ||||
Cyrille Rossant
|
r18018 | height="{height}" | ||
Eugene Van den Bulke
|
r10262 | src="{src}{params}" | ||
frameborder="0" | ||||
allowfullscreen | ||||
yuji96
|
r26778 | {extras} | ||
Eugene Van den Bulke
|
r10262 | ></iframe> | ||
""" | ||||
yuji96
|
r26778 | def __init__(self, src, width, height, extras: Iterable[str] = None, **kwargs): | ||
if extras is None: | ||||
extras = [] | ||||
Eugene Van den Bulke
|
r10262 | self.src = src | ||
Eugene Van den Bulke
|
r10249 | self.width = width | ||
self.height = height | ||||
yuji96
|
r26778 | self.extras = extras | ||
Eugene Van den Bulke
|
r10249 | self.params = kwargs | ||
def _repr_html_(self): | ||||
Eugene Van den Bulke
|
r10262 | """return the embed iframe""" | ||
Eugene Van den Bulke
|
r10249 | if self.params: | ||
Terry Davis
|
r25516 | from urllib.parse import urlencode | ||
Eugene Van den Bulke
|
r10249 | params = "?" + urlencode(self.params) | ||
else: | ||||
params = "" | ||||
yuji96
|
r26777 | return self.iframe.format( | ||
src=self.src, | ||||
width=self.width, | ||||
height=self.height, | ||||
params=params, | ||||
yuji96
|
r26778 | extras=" ".join(self.extras), | ||
yuji96
|
r26777 | ) | ||
yuji96
|
r26767 | |||
Eugene Van den Bulke
|
r10249 | |||
Eugene Van den Bulke
|
r10260 | class YouTubeVideo(IFrame): | ||
Brian E. Granger
|
r4408 | """Class for embedding a YouTube Video in an IPython session, based on its video id. | ||
Bernardo B. Marques
|
r4872 | |||
MinRK
|
r14829 | e.g. to embed the video from https://www.youtube.com/watch?v=foo , you would | ||
Thomas Kluyver
|
r13587 | do:: | ||
Bernardo B. Marques
|
r4872 | |||
Thomas Kluyver
|
r13587 | vid = YouTubeVideo("foo") | ||
display(vid) | ||||
Bernardo B. Marques
|
r4872 | |||
Thomas Kluyver
|
r13587 | To start from 30 seconds:: | ||
Eugene Van den Bulke
|
r10249 | |||
Thomas Kluyver
|
r13587 | vid = YouTubeVideo("abc", start=30) | ||
display(vid) | ||||
Eugene Van den Bulke
|
r10249 | |||
Thomas Kluyver
|
r13587 | To calculate seconds from time as hours, minutes, seconds use | ||
:class:`datetime.timedelta`:: | ||||
Eugene Van den Bulke
|
r10249 | |||
Thomas Kluyver
|
r13587 | start=int(timedelta(hours=1, minutes=46, seconds=40).total_seconds()) | ||
Danny Staple
|
r8651 | |||
Eugene Van den Bulke
|
r10249 | Other parameters can be provided as documented at | ||
Thomas Kluyver
|
r23220 | https://developers.google.com/youtube/player_parameters#Parameters | ||
Ben Kasel
|
r21862 | |||
When converting the notebook using nbconvert, a jpeg representation of the video | ||||
will be inserted in the document. | ||||
Brian E. Granger
|
r4408 | """ | ||
Bernardo B. Marques
|
r4872 | |||
yuji96
|
r26767 | def __init__(self, id, width=400, height=300, allow_autoplay=False, **kwargs): | ||
Ben Kasel
|
r21861 | self.id=id | ||
MinRK
|
r14829 | src = "https://www.youtube.com/embed/{0}".format(id) | ||
yuji96
|
r26767 | if allow_autoplay: | ||
yuji96
|
r26779 | extras = list(kwargs.get("extras", [])) + ['allow="autoplay"'] | ||
kwargs.update(autoplay=1, extras=extras) | ||||
Eugene Van den Bulke
|
r10262 | super(YouTubeVideo, self).__init__(src, width, height, **kwargs) | ||
yuji96
|
r26767 | |||
Ben Kasel
|
r21861 | def _repr_jpeg_(self): | ||
Srinivas Reddy Thatiparthy
|
r23075 | # Deferred import | ||
from urllib.request import urlopen | ||||
Ben Kasel
|
r21865 | try: | ||
return urlopen("https://img.youtube.com/vi/{id}/hqdefault.jpg".format(id=self.id)).read() | ||||
except IOError: | ||||
return None | ||||
Eugene Van den Bulke
|
r10249 | |||
Eugene Van den Bulke
|
r10261 | class VimeoVideo(IFrame): | ||
Eugene Van den Bulke
|
r10249 | """ | ||
Class for embedding a Vimeo video in an IPython session, based on its video id. | ||||
""" | ||||
Eugene Van den Bulke
|
r10262 | def __init__(self, id, width=400, height=300, **kwargs): | ||
MinRK
|
r14829 | src="https://player.vimeo.com/video/{0}".format(id) | ||
Eugene Van den Bulke
|
r10262 | super(VimeoVideo, self).__init__(src, width, height, **kwargs) | ||
Eugene Van den Bulke
|
r10249 | |||
Eugene Van den Bulke
|
r10260 | class ScribdDocument(IFrame): | ||
""" | ||||
Class for embedding a Scribd document in an IPython session | ||||
Use the start_page params to specify a starting point in the document | ||||
Use the view_mode params to specify display type one off scroll | slideshow | book | ||||
e.g to Display Wes' foundational paper about PANDAS in book mode from page 3 | ||||
ScribdDocument(71048089, width=800, height=400, start_page=3, view_mode="book") | ||||
""" | ||||
Eugene Van den Bulke
|
r10262 | def __init__(self, id, width=400, height=300, **kwargs): | ||
MinRK
|
r14829 | src="https://www.scribd.com/embeds/{0}/content".format(id) | ||
Eugene Van den Bulke
|
r10262 | super(ScribdDocument, self).__init__(src, width, height, **kwargs) | ||
Brian E. Granger
|
r4408 | |||
Greg Caporaso
|
r8365 | class FileLink(object): | ||
Greg Caporaso
|
r8272 | """Class for embedding a local file link in an IPython session, based on path | ||
e.g. to embed a link that was generated in the IPython notebook as my/data.txt | ||||
Thomas Kluyver
|
r9244 | you would do:: | ||
Greg Caporaso
|
r8272 | |||
Thomas Kluyver
|
r9244 | local_file = FileLink("my/data.txt") | ||
display(local_file) | ||||
Eugene Van den Bulke
|
r10249 | |||
Thomas Kluyver
|
r9244 | or in the HTML notebook, just:: | ||
Eugene Van den Bulke
|
r10249 | |||
Thomas Kluyver
|
r9244 | FileLink("my/data.txt") | ||
Greg Caporaso
|
r8272 | """ | ||
Eugene Van den Bulke
|
r10249 | |||
Greg Caporaso
|
r8442 | html_link_str = "<a href='%s' target='_blank'>%s</a>" | ||
Eugene Van den Bulke
|
r10249 | |||
Greg Caporaso
|
r8272 | def __init__(self, | ||
path, | ||||
MinRK
|
r14894 | url_prefix='', | ||
Greg Caporaso
|
r8439 | result_html_prefix='', | ||
result_html_suffix='<br>'): | ||||
Greg Caporaso
|
r8272 | """ | ||
Thomas Kluyver
|
r9244 | Parameters | ||
---------- | ||||
path : str | ||||
path to the file or directory that should be formatted | ||||
Steven Maude
|
r23327 | url_prefix : str | ||
Thomas Kluyver
|
r9244 | prefix to be prepended to all files to form a working link [default: | ||
Steven Maude
|
r23328 | ''] | ||
Thomas Kluyver
|
r9244 | result_html_prefix : str | ||
Steven Maude
|
r23329 | text to append to beginning to link [default: ''] | ||
Eugene Van den Bulke
|
r10249 | result_html_suffix : str | ||
Thomas Kluyver
|
r9244 | text to append at the end of link [default: '<br>'] | ||
Greg Caporaso
|
r8272 | """ | ||
Greg Caporaso
|
r8835 | if isdir(path): | ||
Thomas Kluyver
|
r13349 | raise ValueError("Cannot display a directory using FileLink. " | ||
Greg Caporaso
|
r8835 | "Use FileLinks to display '%s'." % path) | ||
Cristian Ciupitu
|
r24227 | self.path = fsdecode(path) | ||
Greg Caporaso
|
r8437 | self.url_prefix = url_prefix | ||
Greg Caporaso
|
r8439 | self.result_html_prefix = result_html_prefix | ||
self.result_html_suffix = result_html_suffix | ||||
Eugene Van den Bulke
|
r10249 | |||
Greg Caporaso
|
r8272 | def _format_path(self): | ||
Cristian Ciupitu
|
r24230 | fp = ''.join([self.url_prefix, html_escape(self.path)]) | ||
Greg Caporaso
|
r8439 | return ''.join([self.result_html_prefix, | ||
Cristian Ciupitu
|
r24230 | self.html_link_str % \ | ||
(fp, html_escape(self.path, quote=False)), | ||||
Greg Caporaso
|
r8439 | self.result_html_suffix]) | ||
Eugene Van den Bulke
|
r10249 | |||
Greg Caporaso
|
r8272 | def _repr_html_(self): | ||
Greg Caporaso
|
r8442 | """return html link to file | ||
Greg Caporaso
|
r8272 | """ | ||
if not exists(self.path): | ||||
Eugene Van den Bulke
|
r10249 | return ("Path (<tt>%s</tt>) doesn't exist. " | ||
Greg Caporaso
|
r8272 | "It may still be in the process of " | ||
"being generated, or you may have the " | ||||
"incorrect path." % self.path) | ||||
Eugene Van den Bulke
|
r10249 | |||
Greg Caporaso
|
r8272 | return self._format_path() | ||
Eugene Van den Bulke
|
r10249 | |||
Greg Caporaso
|
r8442 | def __repr__(self): | ||
Greg Caporaso
|
r8443 | """return absolute path to file | ||
Greg Caporaso
|
r8442 | """ | ||
return abspath(self.path) | ||||
Greg Caporaso
|
r8272 | |||
Greg Caporaso
|
r8365 | class FileLinks(FileLink): | ||
Greg Caporaso
|
r8272 | """Class for embedding local file links in an IPython session, based on path | ||
Thomas Kluyver
|
r13587 | e.g. to embed links to files that were generated in the IPython notebook | ||
under ``my/data``, you would do:: | ||||
Greg Caporaso
|
r8272 | |||
Thomas Kluyver
|
r13587 | local_files = FileLinks("my/data") | ||
display(local_files) | ||||
Eugene Van den Bulke
|
r10249 | |||
Thomas Kluyver
|
r13587 | or in the HTML notebook, just:: | ||
Eugene Van den Bulke
|
r10249 | |||
Thomas Kluyver
|
r13587 | FileLinks("my/data") | ||
Greg Caporaso
|
r8272 | """ | ||
def __init__(self, | ||||
path, | ||||
MinRK
|
r14894 | url_prefix='', | ||
Greg Caporaso
|
r8439 | included_suffixes=None, | ||
result_html_prefix='', | ||||
Greg Caporaso
|
r8619 | result_html_suffix='<br>', | ||
notebook_display_formatter=None, | ||||
Doug Blank
|
r19166 | terminal_display_formatter=None, | ||
recursive=True): | ||||
Greg Caporaso
|
r8272 | """ | ||
Thomas Kluyver
|
r13587 | See :class:`FileLink` for the ``path``, ``url_prefix``, | ||
``result_html_prefix`` and ``result_html_suffix`` parameters. | ||||
included_suffixes : list | ||||
Filename suffixes to include when formatting output [default: include | ||||
all files] | ||||
notebook_display_formatter : function | ||||
Used to format links for display in the notebook. See discussion of | ||||
formatter functions below. | ||||
terminal_display_formatter : function | ||||
Used to format links for display in the terminal. See discussion of | ||||
formatter functions below. | ||||
Formatter functions must be of the form:: | ||||
f(dirname, fnames, included_suffixes) | ||||
dirname : str | ||||
The name of a directory | ||||
fnames : list | ||||
The files in that directory | ||||
included_suffixes : list | ||||
The file suffixes that should be included in the output (passing None | ||||
meansto include all suffixes in the output in the built-in formatters) | ||||
Doug Blank
|
r19167 | recursive : boolean | ||
Whether to recurse into subdirectories. Default is True. | ||||
Thomas Kluyver
|
r13587 | |||
The function should return a list of lines that will be printed in the | ||||
notebook (if passing notebook_display_formatter) or the terminal (if | ||||
passing terminal_display_formatter). This function is iterated over for | ||||
each directory in self.path. Default formatters are in place, can be | ||||
passed here to support alternative formatting. | ||||
Eugene Van den Bulke
|
r10249 | |||
Greg Caporaso
|
r8272 | """ | ||
Greg Caporaso
|
r8836 | if isfile(path): | ||
Thomas Kluyver
|
r13349 | raise ValueError("Cannot display a file using FileLinks. " | ||
Greg Caporaso
|
r8836 | "Use FileLink to display '%s'." % path) | ||
Greg Caporaso
|
r8439 | self.included_suffixes = included_suffixes | ||
luz.paz
|
r24132 | # remove trailing slashes for more consistent output formatting | ||
Greg Caporaso
|
r8631 | path = path.rstrip('/') | ||
Eugene Van den Bulke
|
r10249 | |||
Greg Caporaso
|
r8835 | self.path = path | ||
self.url_prefix = url_prefix | ||||
self.result_html_prefix = result_html_prefix | ||||
self.result_html_suffix = result_html_suffix | ||||
Eugene Van den Bulke
|
r10249 | |||
Greg Caporaso
|
r8624 | self.notebook_display_formatter = \ | ||
Greg Caporaso
|
r8626 | notebook_display_formatter or self._get_notebook_display_formatter() | ||
Greg Caporaso
|
r8624 | self.terminal_display_formatter = \ | ||
Greg Caporaso
|
r8626 | terminal_display_formatter or self._get_terminal_display_formatter() | ||
Eugene Van den Bulke
|
r10249 | |||
Doug Blank
|
r19166 | self.recursive = recursive | ||
Matthias Bussonnier
|
r27639 | def _get_display_formatter( | ||
self, dirname_output_format, fname_output_format, fp_format, fp_cleaner=None | ||||
): | ||||
"""generate built-in formatter function | ||||
Eugene Van den Bulke
|
r10249 | |||
Matthias Bussonnier
|
r27295 | this is used to define both the notebook and terminal built-in | ||
formatters as they only differ by some wrapper text for each entry | ||||
dirname_output_format: string to use for formatting directory | ||||
names, dirname will be substituted for a single "%s" which | ||||
must appear in this string | ||||
fname_output_format: string to use for formatting file names, | ||||
if a single "%s" appears in the string, fname will be substituted | ||||
if two "%s" appear in the string, the path to fname will be | ||||
substituted for the first and fname will be substituted for the | ||||
second | ||||
fp_format: string to use for formatting filepaths, must contain | ||||
exactly two "%s" and the dirname will be substituted for the first | ||||
and fname will be substituted for the second | ||||
Greg Caporaso
|
r8631 | """ | ||
Greg Caporaso
|
r8802 | def f(dirname, fnames, included_suffixes=None): | ||
Greg Caporaso
|
r8801 | result = [] | ||
Eugene Van den Bulke
|
r10249 | # begin by figuring out which filenames, if any, | ||
Greg Caporaso
|
r8619 | # are going to be displayed | ||
display_fnames = [] | ||||
for fname in fnames: | ||||
if (isfile(join(dirname,fname)) and | ||||
Boris Egorov
|
r18172 | (included_suffixes is None or | ||
Greg Caporaso
|
r8619 | splitext(fname)[1] in included_suffixes)): | ||
display_fnames.append(fname) | ||||
Eugene Van den Bulke
|
r10249 | |||
Greg Caporaso
|
r8619 | if len(display_fnames) == 0: | ||
Greg Caporaso
|
r8631 | # if there are no filenames to display, don't print anything | ||
# (not even the directory name) | ||||
Greg Caporaso
|
r8619 | pass | ||
else: | ||||
Eugene Van den Bulke
|
r10249 | # otherwise print the formatted directory name followed by | ||
Greg Caporaso
|
r8631 | # the formatted filenames | ||
Greg Caporaso
|
r8624 | dirname_output_line = dirname_output_format % dirname | ||
Greg Caporaso
|
r8801 | result.append(dirname_output_line) | ||
Greg Caporaso
|
r8619 | for fname in display_fnames: | ||
Greg Caporaso
|
r8624 | fp = fp_format % (dirname,fname) | ||
Greg Caporaso
|
r9305 | if fp_cleaner is not None: | ||
Greg Caporaso
|
r9093 | fp = fp_cleaner(fp) | ||
Greg Caporaso
|
r8624 | try: | ||
# output can include both a filepath and a filename... | ||||
fname_output_line = fname_output_format % (fp, fname) | ||||
except TypeError: | ||||
# ... or just a single filepath | ||||
fname_output_line = fname_output_format % fname | ||||
Greg Caporaso
|
r8801 | result.append(fname_output_line) | ||
return result | ||||
Greg Caporaso
|
r8619 | return f | ||
Greg Caporaso
|
r8624 | def _get_notebook_display_formatter(self, | ||
spacer=" "): | ||||
Greg Caporaso
|
r8802 | """ generate function to use for notebook formatting | ||
Greg Caporaso
|
r8631 | """ | ||
Greg Caporaso
|
r8624 | dirname_output_format = \ | ||
Greg Caporaso
|
r8631 | self.result_html_prefix + "%s/" + self.result_html_suffix | ||
Greg Caporaso
|
r8624 | fname_output_format = \ | ||
self.result_html_prefix + spacer + self.html_link_str + self.result_html_suffix | ||||
fp_format = self.url_prefix + '%s/%s' | ||||
Greg Caporaso
|
r9093 | if sep == "\\": | ||
Eugene Van den Bulke
|
r10249 | # Working on a platform where the path separator is "\", so | ||
Greg Caporaso
|
r9093 | # must convert these to "/" for generating a URI | ||
def fp_cleaner(fp): | ||||
luzpaz
|
r24084 | # Replace all occurrences of backslash ("\") with a forward | ||
Greg Caporaso
|
r9093 | # slash ("/") - this is necessary on windows when a path is | ||
# provided as input, but we must link to a URI | ||||
return fp.replace('\\','/') | ||||
else: | ||||
fp_cleaner = None | ||||
Eugene Van den Bulke
|
r10249 | |||
Greg Caporaso
|
r8624 | return self._get_display_formatter(dirname_output_format, | ||
fname_output_format, | ||||
Greg Caporaso
|
r9093 | fp_format, | ||
fp_cleaner) | ||||
Greg Caporaso
|
r8619 | |||
Greg Caporaso
|
r8624 | def _get_terminal_display_formatter(self, | ||
spacer=" "): | ||||
Greg Caporaso
|
r8802 | """ generate function to use for terminal formatting | ||
Greg Caporaso
|
r8631 | """ | ||
dirname_output_format = "%s/" | ||||
Greg Caporaso
|
r8624 | fname_output_format = spacer + "%s" | ||
fp_format = '%s/%s' | ||||
Eugene Van den Bulke
|
r10249 | |||
Greg Caporaso
|
r8624 | return self._get_display_formatter(dirname_output_format, | ||
fname_output_format, | ||||
fp_format) | ||||
Eugene Van den Bulke
|
r10249 | |||
Greg Caporaso
|
r8619 | def _format_path(self): | ||
result_lines = [] | ||||
Doug Blank
|
r19166 | if self.recursive: | ||
walked_dir = list(walk(self.path)) | ||||
else: | ||||
Doug Blank
|
r19367 | walked_dir = [next(walk(self.path))] | ||
Greg Caporaso
|
r8800 | walked_dir.sort() | ||
for dirname, subdirs, fnames in walked_dir: | ||||
Greg Caporaso
|
r8802 | result_lines += self.notebook_display_formatter(dirname, fnames, self.included_suffixes) | ||
Greg Caporaso
|
r8619 | return '\n'.join(result_lines) | ||
Eugene Van den Bulke
|
r10249 | |||
Greg Caporaso
|
r8443 | def __repr__(self): | ||
"""return newline-separated absolute paths | ||||
""" | ||||
Greg Caporaso
|
r8619 | result_lines = [] | ||
Doug Blank
|
r19166 | if self.recursive: | ||
walked_dir = list(walk(self.path)) | ||||
else: | ||||
Doug Blank
|
r19367 | walked_dir = [next(walk(self.path))] | ||
Greg Caporaso
|
r8800 | walked_dir.sort() | ||
for dirname, subdirs, fnames in walked_dir: | ||||
Greg Caporaso
|
r8802 | result_lines += self.terminal_display_formatter(dirname, fnames, self.included_suffixes) | ||
Greg Caporaso
|
r8798 | return '\n'.join(result_lines) | ||
Thomas Kluyver
|
r24130 | |||
class Code(TextDisplayObject): | ||||
Thomas Kluyver
|
r24131 | """Display syntax-highlighted source code. | ||
This uses Pygments to highlight the code for HTML and Latex output. | ||||
Parameters | ||||
---------- | ||||
data : str | ||||
The code as a string | ||||
url : str | ||||
A URL to fetch the code from | ||||
filename : str | ||||
A local filename to load the code from | ||||
language : str | ||||
The short name of a Pygments lexer to use for highlighting. | ||||
If not specified, it will guess the lexer based on the filename | ||||
or the code. Available lexers: http://pygments.org/docs/lexers/ | ||||
""" | ||||
Thomas Kluyver
|
r24130 | def __init__(self, data=None, url=None, filename=None, language=None): | ||
self.language = language | ||||
super().__init__(data=data, url=url, filename=filename) | ||||
def _get_lexer(self): | ||||
if self.language: | ||||
from pygments.lexers import get_lexer_by_name | ||||
return get_lexer_by_name(self.language) | ||||
elif self.filename: | ||||
from pygments.lexers import get_lexer_for_filename | ||||
return get_lexer_for_filename(self.filename) | ||||
else: | ||||
from pygments.lexers import guess_lexer | ||||
return guess_lexer(self.data) | ||||
Thomas Kluyver
|
r24134 | def __repr__(self): | ||
return self.data | ||||
Thomas Kluyver
|
r24130 | def _repr_html_(self): | ||
from pygments import highlight | ||||
from pygments.formatters import HtmlFormatter | ||||
fmt = HtmlFormatter() | ||||
style = '<style>{}</style>'.format(fmt.get_style_defs('.output_html')) | ||||
return style + highlight(self.data, self._get_lexer(), fmt) | ||||
def _repr_latex_(self): | ||||
from pygments import highlight | ||||
from pygments.formatters import LatexFormatter | ||||
return highlight(self.data, self._get_lexer(), LatexFormatter()) | ||||