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