558 lines
		
	
	
		
			20 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			558 lines
		
	
	
		
			20 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| """Various display related classes.
 | |
| 
 | |
| Authors : MinRK, gregcaporaso, dannystaple
 | |
| """
 | |
| from os.path import exists, isfile, splitext, abspath, join, isdir
 | |
| from os import walk, sep
 | |
| 
 | |
| from yap_ipython.core.display import DisplayObject
 | |
| 
 | |
| __all__ = ['Audio', 'IFrame', 'YouTubeVideo', 'VimeoVideo', 'ScribdDocument',
 | |
|            'FileLink', 'FileLinks']
 | |
| 
 | |
| 
 | |
| 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).
 | |
| 
 | |
|     Parameters
 | |
|     ----------
 | |
|     data : numpy array, list, unicode, str or bytes
 | |
|         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.
 | |
| 
 | |
|         If the array option is used the waveform will be normalized.
 | |
| 
 | |
|         If a filename or url is used the format support will be browser
 | |
|         dependent.
 | |
|     url : unicode
 | |
|         A URL to download the data from.
 | |
|     filename : unicode
 | |
|         Path to a local file to load the data from.
 | |
|     embed : boolean
 | |
|         Should the audio data be embedded using a data URI (True) or should
 | |
|         the original source be referenced. Set this to True if you want the
 | |
|         audio to playable later with no internet connection in the notebook.
 | |
| 
 | |
