s/ffmpegcolorspace/videoconvert/ in a few places
[platform/upstream/gstreamer.git] / sdk-playback-tutorial-subtitle-management.md
1 # Playback tutorial 2: Subtitle management
2
3 ## Goal
4
5 This tutorial is very similar to the previous one, but instead of
6 switching among different audio streams, we will use subtitle streams.
7 This will allow us to learn:
8
9   - How to choose the subtitle stream
10
11   - How to add external subtitles
12
13   - How to customize the font used for the subtitles
14
15 ## Introduction
16
17 We already know (from the previous tutorial) that container files can
18 hold multiple audio and video streams, and that we can very easily
19 choose among them by changing the `current-audio` or
20 `current-video` `playbin` property. Switching subtitles is just as
21 easy.
22
23 It is worth noting that, just like it happens with audio and video,
24 `playbin` takes care of choosing the right decoder for the subtitles,
25 and that the plugin structure of GStreamer allows adding support for new
26 formats as easily as copying a file. Everything is invisible to the
27 application developer.
28
29 Besides subtitles embedded in the container, `playbin` offers the
30 possibility to add an extra subtitle stream from an external URI.
31
32 This tutorial opens a file which already contains 5 subtitle streams,
33 and adds another one from another file (for the Greek language).
34
35 ## The multilingual player with subtitles
36
37 Copy this code into a text file named `playback-tutorial-2.c` (or find
38 it in the GStreamer installation).
39
40 **playback-tutorial-2.c**
41
42 ``` c
43 #include <stdio.h>
44 #include <gst/gst.h>
45
46 /* Structure to contain all our information, so we can pass it around */
47 typedef struct _CustomData {
48   GstElement *playbin;  /* Our one and only element */
49
50   gint n_video;          /* Number of embedded video streams */
51   gint n_audio;          /* Number of embedded audio streams */
52   gint n_text;           /* Number of embedded subtitle streams */
53
54   gint current_video;    /* Currently playing video stream */
55   gint current_audio;    /* Currently playing audio stream */
56   gint current_text;     /* Currently playing subtitle stream */
57
58   GMainLoop *main_loop;  /* GLib's Main Loop */
59 } CustomData;
60
61 /* playbin flags */
62 typedef enum {
63   GST_PLAY_FLAG_VIDEO         = (1 << 0), /* We want video output */
64   GST_PLAY_FLAG_AUDIO         = (1 << 1), /* We want audio output */
65   GST_PLAY_FLAG_TEXT          = (1 << 2)  /* We want subtitle output */
66 } GstPlayFlags;
67
68 /* Forward definition for the message and keyboard processing functions */
69 static gboolean handle_message (GstBus *bus, GstMessage *msg, CustomData *data);
70 static gboolean handle_keyboard (GIOChannel *source, GIOCondition cond, CustomData *data);
71
72 int main(int argc, char *argv[]) {
73   CustomData data;
74   GstBus *bus;
75   GstStateChangeReturn ret;
76   gint flags;
77   GIOChannel *io_stdin;
78
79   /* Initialize GStreamer */
80   gst_init (&argc, &argv);
81
82   /* Create the elements */
83   data.playbin = gst_element_factory_make ("playbin", "playbin");
84
85   if (!data.playbin) {
86     g_printerr ("Not all elements could be created.\n");
87     return -1;
88   }
89
90   /* Set the URI to play */
91   g_object_set (data.playbin, "uri", "http://docs.gstreamer.com/media/sintel_trailer-480p.ogv", NULL);
92
93   /* Set the subtitle URI to play and some font description */
94   g_object_set (data.playbin, "suburi", "http://docs.gstreamer.com/media/sintel_trailer_gr.srt", NULL);
95   g_object_set (data.playbin, "subtitle-font-desc", "Sans, 18", NULL);
96
97   /* Set flags to show Audio, Video and Subtitles */
98   g_object_get (data.playbin, "flags", &flags, NULL);
99   flags |= GST_PLAY_FLAG_VIDEO | GST_PLAY_FLAG_AUDIO | GST_PLAY_FLAG_TEXT;
100   g_object_set (data.playbin, "flags", flags, NULL);
101
102   /* Add a bus watch, so we get notified when a message arrives */
103   bus = gst_element_get_bus (data.playbin);
104   gst_bus_add_watch (bus, (GstBusFunc)handle_message, &data);
105
106   /* Add a keyboard watch so we get notified of keystrokes */
107 #ifdef G_OS_WIN32
108   io_stdin = g_io_channel_win32_new_fd (fileno (stdin));
109 #else
110   io_stdin = g_io_channel_unix_new (fileno (stdin));
111 #endif
112   g_io_add_watch (io_stdin, G_IO_IN, (GIOFunc)handle_keyboard, &data);
113
114   /* Start playing */
115   ret = gst_element_set_state (data.playbin, GST_STATE_PLAYING);
116   if (ret == GST_STATE_CHANGE_FAILURE) {
117     g_printerr ("Unable to set the pipeline to the playing state.\n");
118     gst_object_unref (data.playbin);
119     return -1;
120   }
121
122   /* Create a GLib Main Loop and set it to run */
123   data.main_loop = g_main_loop_new (NULL, FALSE);
124   g_main_loop_run (data.main_loop);
125
126   /* Free resources */
127   g_main_loop_unref (data.main_loop);
128   g_io_channel_unref (io_stdin);
129   gst_object_unref (bus);
130   gst_element_set_state (data.playbin, GST_STATE_NULL);
131   gst_object_unref (data.playbin);
132   return 0;
133 }
134
135 /* Extract some metadata from the streams and print it on the screen */
136 static void analyze_streams (CustomData *data) {
137   gint i;
138   GstTagList *tags;
139   gchar *str;
140   guint rate;
141
142   /* Read some properties */
143   g_object_get (data->playbin, "n-video", &data->n_video, NULL);
144   g_object_get (data->playbin, "n-audio", &data->n_audio, NULL);
145   g_object_get (data->playbin, "n-text", &data->n_text, NULL);
146
147   g_print ("%d video stream(s), %d audio stream(s), %d text stream(s)\n",
148     data->n_video, data->n_audio, data->n_text);
149
150   g_print ("\n");
151   for (i = 0; i < data->n_video; i++) {
152     tags = NULL;
153     /* Retrieve the stream's video tags */
154     g_signal_emit_by_name (data->playbin, "get-video-tags", i, &tags);
155     if (tags) {
156       g_print ("video stream %d:\n", i);
157       gst_tag_list_get_string (tags, GST_TAG_VIDEO_CODEC, &str);
158       g_print ("  codec: %s\n", str ? str : "unknown");
159       g_free (str);
160       gst_tag_list_free (tags);
161     }
162   }
163
164   g_print ("\n");
165   for (i = 0; i < data->n_audio; i++) {
166     tags = NULL;
167     /* Retrieve the stream's audio tags */
168     g_signal_emit_by_name (data->playbin, "get-audio-tags", i, &tags);
169     if (tags) {
170       g_print ("audio stream %d:\n", i);
171       if (gst_tag_list_get_string (tags, GST_TAG_AUDIO_CODEC, &str)) {
172         g_print ("  codec: %s\n", str);
173         g_free (str);
174       }
175       if (gst_tag_list_get_string (tags, GST_TAG_LANGUAGE_CODE, &str)) {
176         g_print ("  language: %s\n", str);
177         g_free (str);
178       }
179       if (gst_tag_list_get_uint (tags, GST_TAG_BITRATE, &rate)) {
180         g_print ("  bitrate: %d\n", rate);
181       }
182       gst_tag_list_free (tags);
183     }
184   }
185
186   g_print ("\n");
187   for (i = 0; i < data->n_text; i++) {
188     tags = NULL;
189     /* Retrieve the stream's subtitle tags */
190     g_print ("subtitle stream %d:\n", i);
191     g_signal_emit_by_name (data->playbin, "get-text-tags", i, &tags);
192     if (tags) {
193       if (gst_tag_list_get_string (tags, GST_TAG_LANGUAGE_CODE, &str)) {
194         g_print ("  language: %s\n", str);
195         g_free (str);
196       }
197       gst_tag_list_free (tags);
198     } else {
199       g_print ("  no tags found\n");
200     }
201   }
202
203   g_object_get (data->playbin, "current-video", &data->current_video, NULL);
204   g_object_get (data->playbin, "current-audio", &data->current_audio, NULL);
205   g_object_get (data->playbin, "current-text", &data->current_text, NULL);
206
207   g_print ("\n");
208   g_print ("Currently playing video stream %d, audio stream %d and subtitle stream %d\n",
209       data->current_video, data->current_audio, data->current_text);
210   g_print ("Type any number and hit ENTER to select a different subtitle stream\n");
211 }
212
213 /* Process messages from GStreamer */
214 static gboolean handle_message (GstBus *bus, GstMessage *msg, CustomData *data) {
215   GError *err;
216   gchar *debug_info;
217
218   switch (GST_MESSAGE_TYPE (msg)) {
219     case GST_MESSAGE_ERROR:
220       gst_message_parse_error (msg, &err, &debug_info);
221       g_printerr ("Error received from element %s: %s\n", GST_OBJECT_NAME (msg->src), err->message);
222       g_printerr ("Debugging information: %s\n", debug_info ? debug_info : "none");
223       g_clear_error (&err);
224       g_free (debug_info);
225       g_main_loop_quit (data->main_loop);
226       break;
227     case GST_MESSAGE_EOS:
228       g_print ("End-Of-Stream reached.\n");
229       g_main_loop_quit (data->main_loop);
230       break;
231     case GST_MESSAGE_STATE_CHANGED: {
232       GstState old_state, new_state, pending_state;
233       gst_message_parse_state_changed (msg, &old_state, &new_state, &pending_state);
234       if (GST_MESSAGE_SRC (msg) == GST_OBJECT (data->playbin)) {
235         if (new_state == GST_STATE_PLAYING) {
236           /* Once we are in the playing state, analyze the streams */
237           analyze_streams (data);
238         }
239       }
240     } break;
241   }
242
243   /* We want to keep receiving messages */
244   return TRUE;
245 }
246
247 /* Process keyboard input */
248 static gboolean handle_keyboard (GIOChannel *source, GIOCondition cond, CustomData *data) {
249   gchar *str = NULL;
250
251   if (g_io_channel_read_line (source, &str, NULL, NULL, NULL) == G_IO_STATUS_NORMAL) {
252     int index = atoi (str);
253     if (index < 0 || index >= data->n_text) {
254       g_printerr ("Index out of bounds\n");
255     } else {
256       /* If the input was a valid subtitle stream index, set the current subtitle stream */
257       g_print ("Setting current subtitle stream to %d\n", index);
258       g_object_set (data->playbin, "current-text", index, NULL);
259     }
260   }
261   g_free (str);
262   return TRUE;
263 }
264 ```
265
266
267 > ![information] Need help?
268 >
269 > If you need help to compile this code, refer to the **Building the
270 > tutorials** section for your platform: [Linux], [Mac OS X] or
271 > [Windows], or use this specific command on Linux:
272 >
273 > `` gcc playback-tutorial-2.c -o playback-tutorial-2 `pkg-config --cflags --libs gstreamer-1.0` ``
274 >
275 > If you need help to run this code, refer to the **Running the
276 > tutorials** section for your platform: [Linux][1], [Mac OS X][2] or
277 > [Windows][3].
278 >
279 > This tutorial opens a window and displays a movie, with accompanying
280 > audio. The media is fetched from the Internet, so the window might
281 > take a few seconds to appear, depending on your connection
282 > speed. The number of subtitle streams is shown in the terminal, and
283 > the user can switch from one to another by entering a number and
284 > pressing enter. A small delay is to be
285 > expected. _Please read the note at the bottom of this
286 > page._ Bear in mind that
287 > there is no latency management (buffering), so on slow connections,
288 > the movie might stop after a few seconds. See how
289 > [](sdk-basic-tutorial-streaming.md) solves this issue.
290 >
291 > Required libraries: `gstreamer-1.0`
292
293 ## Walkthrough
294
295 This tutorial is copied from
296 [](sdk-playback-tutorial-playbin-usage.md) with some changes, so let's
297 review only the changes.
298
299 ``` c
300 /* Set the subtitle URI to play and some font description */
301 g_object_set (data.playbin, "suburi", "http://docs.gstreamer.com/media/sintel_trailer_gr.srt", NULL);
302 g_object_set (data.playbin, "subtitle-font-desc", "Sans, 18", NULL);
303 ```
304
305 After setting the media URI, we set the `suburi` property, which points
306 `playbin` to a file containing a subtitle stream. In this case, the
307 media file already contains multiple subtitle streams, so the one
308 provided in the `suburi` is added to the list, and will be the currently
309 selected one.
310
311 Note that metadata concerning a subtitle stream (like its language)
312 resides in the container file, therefore, subtitles not embedded in a
313 container will not have metadata. When running this tutorial you will
314 find that the first subtitle stream does not have a language tag.
315
316 The `subtitle-font-desc` property allows specifying the font to render
317 the subtitles. Since [Pango](http://www.pango.org/) is the library used
318 to render fonts, you can check its documentation to see how this font
319 should be specified, in particular, the
320 [pango-font-description-from-string](http://developer.gnome.org/pango/stable/pango-Fonts.html#pango-font-description-from-string) function.
321
322 In a nutshell, the format of the string representation is `[FAMILY-LIST]
323 [STYLE-OPTIONS] [SIZE]` where `FAMILY-LIST` is a comma separated list of
324 families optionally terminated by a comma, `STYLE_OPTIONS` is a
325 whitespace separated list of words where each word describes one of
326 style, variant, weight, or stretch, and `SIZE` is an decimal number
327 (size in points). For example the following are all valid string
328 representations:
329
330   - sans bold 12
331   - serif, monospace bold italic condensed 16
332   - normal 10
333
334 The commonly available font families are: Normal, Sans, Serif and
335 Monospace.
336
337 The available styles are: Normal (the font is upright), Oblique (the
338 font is slanted, but in a roman style), Italic (the font is slanted in
339 an italic style).
340
341 The available weights are: Ultra-Light, Light, Normal, Bold, Ultra-Bold,
342 Heavy.
343
344 The available variants are: Normal, Small\_Caps (A font with the lower
345 case characters replaced by smaller variants of the capital characters)
346
347 The available stretch styles
348 are: Ultra-Condensed, Extra-Condensed, Condensed, Semi-Condensed, Normal, Semi-Expanded, Expanded,
349 Extra-Expanded, Ultra-Expanded
350
351  
352
353 ``` c
354 /* Set flags to show Audio, Video and Subtitles */
355 g_object_get (data.playbin, "flags", &flags, NULL);
356 flags |= GST_PLAY_FLAG_VIDEO | GST_PLAY_FLAG_AUDIO | GST_PLAY_FLAG_TEXT;
357 g_object_set (data.playbin, "flags", flags, NULL);
358 ```
359
360 We set the `flags` property to allow Audio, Video and Text (Subtitles).
361
362 The rest of the tutorial is the same as [](sdk-playback-tutorial-playbin-usage.md), except
363 that the keyboard input changes the `current-text` property instead of
364 the `current-audio`. As before, keep in mind that stream changes are not
365 immediate, since there is a lot of information flowing through the
366 pipeline that needs to reach the end of it before the new stream shows
367 up.
368
369 ## Conclusion
370
371 This tutorial showed how to handle subtitles from `playbin`, whether
372 they are embedded in the container or in a different file:
373
374   - Subtitles are chosen using the `current-tex`t and `n-tex`t
375     properties of `playbin`.
376
377   - External subtitle files can be selected using the `suburi` property.
378
379   - Subtitle appearance can be customized with the
380     `subtitle-font-desc` property.
381
382 The next playback tutorial shows how to change the playback speed.
383
384 Remember that attached to this page you should find the complete source
385 code of the tutorial and any accessory files needed to build it.
386 It has been a pleasure having you here, and see you soon\!
387
388   [information]: images/icons/emoticons/information.png