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