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