1"""
2This module implements ipython_display
3A function to embed images/videos/audio in the IPython Notebook
4"""
5
6# Notes:
7# All media are physically embedded in the IPython Notebook
8# (instead of simple links to the original files)
9# That is because most browsers use a cache system and they won't
10# properly refresh the media when the original files are changed.
11
12import os
13from base64 import b64encode
14
15from moviepy.audio.AudioClip import AudioClip
16from moviepy.tools import extensions_dict
17
18from ..VideoClip import ImageClip, VideoClip
19from .ffmpeg_reader import ffmpeg_parse_infos
20
21try:
22    from IPython.display import HTML
23    ipython_available = True
24    class HTML2(HTML):
25        def __add__(self, other):
26            return HTML2(self.data+other.data)
27
28except ImportError:
29    ipython_available = False
30
31
32sorry = "Sorry, seems like your browser doesn't support HTML5 audio/video"
33templates = {"audio":("<audio controls>"
34                         "<source %(options)s  src='data:audio/%(ext)s;base64,%(data)s'>"
35                     +sorry+"</audio>"),
36             "image":"<img %(options)s "
37                     "src='data:image/%(ext)s;base64,%(data)s'>",
38             "video":("<video %(options)s"
39                       "src='data:video/%(ext)s;base64,%(data)s' controls>"
40                       +sorry+"</video>")}
41
42
43def html_embed(clip, filetype=None, maxduration=60, rd_kwargs=None,
44               center=True, **html_kwargs):
45    """ Returns HTML5 code embedding the clip
46
47    clip
48      Either a file name, or a clip to preview.
49      Either an image, a sound or a video. Clips will actually be
50      written to a file and embedded as if a filename was provided.
51
52
53    filetype
54      One of 'video','image','audio'. If None is given, it is determined
55      based on the extension of ``filename``, but this can bug.
56
57    rd_kwargs
58      keyword arguments for the rendering, like {'fps':15, 'bitrate':'50k'}
59
60
61    **html_kwargs
62      Allow you to give some options, like width=260, autoplay=True,
63      loop=1 etc.
64
65    Examples
66    =========
67
68    >>> import moviepy.editor as mpy
69    >>> # later ...
70    >>> clip.write_videofile("test.mp4")
71    >>> mpy.ipython_display("test.mp4", width=360)
72
73    >>> clip.audio.write_audiofile('test.ogg') # Sound !
74    >>> mpy.ipython_display('test.ogg')
75
76    >>> clip.write_gif("test.gif")
77    >>> mpy.ipython_display('test.gif')
78
79    >>> clip.save_frame("first_frame.jpeg")
80    >>> mpy.ipython_display("first_frame.jpeg")
81
82    """
83
84    if rd_kwargs is None:
85        rd_kwargs = {}
86
87    if "Clip" in str(clip.__class__):
88        TEMP_PREFIX = "__temp__"
89        if isinstance(clip,ImageClip):
90            filename = TEMP_PREFIX+".png"
91            kwargs = {'filename':filename, 'withmask':True}
92            kwargs.update(rd_kwargs)
93            clip.save_frame(**kwargs)
94        elif isinstance(clip,VideoClip):
95            filename = TEMP_PREFIX+".mp4"
96            kwargs = {'filename':filename, 'verbose':False, 'preset':'ultrafast'}
97            kwargs.update(rd_kwargs)
98            clip.write_videofile(**kwargs)
99        elif isinstance(clip,AudioClip):
100            filename = TEMP_PREFIX+".mp3"
101            kwargs = {'filename': filename, 'verbose':False}
102            kwargs.update(rd_kwargs)
103            clip.write_audiofile(**kwargs)
104        else:
105          raise ValueError("Unknown class for the clip. Cannot embed and preview.")
106
107        return html_embed(filename, maxduration=maxduration, rd_kwargs=rd_kwargs,
108                           center=center, **html_kwargs)
109
110    filename = clip
111    options = " ".join(["%s='%s'"%(str(k), str(v)) for k,v in html_kwargs.items()])
112    name, ext = os.path.splitext(filename)
113    ext = ext[1:]
114
115    if filetype is None:
116        ext = filename.split('.')[-1].lower()
117        if ext == "gif":
118            filetype = 'image'
119        elif ext in extensions_dict:
120            filetype = extensions_dict[ext]['type']
121        else:
122            raise ValueError("No file type is known for the provided file. Please provide "
123                             "argument `filetype` (one of 'image', 'video', 'sound') to the "
124                             "ipython display function.")
125
126
127    if filetype== 'video':
128        # The next lines set the HTML5-cvompatible extension and check that the
129        # extension is HTML5-valid
130        exts_htmltype = {'mp4': 'mp4', 'webm':'webm', 'ogv':'ogg'}
131        allowed_exts = " ".join(exts_htmltype.keys())
132        try:
133            ext = exts_htmltype[ext]
134        except:
135            raise ValueError("This video extension cannot be displayed in the "
136                   "IPython Notebook. Allowed extensions: "+allowed_exts)
137
138    if filetype in ['audio', 'video']:
139
140        duration = ffmpeg_parse_infos(filename)['duration']
141        if duration > maxduration:
142            raise ValueError("The duration of video %s (%.1f) exceeds the 'maxduration' "%(filename, duration)+
143                             "attribute. You can increase 'maxduration', by passing 'maxduration' parameter"
144                             "to ipython_display function."
145                             "But note that embedding large videos may take all the memory away !")
146
147    with open(filename, "rb") as f:
148        data= b64encode(f.read()).decode("utf-8")
149
150    template = templates[filetype]
151
152    result = template%{'data':data, 'options':options, 'ext':ext}
153    if center:
154        result = r"<div align=middle>%s</div>"%result
155
156    return result
157
158
159def ipython_display(clip, filetype=None, maxduration=60, t=None, fps=None,
160                    rd_kwargs=None, center=True, **html_kwargs):
161    """
162    clip
163      Either the name of a file, or a clip to preview. The clip will
164      actually be written to a file and embedded as if a filename was
165      provided.
166
167    filetype:
168      One of 'video','image','audio'. If None is given, it is determined
169      based on the extension of ``filename``, but this can bug.
170
171    maxduration
172      An error will be raised if the clip's duration is more than the indicated
173      value (in seconds), to avoid spoiling the  browser's cache and the RAM.
174
175    t
176      If not None, only the frame at time t will be displayed in the notebook,
177      instead of a video of the clip
178
179    fps
180      Enables to specify an fps, as required for clips whose fps is unknown.
181
182    **kwargs:
183      Allow you to give some options, like width=260, etc. When editing
184      looping gifs, a good choice is loop=1, autoplay=1.
185
186    Remarks: If your browser doesn't support HTML5, this should warn you.
187    If nothing is displayed, maybe your file or filename is wrong.
188    Important: The media will be physically embedded in the notebook.
189
190    Examples
191    =========
192
193    >>> import moviepy.editor as mpy
194    >>> # later ...
195    >>> clip.write_videofile("test.mp4")
196    >>> mpy.ipython_display("test.mp4", width=360)
197
198    >>> clip.audio.write_audiofile('test.ogg') # Sound !
199    >>> mpy.ipython_display('test.ogg')
200
201    >>> clip.write_gif("test.gif")
202    >>> mpy.ipython_display('test.gif')
203
204    >>> clip.save_frame("first_frame.jpeg")
205    >>> mpy.ipython_display("first_frame.jpeg")
206    """
207
208    if not ipython_available:
209        raise ImportError("Only works inside an IPython Notebook")
210
211    if rd_kwargs is None:
212        rd_kwargs = {}
213
214    if fps is not None:
215        rd_kwargs['fps'] = fps
216
217    if t is not None:
218        clip = clip.to_ImageClip(t)
219
220    return HTML2(html_embed(clip, filetype=filetype, maxduration=maxduration,
221                center=center, rd_kwargs=rd_kwargs, **html_kwargs))
222