1 /* GStreamer
2 * Copyright (C) 2003-2004 Benjamin Otte <otte@gnome.org>
3 *
4 * gstid3tag.c: plugin for reading / modifying id3 tags
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., 59 Temple Place - Suite 330,
19 * Boston, MA 02111-1307, USA.
20 */
22 #ifdef HAVE_CONFIG_H
23 #include "config.h"
24 #endif
25 #include "gstmad.h"
26 #include <stdlib.h>
27 #include <string.h>
28 #include <gst/gsttagsetter.h>
30 #define ID3_TYPE_FIND_SIZE 40960
31 GST_DEBUG_CATEGORY_STATIC (gst_id3_tag_debug);
32 #define GST_CAT_DEFAULT gst_id3_tag_debug
34 #define GST_TYPE_ID3_TAG (gst_id3_tag_get_type(GST_ID3_TAG_PARSE_BASE ))
35 #define GST_ID3_TAG(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_ID3_TAG, GstID3Tag))
36 #define GST_ID3_TAG_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_ID3_TAG, GstID3TagClass))
37 #define GST_IS_ID3_TAG(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_ID3_TAG))
38 #define GST_IS_ID3_TAG_CLASS(obj) (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_ID3_TAG))
39 #define GST_ID3_TAG_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_ID3_TAG, GstID3TagClass))
41 typedef struct _GstID3Tag GstID3Tag;
42 typedef struct _GstID3TagClass GstID3TagClass;
44 typedef enum
45 {
46 GST_ID3_TAG_STATE_READING_V2_TAG,
47 GST_ID3_TAG_STATE_SEEKING_TO_V1_TAG,
48 GST_ID3_TAG_STATE_READING_V1_TAG,
49 GST_ID3_TAG_STATE_SEEKING_TO_NORMAL,
50 GST_ID3_TAG_STATE_NORMAL_START,
51 GST_ID3_TAG_STATE_NORMAL
52 }
53 GstID3TagState;
55 typedef enum
56 {
57 GST_ID3_TAG_PARSE_BASE = 0,
58 GST_ID3_TAG_PARSE_DEMUX = 1,
59 GST_ID3_TAG_PARSE_MUX = 2,
60 GST_ID3_TAG_PARSE_ANY = 3
61 }
62 GstID3ParseMode;
64 #define IS_DEMUXER(tag) ((tag)->parse_mode & GST_ID3_TAG_PARSE_DEMUX)
65 #define IS_MUXER(tag) ((tag)->parse_mode & GST_ID3_TAG_PARSE_MUX)
66 #define CAN_BE_DEMUXER(tag) (GST_ID3_TAG_GET_CLASS(tag)->type & GST_ID3_TAG_PARSE_DEMUX)
67 #define CAN_BE_MUXER(tag) (GST_ID3_TAG_GET_CLASS(tag)->type & GST_ID3_TAG_PARSE_MUX)
69 struct _GstID3Tag
70 {
71 GstElement element;
73 /* pads */
74 GstPad *sinkpad;
75 GstPad *srcpad;
77 /* caps */
78 GstID3ParseMode parse_mode;
79 GstCaps *found_caps;
81 /* tags */
82 GstTagList *event_tags;
83 GstTagList *parsed_tags;
85 /* state */
86 GstID3TagState state;
88 GstEvent *segment;
89 GstBuffer *buffer;
90 gboolean prefer_v1tag;
91 glong v1tag_size;
92 glong v1tag_size_new;
93 guint64 v1tag_offset;
94 gboolean v1tag_render;
95 glong v2tag_size;
96 glong v2tag_size_new;
97 gboolean v2tag_render;
98 };
100 struct _GstID3TagClass
101 {
102 GstElementClass parent_class;
104 GstID3ParseMode type;
105 };
107 /* signals and args */
108 enum
109 {
110 /* FILL ME */
111 LAST_SIGNAL
112 };
114 enum
115 {
116 ARG_0,
117 ARG_V1_TAG,
118 ARG_V2_TAG,
119 ARG_PREFER_V1
120 /* FILL ME */
121 };
123 GST_DEBUG_CATEGORY_EXTERN (mad_debug);
125 static GstStaticPadTemplate id3_tag_src_any_template_factory =
126 GST_STATIC_PAD_TEMPLATE ("src",
127 GST_PAD_SRC,
128 /* FIXME: for spider - should be GST_PAD_ALWAYS, */
129 GST_PAD_SOMETIMES,
130 GST_STATIC_CAPS ("ANY")
131 );
133 static GstStaticPadTemplate id3_tag_src_id3_template_factory =
134 GST_STATIC_PAD_TEMPLATE ("src",
135 GST_PAD_SRC,
136 GST_PAD_ALWAYS,
137 GST_STATIC_CAPS ("application/x-id3")
138 );
140 static GstStaticPadTemplate id3_tag_sink_any_template_factory =
141 GST_STATIC_PAD_TEMPLATE ("sink",
142 GST_PAD_SINK,
143 GST_PAD_ALWAYS,
144 /* FIXME: find a way to extend this generically */
145 GST_STATIC_CAPS ("audio/mpeg, mpegversion=(int)1; audio/x-flac")
146 );
148 static GstStaticPadTemplate id3_tag_sink_id3_template_factory =
149 GST_STATIC_PAD_TEMPLATE ("sink",
150 GST_PAD_SINK,
151 GST_PAD_ALWAYS,
152 GST_STATIC_CAPS ("application/x-id3")
153 );
156 static void gst_id3_tag_class_init (gpointer g_class, gpointer class_data);
157 static void gst_id3_tag_init (GTypeInstance * instance, gpointer g_class);
158 static void gst_id3_tag_set_property (GObject * object,
159 guint prop_id, const GValue * value, GParamSpec * pspec);
160 static void gst_id3_tag_get_property (GObject * object,
161 guint prop_id, GValue * value, GParamSpec * pspec);
163 static gboolean gst_id3_tag_src_event (GstPad * pad, GstEvent * event);
164 static const GstQueryType *gst_id3_tag_get_query_types (GstPad * pad);
166 static gboolean gst_id3_tag_src_query (GstPad * pad, GstQuery * query);
168 static gboolean gst_id3_tag_sink_event (GstPad * pad, GstEvent * event);
169 static GstFlowReturn gst_id3_tag_chain (GstPad * pad, GstBuffer * buffer);
170 static GstPadLinkReturn gst_id3_tag_src_link (GstPad * pad, GstPad * peer);
172 static GstStateChangeReturn gst_id3_tag_change_state (GstElement * element,
173 GstStateChange transition);
175 static GstElementClass *parent_class = NULL;
177 /* static guint gst_id3_tag_signals[LAST_SIGNAL] = { 0 }; */
179 GType
180 gst_id3_tag_get_type (guint type)
181 {
182 static GType id3_tag_type[4] = { 0, 0, 0, 0 };
183 static gchar *name[4] = { "GstID3TagBase", "GstID3Demux", "GstID3Mux",
184 "GstID3Tag"
185 };
187 g_assert (type < 4);
189 if (!id3_tag_type[type]) {
190 GTypeInfo id3_tag_info = {
191 sizeof (GstID3TagClass),
192 NULL,
193 NULL,
194 gst_id3_tag_class_init,
195 NULL,
196 GUINT_TO_POINTER (type),
197 sizeof (GstID3Tag),
198 0,
199 gst_id3_tag_init,
200 };
201 static const GInterfaceInfo tag_setter_info = {
202 NULL,
203 NULL,
204 NULL
205 };
207 id3_tag_type[type] = g_type_register_static (
208 (type == GST_ID3_TAG_PARSE_BASE) ? GST_TYPE_ELEMENT :
209 GST_TYPE_ID3_TAG, name[type], &id3_tag_info, 0);
211 if (type & GST_ID3_TAG_PARSE_MUX) {
212 g_type_add_interface_static (id3_tag_type[type], GST_TYPE_TAG_SETTER,
213 &tag_setter_info);
214 }
215 }
216 return id3_tag_type[type];
217 }
219 /* elementfactory information */
220 GstElementDetails gst_id3_tag_details[3] = {
221 GST_ELEMENT_DETAILS ("id3 tag extractor",
222 "Codec/Demuxer/Audio",
223 "Extract ID3 tagging information",
224 "Benjamin Otte <otte@gnome.org>"),
225 GST_ELEMENT_DETAILS ("id3 muxer",
226 "Codec/Muxer/Audio",
227 "Add ID3 tagging information",
228 "Benjamin Otte <otte@gnome.org>"),
229 GST_ELEMENT_DETAILS ("id3 tag extractor",
230 "Tag",
231 "Extract tagging information from mp3s",
232 "Benjamin Otte <otte@gnome.org>")
233 };
235 static void
236 gst_id3_tag_class_init (gpointer g_class, gpointer class_data)
237 {
238 GstElementClass *gstelement_class = GST_ELEMENT_CLASS (g_class);
239 GObjectClass *gobject_class = G_OBJECT_CLASS (g_class);
240 GstID3TagClass *tag_class = GST_ID3_TAG_CLASS (g_class);
242 tag_class->type = GPOINTER_TO_UINT (class_data);
244 if (tag_class->type == GST_ID3_TAG_PARSE_BASE) {
245 parent_class = g_type_class_peek_parent (g_class);
246 gstelement_class->change_state = gst_id3_tag_change_state;
247 } else {
248 gst_element_class_set_details (gstelement_class,
249 &gst_id3_tag_details[tag_class->type - 1]);
250 }
252 gobject_class->set_property = GST_DEBUG_FUNCPTR (gst_id3_tag_set_property);
253 gobject_class->get_property = GST_DEBUG_FUNCPTR (gst_id3_tag_get_property);
255 if (tag_class->type & GST_ID3_TAG_PARSE_DEMUX) {
256 g_object_class_install_property (gobject_class, ARG_PREFER_V1,
257 g_param_spec_boolean ("prefer-v1", "prefer version 1 tag",
258 "Prefer tags from tag at end of file", FALSE,
259 G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
260 gst_element_class_add_pad_template (gstelement_class,
261 gst_static_pad_template_get (&id3_tag_src_any_template_factory));
262 } else {
263 gst_element_class_add_pad_template (gstelement_class,
264 gst_static_pad_template_get (&id3_tag_src_id3_template_factory));
265 }
267 if (tag_class->type & GST_ID3_TAG_PARSE_MUX) {
268 g_object_class_install_property (gobject_class, ARG_V2_TAG,
269 g_param_spec_boolean ("v2-tag", "add version 2 tag",
270 "Add version 2 tag at start of file", TRUE,
271 G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
272 g_object_class_install_property (gobject_class, ARG_V1_TAG,
273 g_param_spec_boolean ("v1-tag", "add version 1 tag",
274 "Add version 1 tag at end of file", FALSE,
275 G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
276 }
277 if (tag_class->type == GST_ID3_TAG_PARSE_MUX) {
278 gst_element_class_add_pad_template (gstelement_class,
279 gst_static_pad_template_get (&id3_tag_sink_any_template_factory));
280 } else {
281 gst_element_class_add_pad_template (gstelement_class,
282 gst_static_pad_template_get (&id3_tag_sink_id3_template_factory));
283 }
284 }
286 static GstCaps *
287 gst_id3_tag_get_caps (GstPad * pad)
288 {
289 GstID3Tag *tag = GST_ID3_TAG (gst_pad_get_parent (pad));
291 if (tag->found_caps) {
292 GstCaps *caps = gst_caps_copy (tag->found_caps);
294 if (CAN_BE_MUXER (tag)) {
295 gst_caps_append (caps,
296 gst_caps_from_string ("application/x-gst-tags; application/x-id3"));
297 }
298 return caps;
299 } else {
300 return gst_caps_copy (gst_pad_get_pad_template_caps (pad));
301 }
302 }
304 static void
305 gst_id3_tag_add_src_pad (GstID3Tag * tag)
306 {
307 g_assert (tag->srcpad == NULL);
308 tag->srcpad =
309 gst_pad_new_from_template (gst_element_class_get_pad_template
310 (GST_ELEMENT_GET_CLASS (tag), "src"), "src");
311 gst_pad_set_event_function (tag->srcpad,
312 GST_DEBUG_FUNCPTR (gst_id3_tag_src_event));
313 gst_pad_set_query_function (tag->srcpad,
314 GST_DEBUG_FUNCPTR (gst_id3_tag_src_query));
315 gst_pad_set_query_type_function (tag->srcpad,
316 GST_DEBUG_FUNCPTR (gst_id3_tag_get_query_types));
317 gst_pad_set_getcaps_function (tag->srcpad,
318 GST_DEBUG_FUNCPTR (gst_id3_tag_get_caps));
319 gst_pad_set_link_function (tag->srcpad,
320 GST_DEBUG_FUNCPTR (gst_id3_tag_src_link));
321 gst_element_add_pad (GST_ELEMENT (tag), tag->srcpad);
322 }
324 static void
325 gst_id3_tag_init (GTypeInstance * instance, gpointer g_class)
326 {
327 GstID3Tag *tag = GST_ID3_TAG (instance);
329 if (GST_ID3_TAG_GET_CLASS (tag)->type != GST_ID3_TAG_PARSE_BASE) {
330 /* create the sink and src pads */
331 tag->sinkpad =
332 gst_pad_new_from_template (gst_element_class_get_pad_template
333 (GST_ELEMENT_CLASS (g_class), "sink"), "sink");
334 gst_element_add_pad (GST_ELEMENT (tag), tag->sinkpad);
335 gst_pad_set_event_function (tag->sinkpad,
336 GST_DEBUG_FUNCPTR (gst_id3_tag_sink_event));
337 gst_pad_set_chain_function (tag->sinkpad,
338 GST_DEBUG_FUNCPTR (gst_id3_tag_chain));
339 }
340 if (GST_ID3_TAG_GET_CLASS (tag)->type == GST_ID3_TAG_PARSE_MUX) {
341 /* only the muxer class here, all other use sometimes pads */
342 gst_id3_tag_add_src_pad (tag);
343 }
344 /* FIXME: for the alli^H^H^H^Hspider - gst_id3_tag_add_src_pad (tag); */
345 tag->parse_mode = GST_ID3_TAG_PARSE_BASE;
346 tag->buffer = NULL;
347 tag->segment = NULL;
348 }
350 static void
351 gst_id3_tag_set_property (GObject * object, guint prop_id, const GValue * value,
352 GParamSpec * pspec)
353 {
354 GstID3Tag *tag;
356 tag = GST_ID3_TAG (object);
358 switch (prop_id) {
359 case ARG_V1_TAG:
360 tag->v1tag_render = g_value_get_boolean (value);
361 break;
362 case ARG_V2_TAG:
363 tag->v2tag_render = g_value_get_boolean (value);
364 break;
365 case ARG_PREFER_V1:
366 tag->prefer_v1tag = g_value_get_boolean (value);
367 break;
368 default:
369 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
370 break;
371 }
373 /* make sure we render at least one tag */
374 if (GST_ID3_TAG_GET_CLASS (tag)->type == GST_ID3_TAG_PARSE_MUX &&
375 !tag->v1tag_render && !tag->v2tag_render) {
376 g_object_set (object, prop_id == ARG_V1_TAG ? "v2-tag" : "v1-tag", TRUE,
377 NULL);
378 }
379 }
380 static void
381 gst_id3_tag_get_property (GObject * object, guint prop_id, GValue * value,
382 GParamSpec * pspec)
383 {
384 GstID3Tag *tag;
386 tag = GST_ID3_TAG (object);
388 switch (prop_id) {
389 case ARG_V1_TAG:
390 g_value_set_boolean (value, tag->v1tag_render);
391 break;
392 case ARG_V2_TAG:
393 g_value_set_boolean (value, tag->v2tag_render);
394 break;
395 case ARG_PREFER_V1:
396 g_value_set_boolean (value, tag->prefer_v1tag);
397 break;
398 default:
399 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
400 break;
401 }
402 }
404 #define gst_id3_tag_set_state(tag,new_state) G_STMT_START { \
405 GST_LOG_OBJECT (tag, "setting state to %s", #new_state ); \
406 tag->state = new_state; \
407 }G_STMT_END
409 static const GstQueryType *
410 gst_id3_tag_get_query_types (GstPad * pad)
411 {
412 static const GstQueryType gst_id3_tag_src_query_types[] = {
413 GST_QUERY_POSITION,
414 0
415 };
417 return gst_id3_tag_src_query_types;
418 }
420 static gboolean
421 gst_id3_tag_src_query (GstPad * pad, GstQuery * query)
422 {
423 gboolean res = FALSE;
424 GstID3Tag *tag;
426 tag = GST_ID3_TAG (gst_pad_get_parent (pad));
428 switch (GST_QUERY_TYPE (query)) {
429 case GST_QUERY_POSITION:{
430 GstFormat format;
431 gint64 current;
433 gst_query_parse_position (query, &format, NULL);
434 switch (format) {
435 case GST_FORMAT_BYTES:{
436 GstPad *peer;
438 if ((peer = gst_pad_get_peer (tag->sinkpad)) == NULL)
439 break;
441 if (tag->state == GST_ID3_TAG_STATE_NORMAL &&
442 gst_pad_query_position (peer, &format, ¤t)) {
443 if (tag->state == GST_ID3_TAG_STATE_NORMAL) {
444 current -= tag->v2tag_size + tag->v2tag_size_new;
445 } else {
446 current = 0;
447 }
448 gst_query_set_position (query, format, current);
450 res = TRUE;
451 }
452 gst_object_unref (peer);
453 break;
454 }
455 default:
456 break;
457 }
458 break;
459 }
460 case GST_QUERY_DURATION:{
461 GstFormat format;
462 gint64 total;
464 gst_query_parse_duration (query, &format, NULL);
465 switch (format) {
466 case GST_FORMAT_BYTES:{
467 GstPad *peer;
469 if ((peer = gst_pad_get_peer (tag->sinkpad)) == NULL)
470 break;
472 if (tag->state == GST_ID3_TAG_STATE_NORMAL &&
473 gst_pad_query_duration (peer, &format, &total)) {
474 total -= tag->v2tag_size + tag->v1tag_size;
475 total += tag->v2tag_size_new + tag->v1tag_size_new;
477 gst_query_set_duration (query, format, total);
479 res = TRUE;
480 }
481 gst_object_unref (peer);
482 break;
483 }
484 default:
485 break;
486 }
487 break;
488 }
489 default:
490 break;
491 }
492 return res;
493 }
495 static gboolean
496 gst_id3_tag_src_event (GstPad * pad, GstEvent * event)
497 {
498 GstID3Tag *tag;
499 gboolean res = FALSE;
501 tag = GST_ID3_TAG (gst_pad_get_parent (pad));
503 switch (GST_EVENT_TYPE (event)) {
504 case GST_EVENT_SEEK:
505 {
506 gdouble rate;
507 GstFormat format;
508 GstSeekType cur_type, stop_type;
509 GstSeekFlags flags;
510 gint64 cur, stop;
512 gst_event_parse_seek (event, &rate, &format, &flags,
513 &cur_type, &cur, &stop_type, &stop);
515 if (format == GST_FORMAT_BYTES &&
516 tag->state == GST_ID3_TAG_STATE_NORMAL &&
517 gst_pad_is_linked (tag->sinkpad)) {
518 GstEvent *new;
519 gint diff = 0;
521 switch (cur_type) {
522 case GST_SEEK_TYPE_SET:
523 diff = tag->v2tag_size - tag->v2tag_size_new;
524 break;
525 case GST_SEEK_TYPE_CUR:
526 break;
527 case GST_SEEK_TYPE_END:
528 diff = cur ? tag->v1tag_size_new - tag->v1tag_size : 0;
529 break;
530 default:
531 g_assert_not_reached ();
532 break;
533 }
534 new = gst_event_new_seek (rate, format, flags,
535 cur_type, cur + diff, stop_type, stop);
536 gst_pad_push_event (tag->sinkpad, new);
537 res = TRUE;
538 }
539 break;
540 }
541 default:
542 break;
543 }
545 gst_event_unref (event);
546 gst_object_unref (tag);
548 return res;
549 }
551 GstTagList *
552 gst_mad_id3_to_tag_list (const struct id3_tag * tag)
553 {
554 const struct id3_frame *frame;
555 const id3_ucs4_t *ucs4;
556 id3_utf8_t *utf8;
557 GstTagList *tag_list;
558 GType tag_type;
559 guint i = 0;
561 tag_list = gst_tag_list_new ();
563 while ((frame = id3_tag_findframe (tag, NULL, i++)) != NULL) {
564 const union id3_field *field;
565 unsigned int nstrings, j;
566 const gchar *tag_name;
568 /* find me the function to query the frame id */
569 gchar *id = g_strndup (frame->id, 5);
571 tag_name = gst_tag_from_id3_tag (id);
572 if (tag_name == NULL) {
573 g_free (id);
574 continue;
575 }
577 if (strcmp (id, "COMM") == 0) {
578 ucs4 = id3_field_getfullstring (&frame->fields[3]);
579 g_assert (ucs4);
581 utf8 = id3_ucs4_utf8duplicate (ucs4);
582 if (utf8 == 0)
583 continue;
585 if (!g_utf8_validate ((char *) utf8, -1, NULL)) {
586 g_warning ("converted string is not valid utf-8");
587 g_free (utf8);
588 continue;
589 }
591 gst_tag_list_add (tag_list, GST_TAG_MERGE_APPEND,
592 GST_TAG_COMMENT, utf8, NULL);
594 g_free (utf8);
595 continue;
596 }
598 field = &frame->fields[1];
599 nstrings = id3_field_getnstrings (field);
601 for (j = 0; j < nstrings; ++j) {
602 ucs4 = id3_field_getstrings (field, j);
603 g_assert (ucs4);
605 if (strcmp (id, ID3_FRAME_GENRE) == 0)
606 ucs4 = id3_genre_name (ucs4);
608 utf8 = id3_ucs4_utf8duplicate (ucs4);
609 if (utf8 == 0)
610 continue;
612 if (!g_utf8_validate ((char *) utf8, -1, NULL)) {
613 g_warning ("converted string is not valid utf-8");
614 free (utf8);
615 continue;
616 }
618 tag_type = gst_tag_get_type (tag_name);
620 /* be sure to add non-string tags here */
621 switch (tag_type) {
622 case G_TYPE_UINT:
623 {
624 guint tmp;
625 gchar *check;
627 tmp = strtoul ((char *) utf8, &check, 10);
629 if (strcmp (tag_name, GST_TAG_DATE) == 0) {
630 GDate *d;
632 if (*check != '\0')
633 break;
634 if (tmp == 0)
635 break;
636 d = g_date_new_dmy (1, 1, tmp);
637 tmp = g_date_get_julian (d);
638 g_date_free (d);
639 } else if (strcmp (tag_name, GST_TAG_TRACK_NUMBER) == 0) {
640 if (*check == '/') {
641 guint total;
643 check++;
644 total = strtoul (check, &check, 10);
645 if (*check != '\0')
646 break;
648 gst_tag_list_add (tag_list, GST_TAG_MERGE_APPEND,
649 GST_TAG_TRACK_COUNT, total, NULL);
650 }
651 } else if (strcmp (tag_name, GST_TAG_ALBUM_VOLUME_NUMBER) == 0) {
652 if (*check == '/') {
653 guint total;
655 check++;
656 total = strtoul (check, &check, 10);
657 if (*check != '\0')
658 break;
660 gst_tag_list_add (tag_list, GST_TAG_MERGE_APPEND,
661 GST_TAG_ALBUM_VOLUME_COUNT, total, NULL);
662 }
663 }
665 if (*check != '\0')
666 break;
667 gst_tag_list_add (tag_list, GST_TAG_MERGE_APPEND, tag_name, tmp,
668 NULL);
669 break;
670 }
671 case G_TYPE_UINT64:
672 {
673 guint64 tmp;
675 g_assert (strcmp (tag_name, GST_TAG_DURATION) == 0);
676 tmp = strtoul ((char *) utf8, NULL, 10);
677 if (tmp == 0) {
678 break;
679 }
680 gst_tag_list_add (tag_list, GST_TAG_MERGE_APPEND,
681 GST_TAG_DURATION, tmp * 1000 * 1000, NULL);
682 break;
683 }
684 case G_TYPE_STRING:{
685 gst_tag_list_add (tag_list, GST_TAG_MERGE_APPEND,
686 tag_name, (const gchar *) utf8, NULL);
687 break;
688 }
689 /* handles GST_TYPE_DATE and anything else */
690 default:{
691 GValue src = { 0, };
692 GValue dest = { 0, };
694 g_value_init (&src, G_TYPE_STRING);
695 g_value_set_string (&src, (const gchar *) utf8);
697 g_value_init (&dest, tag_type);
698 if (g_value_transform (&src, &dest)) {
699 gst_tag_list_add_values (tag_list, GST_TAG_MERGE_APPEND,
700 tag_name, &dest, NULL);
701 } else {
702 GST_WARNING ("Failed to transform tag from string to type '%s'",
703 g_type_name (tag_type));
704 }
705 g_value_unset (&src);
706 g_value_unset (&dest);
707 break;
708 }
709 }
710 free (utf8);
711 }
712 g_free (id);
713 }
715 return tag_list;
716 }
717 static void
718 tag_list_to_id3_tag_foreach (const GstTagList * list, const gchar * tag_name,
719 gpointer user_data)
720 {
721 struct id3_frame *frame;
722 union id3_field *field;
723 guint values = gst_tag_list_get_tag_size (list, tag_name);
724 const gchar *id = gst_tag_to_id3_tag (tag_name);
725 struct id3_tag *tag = (struct id3_tag *) user_data;
727 if (id == NULL)
728 return;
730 if (values == 0)
731 return;
733 frame = id3_frame_new (id);
734 if (id3_tag_attachframe (tag, frame) != 0) {
735 GST_WARNING ("could not attach frame (%s) to id3 tag", id);
736 return;
737 }
738 /* encode in UTF-8 - libid3tag uses Latin1 by default... */
739 field = id3_frame_field (frame, 0);
740 id3_field_settextencoding (field, ID3_FIELD_TEXTENCODING_UTF_8);
741 field = id3_frame_field (frame, 1);
742 g_assert (field);
743 while (values-- > 0) {
744 gunichar *put;
746 if (strcmp (tag_name, GST_TAG_DATE) == 0) {
747 gchar *str;
748 guint u;
749 GDate *d;
751 if (!gst_tag_list_get_uint_index (list, tag_name, values, &u))
752 g_assert_not_reached ();
753 d = g_date_new_julian (u);
754 str = g_strdup_printf ("%u", (guint) (g_date_get_year (d)));
755 put = g_utf8_to_ucs4_fast (str, -1, NULL);
756 g_date_free (d);
757 g_free (str);
758 } else if (strcmp (tag_name, GST_TAG_TRACK_NUMBER) == 0) {
759 gchar *str;
760 guint u;
762 if (!gst_tag_list_get_uint_index (list, tag_name, values, &u))
763 g_assert_not_reached ();
764 str = g_strdup_printf ("%u", u);
765 put = g_utf8_to_ucs4_fast (str, -1, NULL);
766 g_free (str);
767 } else if (strcmp (tag_name, GST_TAG_COMMENT) == 0) {
768 gchar *str;
769 id3_ucs4_t ucs4_empty[] = { 0 };
771 if (!gst_tag_list_get_string_index (list, tag_name, values, &str))
772 g_assert_not_reached ();
773 put = g_utf8_to_ucs4_fast (str, -1, NULL);
774 g_free (str);
776 if (id3_field_setlanguage (&frame->fields[1], "XXX") == -1 ||
777 id3_field_setstring (&frame->fields[2], ucs4_empty) == -1 ||
778 id3_field_setfullstring (&frame->fields[3], (id3_ucs4_t *) put) == -1)
779 GST_WARNING ("could not add a string to the id3 COMM field");
781 g_free (put);
782 return;
783 } else {
784 gchar *str;
786 if (gst_tag_get_type (tag_name) != G_TYPE_STRING) {
787 GST_WARNING ("unhandled GStreamer tag %s", tag_name);
788 return;
789 }
790 if (!gst_tag_list_get_string_index (list, tag_name, values, &str))
791 g_assert_not_reached ();
792 put = g_utf8_to_ucs4_fast (str, -1, NULL);
793 g_free (str);
794 }
795 if (id3_field_addstring (field, (id3_ucs4_t *) put) != 0) {
796 GST_WARNING ("could not add a string to id3 tag field");
797 g_free (put);
798 return;
799 }
800 }
801 id3_field_settextencoding (field, ID3_FIELD_TEXTENCODING_UTF_8);
802 }
803 struct id3_tag *
804 gst_mad_tag_list_to_id3_tag (GstTagList * list)
805 {
806 struct id3_tag *tag;
808 tag = id3_tag_new ();
810 gst_tag_list_foreach (list, tag_list_to_id3_tag_foreach, tag);
811 return tag;
812 }
813 static GstTagList *
814 gst_id3_tag_get_tag_to_render (GstID3Tag * tag)
815 {
816 GstTagList *ret = NULL;
818 if (tag->event_tags)
819 ret = gst_tag_list_copy (tag->event_tags);
820 if (ret) {
821 if (tag->parsed_tags)
822 gst_tag_list_insert (ret, tag->parsed_tags, GST_TAG_MERGE_KEEP);
823 } else if (tag->parsed_tags) {
824 ret = gst_tag_list_copy (tag->parsed_tags);
825 }
826 if (ret && gst_tag_setter_get_tag_list (GST_TAG_SETTER (tag))) {
827 gst_tag_list_insert (ret,
828 gst_tag_setter_get_tag_list (GST_TAG_SETTER (tag)),
829 gst_tag_setter_get_tag_merge_mode (GST_TAG_SETTER (tag)));
830 } else if (gst_tag_setter_get_tag_list (GST_TAG_SETTER (tag))) {
831 ret =
832 gst_tag_list_copy (gst_tag_setter_get_tag_list (GST_TAG_SETTER (tag)));
833 }
834 return ret;
835 }
837 static gboolean
838 gst_id3_tag_sink_event (GstPad * pad, GstEvent * event)
839 {
840 GstID3Tag *tag = GST_ID3_TAG (gst_pad_get_parent (pad));
842 switch (GST_EVENT_TYPE (event)) {
843 case GST_EVENT_NEWSEGMENT:
844 GST_DEBUG_OBJECT (tag, "Have new segment event in mode %d", tag->state);
845 switch (tag->state) {
846 case GST_ID3_TAG_STATE_READING_V2_TAG:{
847 GstFormat format = GST_FORMAT_UNDEFINED;
848 gint64 value, end_value;
850 gst_event_parse_new_segment (event, NULL, NULL, &format, &value,
851 &end_value, NULL);
853 if (format == GST_FORMAT_BYTES || format == GST_FORMAT_DEFAULT) {
854 if (value !=
855 (tag->buffer ? GST_BUFFER_OFFSET (tag->buffer) +
856 GST_BUFFER_SIZE (tag->buffer)
857 : 0))
858 GST_ELEMENT_ERROR (tag, CORE, EVENT, (NULL),
859 ("Got seek to %" G_GINT64_FORMAT " during ID3v2 tag reading"
860 " (allowed was %" G_GINT64_FORMAT ")", value,
861 (guint64) (tag->buffer ? GST_BUFFER_OFFSET (tag->buffer)
862 + GST_BUFFER_SIZE (tag->buffer) : 0)));
863 }
864 tag->segment = event;
865 break;
866 }
867 case GST_ID3_TAG_STATE_SEEKING_TO_V1_TAG:
868 /* just assume it's the right seek for now */
869 gst_id3_tag_set_state (tag, GST_ID3_TAG_STATE_READING_V1_TAG);
870 gst_event_unref (event);
871 break;
872 case GST_ID3_TAG_STATE_READING_V1_TAG:
873 GST_ELEMENT_ERROR (tag, CORE, EVENT, (NULL),
874 ("Seek during ID3v1 tag reading"));
875 gst_event_unref (event);
876 break;
877 case GST_ID3_TAG_STATE_SEEKING_TO_NORMAL:
878 /* just assume it's the right seek for now */
879 gst_id3_tag_set_state (tag, GST_ID3_TAG_STATE_NORMAL_START);
880 if (tag->segment != NULL)
881 gst_event_unref (tag->segment);
882 tag->segment = event;
883 break;
884 case GST_ID3_TAG_STATE_NORMAL_START:
885 if (!CAN_BE_DEMUXER (tag)) {
886 /* initial discont, ignore */
887 GST_LOG_OBJECT (tag, "Ignoring initial newsegment");
888 gst_event_unref (event);
889 break;
890 } else {
891 GST_ERROR_OBJECT (tag, "tag event not sent, FIXME");
892 gst_id3_tag_set_state (tag, GST_ID3_TAG_STATE_NORMAL);
893 /* fall through */
894 }
895 case GST_ID3_TAG_STATE_NORMAL:{
896 GstFormat format = GST_FORMAT_UNDEFINED;
897 gdouble rate;
898 gint64 value, end_value, base;
900 gst_event_parse_new_segment (event, NULL, &rate, &format, &value,
901 &end_value, &base);
902 if (format == GST_FORMAT_BYTES || format == GST_FORMAT_DEFAULT) {
903 if (value > tag->v2tag_size) {
904 value -= tag->v2tag_size;
905 gst_event_unref (event);
906 event =
907 gst_event_new_new_segment (FALSE, rate, format, value,
908 end_value, base);
909 }
910 }
911 if (tag->srcpad)
912 gst_pad_push_event (tag->srcpad, event);
913 else
914 gst_event_unref (event);
915 break;
916 }
917 default:
918 g_assert_not_reached ();
919 }
920 break;
921 case GST_EVENT_TAG:
922 {
923 GstTagList *list;
925 GST_DEBUG_OBJECT (tag, "Have tags event in mode %d", tag->state);
926 gst_event_parse_tag (event, &list);
928 if (tag->event_tags) {
929 gst_tag_list_insert (tag->event_tags, list, GST_TAG_MERGE_PREPEND);
930 } else {
931 tag->event_tags = gst_tag_list_copy (list);
932 }
933 gst_event_unref (event);
934 break;
935 }
936 case GST_EVENT_EOS:
937 GST_DEBUG_OBJECT (tag, "Have EOS in mode %d", tag->state);
938 if (tag->v1tag_render && IS_MUXER (tag)) {
939 GstTagList *merged;
940 struct id3_tag *id3;
942 GST_LOG_OBJECT (tag, "rendering v1 tag after eos event");
943 merged = gst_id3_tag_get_tag_to_render (tag);
944 if (merged) {
945 id3 = gst_mad_tag_list_to_id3_tag (merged);
946 if (id3) {
947 GstBuffer *tag_buffer;
949 id3_tag_options (id3, ID3_TAG_OPTION_ID3V1, ID3_TAG_OPTION_ID3V1);
950 tag_buffer = gst_buffer_new_and_alloc (128);
951 if (128 != id3_tag_render (id3, tag_buffer->data))
952 g_assert_not_reached ();
953 gst_pad_push (tag->srcpad, tag_buffer);
954 id3_tag_delete (id3);
955 }
956 gst_tag_list_free (merged);
957 }
958 }
959 if (tag->state == GST_ID3_TAG_STATE_SEEKING_TO_NORMAL) {
960 /* Absorb EOS while finishing reading V1 TAG */
961 GST_LOG_OBJECT (tag, "Ignoring EOS event after reading id3v1");
962 gst_event_unref (event);
963 break;
964 }
965 /* fall through */
966 default:
967 gst_pad_event_default (pad, event);
968 break;
969 }
970 return TRUE;
971 }
972 typedef struct
973 {
974 guint best_probability;
975 GstCaps *caps;
976 GstBuffer *buffer;
977 }
978 SimpleTypeFind;
979 guint8 *
980 simple_find_peek (gpointer data, gint64 offset, guint size)
981 {
982 SimpleTypeFind *find = (SimpleTypeFind *) data;
984 if (offset < 0)
985 return NULL;
987 if (GST_BUFFER_SIZE (find->buffer) >= offset + size) {
988 return GST_BUFFER_DATA (find->buffer) + offset;
989 }
990 return NULL;
991 }
992 static void
993 simple_find_suggest (gpointer data, guint probability, const GstCaps * caps)
994 {
995 SimpleTypeFind *find = (SimpleTypeFind *) data;
997 if (probability > find->best_probability) {
998 GstCaps *copy = gst_caps_copy (caps);
1000 gst_caps_replace (&find->caps, copy);
1001 gst_caps_unref (copy);
1002 find->best_probability = probability;
1003 }
1004 }
1005 static GstCaps *
1006 gst_id3_tag_do_typefind (GstID3Tag * tag, GstBuffer * buffer)
1007 {
1008 GList *walk, *type_list;
1009 SimpleTypeFind find;
1010 GstTypeFind gst_find;
1012 /* this will help us detecting the media stream type after
1013 * this id3 thingy... Please note that this is a cruel hack
1014 * for as long as spider doesn't support multi-type-finding.
1015 */
1016 walk = type_list = gst_type_find_factory_get_list ();
1018 find.buffer = buffer;
1019 find.best_probability = 0;
1020 find.caps = NULL;
1021 gst_find.data = &find;
1022 gst_find.peek = simple_find_peek;
1023 gst_find.get_length = NULL;
1024 gst_find.suggest = simple_find_suggest;
1025 while (walk) {
1026 GstTypeFindFactory *factory = GST_TYPE_FIND_FACTORY (walk->data);
1028 gst_type_find_factory_call_function (factory, &gst_find);
1029 if (find.best_probability >= GST_TYPE_FIND_MAXIMUM)
1030 break;
1031 walk = g_list_next (walk);
1032 }
1033 g_list_free (type_list);
1034 if (find.best_probability > 0) {
1035 return find.caps;
1036 } else {
1037 GST_ELEMENT_ERROR (tag, CORE, CAPS, (NULL), ("no caps found"));
1038 return NULL;
1039 }
1040 }
1041 static gboolean
1042 gst_id3_tag_do_caps_nego (GstID3Tag * tag, GstBuffer * buffer)
1043 {
1044 if (buffer != NULL && CAN_BE_DEMUXER (tag)) {
1045 tag->found_caps = gst_id3_tag_do_typefind (tag, buffer);
1046 if (!tag->found_caps) {
1047 return FALSE;
1048 }
1049 }
1050 if (tag->srcpad == NULL) {
1051 gst_id3_tag_add_src_pad (tag);
1052 gst_element_no_more_pads (GST_ELEMENT (tag));
1053 }
1054 if (!gst_pad_is_linked (tag->srcpad)) {
1055 GST_DEBUG_OBJECT (tag, "srcpad not linked, not proceeding");
1056 tag->parse_mode = GST_ID3_TAG_GET_CLASS (tag)->type;
1057 return TRUE;
1058 } else {
1059 GST_DEBUG_OBJECT (tag, "renegotiating");
1060 //return gst_pad_renegotiate (tag->srcpad) != GST_PAD_LINK_REFUSED;
1061 return TRUE;
1062 }
1063 }
1065 static GstPadLinkReturn
1066 gst_id3_tag_src_link (GstPad * pad, GstPad * peer)
1067 {
1068 GstID3Tag *tag;
1070 //const gchar *mimetype;
1072 tag = GST_ID3_TAG (gst_pad_get_parent (pad));
1074 #if 0
1075 if (!tag->found_caps && CAN_BE_DEMUXER (tag))
1076 return GST_PAD_LINK_DELAYED;
1077 if (!CAN_BE_MUXER (tag) || !CAN_BE_DEMUXER (tag)) {
1078 tag->parse_mode = GST_ID3_TAG_GET_CLASS (tag)->type;
1079 return GST_PAD_LINK_OK;
1080 }
1082 mimetype = gst_structure_get_name (gst_caps_get_structure (caps, 0));
1084 if (strcmp (mimetype, "application/x-id3") == 0) {
1085 tag->parse_mode = GST_ID3_TAG_PARSE_MUX;
1086 GST_LOG_OBJECT (tag, "normal operation, using application/x-id3 output");
1087 } else if (strcmp (mimetype, "application/x-gst-tags") == 0) {
1088 tag->parse_mode = GST_ID3_TAG_PARSE_ANY;
1089 GST_LOG_OBJECT (tag, "fast operation, just outputting tags");
1090 } else {
1091 tag->parse_mode = GST_ID3_TAG_PARSE_DEMUX;
1092 GST_LOG_OBJECT (tag, "parsing operation, extracting tags");
1093 }
1094 #endif
1095 if (GST_PAD_LINKFUNC (peer))
1096 return GST_PAD_LINKFUNC (peer) (peer, pad);
1097 else
1098 return GST_PAD_LINK_OK;
1099 }
1100 static void
1101 gst_id3_tag_send_tag_event (GstID3Tag * tag)
1102 {
1103 /* FIXME: what's the correct merge mode? Docs need to tell... */
1104 GstTagList *merged = gst_tag_list_merge (tag->event_tags, tag->parsed_tags,
1105 GST_TAG_MERGE_KEEP);
1107 if (tag->parsed_tags)
1108 gst_element_post_message (GST_ELEMENT (tag),
1109 gst_message_new_tag (GST_OBJECT (tag),
1110 gst_tag_list_copy (tag->parsed_tags)));
1112 if (merged) {
1113 GstEvent *event = gst_event_new_tag (merged);
1115 GST_EVENT_TIMESTAMP (event) = 0;
1116 gst_pad_push_event (tag->srcpad, event);
1117 }
1118 }
1119 static GstFlowReturn
1120 gst_id3_tag_chain (GstPad * pad, GstBuffer * buffer)
1121 {
1122 GstID3Tag *tag;
1124 tag = GST_ID3_TAG (gst_pad_get_parent (pad));
1125 GST_DEBUG_OBJECT (tag, "Chain, state = %d", tag->state);
1127 switch (tag->state) {
1128 case GST_ID3_TAG_STATE_SEEKING_TO_V1_TAG:
1129 case GST_ID3_TAG_STATE_SEEKING_TO_NORMAL:
1130 /* we're waiting for the seek to finish, just discard all the stuff */
1131 gst_buffer_unref (buffer);
1132 return GST_FLOW_OK;
1133 case GST_ID3_TAG_STATE_READING_V1_TAG:
1134 if (tag->buffer) {
1135 GstBuffer *temp;
1137 temp = gst_buffer_merge (tag->buffer, buffer);
1138 gst_buffer_unref (tag->buffer);
1139 tag->buffer = temp;
1140 gst_buffer_unref (buffer);
1141 } else {
1142 tag->buffer = buffer;
1143 tag->v1tag_offset = buffer->offset;
1144 }
1145 if (GST_BUFFER_SIZE (tag->buffer) < 128)
1146 return GST_FLOW_OK;
1147 g_assert (tag->v1tag_size == 0);
1148 tag->v1tag_size = id3_tag_query (GST_BUFFER_DATA (tag->buffer),
1149 GST_BUFFER_SIZE (tag->buffer));
1150 if (tag->v1tag_size == 128) {
1151 GstTagList *newtag;
1153 newtag = gst_tag_list_new_from_id3v1 (GST_BUFFER_DATA (tag->buffer));
1154 GST_LOG_OBJECT (tag, "have read ID3v1 tag");
1155 if (newtag) {
1156 if (tag->parsed_tags) {
1157 /* FIXME: use append/prepend here ? */
1158 gst_tag_list_insert (tag->parsed_tags, newtag,
1159 tag->prefer_v1tag ? GST_TAG_MERGE_REPLACE : GST_TAG_MERGE_KEEP);
1160 gst_tag_list_free (newtag);
1161 } else {
1162 tag->parsed_tags = newtag;
1163 }
1164 } else {
1165 GST_WARNING_OBJECT (tag, "detected ID3v1 tag, but couldn't parse it");
1166 }
1167 } else {
1168 if (tag->v1tag_size != 0) {
1169 GST_WARNING_OBJECT (tag, "bad non-ID3v1 tag at end of file");
1170 } else {
1171 GST_LOG_OBJECT (tag, "no ID3v1 tag (%" G_GUINT64_FORMAT ")",
1172 GST_BUFFER_OFFSET (tag->buffer));
1173 tag->v1tag_offset = G_MAXUINT64;
1174 }
1175 }
1176 gst_buffer_unref (tag->buffer);
1177 tag->buffer = NULL;
1178 if (tag->parse_mode != GST_ID3_TAG_PARSE_ANY) {
1179 /* seek to beginning */
1180 GST_LOG_OBJECT (tag, "seeking back to beginning");
1181 gst_id3_tag_set_state (tag, GST_ID3_TAG_STATE_SEEKING_TO_NORMAL);
1182 if (!gst_pad_push_event (tag->sinkpad,
1183 gst_event_new_seek (1.0, GST_FORMAT_BYTES, GST_SEEK_FLAG_FLUSH,
1184 GST_SEEK_TYPE_SET, tag->v2tag_size, GST_SEEK_TYPE_NONE,
1185 0))) {
1186 GST_ELEMENT_ERROR (tag, CORE, SEEK, (NULL),
1187 ("can't seek back to beginning from reading ID3v1 tag"));
1188 }
1189 } else {
1190 gst_id3_tag_send_tag_event (tag);
1191 /* set eos, we're done parsing tags */
1192 GST_LOG_OBJECT (tag, "Finished reading ID3v1 tag");
1193 gst_id3_tag_set_state (tag, GST_ID3_TAG_STATE_NORMAL_START);
1194 //gst_element_set_eos (GST_ELEMENT (tag));
1195 gst_pad_push_event (tag->srcpad, gst_event_new_eos ());
1196 }
1197 return GST_FLOW_OK;
1198 case GST_ID3_TAG_STATE_READING_V2_TAG:
1199 if (tag->buffer) {
1200 GstBuffer *temp;
1202 temp = gst_buffer_merge (tag->buffer, buffer);
1203 gst_buffer_unref (tag->buffer);
1204 tag->buffer = temp;
1205 gst_buffer_unref (buffer);
1206 } else {
1207 tag->buffer = buffer;
1208 }
1209 if (GST_BUFFER_SIZE (tag->buffer) < 10)
1210 return GST_FLOW_OK;
1211 if (tag->v2tag_size == 0) {
1212 tag->v2tag_size = id3_tag_query (GST_BUFFER_DATA (tag->buffer),
1213 GST_BUFFER_SIZE (tag->buffer));
1214 /* no footers supported */
1215 if (tag->v2tag_size < 0)
1216 tag->v2tag_size = 0;
1217 }
1218 if (GST_BUFFER_SIZE (tag->buffer) < tag->v2tag_size + ID3_TYPE_FIND_SIZE)
1219 return GST_FLOW_OK;
1220 if (tag->v2tag_size != 0) {
1221 struct id3_tag *v2tag;
1223 v2tag = id3_tag_parse (GST_BUFFER_DATA (tag->buffer),
1224 GST_BUFFER_SIZE (tag->buffer));
1225 if (v2tag) {
1226 GstTagList *list;
1228 list = gst_mad_id3_to_tag_list (v2tag);
1229 id3_tag_delete (v2tag);
1230 GST_LOG_OBJECT (tag, "parsed ID3v2 tag");
1231 /* no other tag parsed yet */
1232 g_assert (tag->parsed_tags == NULL);
1233 tag->parsed_tags = list;
1234 } else {
1235 GST_WARNING_OBJECT (tag, "detected ID3v2 tag, but couldn't parse it");
1236 }
1237 }
1239 /* seek to ID3v1 tag */
1240 gst_id3_tag_set_state (tag, GST_ID3_TAG_STATE_SEEKING_TO_V1_TAG);
1242 if (gst_pad_push_event (tag->sinkpad,
1243 gst_event_new_seek (1.0, GST_FORMAT_BYTES, GST_SEEK_FLAG_FLUSH,
1244 GST_SEEK_TYPE_END, -128, GST_SEEK_TYPE_NONE, 0))) {
1245 gst_buffer_unref (tag->buffer);
1246 tag->buffer = NULL;
1247 return GST_FLOW_OK;
1248 } else {
1249 GST_DEBUG_OBJECT (tag, "Can't seek to read ID3v1 tag");
1250 }
1252 /* Can't seek, strip off the id3v2 tag and send buffer */
1253 GST_LOG_OBJECT (tag,
1254 "removing first %ld bytes, because they're the ID3v2 tag",
1255 tag->v2tag_size);
1256 buffer =
1257 gst_buffer_create_sub (tag->buffer, tag->v2tag_size,
1258 GST_BUFFER_SIZE (tag->buffer) - tag->v2tag_size);
1259 /* the offsets will be corrected further down, we just copy them */
1260 if (GST_BUFFER_OFFSET_IS_VALID (tag->buffer))
1261 GST_BUFFER_OFFSET (buffer) =
1262 GST_BUFFER_OFFSET (tag->buffer) + tag->v2tag_size;
1263 if (GST_BUFFER_OFFSET_END_IS_VALID (tag->buffer))
1264 GST_BUFFER_OFFSET_END (buffer) =
1265 GST_BUFFER_OFFSET_END (tag->buffer) + tag->v2tag_size;
1266 gst_buffer_unref (tag->buffer);
1267 tag->buffer = NULL;
1269 gst_id3_tag_set_state (tag, GST_ID3_TAG_STATE_NORMAL_START);
1270 /* fall through */
1271 case GST_ID3_TAG_STATE_NORMAL_START:
1272 g_assert (tag->buffer == NULL);
1274 if (!IS_MUXER (tag) && (tag->found_caps == NULL))
1275 if (!gst_id3_tag_do_caps_nego (tag, buffer)) {
1276 gst_buffer_unref (buffer);
1277 return GST_FLOW_OK;
1278 }
1280 /* If we didn't get a segment event to pass on, someone
1281 * downstream is going to complain */
1282 if (tag->segment != NULL) {
1283 gst_pad_push_event (tag->srcpad, tag->segment);
1284 tag->segment = NULL;
1285 }
1287 gst_id3_tag_send_tag_event (tag);
1289 if (IS_MUXER (tag) && tag->v2tag_render) {
1290 struct id3_tag *id3;
1291 GstTagList *merged;
1292 GstBuffer *tag_buffer;
1294 /* render tag */
1295 tag->v2tag_size_new = 0;
1296 merged = gst_id3_tag_get_tag_to_render (tag);
1297 if (merged) {
1298 id3 = gst_mad_tag_list_to_id3_tag (merged);
1299 if (id3) {
1300 glong estimated;
1302 estimated = id3_tag_render (id3, NULL);
1303 tag_buffer = gst_buffer_new_and_alloc (estimated);
1304 tag->v2tag_size_new =
1305 id3_tag_render (id3, GST_BUFFER_DATA (tag_buffer));
1306 g_assert (estimated >= tag->v2tag_size_new);
1307 GST_BUFFER_SIZE (tag_buffer) = tag->v2tag_size_new;
1308 gst_pad_push (tag->srcpad, tag_buffer);
1309 id3_tag_delete (id3);
1310 }
1311 gst_tag_list_free (merged);
1312 }
1313 }
1315 gst_id3_tag_set_state (tag, GST_ID3_TAG_STATE_NORMAL);
1316 tag->v1tag_size_new = (tag->v1tag_render &&
1317 IS_MUXER (tag) &&
1318 (tag->parsed_tags != NULL ||
1319 gst_tag_setter_get_tag_list (GST_TAG_SETTER (tag)) !=
1320 NULL)) ? 128 : 0;
1321 /* fall through */
1322 case GST_ID3_TAG_STATE_NORMAL:
1323 if (tag->parse_mode == GST_ID3_TAG_PARSE_ANY) {
1324 gst_buffer_unref (buffer);
1325 //gst_element_set_eos (GST_ELEMENT (tag));
1326 gst_pad_push_event (tag->srcpad, gst_event_new_eos ());
1327 } else {
1328 if (GST_BUFFER_OFFSET_IS_VALID (buffer)) {
1329 if (buffer->offset >= tag->v1tag_offset) {
1330 gst_buffer_unref (buffer);
1331 return GST_FLOW_OK;
1332 } else if (buffer->offset + buffer->size > tag->v1tag_offset) {
1333 GstBuffer *sub = gst_buffer_create_sub (buffer, 0,
1334 buffer->size - 128);
1336 gst_buffer_unref (buffer);
1337 buffer = sub;
1338 }
1339 }
1340 if (tag->v2tag_size) {
1341 GstBuffer *sub =
1342 gst_buffer_create_sub (buffer, 0, GST_BUFFER_SIZE (buffer));
1343 if (GST_BUFFER_OFFSET_IS_VALID (buffer))
1344 GST_BUFFER_OFFSET (sub) =
1345 GST_BUFFER_OFFSET (buffer) - tag->v2tag_size +
1346 tag->v2tag_size_new;
1347 if (GST_BUFFER_OFFSET_END_IS_VALID (buffer))
1348 GST_BUFFER_OFFSET_END (sub) =
1349 GST_BUFFER_OFFSET_END (buffer) - tag->v2tag_size +
1350 tag->v2tag_size_new;
1351 gst_buffer_unref (buffer);
1352 buffer = sub;
1353 }
1354 gst_pad_push (tag->srcpad, buffer);
1355 }
1356 return GST_FLOW_OK;
1357 }
1358 return GST_FLOW_OK;
1359 }
1361 static GstStateChangeReturn
1362 gst_id3_tag_change_state (GstElement * element, GstStateChange transition)
1363 {
1364 GstID3Tag *tag;
1365 GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;
1367 tag = GST_ID3_TAG (element);
1369 switch (transition) {
1370 case GST_STATE_CHANGE_READY_TO_PAUSED:
1371 g_assert (tag->parsed_tags == NULL);
1372 g_assert (tag->buffer == NULL);
1373 tag->v1tag_size = 0;
1374 tag->v1tag_offset = G_MAXUINT64;
1375 tag->v2tag_size = 0;
1376 if (CAN_BE_DEMUXER (tag)) {
1377 gst_id3_tag_set_state (tag, GST_ID3_TAG_STATE_READING_V2_TAG);
1378 } else {
1379 gst_id3_tag_set_state (tag, GST_ID3_TAG_STATE_NORMAL_START);
1380 }
1381 break;
1382 default:
1383 break;
1384 }
1386 ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
1388 switch (transition) {
1389 case GST_STATE_CHANGE_PAUSED_TO_READY:
1390 if (tag->parsed_tags) {
1391 gst_tag_list_free (tag->parsed_tags);
1392 tag->parsed_tags = NULL;
1393 }
1394 if (tag->event_tags) {
1395 gst_tag_list_free (tag->event_tags);
1396 tag->event_tags = NULL;
1397 }
1398 if (tag->buffer) {
1399 gst_buffer_unref (tag->buffer);
1400 tag->buffer = NULL;
1401 }
1402 if (tag->found_caps) {
1403 gst_caps_unref (tag->found_caps);
1404 tag->found_caps = NULL;
1405 }
1406 if (tag->segment) {
1407 gst_event_unref (tag->segment);
1408 tag->segment = NULL;
1409 }
1410 tag->parse_mode = GST_ID3_TAG_PARSE_BASE;
1411 break;
1412 default:
1413 break;
1414 }
1417 return ret;
1418 }
1420 /*** PLUGIN INITIALIZATION ****************************************************/
1422 static gboolean
1423 plugin_init (GstPlugin * plugin)
1424 {
1426 if (!gst_element_register (plugin, "mad", GST_RANK_SECONDARY,
1427 gst_mad_get_type ())
1428 || !gst_element_register (plugin, "id3demux", GST_RANK_PRIMARY,
1429 gst_id3_tag_get_type (GST_ID3_TAG_PARSE_DEMUX))
1430 || !gst_element_register (plugin, "id3mux", GST_RANK_NONE, /* removed for spider */
1431 gst_id3_tag_get_type (GST_ID3_TAG_PARSE_MUX))
1432 /* FIXME 0.9: remove this element */
1433 || !gst_element_register (plugin, "id3tag", GST_RANK_NONE,
1434 gst_id3_tag_get_type (GST_ID3_TAG_PARSE_ANY))) {
1435 return FALSE;
1436 }
1438 GST_DEBUG_CATEGORY_INIT (gst_id3_tag_debug, "id3tag", 0,
1439 "id3 tag reader / setter");
1441 return TRUE;
1442 }
1444 GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
1445 GST_VERSION_MINOR,
1446 "mad",
1447 "id3 tag manipulation and mp3 decoding based on the mad library",
1448 plugin_init, VERSION, "GPL", GST_PACKAGE, GST_ORIGIN)