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