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