1""" Experimental module for subtitles support. """
2
3import re
4
5import numpy as np
6
7from moviepy.tools import cvsecs
8from moviepy.video.VideoClip import TextClip, VideoClip
9
10
11class SubtitlesClip(VideoClip):
12    """ A Clip that serves as "subtitle track" in videos.
13
14    One particularity of this class is that the images of the
15    subtitle texts are not generated beforehand, but only if
16    needed.
17
18    Parameters
19    ==========
20
21    subtitles
22      Either the name of a file, or a list
23
24    Examples
25    =========
26
27    >>> from moviepy.video.tools.subtitles import SubtitlesClip
28    >>> from moviepy.video.io.VideoFileClip import VideoFileClip
29    >>> generator = lambda txt: TextClip(txt, font='Georgia-Regular', fontsize=24, color='white')
30    >>> sub = SubtitlesClip("subtitles.srt", generator)
31    >>> myvideo = VideoFileClip("myvideo.avi")
32    >>> final = CompositeVideoClip([clip, subtitles])
33    >>> final.write_videofile("final.mp4", fps=myvideo.fps)
34
35    """
36
37    def __init__(self, subtitles, make_textclip=None):
38
39        VideoClip.__init__(self, has_constant_size=False)
40
41        if isinstance(subtitles, str):
42            subtitles = file_to_subtitles(subtitles)
43
44        #subtitles = [(map(cvsecs, tt),txt) for tt, txt in subtitles]
45        self.subtitles = subtitles
46        self.textclips = dict()
47
48        if make_textclip is None:
49            make_textclip = lambda txt: TextClip(txt, font='Georgia-Bold',
50                                        fontsize=24, color='white',
51                                        stroke_color='black', stroke_width=0.5)
52
53        self.make_textclip = make_textclip
54        self.start=0
55        self.duration = max([tb for ((ta,tb), txt) in self.subtitles])
56        self.end=self.duration
57
58        def add_textclip_if_none(t):
59            """ Will generate a textclip if it hasn't been generated asked
60            to generate it yet. If there is no subtitle to show at t, return
61            false. """
62            sub =[((ta,tb),txt) for ((ta,tb),txt) in self.textclips.keys()
63                   if (ta<=t<tb)]
64            if not sub:
65                sub = [((ta,tb),txt) for ((ta,tb),txt) in self.subtitles if
66                       (ta<=t<tb)]
67                if not sub:
68                    return False
69            sub = sub[0]
70            if sub not in self.textclips.keys():
71                self.textclips[sub] = self.make_textclip(sub[1])
72
73            return sub
74
75        def make_frame(t):
76            sub = add_textclip_if_none(t)
77            return (self.textclips[sub].get_frame(t) if sub
78                    else np.array([[[0,0,0]]]))
79
80        def make_mask_frame(t):
81            sub = add_textclip_if_none(t)
82            return (self.textclips[sub].mask.get_frame(t) if sub
83                    else np.array([[0]]))
84
85        self.make_frame = make_frame
86        hasmask = bool(self.make_textclip('T').mask)
87        self.mask = VideoClip(make_mask_frame, ismask=True) if hasmask else None
88
89    def in_subclip(self, t_start= None, t_end= None):
90        """ Returns a sequence of [(t1,t2), txt] covering all the given subclip
91        from t_start to t_end. The first and last times will be cropped so as
92        to be exactly t_start and t_end if possible. """
93
94        def is_in_subclip(t1,t2):
95            try:
96                return (t_start<=t1<t_end) or (t_start< t2 <=t_end)
97            except:
98                return False
99        def try_cropping(t1,t2):
100            try:
101                return (max(t1, t_start), min(t2, t_end))
102            except:
103                return (t1, t2)
104        return [(try_cropping(t1,t2), txt) for ((t1,t2), txt) in self.subtitles
105                                               if is_in_subclip(t1,t2)]
106
107
108
109    def __iter__(self):
110        return iter(self.subtitles)
111
112
113
114    def __getitem__(self, k):
115        return self.subtitles[k]
116
117
118
119    def __str__(self):
120
121        def to_srt(sub_element):
122            (ta, tb), txt = sub_element
123            fta = cvsecs(ta)
124            ftb = cvsecs(tb)
125            return "%s - %s\n%s"%(fta, ftb, txt)
126
127        return "\n\n".join(to_srt(s) for s in self.subtitles)
128
129
130
131    def match_expr(self, expr):
132
133        return SubtitlesClip([e for e in self.subtitles
134                              if re.findall(expr, e[1]) != []])
135
136
137    def write_srt(self, filename):
138        with open(filename, 'w+') as f:
139            f.write(str(self))
140
141
142def file_to_subtitles(filename):
143    """ Converts a srt file into subtitles.
144
145    The returned list is of the form ``[((ta,tb),'some text'),...]``
146    and can be fed to SubtitlesClip.
147
148    Only works for '.srt' format for the moment.
149    """
150    times_texts = []
151    current_times = None
152    current_text = ""
153    with open(filename,'r') as f:
154        for line in f:
155            times = re.findall("([0-9]*:[0-9]*:[0-9]*,[0-9]*)", line)
156            if times:
157                current_times = [cvsecs(t) for t in times]
158            elif line.strip() == '':
159                times_texts.append((current_times, current_text.strip('\n')))
160                current_times, current_text = None, ""
161            elif current_times:
162                current_text += line
163    return times_texts
164