9 min read

In this article by Roberto Ulloa, author of the book Kivy – Interactive Applications and Games in Python Second Edition, we will learn how to use the progression of a video to display subtitles at the right moment.

(For more resources related to this topic, see here.)

Let’s add subtitles to our application. We will do this in four simple steps:

  1. Create a Subtitle widget (subtitle.kv) derived from the Label class that will display the subtitles
  2. Place a Subtitle instance (video.kv) on top of the video widget
  3. Create a Subtitles class (subtitles.py) that will read and parse a subtitle file
  4. Track the Video progression (video.py) to display the corresponding subtitle

The Step 1 involves the creation of a new widget in the subtitle.kv file:

1. # File name: subtitle.kv
2. <Subtitle@Label>:
3.     halign: 'center'
4.     font_size: '20px'
5.     size: self.texture_size[0] + 20, self.texture_size[1] + 20
6.     y: 50
7.     bcolor: .1, .1, .1, 0
8.     canvas.before:
9.         Color:
10.            rgba: self.bcolor
11.         Rectangle:
12.             pos: self.pos
13.             size: self.size

There are two interesting elements in this code. The first one is the definition of the size property (line 4). We define it as 20 pixels bigger than the texture_size width and height. The texture_size property indicates the size of the text determined by the font size and text, and we use it to adjust the Subtitles widget size to its content.

The texture_size is a read-only property because its value is calculated and dependent on other parameters, such as font size and height for text display. This means that we will read from this property but not write on it.

The second element is the creation of the bcolor property (line 7) to store a background color, and how the rgba color of the rectangle has been bound to it (line 10). The Label widget (like many other widgets) doesn’t have a background color, and creating a rectangle is the usual way to create such features. We add the bcolor property in order to change the color of the rectangle from outside the instance.

We cannot directly modify parameters of the vertex instructions; however, we can create properties that control parameters inside the vertex instructions.

Let’s move on to Step 2 mentioned earlier. We need to add a Subtitle instance to our current Video widget in the video.kv file:

14. # File name: video.kv
15. ...
16. #:set _default_surl 
     "http://www.ted.com/talks/subtitles/id/97/lang/en" 18. <Video>: 19.     surl: _default_surl 20.     slabel: _slabel 21.     ... 23.     Subtitle: 24.         id: _slabel 25.         x: (root.width - self.width)/2

We added another constant variable called _default_surl (line 16), which contains the link to the URL with the corresponding subtitle TED video file. We set this value to the surl property (line 19), which we just created to store the subtitles’ URL. We added the slabel property (line 20), that references the Subtitle instance through its ID (line 24). Then we made sure that the subtitle is centered (line 25).

In order to start Step 3 (parse the subtitle file), we need to take a look at the format of the TED subtitles:

