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