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