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