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