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