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