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