##// END OF EJS Templates
Show raw code as plain text repr
Thomas Kluyver -
Show More
@@ -1,603 +1,606 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, TextDisplayObject
9 9
10 10 __all__ = ['Audio', 'IFrame', 'YouTubeVideo', 'VimeoVideo', 'ScribdDocument',
11 11 'FileLink', 'FileLinks', 'Code']
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 audio 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#Parameters
255 255
256 256 When converting the notebook using nbconvert, a jpeg representation of the video
257 257 will be inserted in the document.
258 258 """
259 259
260 260 def __init__(self, id, width=400, height=300, **kwargs):
261 261 self.id=id
262 262 src = "https://www.youtube.com/embed/{0}".format(id)
263 263 super(YouTubeVideo, self).__init__(src, width, height, **kwargs)
264 264
265 265 def _repr_jpeg_(self):
266 266 # Deferred import
267 267 from urllib.request import urlopen
268 268
269 269 try:
270 270 return urlopen("https://img.youtube.com/vi/{id}/hqdefault.jpg".format(id=self.id)).read()
271 271 except IOError:
272 272 return None
273 273
274 274 class VimeoVideo(IFrame):
275 275 """
276 276 Class for embedding a Vimeo video in an IPython session, based on its video id.
277 277 """
278 278
279 279 def __init__(self, id, width=400, height=300, **kwargs):
280 280 src="https://player.vimeo.com/video/{0}".format(id)
281 281 super(VimeoVideo, self).__init__(src, width, height, **kwargs)
282 282
283 283 class ScribdDocument(IFrame):
284 284 """
285 285 Class for embedding a Scribd document in an IPython session
286 286
287 287 Use the start_page params to specify a starting point in the document
288 288 Use the view_mode params to specify display type one off scroll | slideshow | book
289 289
290 290 e.g to Display Wes' foundational paper about PANDAS in book mode from page 3
291 291
292 292 ScribdDocument(71048089, width=800, height=400, start_page=3, view_mode="book")
293 293 """
294 294
295 295 def __init__(self, id, width=400, height=300, **kwargs):
296 296 src="https://www.scribd.com/embeds/{0}/content".format(id)
297 297 super(ScribdDocument, self).__init__(src, width, height, **kwargs)
298 298
299 299 class FileLink(object):
300 300 """Class for embedding a local file link in an IPython session, based on path
301 301
302 302 e.g. to embed a link that was generated in the IPython notebook as my/data.txt
303 303
304 304 you would do::
305 305
306 306 local_file = FileLink("my/data.txt")
307 307 display(local_file)
308 308
309 309 or in the HTML notebook, just::
310 310
311 311 FileLink("my/data.txt")
312 312 """
313 313
314 314 html_link_str = "<a href='%s' target='_blank'>%s</a>"
315 315
316 316 def __init__(self,
317 317 path,
318 318 url_prefix='',
319 319 result_html_prefix='',
320 320 result_html_suffix='<br>'):
321 321 """
322 322 Parameters
323 323 ----------
324 324 path : str
325 325 path to the file or directory that should be formatted
326 326 url_prefix : str
327 327 prefix to be prepended to all files to form a working link [default:
328 328 '']
329 329 result_html_prefix : str
330 330 text to append to beginning to link [default: '']
331 331 result_html_suffix : str
332 332 text to append at the end of link [default: '<br>']
333 333 """
334 334 if isdir(path):
335 335 raise ValueError("Cannot display a directory using FileLink. "
336 336 "Use FileLinks to display '%s'." % path)
337 337 self.path = path
338 338 self.url_prefix = url_prefix
339 339 self.result_html_prefix = result_html_prefix
340 340 self.result_html_suffix = result_html_suffix
341 341
342 342 def _format_path(self):
343 343 fp = ''.join([self.url_prefix,self.path])
344 344 return ''.join([self.result_html_prefix,
345 345 self.html_link_str % (fp, self.path),
346 346 self.result_html_suffix])
347 347
348 348 def _repr_html_(self):
349 349 """return html link to file
350 350 """
351 351 if not exists(self.path):
352 352 return ("Path (<tt>%s</tt>) doesn't exist. "
353 353 "It may still be in the process of "
354 354 "being generated, or you may have the "
355 355 "incorrect path." % self.path)
356 356
357 357 return self._format_path()
358 358
359 359 def __repr__(self):
360 360 """return absolute path to file
361 361 """
362 362 return abspath(self.path)
363 363
364 364 class FileLinks(FileLink):
365 365 """Class for embedding local file links in an IPython session, based on path
366 366
367 367 e.g. to embed links to files that were generated in the IPython notebook
368 368 under ``my/data``, you would do::
369 369
370 370 local_files = FileLinks("my/data")
371 371 display(local_files)
372 372
373 373 or in the HTML notebook, just::
374 374
375 375 FileLinks("my/data")
376 376 """
377 377 def __init__(self,
378 378 path,
379 379 url_prefix='',
380 380 included_suffixes=None,
381 381 result_html_prefix='',
382 382 result_html_suffix='<br>',
383 383 notebook_display_formatter=None,
384 384 terminal_display_formatter=None,
385 385 recursive=True):
386 386 """
387 387 See :class:`FileLink` for the ``path``, ``url_prefix``,
388 388 ``result_html_prefix`` and ``result_html_suffix`` parameters.
389 389
390 390 included_suffixes : list
391 391 Filename suffixes to include when formatting output [default: include
392 392 all files]
393 393
394 394 notebook_display_formatter : function
395 395 Used to format links for display in the notebook. See discussion of
396 396 formatter functions below.
397 397
398 398 terminal_display_formatter : function
399 399 Used to format links for display in the terminal. See discussion of
400 400 formatter functions below.
401 401
402 402 Formatter functions must be of the form::
403 403
404 404 f(dirname, fnames, included_suffixes)
405 405
406 406 dirname : str
407 407 The name of a directory
408 408 fnames : list
409 409 The files in that directory
410 410 included_suffixes : list
411 411 The file suffixes that should be included in the output (passing None
412 412 meansto include all suffixes in the output in the built-in formatters)
413 413 recursive : boolean
414 414 Whether to recurse into subdirectories. Default is True.
415 415
416 416 The function should return a list of lines that will be printed in the
417 417 notebook (if passing notebook_display_formatter) or the terminal (if
418 418 passing terminal_display_formatter). This function is iterated over for
419 419 each directory in self.path. Default formatters are in place, can be
420 420 passed here to support alternative formatting.
421 421
422 422 """
423 423 if isfile(path):
424 424 raise ValueError("Cannot display a file using FileLinks. "
425 425 "Use FileLink to display '%s'." % path)
426 426 self.included_suffixes = included_suffixes
427 427 # remove trailing slashs for more consistent output formatting
428 428 path = path.rstrip('/')
429 429
430 430 self.path = path
431 431 self.url_prefix = url_prefix
432 432 self.result_html_prefix = result_html_prefix
433 433 self.result_html_suffix = result_html_suffix
434 434
435 435 self.notebook_display_formatter = \
436 436 notebook_display_formatter or self._get_notebook_display_formatter()
437 437 self.terminal_display_formatter = \
438 438 terminal_display_formatter or self._get_terminal_display_formatter()
439 439
440 440 self.recursive = recursive
441 441
442 442 def _get_display_formatter(self,
443 443 dirname_output_format,
444 444 fname_output_format,
445 445 fp_format,
446 446 fp_cleaner=None):
447 447 """ generate built-in formatter function
448 448
449 449 this is used to define both the notebook and terminal built-in
450 450 formatters as they only differ by some wrapper text for each entry
451 451
452 452 dirname_output_format: string to use for formatting directory
453 453 names, dirname will be substituted for a single "%s" which
454 454 must appear in this string
455 455 fname_output_format: string to use for formatting file names,
456 456 if a single "%s" appears in the string, fname will be substituted
457 457 if two "%s" appear in the string, the path to fname will be
458 458 substituted for the first and fname will be substituted for the
459 459 second
460 460 fp_format: string to use for formatting filepaths, must contain
461 461 exactly two "%s" and the dirname will be subsituted for the first
462 462 and fname will be substituted for the second
463 463 """
464 464 def f(dirname, fnames, included_suffixes=None):
465 465 result = []
466 466 # begin by figuring out which filenames, if any,
467 467 # are going to be displayed
468 468 display_fnames = []
469 469 for fname in fnames:
470 470 if (isfile(join(dirname,fname)) and
471 471 (included_suffixes is None or
472 472 splitext(fname)[1] in included_suffixes)):
473 473 display_fnames.append(fname)
474 474
475 475 if len(display_fnames) == 0:
476 476 # if there are no filenames to display, don't print anything
477 477 # (not even the directory name)
478 478 pass
479 479 else:
480 480 # otherwise print the formatted directory name followed by
481 481 # the formatted filenames
482 482 dirname_output_line = dirname_output_format % dirname
483 483 result.append(dirname_output_line)
484 484 for fname in display_fnames:
485 485 fp = fp_format % (dirname,fname)
486 486 if fp_cleaner is not None:
487 487 fp = fp_cleaner(fp)
488 488 try:
489 489 # output can include both a filepath and a filename...
490 490 fname_output_line = fname_output_format % (fp, fname)
491 491 except TypeError:
492 492 # ... or just a single filepath
493 493 fname_output_line = fname_output_format % fname
494 494 result.append(fname_output_line)
495 495 return result
496 496 return f
497 497
498 498 def _get_notebook_display_formatter(self,
499 499 spacer="&nbsp;&nbsp;"):
500 500 """ generate function to use for notebook formatting
501 501 """
502 502 dirname_output_format = \
503 503 self.result_html_prefix + "%s/" + self.result_html_suffix
504 504 fname_output_format = \
505 505 self.result_html_prefix + spacer + self.html_link_str + self.result_html_suffix
506 506 fp_format = self.url_prefix + '%s/%s'
507 507 if sep == "\\":
508 508 # Working on a platform where the path separator is "\", so
509 509 # must convert these to "/" for generating a URI
510 510 def fp_cleaner(fp):
511 511 # Replace all occurrences of backslash ("\") with a forward
512 512 # slash ("/") - this is necessary on windows when a path is
513 513 # provided as input, but we must link to a URI
514 514 return fp.replace('\\','/')
515 515 else:
516 516 fp_cleaner = None
517 517
518 518 return self._get_display_formatter(dirname_output_format,
519 519 fname_output_format,
520 520 fp_format,
521 521 fp_cleaner)
522 522
523 523 def _get_terminal_display_formatter(self,
524 524 spacer=" "):
525 525 """ generate function to use for terminal formatting
526 526 """
527 527 dirname_output_format = "%s/"
528 528 fname_output_format = spacer + "%s"
529 529 fp_format = '%s/%s'
530 530
531 531 return self._get_display_formatter(dirname_output_format,
532 532 fname_output_format,
533 533 fp_format)
534 534
535 535 def _format_path(self):
536 536 result_lines = []
537 537 if self.recursive:
538 538 walked_dir = list(walk(self.path))
539 539 else:
540 540 walked_dir = [next(walk(self.path))]
541 541 walked_dir.sort()
542 542 for dirname, subdirs, fnames in walked_dir:
543 543 result_lines += self.notebook_display_formatter(dirname, fnames, self.included_suffixes)
544 544 return '\n'.join(result_lines)
545 545
546 546 def __repr__(self):
547 547 """return newline-separated absolute paths
548 548 """
549 549 result_lines = []
550 550 if self.recursive:
551 551 walked_dir = list(walk(self.path))
552 552 else:
553 553 walked_dir = [next(walk(self.path))]
554 554 walked_dir.sort()
555 555 for dirname, subdirs, fnames in walked_dir:
556 556 result_lines += self.terminal_display_formatter(dirname, fnames, self.included_suffixes)
557 557 return '\n'.join(result_lines)
558 558
559 559
560 560 class Code(TextDisplayObject):
561 561 """Display syntax-highlighted source code.
562 562
563 563 This uses Pygments to highlight the code for HTML and Latex output.
564 564
565 565 Parameters
566 566 ----------
567 567 data : str
568 568 The code as a string
569 569 url : str
570 570 A URL to fetch the code from
571 571 filename : str
572 572 A local filename to load the code from
573 573 language : str
574 574 The short name of a Pygments lexer to use for highlighting.
575 575 If not specified, it will guess the lexer based on the filename
576 576 or the code. Available lexers: http://pygments.org/docs/lexers/
577 577 """
578 578 def __init__(self, data=None, url=None, filename=None, language=None):
579 579 self.language = language
580 580 super().__init__(data=data, url=url, filename=filename)
581 581
582 582 def _get_lexer(self):
583 583 if self.language:
584 584 from pygments.lexers import get_lexer_by_name
585 585 return get_lexer_by_name(self.language)
586 586 elif self.filename:
587 587 from pygments.lexers import get_lexer_for_filename
588 588 return get_lexer_for_filename(self.filename)
589 589 else:
590 590 from pygments.lexers import guess_lexer
591 591 return guess_lexer(self.data)
592 592
593 def __repr__(self):
594 return self.data
595
593 596 def _repr_html_(self):
594 597 from pygments import highlight
595 598 from pygments.formatters import HtmlFormatter
596 599 fmt = HtmlFormatter()
597 600 style = '<style>{}</style>'.format(fmt.get_style_defs('.output_html'))
598 601 return style + highlight(self.data, self._get_lexer(), fmt)
599 602
600 603 def _repr_latex_(self):
601 604 from pygments import highlight
602 605 from pygments.formatters import LatexFormatter
603 606 return highlight(self.data, self._get_lexer(), LatexFormatter())
General Comments 0
You need to be logged in to leave comments. Login now