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