|         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`.
 | |
| 
 | |
|     Examples
 | |
|     --------
 | |
|     ::
 | |
| 
 | |
|         # Generate a sound
 | |
|         import numpy as np
 | |
|         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)
 | |
| 
 | |
|         # Can also do stereo or more channels
 | |
|         dataleft = np.sin(2*np.pi*220*t)
 | |
|         dataright = np.sin(2*np.pi*224*t)
 | |
|         Audio([dataleft, dataright],rate=framerate)
 | |
| 
 | |
|         Audio("http://www.nch.com.au/acm/8k16bitpcm.wav")  # From URL
 | |
|         Audio(url="http://www.w3schools.com/html/horse.ogg")
 | |
| 
 | |
|         Audio('/path/to/sound.wav')  # From file
 | |
|         Audio(filename='/path/to/sound.ogg')
 | |
| 
 | |
|         Audio(b'RAW_WAV_DATA..)  # From bytes
 | |
|         Audio(data=b'RAW_WAV_DATA..)
 | |
| 
 | |
|     """
 | |
|     _read_flags = 'rb'
 | |
| 
 | |
|     def __init__(self, data=None, filename=None, url=None, embed=None, rate=None, autoplay=False):
 | |
|         if filename is None and url is None and data is None:
 | |
|             raise ValueError("No image data found. Expecting filename, url, or data.")
 | |
|         if embed is False and url is None:
 | |
|             raise ValueError("No url found. Expecting url when embed=False")
 | |
| 
 | |
|         if url is not None and embed is not True:
 | |
|             self.embed = False
 | |
|         else:
 | |
|             self.embed = True
 | |
|         self.autoplay = autoplay
 | |
|         super(Audio, self).__init__(data=data, url=url, filename=filename)
 | |
| 
 | |
|         if self.data is not None and not isinstance(self.data, bytes):
 | |
|             self.data = self._make_wav(data,rate)
 | |
| 
 | |
|     def reload(self):
 | |
|         """Reload the raw data from file or URL."""
 | |
|         import mimetypes
 | |
|         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"
 | |
| 
 | |
|     def _make_wav(self, data, rate):
 | |
|         """ Transform a numpy array to a PCM bytestring """
 | |
|         import struct
 | |
|         from io import BytesIO
 | |
|         import wave
 | |
| 
 | |
|         try:
 | |
|             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')
 | |
|             scaled = np.int16(data/np.max(np.abs(data))*32767).tolist()
 | |
|         except ImportError:
 | |
|             # check that it is a "1D" list
 | |
|             idata = iter(data)  # fails if not an iterable
 | |
|             try:
 | |
|                 iter(idata.next())
 | |
|                 raise TypeError('Only lists of mono audio are '
 | |
|                     'supported if numpy is not installed')
 | |
|             except TypeError:
 | |
|                 # this means it's not a nested list, which is what we want
 | |
|                 pass
 | |
|             maxabsvalue = float(max([abs(x) for x in data]))
 | |
|             scaled = [int(x/maxabsvalue*32767) for x in data]
 | |
|             nchan = 1
 | |
| 
 | |
|         fp = BytesIO()
 | |
|         waveobj = wave.open(fp,mode='wb')
 | |
|         waveobj.setnchannels(nchan)
 | |
|         waveobj.setframerate(rate)
 | |
|         waveobj.setsampwidth(2)
 | |
|         waveobj.setcomptype('NONE','NONE')
 | |
|         waveobj.writeframes(b''.join([struct.pack('<h',x) for x in scaled]))
 | |
|         val = fp.getvalue()
 | |
|         waveobj.close()
 | |
| 
 | |
|         return val
 | |
| 
 | |
|     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
 | |
| 
 | |
|     def _repr_html_(self):
 | |
|         src = """
 | |
|                 <audio controls="controls" {autoplay}>
 | |
|                     <source src="{src}" type="{type}" />
 | |
|                     Your browser does not support the audio element.
 | |
|                 </audio>
 | |
|               """
 | |
|         return src.format(src=self.src_attr(),type=self.mimetype, autoplay=self.autoplay_attr())
 | |
| 
 | |
|     def src_attr(self):
 | |
|         import base64
 | |
|         if self.embed and (self.data is not None):
 | |
|             data = base64=base64.b64encode(self.data).decode('ascii')
 | |
|             return """data:{type};base64,{base64}""".format(type=self.mimetype,
 | |
|                                                             base64=data)
 | |
|         elif self.url is not None:
 | |
|             return self.url
 | |
|         else:
 | |
|             return ""
 | |
| 
 | |
|     def autoplay_attr(self):
 | |
|         if(self.autoplay):
 | |
|             return 'autoplay="autoplay"'
 | |
|         else:
 | |
|             return ''
 | |
| 
 | |
| class IFrame(object):
 | |
|     """
 | |
|     Generic class to embed an iframe in an yap_ipython notebook
 | |
|     """
 | |
| 
 | |
|     iframe = """
 | |
|         <iframe
 | |
|             width="{width}"
 | |
|             height="{height}"
 | |
|             src="{src}{params}"
 | |
|             frameborder="0"
 | |
|             allowfullscreen
 | |
|         ></iframe>
 | |
|         """
 | |
| 
 | |
|     def __init__(self, src, width, height, **kwargs):
 | |
|         self.src = src
 | |
|         self.width = width
 | |
|         self.height = height
 | |
|         self.params = kwargs
 | |
| 
 | |
|     def _repr_html_(self):
 | |
|         """return the embed iframe"""
 | |
|         if self.params:
 | |
|             try:
 | |
|                 from urllib.parse import urlencode # Py 3
 | |
|             except ImportError:
 | |
|                 from urllib import urlencode
 | |
|             params = "?" + urlencode(self.params)
 | |
|         else:
 | |
|             params = ""
 | |
|         return self.iframe.format(src=self.src,
 | |
|                                   width=self.width,
 | |
|                                   height=self.height,
 | |
|                                   params=params)
 | |
| 
 | |
| class YouTubeVideo(IFrame):
 | |
|     """Class for embedding a YouTube Video in an yap_ipython session, based on its video id.
 | |
| 
 | |
|     e.g. to embed the video from https://www.youtube.com/watch?v=foo , you would
 | |
|     do::
 | |
| 
 | |
|         vid = YouTubeVideo("foo")
 | |
|         display(vid)
 | |
| 
 | |
|     To start from 30 seconds::
 | |
| 
 | |
|         vid = YouTubeVideo("abc", start=30)
 | |
|         display(vid)
 | |
| 
 | |
|     To calculate seconds from time as hours, minutes, seconds use
 | |
|     :class:`datetime.timedelta`::
 | |
| 
 | |
|         start=int(timedelta(hours=1, minutes=46, seconds=40).total_seconds())
 | |
| 
 | |
|     Other parameters can be provided as documented at
 | |
|     https://developers.google.com/youtube/player_parameters#Parameters
 | |
|     
 | |
|     When converting the notebook using nbconvert, a jpeg representation of the video
 | |
|     will be inserted in the document.
 | |
|     """
 | |
| 
 | |
|     def __init__(self, id, width=400, height=300, **kwargs):
 | |
|         self.id=id
 | |
|         src = "https://www.youtube.com/embed/{0}".format(id)
 | |
|         super(YouTubeVideo, self).__init__(src, width, height, **kwargs)
 | |
|     
 | |
|     def _repr_jpeg_(self):
 | |
|         # Deferred import
 | |
|         from urllib.request import urlopen
 | |
| 
 | |
|         try:
 | |
|             return urlopen("https://img.youtube.com/vi/{id}/hqdefault.jpg".format(id=self.id)).read()
 | |
|         except IOError:
 | |
|             return None
 | |
| 
 | |
| class VimeoVideo(IFrame):
 | |
|     """
 | |
|     Class for embedding a Vimeo video in an yap_ipython session, based on its video id.
 | |
|     """
 | |
| 
 | |
|     def __init__(self, id, width=400, height=300, **kwargs):
 | |
|         src="https://player.vimeo.com/video/{0}".format(id)
 | |
|         super(VimeoVideo, self).__init__(src, width, height, **kwargs)
 | |
| 
 | |
| class ScribdDocument(IFrame):
 | |
|     """
 | |
|     Class for embedding a Scribd document in an yap_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")
 | |
|     """
 | |
| 
 | |
|     def __init__(self, id, width=400, height=300, **kwargs):
 | |
|         src="https://www.scribd.com/embeds/{0}/content".format(id)
 | |
|         super(ScribdDocument, self).__init__(src, width, height, **kwargs)
 | |
| 
 | |
| class FileLink(object):
 | |
|     """Class for embedding a local file link in an yap_ipython session, based on path
 | |
| 
 | |
|     e.g. to embed a link that was generated in the yap_ipython notebook as my/data.txt
 | |
| 
 | |
|     you would do::
 | |
| 
 | |
|         local_file = FileLink("my/data.txt")
 | |
|         display(local_file)
 | |
| 
 | |
|     or in the HTML notebook, just::
 | |
| 
 | |
|         FileLink("my/data.txt")
 | |
|     """
 | |
| 
 | |
|     html_link_str = "<a href='%s' target='_blank'>%s</a>"
 | |
| 
 | |
|     def __init__(self,
 | |
|                  path,
 | |
|                  url_prefix='',
 | |
|                  result_html_prefix='',
 | |
|                  result_html_suffix='<br>'):
 | |
|         """
 | |
|         Parameters
 | |
|         ----------
 | |
|         path : str
 | |
|             path to the file or directory that should be formatted
 | |
|         url_prefix : str
 | |
|             prefix to be prepended to all files to form a working link [default:
 | |
|             '']
 | |
|         result_html_prefix : str
 | |
|             text to append to beginning to link [default: '']
 | |
|         result_html_suffix : str
 | |
|             text to append at the end of link [default: '<br>']
 | |
|         """
 | |
|         if isdir(path):
 | |
|             raise ValueError("Cannot display a directory using FileLink. "
 | |
|               "Use FileLinks to display '%s'." % path)
 | |
|         self.path = path
 | |
|         self.url_prefix = url_prefix
 | |
|         self.result_html_prefix = result_html_prefix
 | |
|         self.result_html_suffix = result_html_suffix
 | |
| 
 | |
|     def _format_path(self):
 | |
|         fp = ''.join([self.url_prefix,self.path])
 | |
|         return ''.join([self.result_html_prefix,
 | |
|                         self.html_link_str % (fp, self.path),
 | |
|                         self.result_html_suffix])
 | |
| 
 | |
|     def _repr_html_(self):
 | |
|         """return html link to file
 | |
|         """
 | |
|         if not exists(self.path):
 | |
|             return ("Path (<tt>%s</tt>) doesn't exist. "
 | |
|                     "It may still be in the process of "
 | |
|                     "being generated, or you may have the "
 | |
|                     "incorrect path." % self.path)
 | |
| 
 | |
|         return self._format_path()
 | |
| 
 | |
|     def __repr__(self):
 | |
|         """return absolute path to file
 | |
|         """
 | |
|         return abspath(self.path)
 | |
| 
 | |
| class FileLinks(FileLink):
 | |
|     """Class for embedding local file links in an yap_ipython session, based on path
 | |
| 
 | |
|     e.g. to embed links to files that were generated in the yap_ipython notebook
 | |
|     under ``my/data``, you would do::
 | |
| 
 | |
|         local_files = FileLinks("my/data")
 | |
|         display(local_files)
 | |
| 
 | |
|     or in the HTML notebook, just::
 | |
| 
 | |
|         FileLinks("my/data")
 | |
|     """
 | |
|     def __init__(self,
 | |
|                  path,
 | |
|                  url_prefix='',
 | |
|                  included_suffixes=None,
 | |
|                  result_html_prefix='',
 | |
|                  result_html_suffix='<br>',
 | |
|                  notebook_display_formatter=None,
 | |
|                  terminal_display_formatter=None,
 | |
|                  recursive=True):
 | |
|         """
 | |
|         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)
 | |
|         recursive : boolean
 | |
|           Whether to recurse into subdirectories. Default is True.
 | |
| 
 | |
|         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.
 | |
| 
 | |
|         """
 | |
|         if isfile(path):
 | |
|             raise ValueError("Cannot display a file using FileLinks. "
 | |
|               "Use FileLink to display '%s'." % path)
 | |
|         self.included_suffixes = included_suffixes
 | |
|         # remove trailing slashs for more consistent output formatting
 | |
|         path = path.rstrip('/')
 | |
| 
 | |
|         self.path = path
 | |
|         self.url_prefix = url_prefix
 | |
|         self.result_html_prefix = result_html_prefix
 | |
|         self.result_html_suffix = result_html_suffix
 | |
| 
 | |
|         self.notebook_display_formatter = \
 | |
|              notebook_display_formatter or self._get_notebook_display_formatter()
 | |
|         self.terminal_display_formatter = \
 | |
|              terminal_display_formatter or self._get_terminal_display_formatter()
 | |
| 
 | |
|         self.recursive = recursive
 | |
| 
 | |
|     def _get_display_formatter(self,
 | |
|                                dirname_output_format,
 | |
|                                fname_output_format,
 | |
|                                fp_format,
 | |
|                                fp_cleaner=None):
 | |
|         """ generate built-in formatter function
 | |
| 
 | |
|            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 subsituted for the first
 | |
|             and fname will be substituted for the second
 | |
|         """
 | |
|         def f(dirname, fnames, included_suffixes=None):
 | |
|             result = []
 | |
|             # begin by figuring out which filenames, if any,
 | |
|             # are going to be displayed
 | |
|             display_fnames = []
 | |
|             for fname in fnames:
 | |
|                 if (isfile(join(dirname,fname)) and
 | |
|                        (included_suffixes is None or
 | |
|                         splitext(fname)[1] in included_suffixes)):
 | |
|                       display_fnames.append(fname)
 | |
| 
 | |
|             if len(display_fnames) == 0:
 | |
|                 # if there are no filenames to display, don't print anything
 | |
|                 # (not even the directory name)
 | |
|                 pass
 | |
|             else:
 | |
|                 # otherwise print the formatted directory name followed by
 | |
|                 # the formatted filenames
 | |
|                 dirname_output_line = dirname_output_format % dirname
 | |
|                 result.append(dirname_output_line)
 | |
|                 for fname in display_fnames:
 | |
|                     fp = fp_format % (dirname,fname)
 | |
|                     if fp_cleaner is not None:
 | |
|                         fp = fp_cleaner(fp)
 | |
|                     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
 | |
|                     result.append(fname_output_line)
 | |
|             return result
 | |
|         return f
 | |
| 
 | |
|     def _get_notebook_display_formatter(self,
 | |
|                                         spacer="  "):
 | |
|         """ generate function to use for notebook formatting
 | |
|         """
 | |
|         dirname_output_format = \
 | |
|          self.result_html_prefix + "%s/" + self.result_html_suffix
 | |
|         fname_output_format = \
 | |
|          self.result_html_prefix + spacer + self.html_link_str + self.result_html_suffix
 | |
|         fp_format = self.url_prefix + '%s/%s'
 | |
|         if sep == "\\":
 | |
|             # Working on a platform where the path separator is "\", so
 | |
|             # must convert these to "/" for generating a URI
 | |
|             def fp_cleaner(fp):
 | |
|                 # Replace all occurrences of backslash ("\") with a forward
 | |
|                 # 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
 | |
| 
 | |
|         return self._get_display_formatter(dirname_output_format,
 | |
|                                            fname_output_format,
 | |
|                                            fp_format,
 | |
|                                            fp_cleaner)
 | |
| 
 | |
|     def _get_terminal_display_formatter(self,
 | |
|                                         spacer="  "):
 | |
|         """ generate function to use for terminal formatting
 | |
|         """
 | |
|         dirname_output_format = "%s/"
 | |
|         fname_output_format = spacer + "%s"
 | |
|         fp_format = '%s/%s'
 | |
| 
 | |
|         return self._get_display_formatter(dirname_output_format,
 | |
|                                            fname_output_format,
 | |
|                                            fp_format)
 | |
| 
 | |
|     def _format_path(self):
 | |
|         result_lines = []
 | |
|         if self.recursive:
 | |
|             walked_dir = list(walk(self.path))
 | |
|         else:
 | |
|             walked_dir = [next(walk(self.path))]
 | |
|         walked_dir.sort()
 | |
|         for dirname, subdirs, fnames in walked_dir:
 | |
|             result_lines += self.notebook_display_formatter(dirname, fnames, self.included_suffixes)
 | |
|         return '\n'.join(result_lines)
 | |
| 
 | |
|     def __repr__(self):
 | |
|         """return newline-separated absolute paths
 | |
|         """
 | |
|         result_lines = []
 | |
|         if self.recursive:
 | |
|             walked_dir = list(walk(self.path))
 | |
|         else:
 | |
|             walked_dir = [next(walk(self.path))]
 | |
|         walked_dir.sort()
 | |
|         for dirname, subdirs, fnames in walked_dir:
 | |
|             result_lines += self.terminal_display_formatter(dirname, fnames, self.included_suffixes)
 | |
|         return '\n'.join(result_lines)
 |