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