##// END OF EJS Templates
don't overwrite extras entered by user
yuji96 -
Show More
@@ -1,671 +1,672 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, Iterable
11 from typing import Tuple, Iterable
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 {extras}
266 {extras}
267 ></iframe>
267 ></iframe>
268 """
268 """
269
269
270 def __init__(self, src, width, height, extras: Iterable[str] = None, **kwargs):
270 def __init__(self, src, width, height, extras: Iterable[str] = None, **kwargs):
271 if extras is None:
271 if extras is None:
272 extras = []
272 extras = []
273
273
274 self.src = src
274 self.src = src
275 self.width = width
275 self.width = width
276 self.height = height
276 self.height = height
277 self.extras = extras
277 self.extras = extras
278 self.params = kwargs
278 self.params = kwargs
279
279
280 def _repr_html_(self):
280 def _repr_html_(self):
281 """return the embed iframe"""
281 """return the embed iframe"""
282 if self.params:
282 if self.params:
283 from urllib.parse import urlencode
283 from urllib.parse import urlencode
284 params = "?" + urlencode(self.params)
284 params = "?" + urlencode(self.params)
285 else:
285 else:
286 params = ""
286 params = ""
287 return self.iframe.format(
287 return self.iframe.format(
288 src=self.src,
288 src=self.src,
289 width=self.width,
289 width=self.width,
290 height=self.height,
290 height=self.height,
291 params=params,
291 params=params,
292 extras=" ".join(self.extras),
292 extras=" ".join(self.extras),
293 )
293 )
294
294
295
295
296 class YouTubeVideo(IFrame):
296 class YouTubeVideo(IFrame):
297 """Class for embedding a YouTube Video in an IPython session, based on its video id.
297 """Class for embedding a YouTube Video in an IPython session, based on its video id.
298
298
299 e.g. to embed the video from https://www.youtube.com/watch?v=foo , you would
299 e.g. to embed the video from https://www.youtube.com/watch?v=foo , you would
300 do::
300 do::
301
301
302 vid = YouTubeVideo("foo")
302 vid = YouTubeVideo("foo")
303 display(vid)
303 display(vid)
304
304
305 To start from 30 seconds::
305 To start from 30 seconds::
306
306
307 vid = YouTubeVideo("abc", start=30)
307 vid = YouTubeVideo("abc", start=30)
308 display(vid)
308 display(vid)
309
309
310 To calculate seconds from time as hours, minutes, seconds use
310 To calculate seconds from time as hours, minutes, seconds use
311 :class:`datetime.timedelta`::
311 :class:`datetime.timedelta`::
312
312
313 start=int(timedelta(hours=1, minutes=46, seconds=40).total_seconds())
313 start=int(timedelta(hours=1, minutes=46, seconds=40).total_seconds())
314
314
315 Other parameters can be provided as documented at
315 Other parameters can be provided as documented at
316 https://developers.google.com/youtube/player_parameters#Parameters
316 https://developers.google.com/youtube/player_parameters#Parameters
317
317
318 When converting the notebook using nbconvert, a jpeg representation of the video
318 When converting the notebook using nbconvert, a jpeg representation of the video
319 will be inserted in the document.
319 will be inserted in the document.
320 """
320 """
321
321
322 def __init__(self, id, width=400, height=300, allow_autoplay=False, **kwargs):
322 def __init__(self, id, width=400, height=300, allow_autoplay=False, **kwargs):
323 self.id=id
323 self.id=id
324 src = "https://www.youtube.com/embed/{0}".format(id)
324 src = "https://www.youtube.com/embed/{0}".format(id)
325 if allow_autoplay:
325 if allow_autoplay:
326 kwargs.update(autoplay=1, extras=['allow="autoplay"'])
326 extras = list(kwargs.get("extras", [])) + ['allow="autoplay"']
327 kwargs.update(autoplay=1, extras=extras)
327 super(YouTubeVideo, self).__init__(src, width, height, **kwargs)
328 super(YouTubeVideo, self).__init__(src, width, height, **kwargs)
328
329
329 def _repr_jpeg_(self):
330 def _repr_jpeg_(self):
330 # Deferred import
331 # Deferred import
331 from urllib.request import urlopen
332 from urllib.request import urlopen
332
333
333 try:
334 try:
334 return urlopen("https://img.youtube.com/vi/{id}/hqdefault.jpg".format(id=self.id)).read()
335 return urlopen("https://img.youtube.com/vi/{id}/hqdefault.jpg".format(id=self.id)).read()
335 except IOError:
336 except IOError:
336 return None
337 return None
337
338
338 class VimeoVideo(IFrame):
339 class VimeoVideo(IFrame):
339 """
340 """
340 Class for embedding a Vimeo video in an IPython session, based on its video id.
341 Class for embedding a Vimeo video in an IPython session, based on its video id.
341 """
342 """
342
343
343 def __init__(self, id, width=400, height=300, **kwargs):
344 def __init__(self, id, width=400, height=300, **kwargs):
344 src="https://player.vimeo.com/video/{0}".format(id)
345 src="https://player.vimeo.com/video/{0}".format(id)
345 super(VimeoVideo, self).__init__(src, width, height, **kwargs)
346 super(VimeoVideo, self).__init__(src, width, height, **kwargs)
346
347
347 class ScribdDocument(IFrame):
348 class ScribdDocument(IFrame):
348 """
349 """
349 Class for embedding a Scribd document in an IPython session
350 Class for embedding a Scribd document in an IPython session
350
351
351 Use the start_page params to specify a starting point in the document
352 Use the start_page params to specify a starting point in the document
352 Use the view_mode params to specify display type one off scroll | slideshow | book
353 Use the view_mode params to specify display type one off scroll | slideshow | book
353
354
354 e.g to Display Wes' foundational paper about PANDAS in book mode from page 3
355 e.g to Display Wes' foundational paper about PANDAS in book mode from page 3
355
356
356 ScribdDocument(71048089, width=800, height=400, start_page=3, view_mode="book")
357 ScribdDocument(71048089, width=800, height=400, start_page=3, view_mode="book")
357 """
358 """
358
359
359 def __init__(self, id, width=400, height=300, **kwargs):
360 def __init__(self, id, width=400, height=300, **kwargs):
360 src="https://www.scribd.com/embeds/{0}/content".format(id)
361 src="https://www.scribd.com/embeds/{0}/content".format(id)
361 super(ScribdDocument, self).__init__(src, width, height, **kwargs)
362 super(ScribdDocument, self).__init__(src, width, height, **kwargs)
362
363
363 class FileLink(object):
364 class FileLink(object):
364 """Class for embedding a local file link in an IPython session, based on path
365 """Class for embedding a local file link in an IPython session, based on path
365
366
366 e.g. to embed a link that was generated in the IPython notebook as my/data.txt
367 e.g. to embed a link that was generated in the IPython notebook as my/data.txt
367
368
368 you would do::
369 you would do::
369
370
370 local_file = FileLink("my/data.txt")
371 local_file = FileLink("my/data.txt")
371 display(local_file)
372 display(local_file)
372
373
373 or in the HTML notebook, just::
374 or in the HTML notebook, just::
374
375
375 FileLink("my/data.txt")
376 FileLink("my/data.txt")
376 """
377 """
377
378
378 html_link_str = "<a href='%s' target='_blank'>%s</a>"
379 html_link_str = "<a href='%s' target='_blank'>%s</a>"
379
380
380 def __init__(self,
381 def __init__(self,
381 path,
382 path,
382 url_prefix='',
383 url_prefix='',
383 result_html_prefix='',
384 result_html_prefix='',
384 result_html_suffix='<br>'):
385 result_html_suffix='<br>'):
385 """
386 """
386 Parameters
387 Parameters
387 ----------
388 ----------
388 path : str
389 path : str
389 path to the file or directory that should be formatted
390 path to the file or directory that should be formatted
390 url_prefix : str
391 url_prefix : str
391 prefix to be prepended to all files to form a working link [default:
392 prefix to be prepended to all files to form a working link [default:
392 '']
393 '']
393 result_html_prefix : str
394 result_html_prefix : str
394 text to append to beginning to link [default: '']
395 text to append to beginning to link [default: '']
395 result_html_suffix : str
396 result_html_suffix : str
396 text to append at the end of link [default: '<br>']
397 text to append at the end of link [default: '<br>']
397 """
398 """
398 if isdir(path):
399 if isdir(path):
399 raise ValueError("Cannot display a directory using FileLink. "
400 raise ValueError("Cannot display a directory using FileLink. "
400 "Use FileLinks to display '%s'." % path)
401 "Use FileLinks to display '%s'." % path)
401 self.path = fsdecode(path)
402 self.path = fsdecode(path)
402 self.url_prefix = url_prefix
403 self.url_prefix = url_prefix
403 self.result_html_prefix = result_html_prefix
404 self.result_html_prefix = result_html_prefix
404 self.result_html_suffix = result_html_suffix
405 self.result_html_suffix = result_html_suffix
405
406
406 def _format_path(self):
407 def _format_path(self):
407 fp = ''.join([self.url_prefix, html_escape(self.path)])
408 fp = ''.join([self.url_prefix, html_escape(self.path)])
408 return ''.join([self.result_html_prefix,
409 return ''.join([self.result_html_prefix,
409 self.html_link_str % \
410 self.html_link_str % \
410 (fp, html_escape(self.path, quote=False)),
411 (fp, html_escape(self.path, quote=False)),
411 self.result_html_suffix])
412 self.result_html_suffix])
412
413
413 def _repr_html_(self):
414 def _repr_html_(self):
414 """return html link to file
415 """return html link to file
415 """
416 """
416 if not exists(self.path):
417 if not exists(self.path):
417 return ("Path (<tt>%s</tt>) doesn't exist. "
418 return ("Path (<tt>%s</tt>) doesn't exist. "
418 "It may still be in the process of "
419 "It may still be in the process of "
419 "being generated, or you may have the "
420 "being generated, or you may have the "
420 "incorrect path." % self.path)
421 "incorrect path." % self.path)
421
422
422 return self._format_path()
423 return self._format_path()
423
424
424 def __repr__(self):
425 def __repr__(self):
425 """return absolute path to file
426 """return absolute path to file
426 """
427 """
427 return abspath(self.path)
428 return abspath(self.path)
428
429
429 class FileLinks(FileLink):
430 class FileLinks(FileLink):
430 """Class for embedding local file links in an IPython session, based on path
431 """Class for embedding local file links in an IPython session, based on path
431
432
432 e.g. to embed links to files that were generated in the IPython notebook
433 e.g. to embed links to files that were generated in the IPython notebook
433 under ``my/data``, you would do::
434 under ``my/data``, you would do::
434
435
435 local_files = FileLinks("my/data")
436 local_files = FileLinks("my/data")
436 display(local_files)
437 display(local_files)
437
438
438 or in the HTML notebook, just::
439 or in the HTML notebook, just::
439
440
440 FileLinks("my/data")
441 FileLinks("my/data")
441 """
442 """
442 def __init__(self,
443 def __init__(self,
443 path,
444 path,
444 url_prefix='',
445 url_prefix='',
445 included_suffixes=None,
446 included_suffixes=None,
446 result_html_prefix='',
447 result_html_prefix='',
447 result_html_suffix='<br>',
448 result_html_suffix='<br>',
448 notebook_display_formatter=None,
449 notebook_display_formatter=None,
449 terminal_display_formatter=None,
450 terminal_display_formatter=None,
450 recursive=True):
451 recursive=True):
451 """
452 """
452 See :class:`FileLink` for the ``path``, ``url_prefix``,
453 See :class:`FileLink` for the ``path``, ``url_prefix``,
453 ``result_html_prefix`` and ``result_html_suffix`` parameters.
454 ``result_html_prefix`` and ``result_html_suffix`` parameters.
454
455
455 included_suffixes : list
456 included_suffixes : list
456 Filename suffixes to include when formatting output [default: include
457 Filename suffixes to include when formatting output [default: include
457 all files]
458 all files]
458
459
459 notebook_display_formatter : function
460 notebook_display_formatter : function
460 Used to format links for display in the notebook. See discussion of
461 Used to format links for display in the notebook. See discussion of
461 formatter functions below.
462 formatter functions below.
462
463
463 terminal_display_formatter : function
464 terminal_display_formatter : function
464 Used to format links for display in the terminal. See discussion of
465 Used to format links for display in the terminal. See discussion of
465 formatter functions below.
466 formatter functions below.
466
467
467 Formatter functions must be of the form::
468 Formatter functions must be of the form::
468
469
469 f(dirname, fnames, included_suffixes)
470 f(dirname, fnames, included_suffixes)
470
471
471 dirname : str
472 dirname : str
472 The name of a directory
473 The name of a directory
473 fnames : list
474 fnames : list
474 The files in that directory
475 The files in that directory
475 included_suffixes : list
476 included_suffixes : list
476 The file suffixes that should be included in the output (passing None
477 The file suffixes that should be included in the output (passing None
477 meansto include all suffixes in the output in the built-in formatters)
478 meansto include all suffixes in the output in the built-in formatters)
478 recursive : boolean
479 recursive : boolean
479 Whether to recurse into subdirectories. Default is True.
480 Whether to recurse into subdirectories. Default is True.
480
481
481 The function should return a list of lines that will be printed in the
482 The function should return a list of lines that will be printed in the
482 notebook (if passing notebook_display_formatter) or the terminal (if
483 notebook (if passing notebook_display_formatter) or the terminal (if
483 passing terminal_display_formatter). This function is iterated over for
484 passing terminal_display_formatter). This function is iterated over for
484 each directory in self.path. Default formatters are in place, can be
485 each directory in self.path. Default formatters are in place, can be
485 passed here to support alternative formatting.
486 passed here to support alternative formatting.
486
487
487 """
488 """
488 if isfile(path):
489 if isfile(path):
489 raise ValueError("Cannot display a file using FileLinks. "
490 raise ValueError("Cannot display a file using FileLinks. "
490 "Use FileLink to display '%s'." % path)
491 "Use FileLink to display '%s'." % path)
491 self.included_suffixes = included_suffixes
492 self.included_suffixes = included_suffixes
492 # remove trailing slashes for more consistent output formatting
493 # remove trailing slashes for more consistent output formatting
493 path = path.rstrip('/')
494 path = path.rstrip('/')
494
495
495 self.path = path
496 self.path = path
496 self.url_prefix = url_prefix
497 self.url_prefix = url_prefix
497 self.result_html_prefix = result_html_prefix
498 self.result_html_prefix = result_html_prefix
498 self.result_html_suffix = result_html_suffix
499 self.result_html_suffix = result_html_suffix
499
500
500 self.notebook_display_formatter = \
501 self.notebook_display_formatter = \
501 notebook_display_formatter or self._get_notebook_display_formatter()
502 notebook_display_formatter or self._get_notebook_display_formatter()
502 self.terminal_display_formatter = \
503 self.terminal_display_formatter = \
503 terminal_display_formatter or self._get_terminal_display_formatter()
504 terminal_display_formatter or self._get_terminal_display_formatter()
504
505
505 self.recursive = recursive
506 self.recursive = recursive
506
507
507 def _get_display_formatter(self,
508 def _get_display_formatter(self,
508 dirname_output_format,
509 dirname_output_format,
509 fname_output_format,
510 fname_output_format,
510 fp_format,
511 fp_format,
511 fp_cleaner=None):
512 fp_cleaner=None):
512 """ generate built-in formatter function
513 """ generate built-in formatter function
513
514
514 this is used to define both the notebook and terminal built-in
515 this is used to define both the notebook and terminal built-in
515 formatters as they only differ by some wrapper text for each entry
516 formatters as they only differ by some wrapper text for each entry
516
517
517 dirname_output_format: string to use for formatting directory
518 dirname_output_format: string to use for formatting directory
518 names, dirname will be substituted for a single "%s" which
519 names, dirname will be substituted for a single "%s" which
519 must appear in this string
520 must appear in this string
520 fname_output_format: string to use for formatting file names,
521 fname_output_format: string to use for formatting file names,
521 if a single "%s" appears in the string, fname will be substituted
522 if a single "%s" appears in the string, fname will be substituted
522 if two "%s" appear in the string, the path to fname will be
523 if two "%s" appear in the string, the path to fname will be
523 substituted for the first and fname will be substituted for the
524 substituted for the first and fname will be substituted for the
524 second
525 second
525 fp_format: string to use for formatting filepaths, must contain
526 fp_format: string to use for formatting filepaths, must contain
526 exactly two "%s" and the dirname will be substituted for the first
527 exactly two "%s" and the dirname will be substituted for the first
527 and fname will be substituted for the second
528 and fname will be substituted for the second
528 """
529 """
529 def f(dirname, fnames, included_suffixes=None):
530 def f(dirname, fnames, included_suffixes=None):
530 result = []
531 result = []
531 # begin by figuring out which filenames, if any,
532 # begin by figuring out which filenames, if any,
532 # are going to be displayed
533 # are going to be displayed
533 display_fnames = []
534 display_fnames = []
534 for fname in fnames:
535 for fname in fnames:
535 if (isfile(join(dirname,fname)) and
536 if (isfile(join(dirname,fname)) and
536 (included_suffixes is None or
537 (included_suffixes is None or
537 splitext(fname)[1] in included_suffixes)):
538 splitext(fname)[1] in included_suffixes)):
538 display_fnames.append(fname)
539 display_fnames.append(fname)
539
540
540 if len(display_fnames) == 0:
541 if len(display_fnames) == 0:
541 # if there are no filenames to display, don't print anything
542 # if there are no filenames to display, don't print anything
542 # (not even the directory name)
543 # (not even the directory name)
543 pass
544 pass
544 else:
545 else:
545 # otherwise print the formatted directory name followed by
546 # otherwise print the formatted directory name followed by
546 # the formatted filenames
547 # the formatted filenames
547 dirname_output_line = dirname_output_format % dirname
548 dirname_output_line = dirname_output_format % dirname
548 result.append(dirname_output_line)
549 result.append(dirname_output_line)
549 for fname in display_fnames:
550 for fname in display_fnames:
550 fp = fp_format % (dirname,fname)
551 fp = fp_format % (dirname,fname)
551 if fp_cleaner is not None:
552 if fp_cleaner is not None:
552 fp = fp_cleaner(fp)
553 fp = fp_cleaner(fp)
553 try:
554 try:
554 # output can include both a filepath and a filename...
555 # output can include both a filepath and a filename...
555 fname_output_line = fname_output_format % (fp, fname)
556 fname_output_line = fname_output_format % (fp, fname)
556 except TypeError:
557 except TypeError:
557 # ... or just a single filepath
558 # ... or just a single filepath
558 fname_output_line = fname_output_format % fname
559 fname_output_line = fname_output_format % fname
559 result.append(fname_output_line)
560 result.append(fname_output_line)
560 return result
561 return result
561 return f
562 return f
562
563
563 def _get_notebook_display_formatter(self,
564 def _get_notebook_display_formatter(self,
564 spacer="&nbsp;&nbsp;"):
565 spacer="&nbsp;&nbsp;"):
565 """ generate function to use for notebook formatting
566 """ generate function to use for notebook formatting
566 """
567 """
567 dirname_output_format = \
568 dirname_output_format = \
568 self.result_html_prefix + "%s/" + self.result_html_suffix
569 self.result_html_prefix + "%s/" + self.result_html_suffix
569 fname_output_format = \
570 fname_output_format = \
570 self.result_html_prefix + spacer + self.html_link_str + self.result_html_suffix
571 self.result_html_prefix + spacer + self.html_link_str + self.result_html_suffix
571 fp_format = self.url_prefix + '%s/%s'
572 fp_format = self.url_prefix + '%s/%s'
572 if sep == "\\":
573 if sep == "\\":
573 # Working on a platform where the path separator is "\", so
574 # Working on a platform where the path separator is "\", so
574 # must convert these to "/" for generating a URI
575 # must convert these to "/" for generating a URI
575 def fp_cleaner(fp):
576 def fp_cleaner(fp):
576 # Replace all occurrences of backslash ("\") with a forward
577 # Replace all occurrences of backslash ("\") with a forward
577 # slash ("/") - this is necessary on windows when a path is
578 # slash ("/") - this is necessary on windows when a path is
578 # provided as input, but we must link to a URI
579 # provided as input, but we must link to a URI
579 return fp.replace('\\','/')
580 return fp.replace('\\','/')
580 else:
581 else:
581 fp_cleaner = None
582 fp_cleaner = None
582
583
583 return self._get_display_formatter(dirname_output_format,
584 return self._get_display_formatter(dirname_output_format,
584 fname_output_format,
585 fname_output_format,
585 fp_format,
586 fp_format,
586 fp_cleaner)
587 fp_cleaner)
587
588
588 def _get_terminal_display_formatter(self,
589 def _get_terminal_display_formatter(self,
589 spacer=" "):
590 spacer=" "):
590 """ generate function to use for terminal formatting
591 """ generate function to use for terminal formatting
591 """
592 """
592 dirname_output_format = "%s/"
593 dirname_output_format = "%s/"
593 fname_output_format = spacer + "%s"
594 fname_output_format = spacer + "%s"
594 fp_format = '%s/%s'
595 fp_format = '%s/%s'
595
596
596 return self._get_display_formatter(dirname_output_format,
597 return self._get_display_formatter(dirname_output_format,
597 fname_output_format,
598 fname_output_format,
598 fp_format)
599 fp_format)
599
600
600 def _format_path(self):
601 def _format_path(self):
601 result_lines = []
602 result_lines = []
602 if self.recursive:
603 if self.recursive:
603 walked_dir = list(walk(self.path))
604 walked_dir = list(walk(self.path))
604 else:
605 else:
605 walked_dir = [next(walk(self.path))]
606 walked_dir = [next(walk(self.path))]
606 walked_dir.sort()
607 walked_dir.sort()
607 for dirname, subdirs, fnames in walked_dir:
608 for dirname, subdirs, fnames in walked_dir:
608 result_lines += self.notebook_display_formatter(dirname, fnames, self.included_suffixes)
609 result_lines += self.notebook_display_formatter(dirname, fnames, self.included_suffixes)
609 return '\n'.join(result_lines)
610 return '\n'.join(result_lines)
610
611
611 def __repr__(self):
612 def __repr__(self):
612 """return newline-separated absolute paths
613 """return newline-separated absolute paths
613 """
614 """
614 result_lines = []
615 result_lines = []
615 if self.recursive:
616 if self.recursive:
616 walked_dir = list(walk(self.path))
617 walked_dir = list(walk(self.path))
617 else:
618 else:
618 walked_dir = [next(walk(self.path))]
619 walked_dir = [next(walk(self.path))]
619 walked_dir.sort()
620 walked_dir.sort()
620 for dirname, subdirs, fnames in walked_dir:
621 for dirname, subdirs, fnames in walked_dir:
621 result_lines += self.terminal_display_formatter(dirname, fnames, self.included_suffixes)
622 result_lines += self.terminal_display_formatter(dirname, fnames, self.included_suffixes)
622 return '\n'.join(result_lines)
623 return '\n'.join(result_lines)
623
624
624
625
625 class Code(TextDisplayObject):
626 class Code(TextDisplayObject):
626 """Display syntax-highlighted source code.
627 """Display syntax-highlighted source code.
627
628
628 This uses Pygments to highlight the code for HTML and Latex output.
629 This uses Pygments to highlight the code for HTML and Latex output.
629
630
630 Parameters
631 Parameters
631 ----------
632 ----------
632 data : str
633 data : str
633 The code as a string
634 The code as a string
634 url : str
635 url : str
635 A URL to fetch the code from
636 A URL to fetch the code from
636 filename : str
637 filename : str
637 A local filename to load the code from
638 A local filename to load the code from
638 language : str
639 language : str
639 The short name of a Pygments lexer to use for highlighting.
640 The short name of a Pygments lexer to use for highlighting.
640 If not specified, it will guess the lexer based on the filename
641 If not specified, it will guess the lexer based on the filename
641 or the code. Available lexers: http://pygments.org/docs/lexers/
642 or the code. Available lexers: http://pygments.org/docs/lexers/
642 """
643 """
643 def __init__(self, data=None, url=None, filename=None, language=None):
644 def __init__(self, data=None, url=None, filename=None, language=None):
644 self.language = language
645 self.language = language
645 super().__init__(data=data, url=url, filename=filename)
646 super().__init__(data=data, url=url, filename=filename)
646
647
647 def _get_lexer(self):
648 def _get_lexer(self):
648 if self.language:
649 if self.language:
649 from pygments.lexers import get_lexer_by_name
650 from pygments.lexers import get_lexer_by_name
650 return get_lexer_by_name(self.language)
651 return get_lexer_by_name(self.language)
651 elif self.filename:
652 elif self.filename:
652 from pygments.lexers import get_lexer_for_filename
653 from pygments.lexers import get_lexer_for_filename
653 return get_lexer_for_filename(self.filename)
654 return get_lexer_for_filename(self.filename)
654 else:
655 else:
655 from pygments.lexers import guess_lexer
656 from pygments.lexers import guess_lexer
656 return guess_lexer(self.data)
657 return guess_lexer(self.data)
657
658
658 def __repr__(self):
659 def __repr__(self):
659 return self.data
660 return self.data
660
661
661 def _repr_html_(self):
662 def _repr_html_(self):
662 from pygments import highlight
663 from pygments import highlight
663 from pygments.formatters import HtmlFormatter
664 from pygments.formatters import HtmlFormatter
664 fmt = HtmlFormatter()
665 fmt = HtmlFormatter()
665 style = '<style>{}</style>'.format(fmt.get_style_defs('.output_html'))
666 style = '<style>{}</style>'.format(fmt.get_style_defs('.output_html'))
666 return style + highlight(self.data, self._get_lexer(), fmt)
667 return style + highlight(self.data, self._get_lexer(), fmt)
667
668
668 def _repr_latex_(self):
669 def _repr_latex_(self):
669 from pygments import highlight
670 from pygments import highlight
670 from pygments.formatters import LatexFormatter
671 from pygments.formatters import LatexFormatter
671 return highlight(self.data, self._get_lexer(), LatexFormatter())
672 return highlight(self.data, self._get_lexer(), LatexFormatter())
General Comments 0
You need to be logged in to leave comments. Login now