display.py 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558
  1. """Various display related classes.
  2. Authors : MinRK, gregcaporaso, dannystaple
  3. """
  4. from os.path import exists, isfile, splitext, abspath, join, isdir
  5. from os import walk, sep
  6. from IPython.core.display import DisplayObject
  7. __all__ = ['Audio', 'IFrame', 'YouTubeVideo', 'VimeoVideo', 'ScribdDocument',
  8. 'FileLink', 'FileLinks']
  9. class Audio(DisplayObject):
  10. """Create an audio object.
  11. When this object is returned by an input cell or passed to the
  12. display function, it will result in Audio controls being displayed
  13. in the frontend (only works in the notebook).
  14. Parameters
  15. ----------
  16. data : numpy array, list, unicode, str or bytes
  17. Can be one of
  18. * Numpy 1d array containing the desired waveform (mono)
  19. * Numpy 2d array containing waveforms for each channel.
  20. Shape=(NCHAN, NSAMPLES). For the standard channel order, see
  21. http://msdn.microsoft.com/en-us/library/windows/hardware/dn653308(v=vs.85).aspx
  22. * List of float or integer representing the waveform (mono)
  23. * String containing the filename
  24. * Bytestring containing raw PCM data or
  25. * URL pointing to a file on the web.
  26. If the array option is used the waveform will be normalized.
  27. If a filename or url is used the format support will be browser
  28. dependent.
  29. url : unicode
  30. A URL to download the data from.
  31. filename : unicode
  32. Path to a local file to load the data from.
  33. embed : boolean
  34. Should the audio data be embedded using a data URI (True) or should
  35. the original source be referenced. Set this to True if you want the
  36. audio to playable later with no internet connection in the notebook.
  37. Default is `True`, unless the keyword argument `url` is set, then
  38. default value is `False`.
  39. rate : integer
  40. The sampling rate of the raw data.
  41. Only required when data parameter is being used as an array
  42. autoplay : bool
  43. Set to True if the audio should immediately start playing.
  44. Default is `False`.
  45. Examples
  46. --------
  47. ::
  48. # Generate a sound
  49. import numpy as np
  50. framerate = 44100
  51. t = np.linspace(0,5,framerate*5)
  52. data = np.sin(2*np.pi*220*t) + np.sin(2*np.pi*224*t))
  53. Audio(data,rate=framerate)
  54. # Can also do stereo or more channels
  55. dataleft = np.sin(2*np.pi*220*t)
  56. dataright = np.sin(2*np.pi*224*t)
  57. Audio([dataleft, dataright],rate=framerate)
  58. Audio("http://www.nch.com.au/acm/8k16bitpcm.wav") # From URL
  59. Audio(url="http://www.w3schools.com/html/horse.ogg")
  60. Audio('/path/to/sound.wav') # From file
  61. Audio(filename='/path/to/sound.ogg')
  62. Audio(b'RAW_WAV_DATA..) # From bytes
  63. Audio(data=b'RAW_WAV_DATA..)
  64. """
  65. _read_flags = 'rb'
  66. def __init__(self, data=None, filename=None, url=None, embed=None, rate=None, autoplay=False):
  67. if filename is None and url is None and data is None:
  68. raise ValueError("No image data found. Expecting filename, url, or data.")
  69. if embed is False and url is None:
  70. raise ValueError("No url found. Expecting url when embed=False")
  71. if url is not None and embed is not True:
  72. self.embed = False
  73. else:
  74. self.embed = True
  75. self.autoplay = autoplay
  76. super(Audio, self).__init__(data=data, url=url, filename=filename)
  77. if self.data is not None and not isinstance(self.data, bytes):
  78. self.data = self._make_wav(data,rate)
  79. def reload(self):
  80. """Reload the raw data from file or URL."""
  81. import mimetypes
  82. if self.embed:
  83. super(Audio, self).reload()
  84. if self.filename is not None:
  85. self.mimetype = mimetypes.guess_type(self.filename)[0]
  86. elif self.url is not None:
  87. self.mimetype = mimetypes.guess_type(self.url)[0]
  88. else:
  89. self.mimetype = "audio/wav"
  90. def _make_wav(self, data, rate):
  91. """ Transform a numpy array to a PCM bytestring """
  92. import struct
  93. from io import BytesIO
  94. import wave
  95. try:
  96. import numpy as np
  97. data = np.array(data, dtype=float)
  98. if len(data.shape) == 1:
  99. nchan = 1
  100. elif len(data.shape) == 2:
  101. # In wave files,channels are interleaved. E.g.,
  102. # "L1R1L2R2..." for stereo. See
  103. # http://msdn.microsoft.com/en-us/library/windows/hardware/dn653308(v=vs.85).aspx
  104. # for channel ordering
  105. nchan = data.shape[0]
  106. data = data.T.ravel()
  107. else:
  108. raise ValueError('Array audio input must be a 1D or 2D array')
  109. scaled = np.int16(data/np.max(np.abs(data))*32767).tolist()
  110. except ImportError:
  111. # check that it is a "1D" list
  112. idata = iter(data) # fails if not an iterable
  113. try:
  114. iter(idata.next())
  115. raise TypeError('Only lists of mono audio are '
  116. 'supported if numpy is not installed')
  117. except TypeError:
  118. # this means it's not a nested list, which is what we want
  119. pass
  120. maxabsvalue = float(max([abs(x) for x in data]))
  121. scaled = [int(x/maxabsvalue*32767) for x in data]
  122. nchan = 1
  123. fp = BytesIO()
  124. waveobj = wave.open(fp,mode='wb')
  125. waveobj.setnchannels(nchan)
  126. waveobj.setframerate(rate)
  127. waveobj.setsampwidth(2)
  128. waveobj.setcomptype('NONE','NONE')
  129. waveobj.writeframes(b''.join([struct.pack('<h',x) for x in scaled]))
  130. val = fp.getvalue()
  131. waveobj.close()
  132. return val
  133. def _data_and_metadata(self):
  134. """shortcut for returning metadata with url information, if defined"""
  135. md = {}
  136. if self.url:
  137. md['url'] = self.url
  138. if md:
  139. return self.data, md
  140. else:
  141. return self.data
  142. def _repr_html_(self):
  143. src = """
  144. <audio controls="controls" {autoplay}>
  145. <source src="{src}" type="{type}" />
  146. Your browser does not support the audio element.
  147. </audio>
  148. """
  149. return src.format(src=self.src_attr(),type=self.mimetype, autoplay=self.autoplay_attr())
  150. def src_attr(self):
  151. import base64
  152. if self.embed and (self.data is not None):
  153. data = base64=base64.b64encode(self.data).decode('ascii')
  154. return """data:{type};base64,{base64}""".format(type=self.mimetype,
  155. base64=data)
  156. elif self.url is not None:
  157. return self.url
  158. else:
  159. return ""
  160. def autoplay_attr(self):
  161. if(self.autoplay):
  162. return 'autoplay="autoplay"'
  163. else:
  164. return ''
  165. class IFrame(object):
  166. """
  167. Generic class to embed an iframe in an IPython notebook
  168. """
  169. iframe = """
  170. <iframe
  171. width="{width}"
  172. height="{height}"
  173. src="{src}{params}"
  174. frameborder="0"
  175. allowfullscreen
  176. ></iframe>
  177. """
  178. def __init__(self, src, width, height, **kwargs):
  179. self.src = src
  180. self.width = width
  181. self.height = height
  182. self.params = kwargs
  183. def _repr_html_(self):
  184. """return the embed iframe"""
  185. if self.params:
  186. try:
  187. from urllib.parse import urlencode # Py 3
  188. except ImportError:
  189. from urllib import urlencode
  190. params = "?" + urlencode(self.params)
  191. else:
  192. params = ""
  193. return self.iframe.format(src=self.src,
  194. width=self.width,
  195. height=self.height,
  196. params=params)
  197. class YouTubeVideo(IFrame):
  198. """Class for embedding a YouTube Video in an IPython session, based on its video id.
  199. e.g. to embed the video from https://www.youtube.com/watch?v=foo , you would
  200. do::
  201. vid = YouTubeVideo("foo")
  202. display(vid)
  203. To start from 30 seconds::
  204. vid = YouTubeVideo("abc", start=30)
  205. display(vid)
  206. To calculate seconds from time as hours, minutes, seconds use
  207. :class:`datetime.timedelta`::
  208. start=int(timedelta(hours=1, minutes=46, seconds=40).total_seconds())
  209. Other parameters can be provided as documented at
  210. https://developers.google.com/youtube/player_parameters#Parameters
  211. When converting the notebook using nbconvert, a jpeg representation of the video
  212. will be inserted in the document.
  213. """
  214. def __init__(self, id, width=400, height=300, **kwargs):
  215. self.id=id
  216. src = "https://www.youtube.com/embed/{0}".format(id)
  217. super(YouTubeVideo, self).__init__(src, width, height, **kwargs)
  218. def _repr_jpeg_(self):
  219. try:
  220. from urllib.request import urlopen # Py3
  221. except ImportError:
  222. from urllib2 import urlopen
  223. try:
  224. return urlopen("https://img.youtube.com/vi/{id}/hqdefault.jpg".format(id=self.id)).read()
  225. except IOError:
  226. return None
  227. class VimeoVideo(IFrame):
  228. """
  229. Class for embedding a Vimeo video in an IPython session, based on its video id.
  230. """
  231. def __init__(self, id, width=400, height=300, **kwargs):
  232. src="https://player.vimeo.com/video/{0}".format(id)
  233. super(VimeoVideo, self).__init__(src, width, height, **kwargs)
  234. class ScribdDocument(IFrame):
  235. """
  236. Class for embedding a Scribd document in an IPython session
  237. Use the start_page params to specify a starting point in the document
  238. Use the view_mode params to specify display type one off scroll | slideshow | book
  239. e.g to Display Wes' foundational paper about PANDAS in book mode from page 3
  240. ScribdDocument(71048089, width=800, height=400, start_page=3, view_mode="book")
  241. """
  242. def __init__(self, id, width=400, height=300, **kwargs):
  243. src="https://www.scribd.com/embeds/{0}/content".format(id)
  244. super(ScribdDocument, self).__init__(src, width, height, **kwargs)
  245. class FileLink(object):
  246. """Class for embedding a local file link in an IPython session, based on path
  247. e.g. to embed a link that was generated in the IPython notebook as my/data.txt
  248. you would do::
  249. local_file = FileLink("my/data.txt")
  250. display(local_file)
  251. or in the HTML notebook, just::
  252. FileLink("my/data.txt")
  253. """
  254. html_link_str = "<a href='%s' target='_blank'>%s</a>"
  255. def __init__(self,
  256. path,
  257. url_prefix='',
  258. result_html_prefix='',
  259. result_html_suffix='<br>'):
  260. """
  261. Parameters
  262. ----------
  263. path : str
  264. path to the file or directory that should be formatted
  265. url_prefix : str
  266. prefix to be prepended to all files to form a working link [default:
  267. '']
  268. result_html_prefix : str
  269. text to append to beginning to link [default: '']
  270. result_html_suffix : str
  271. text to append at the end of link [default: '<br>']
  272. """
  273. if isdir(path):
  274. raise ValueError("Cannot display a directory using FileLink. "
  275. "Use FileLinks to display '%s'." % path)
  276. self.path = path
  277. self.url_prefix = url_prefix
  278. self.result_html_prefix = result_html_prefix
  279. self.result_html_suffix = result_html_suffix
  280. def _format_path(self):
  281. fp = ''.join([self.url_prefix,self.path])
  282. return ''.join([self.result_html_prefix,
  283. self.html_link_str % (fp, self.path),
  284. self.result_html_suffix])
  285. def _repr_html_(self):
  286. """return html link to file
  287. """
  288. if not exists(self.path):
  289. return ("Path (<tt>%s</tt>) doesn't exist. "
  290. "It may still be in the process of "
  291. "being generated, or you may have the "
  292. "incorrect path." % self.path)
  293. return self._format_path()
  294. def __repr__(self):
  295. """return absolute path to file
  296. """
  297. return abspath(self.path)
  298. class FileLinks(FileLink):
  299. """Class for embedding local file links in an IPython session, based on path
  300. e.g. to embed links to files that were generated in the IPython notebook
  301. under ``my/data``, you would do::
  302. local_files = FileLinks("my/data")
  303. display(local_files)
  304. or in the HTML notebook, just::
  305. FileLinks("my/data")
  306. """
  307. def __init__(self,
  308. path,
  309. url_prefix='',
  310. included_suffixes=None,
  311. result_html_prefix='',
  312. result_html_suffix='<br>',
  313. notebook_display_formatter=None,
  314. terminal_display_formatter=None,
  315. recursive=True):
  316. """
  317. See :class:`FileLink` for the ``path``, ``url_prefix``,
  318. ``result_html_prefix`` and ``result_html_suffix`` parameters.
  319. included_suffixes : list
  320. Filename suffixes to include when formatting output [default: include
  321. all files]
  322. notebook_display_formatter : function
  323. Used to format links for display in the notebook. See discussion of
  324. formatter functions below.
  325. terminal_display_formatter : function
  326. Used to format links for display in the terminal. See discussion of
  327. formatter functions below.
  328. Formatter functions must be of the form::
  329. f(dirname, fnames, included_suffixes)
  330. dirname : str
  331. The name of a directory
  332. fnames : list
  333. The files in that directory
  334. included_suffixes : list
  335. The file suffixes that should be included in the output (passing None
  336. meansto include all suffixes in the output in the built-in formatters)
  337. recursive : boolean
  338. Whether to recurse into subdirectories. Default is True.
  339. The function should return a list of lines that will be printed in the
  340. notebook (if passing notebook_display_formatter) or the terminal (if
  341. passing terminal_display_formatter). This function is iterated over for
  342. each directory in self.path. Default formatters are in place, can be
  343. passed here to support alternative formatting.
  344. """
  345. if isfile(path):
  346. raise ValueError("Cannot display a file using FileLinks. "
  347. "Use FileLink to display '%s'." % path)
  348. self.included_suffixes = included_suffixes
  349. # remove trailing slashs for more consistent output formatting
  350. path = path.rstrip('/')
  351. self.path = path
  352. self.url_prefix = url_prefix
  353. self.result_html_prefix = result_html_prefix
  354. self.result_html_suffix = result_html_suffix
  355. self.notebook_display_formatter = \
  356. notebook_display_formatter or self._get_notebook_display_formatter()
  357. self.terminal_display_formatter = \
  358. terminal_display_formatter or self._get_terminal_display_formatter()
  359. self.recursive = recursive
  360. def _get_display_formatter(self,
  361. dirname_output_format,
  362. fname_output_format,
  363. fp_format,
  364. fp_cleaner=None):
  365. """ generate built-in formatter function
  366. this is used to define both the notebook and terminal built-in
  367. formatters as they only differ by some wrapper text for each entry
  368. dirname_output_format: string to use for formatting directory
  369. names, dirname will be substituted for a single "%s" which
  370. must appear in this string
  371. fname_output_format: string to use for formatting file names,
  372. if a single "%s" appears in the string, fname will be substituted
  373. if two "%s" appear in the string, the path to fname will be
  374. substituted for the first and fname will be substituted for the
  375. second
  376. fp_format: string to use for formatting filepaths, must contain
  377. exactly two "%s" and the dirname will be subsituted for the first
  378. and fname will be substituted for the second
  379. """
  380. def f(dirname, fnames, included_suffixes=None):
  381. result = []
  382. # begin by figuring out which filenames, if any,
  383. # are going to be displayed
  384. display_fnames = []
  385. for fname in fnames:
  386. if (isfile(join(dirname,fname)) and
  387. (included_suffixes is None or
  388. splitext(fname)[1] in included_suffixes)):
  389. display_fnames.append(fname)
  390. if len(display_fnames) == 0:
  391. # if there are no filenames to display, don't print anything
  392. # (not even the directory name)
  393. pass
  394. else:
  395. # otherwise print the formatted directory name followed by
  396. # the formatted filenames
  397. dirname_output_line = dirname_output_format % dirname
  398. result.append(dirname_output_line)
  399. for fname in display_fnames:
  400. fp = fp_format % (dirname,fname)
  401. if fp_cleaner is not None:
  402. fp = fp_cleaner(fp)
  403. try:
  404. # output can include both a filepath and a filename...
  405. fname_output_line = fname_output_format % (fp, fname)
  406. except TypeError:
  407. # ... or just a single filepath
  408. fname_output_line = fname_output_format % fname
  409. result.append(fname_output_line)
  410. return result
  411. return f
  412. def _get_notebook_display_formatter(self,
  413. spacer="&nbsp;&nbsp;"):
  414. """ generate function to use for notebook formatting
  415. """
  416. dirname_output_format = \
  417. self.result_html_prefix + "%s/" + self.result_html_suffix
  418. fname_output_format = \
  419. self.result_html_prefix + spacer + self.html_link_str + self.result_html_suffix
  420. fp_format = self.url_prefix + '%s/%s'
  421. if sep == "\\":
  422. # Working on a platform where the path separator is "\", so
  423. # must convert these to "/" for generating a URI
  424. def fp_cleaner(fp):
  425. # Replace all occurences of backslash ("\") with a forward
  426. # slash ("/") - this is necessary on windows when a path is
  427. # provided as input, but we must link to a URI
  428. return fp.replace('\\','/')
  429. else:
  430. fp_cleaner = None
  431. return self._get_display_formatter(dirname_output_format,
  432. fname_output_format,
  433. fp_format,
  434. fp_cleaner)
  435. def _get_terminal_display_formatter(self,
  436. spacer=" "):
  437. """ generate function to use for terminal formatting
  438. """
  439. dirname_output_format = "%s/"
  440. fname_output_format = spacer + "%s"
  441. fp_format = '%s/%s'
  442. return self._get_display_formatter(dirname_output_format,
  443. fname_output_format,
  444. fp_format)
  445. def _format_path(self):
  446. result_lines = []
  447. if self.recursive:
  448. walked_dir = list(walk(self.path))
  449. else:
  450. walked_dir = [next(walk(self.path))]
  451. walked_dir.sort()
  452. for dirname, subdirs, fnames in walked_dir:
  453. result_lines += self.notebook_display_formatter(dirname, fnames, self.included_suffixes)
  454. return '\n'.join(result_lines)
  455. def __repr__(self):
  456. """return newline-separated absolute paths
  457. """
  458. result_lines = []
  459. if self.recursive:
  460. walked_dir = list(walk(self.path))
  461. else:
  462. walked_dir = [next(walk(self.path))]
  463. walked_dir.sort()
  464. for dirname, subdirs, fnames in walked_dir:
  465. result_lines += self.terminal_display_formatter(dirname, fnames, self.included_suffixes)
  466. return '\n'.join(result_lines)