1 /*
2 * GStreamer
3 * Copyright (C) 2006 James Livingston <doclivingston@gmail.com>
4 * Copyright (C) 2008 Vincent Penquerc'h <ogg.k.ogg.k@googlemail.com>
5 *
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Library General Public
8 * License as published by the Free Software Foundation; either
9 * version 2 of the License, or (at your option) any later version.
10 *
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Library General Public License for more details.
15 *
16 * You should have received a copy of the GNU Library General Public
17 * License along with this library; if not, write to the
18 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
19 * Boston, MA 02110-1301, USA.
20 */
21
22 /**
23 * SECTION:element-katetag
24 * @title: katetag
25 * @see_also: #oggdemux, #oggmux, #kateparse, #GstTagSetter
26 * @short_description: retags kate streams
27 *
28 * The katetag element can change the tag contained within a raw
29 * kate stream. Specifically, it modifies the comments header packet
30 * of the kate stream, as well as the language and category of the
31 * kate stream.
32 *
33 * The element will also process the stream as the #kateparse element does
34 * so it can be used when remuxing an Ogg Kate stream, without additional
35 * elements.
36 *
37 * Applications can set the tags to write using the #GstTagSetter interface.
38 * Tags contained within the kate stream will be picked up
39 * automatically (and merged according to the merge mode set via the tag
40 * setter interface).
41 *
42 * ## Example pipelines
43 *
44 * This element is only useful with gst-launch-1.0 for modifying the language
45 * and/or category (which are properties of the stream located in the kate
46 * beginning of stream header), because it does not support setting the tags
47 * on a #GstTagSetter interface. Conceptually, the element will usually be
48 * used like:
49 * |[
50 * gst-launch-1.0 -v filesrc location=foo.ogg ! oggdemux ! katetag ! oggmux ! filesink location=bar.ogg
51 * ]|
52 *
53 * This pipeline will set the language and category of the stream to the
54 * given values:
55 * |[
56 * gst-launch-1.0 -v filesrc location=foo.ogg ! oggdemux ! katetag language=pt_BR category=subtitles ! oggmux ! filesink location=bar.ogg
57 * ]|
58 *
59 */
60
61 #ifdef HAVE_CONFIG_H
62 # include "config.h"
63 #endif
64
65 #include <string.h>
66 #include <glib.h>
67 #include <gst/tag/tag.h>
68 #include <gst/gsttagsetter.h>
69
70 #include <kate/kate.h>
71
72 #include "gstkatetag.h"
73
74
75 GST_DEBUG_CATEGORY_EXTERN (gst_katetag_debug);
76 #define GST_CAT_DEFAULT gst_katetag_debug
77
78 enum
79 {
80 ARG_0,
81 ARG_LANGUAGE,
82 ARG_CATEGORY,
83 ARG_ORIGINAL_CANVAS_WIDTH,
84 ARG_ORIGINAL_CANVAS_HEIGHT,
85 };
86
87 static GstFlowReturn gst_kate_tag_parse_packet (GstKateParse * parse,
88 GstBuffer * buffer);
89 static void gst_kate_tag_set_property (GObject * object, guint prop_id,
90 const GValue * value, GParamSpec * pspec);
91 static void gst_kate_tag_get_property (GObject * object, guint prop_id,
92 GValue * value, GParamSpec * pspec);
93 static void gst_kate_tag_dispose (GObject * object);
94
95 #define gst_kate_tag_parent_class parent_class
96 G_DEFINE_TYPE_WITH_CODE (GstKateTag, gst_kate_tag, GST_TYPE_KATE_PARSE,
97 G_IMPLEMENT_INTERFACE (GST_TYPE_TAG_SETTER, NULL));
98
99 static void
gst_kate_tag_class_init(GstKateTagClass * klass)100 gst_kate_tag_class_init (GstKateTagClass * klass)
101 {
102 GObjectClass *gobject_class;
103 GstElementClass *gstelement_class;
104 GstKateParseClass *gstkateparse_class;
105
106 gobject_class = (GObjectClass *) klass;
107 gstelement_class = (GstElementClass *) klass;
108 gstkateparse_class = GST_KATE_PARSE_CLASS (klass);
109
110 gobject_class->set_property = GST_DEBUG_FUNCPTR (gst_kate_tag_set_property);
111 gobject_class->get_property = GST_DEBUG_FUNCPTR (gst_kate_tag_get_property);
112 gobject_class->dispose = GST_DEBUG_FUNCPTR (gst_kate_tag_dispose);
113
114 g_object_class_install_property (gobject_class, ARG_LANGUAGE,
115 g_param_spec_string ("language", "Language",
116 "Set the language of the stream", "",
117 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
118
119 g_object_class_install_property (gobject_class, ARG_CATEGORY,
120 g_param_spec_string ("category", "Category",
121 "Set the category of the stream", "",
122 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
123
124 g_object_class_install_property (gobject_class, ARG_ORIGINAL_CANVAS_WIDTH,
125 g_param_spec_int ("original-canvas-width", "Original canvas width",
126 "Set the width of the canvas this stream was authored for (0 is unspecified)",
127 0, G_MAXINT, 0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
128
129 g_object_class_install_property (gobject_class, ARG_ORIGINAL_CANVAS_HEIGHT,
130 g_param_spec_int ("original-canvas-height", "Original canvas height",
131 "Set the height of the canvas this stream was authored for (0 is unspecified)",
132 0, G_MAXINT, 0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
133
134 gst_element_class_set_static_metadata (gstelement_class, "Kate stream tagger",
135 "Formatter/Metadata",
136 "Retags kate streams", "Vincent Penquerc'h <ogg.k.ogg.k@googlemail.com>");
137
138 gstkateparse_class->parse_packet =
139 GST_DEBUG_FUNCPTR (gst_kate_tag_parse_packet);
140 }
141
142 static void
gst_kate_tag_init(GstKateTag * kt)143 gst_kate_tag_init (GstKateTag * kt)
144 {
145 kt->language = NULL;
146 kt->category = NULL;
147 kt->original_canvas_width = -1;
148 kt->original_canvas_height = -1;
149 }
150
151 static void
gst_kate_tag_dispose(GObject * object)152 gst_kate_tag_dispose (GObject * object)
153 {
154 GstKateTag *kt = GST_KATE_TAG (object);
155
156 GST_LOG_OBJECT (kt, "disposing");
157
158 if (kt->language) {
159 g_free (kt->language);
160 kt->language = NULL;
161 }
162 if (kt->category) {
163 g_free (kt->category);
164 kt->category = NULL;
165 }
166
167 GST_CALL_PARENT (G_OBJECT_CLASS, dispose, (object));
168 }
169
170 static void
gst_kate_tag_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)171 gst_kate_tag_set_property (GObject * object, guint prop_id,
172 const GValue * value, GParamSpec * pspec)
173 {
174 GstKateTag *kt = GST_KATE_TAG (object);
175 const char *str;
176
177 switch (prop_id) {
178 case ARG_LANGUAGE:
179 if (kt->language) {
180 g_free (kt->language);
181 kt->language = NULL;
182 }
183 str = g_value_get_string (value);
184 if (str)
185 kt->language = g_strdup (str);
186 break;
187 case ARG_CATEGORY:
188 if (kt->category) {
189 g_free (kt->category);
190 kt->category = NULL;
191 }
192 str = g_value_get_string (value);
193 if (str)
194 kt->category = g_strdup (str);
195 break;
196 case ARG_ORIGINAL_CANVAS_WIDTH:
197 kt->original_canvas_width = g_value_get_int (value);
198 break;
199 case ARG_ORIGINAL_CANVAS_HEIGHT:
200 kt->original_canvas_height = g_value_get_int (value);
201 break;
202 default:
203 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
204 break;
205 }
206 }
207
208 static void
gst_kate_tag_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)209 gst_kate_tag_get_property (GObject * object, guint prop_id,
210 GValue * value, GParamSpec * pspec)
211 {
212 GstKateTag *kt = GST_KATE_TAG (object);
213
214 switch (prop_id) {
215 case ARG_LANGUAGE:
216 g_value_set_string (value, kt->language ? kt->language : "");
217 break;
218 case ARG_CATEGORY:
219 g_value_set_string (value, kt->category ? kt->category : "");
220 break;
221 case ARG_ORIGINAL_CANVAS_WIDTH:
222 g_value_set_int (value, kt->original_canvas_width);
223 break;
224 case ARG_ORIGINAL_CANVAS_HEIGHT:
225 g_value_set_int (value, kt->original_canvas_height);
226 break;
227 default:
228 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
229 break;
230 }
231 }
232
233 static guint16
encode_canvas_size(size_t size)234 encode_canvas_size (size_t size)
235 {
236 size_t base = size;
237 size_t shift = 0;
238 int value;
239
240 while (base & ~((1 << 12) - 1)) {
241 /* we have a high bit we can't fit, increase shift if we wouldn't lose low bits */
242 if ((size >> shift) & 1)
243 return 0;
244 ++shift;
245 base >>= 1;
246 }
247 if (G_UNLIKELY (shift >= 16))
248 return 0;
249
250 /* the size can be represented in our encoding */
251 value = (base << 4) | shift;
252
253 return (guint16) value;
254 }
255
256 static GstFlowReturn
gst_kate_tag_parse_packet(GstKateParse * parse,GstBuffer * buffer)257 gst_kate_tag_parse_packet (GstKateParse * parse, GstBuffer * buffer)
258 {
259 GstTagList *old_tags, *new_tags;
260 const GstTagList *user_tags;
261 GstKateTag *kt;
262 gchar *encoder = NULL;
263 GstBuffer *new_buf;
264 GstMapInfo info;
265
266 kt = GST_KATE_TAG (parse);
267
268 if (!gst_buffer_map (buffer, &info, GST_MAP_READ)) {
269 GST_ERROR_OBJECT (parse, "Failed to map buffer");
270 return GST_FLOW_ERROR;
271 }
272
273 /* rewrite the language and category */
274 if (info.size >= 64 && info.data[0] == 0x80) {
275 GstBuffer *new_buffer;
276
277 gst_buffer_unmap (buffer, &info);
278 new_buffer = gst_buffer_copy (buffer);
279 gst_buffer_unref (buffer);
280 buffer = new_buffer;
281
282 if (!gst_buffer_map (buffer, &info, GST_MAP_READWRITE)) {
283 GST_ERROR_OBJECT (parse, "Failed to map copied buffer READWRITE");
284 return GST_FLOW_ERROR;
285 }
286 /* language is at offset 32, 16 bytes, zero terminated */
287 if (kt->language) {
288 strncpy ((char *) info.data + 32, kt->language, 15);
289 info.data[47] = 0;
290 }
291 /* category is at offset 48, 16 bytes, zero terminated */
292 if (kt->category) {
293 strncpy ((char *) info.data + 48, kt->category, 15);
294 info.data[63] = 0;
295 }
296 if (kt->original_canvas_width >= 0) {
297 guint16 v = encode_canvas_size (kt->original_canvas_width);
298 info.data[16] = v & 0xff;
299 info.data[17] = (v >> 8) & 0xff;
300 }
301 if (kt->original_canvas_height >= 0) {
302 guint16 v = encode_canvas_size (kt->original_canvas_height);
303 info.data[18] = v & 0xff;
304 info.data[19] = (v >> 8) & 0xff;
305 }
306 }
307
308 /* rewrite the comments packet */
309 if (info.size >= 9 && info.data[0] == 0x81) {
310 old_tags =
311 gst_tag_list_from_vorbiscomment (info.data, info.size,
312 (const guint8 *) "\201kate\0\0\0\0", 9, &encoder);
313 user_tags = gst_tag_setter_get_tag_list (GST_TAG_SETTER (kt));
314 gst_buffer_unmap (buffer, &info);
315
316 /* build new tag list */
317 new_tags = gst_tag_list_merge (user_tags, old_tags,
318 gst_tag_setter_get_tag_merge_mode (GST_TAG_SETTER (kt)));
319 gst_tag_list_unref (old_tags);
320
321 new_buf =
322 gst_tag_list_to_vorbiscomment_buffer (new_tags,
323 (const guint8 *) "\201kate\0\0\0\0", 9, encoder);
324 gst_buffer_copy_into (new_buf, buffer, GST_BUFFER_COPY_TIMESTAMPS, 0, -1);
325
326 gst_tag_list_unref (new_tags);
327 g_free (encoder);
328 gst_buffer_unref (buffer);
329
330 /* the buffer will have the framing bit used by Vorbis, but we don't use it */
331 gst_buffer_resize (new_buf, 0, gst_buffer_get_size (new_buf) - 1);
332
333 buffer = new_buf;
334 } else {
335 gst_buffer_unmap (buffer, &info);
336 }
337
338 return GST_KATE_PARSE_CLASS (parent_class)->parse_packet (parse, buffer);
339 }
340