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