26. {
27.     "captions": [{
28.         "duration":1976,
29.         "content": "When you have 21 minutes to speak,",
30.         "startOfParagraph":true,
31.         "startTime":0,
32.     }, ...

TED uses a very simple JSON format (https://en.wikipedia.org/wiki/JSON) with a list of captions. Each caption contains four keys but we will only use duration, content, and startTime. We need to parse this file, and luckily Kivy provides a UrlRequest class (line 34) that will do most of the work for us. Here is the code for subtitles.py that creates the Subtitles class:

33. # File name: subtitles.py
34. from kivy.network.urlrequest import UrlRequest
36. class Subtitles:
38.     def __init__(self, url):
39.         self.subtitles = []
40.         req = UrlRequest(url, self.got_subtitles)
42.     def got_subtitles(self, req, results):
43.         self.subtitles = results['captions']
45.     def next(self, secs):
46.         for sub in self.subtitles:
47.             ms = secs*1000 - 12000
48.             st = 'startTime'
49.             d = 'duration'
50.             if ms >= sub[st] and ms <= sub[st] + sub[d]:
51.                 return sub
52.         return None

The constructor of the Subtitles class will receive a URL (line 38) as a parameter. Then, it will make the petition to instantiate the UrlRequest class (line 40). The first parameter of the class instantiation is the URL of the petition, and the second is the method that is called when the result of the petition is returned (downloaded). Once the request returns the result, the method got_subtitles is called(line 42). The UrlRequest extracts the JSON and places it in the second parameter of got_subtitles. All we had to do is put the captions in a class attribute, which we called subtitles (line 43).

The next method (line 45) receives the seconds (secs) as a parameter and will traverse the loaded JSON dictionary in order to search for the corresponding subtitle that belongs to that time. As soon as it finds one, the method returns it. We subtracted 12000 microseconds (line 47, ms = secs*1000 – 12000) because the TED videos have an introduction of approximately 12 seconds before the talk starts.

Everything is ready for Step 4, in which we put the pieces together in order to see the subtitles working. Here are the modifications to the header of the video.py file:

53. # File name: video.py
54. ...
55. from kivy.properties import StringProperty
56. ...
57. from kivy.lang import Builder
59. Builder.load_file('subtitle.kv')
61. class Video(KivyVideo):
62.     image = ObjectProperty(None)
63.     surl = StringProperty(None)

We imported StringProperty and added the corresponding property (line 55). We will use this property by the end of this chapter when we we can switch TED talks from the GUI. For now, we will just use _default_surl defined in video.kv (line 63). We also loaded the subtitle.kv file (line 59). Now, let’s analyze the rest of the changes to the video.py file:

64.     ...
65.     def on_source(self, instance, value):
66.         self.color = (0,0,0,0)
67.         self.subs = Subtitles(name, self.surl)
68.         self.sub = None
70.     def on_position(self, instance, value):
71.         next = self.subs.next(value)
72.         if next is None:
73.             self.clear_subtitle()
74.         else:
75.             sub = self.sub
76.             st = 'startTime'
77.             if sub is None or sub[st] != next[st]:
78.                 self.display_subtitle(next)
80.     def clear_subtitle(self):
81.         if self.slabel.text != "":
82.             self.sub = None
83.             self.slabel.text = ""
84.             self.slabel.bcolor = (0.1, 0.1, 0.1, 0)
86.     def display_subtitle(self, sub):
87.         self.sub = sub
88.         self.slabel.text = sub['content']
89.         self.slabel.bcolor = (0.1, 0.1, 0.1, .8)
90. (...)

We introduced a few code lines to the on_source method in order to initialize the subtitles attribute with a Subtitles instance (line 67) using the surl property and initialize the sub attribute that contains the currently displayed subtitle (line 68), if any.

Now, let’s study how we keep track of the progression to display the corresponding subtitle. When the video plays inside the Video widget, the on_position event is triggered every second. Therefore, we implemented the logic to display the subtitles in the on_position method (lines 70 to 78). Each time the on_position method is called (each second), we ask the Subtitles instance (line 71) for the next subtitle. If nothing is returned, we clear the subtitle with the clear_subtitle method (line 73). If there is already a subtitle in the current second (line 74), then we make sure that there is no subtitle being displayed, or that the returned subtitle is not the one that we already display (line 164). If the conditions are met, we display the subtitle using the display_subtitle method (line 78).

Notice that the clear_subtitle (lines 80 to 84) and display_subtitle (lines 86 to 89) methods use the bcolor property in order to hide the subtitle. This is another trick to make a widget invisible without removing it from its parent. Let’s take a look at the current result of our videos and subtitles in the following screenshot:

Kivy Interactive Applications in Python, Second Edition

Summary

In this article, we discussed how to control a video and how to associate the subtitles element of the screen with it. We also discussed how the Video widget incorporates synchronization of subtitles that we receive in a JSON format file with the progression of the video and a responsive control bar. We learned how to control its progression and add subtitles to it.

Resources for Article:


Further resources on this subject:


LEAVE A REPLY

Please enter your comment!
Please enter your name here