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