gst-indent
[platform/upstream/gst-plugins-good.git] / ext / flac / gstflactag.c
1
2 /* GStreamer
3  * Copyright (C) 2003 Christophe Fergeau <teuf@gnome.org>
4  *
5  * gstflactag.c: plug-in for reading/modifying vorbis comments in flac files
6  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Library General Public
9  * License as published by the Free Software Foundation; either
10  * version 2 of the License, or (at your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Library General Public License for more details.
16  *
17  * You should have received a copy of the GNU Library General Public
18  * License along with this library; if not, write to the
19  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
20  * Boston, MA 02111-1307, USA.
21  */
22
23 #ifdef HAVE_CONFIG_H
24 #include "config.h"
25 #endif
26 #include <gst/gsttaginterface.h>
27 #include <gst/tag/tag.h>
28 #include <string.h>
29
30 #define GST_TYPE_FLAC_TAG (gst_flac_tag_get_type())
31 #define GST_FLAC_TAG(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_FLAC_TAG, GstFlacTag))
32 #define GST_FLAC_TAG_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_FLAC_TAG, GstFlacTag))
33 #define GST_IS_FLAC_TAG(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_FLAC_TAG))
34 #define GST_IS_FLAC_TAG_CLASS(obj) (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_FLAC_TAG))
35
36 typedef struct _GstFlacTag GstFlacTag;
37 typedef struct _GstFlacTagClass GstFlacTagClass;
38
39 static inline gint
40 min (gint a, gint b)
41 {
42   if (a < b) {
43     return a;
44   } else {
45     return b;
46   }
47 }
48
49
50 typedef enum
51 {
52   GST_FLAC_TAG_STATE_INIT,
53   GST_FLAC_TAG_STATE_METADATA_BLOCKS,
54   GST_FLAC_TAG_STATE_METADATA_NEXT_BLOCK,
55   GST_FLAC_TAG_STATE_WRITING_METADATA_BLOCK,
56   GST_FLAC_TAG_STATE_VC_METADATA_BLOCK,
57   GST_FLAC_TAG_STATE_ADD_VORBIS_COMMENT,
58   GST_FLAC_TAG_STATE_AUDIO_DATA
59 } GstFlacTagState;
60
61
62 struct _GstFlacTag
63 {
64   GstElement element;
65
66   /* pads */
67   GstPad *sinkpad;
68   GstPad *srcpad;
69
70   GstFlacTagState state;
71
72   GstBuffer *buffer;
73   GstBuffer *vorbiscomment;
74   GstTagList *tags;
75
76   guint metadata_bytes_remaining;
77   gboolean metadata_last_block;
78
79   gboolean only_output_tags;
80 };
81
82 struct _GstFlacTagClass
83 {
84   GstElementClass parent_class;
85 };
86
87 /* elementfactory information */
88 static GstElementDetails gst_flac_tag_details =
89 GST_ELEMENT_DETAILS ("flac rettager",
90     "Tag",
91     "Rewrite tags in a FLAC file",
92     "Christope Fergeau <teuf@gnome.org>");
93
94
95 /* signals and args */
96 enum
97 {
98   /* FILL ME */
99   LAST_SIGNAL
100 };
101
102 enum
103 {
104   ARG_0,
105   /* FILL ME */
106 };
107
108 static GstStaticPadTemplate flac_tag_src_template =
109     GST_STATIC_PAD_TEMPLATE ("src",
110     GST_PAD_SRC,
111     GST_PAD_ALWAYS,
112     GST_STATIC_CAPS ("audio/x-flac; application/x-gst-tags")
113     );
114
115 static GstStaticPadTemplate flac_tag_sink_template =
116 GST_STATIC_PAD_TEMPLATE ("sink",
117     GST_PAD_SINK,
118     GST_PAD_ALWAYS,
119     GST_STATIC_CAPS ("audio/x-flac")
120     );
121
122
123 static void gst_flac_tag_base_init (gpointer g_class);
124 static void gst_flac_tag_class_init (GstFlacTagClass * klass);
125 static void gst_flac_tag_init (GstFlacTag * tag);
126
127 static void gst_flac_tag_chain (GstPad * pad, GstData * data);
128
129 static GstElementStateReturn gst_flac_tag_change_state (GstElement * element);
130
131
132 static GstElementClass *parent_class = NULL;
133
134 /* static guint gst_flac_tag_signals[LAST_SIGNAL] = { 0 }; */
135
136 GType
137 gst_flac_tag_get_type (void)
138 {
139   static GType flac_tag_type = 0;
140
141   if (!flac_tag_type) {
142     static const GTypeInfo flac_tag_info = {
143       sizeof (GstFlacTagClass),
144       gst_flac_tag_base_init,
145       NULL,
146       (GClassInitFunc) gst_flac_tag_class_init,
147       NULL,
148       NULL,
149       sizeof (GstFlacTag),
150       0,
151       (GInstanceInitFunc) gst_flac_tag_init,
152     };
153     static const GInterfaceInfo tag_setter_info = {
154       NULL,
155       NULL,
156       NULL
157     };
158
159     flac_tag_type =
160         g_type_register_static (GST_TYPE_ELEMENT, "GstFlacTag", &flac_tag_info,
161         0);
162
163     g_type_add_interface_static (flac_tag_type, GST_TYPE_TAG_SETTER,
164         &tag_setter_info);
165
166   }
167   return flac_tag_type;
168 }
169
170
171 static void
172 gst_flac_tag_base_init (gpointer g_class)
173 {
174   GstElementClass *element_class = GST_ELEMENT_CLASS (g_class);
175
176   gst_element_class_set_details (element_class, &gst_flac_tag_details);
177
178   gst_element_class_add_pad_template (element_class,
179       gst_static_pad_template_get (&flac_tag_sink_template));
180   gst_element_class_add_pad_template (element_class,
181       gst_static_pad_template_get (&flac_tag_src_template));
182 }
183
184
185 static void
186 send_eos (GstFlacTag * tag)
187 {
188   gst_element_set_eos (GST_ELEMENT (tag));
189   gst_pad_push (tag->srcpad, GST_DATA (gst_event_new (GST_EVENT_EOS)));
190   /* Seek to end of sink stream */
191   if (gst_pad_send_event (GST_PAD_PEER (tag->sinkpad),
192           gst_event_new_seek (GST_FORMAT_BYTES | GST_SEEK_METHOD_END |
193               GST_SEEK_FLAG_FLUSH, 0))) {
194   } else {
195     g_warning ("Couldn't seek to eos on sinkpad\n");
196   }
197 }
198
199
200 static gboolean
201 caps_nego (GstFlacTag * tag)
202 {
203   /* do caps nego */
204   GstCaps *caps;
205
206   caps = gst_caps_new_simple ("audio/x-flac", NULL);
207   if (gst_pad_try_set_caps (tag->srcpad, caps) != GST_PAD_LINK_REFUSED) {
208     tag->only_output_tags = FALSE;
209     GST_LOG_OBJECT (tag, "normal operation, using audio/x-flac output");
210   } else {
211     if (gst_pad_try_set_caps (tag->srcpad,
212             gst_caps_new_simple ("application/x-gst-tags", NULL))
213         != GST_PAD_LINK_REFUSED) {
214       tag->only_output_tags = TRUE;
215       GST_LOG_OBJECT (tag, "fast operation, just outputting tags");
216       printf ("output tags only\n");
217     } else {
218       return FALSE;
219     }
220   }
221   return TRUE;
222 }
223
224 static void
225 gst_flac_tag_class_init (GstFlacTagClass * klass)
226 {
227   GstElementClass *gstelement_class;
228   GObjectClass *gobject_class;
229
230   gstelement_class = (GstElementClass *) klass;
231   gobject_class = (GObjectClass *) klass;
232
233   parent_class = g_type_class_ref (GST_TYPE_ELEMENT);
234
235   gstelement_class->change_state = gst_flac_tag_change_state;
236 }
237
238
239 static void
240 gst_flac_tag_init (GstFlacTag * tag)
241 {
242   /* create the sink and src pads */
243   tag->sinkpad =
244       gst_pad_new_from_template (gst_static_pad_template_get
245       (&flac_tag_sink_template), "sink");
246   gst_element_add_pad (GST_ELEMENT (tag), tag->sinkpad);
247   gst_pad_set_chain_function (tag->sinkpad,
248       GST_DEBUG_FUNCPTR (gst_flac_tag_chain));
249
250   tag->srcpad =
251       gst_pad_new_from_template (gst_static_pad_template_get
252       (&flac_tag_src_template), "src");
253   gst_element_add_pad (GST_ELEMENT (tag), tag->srcpad);
254
255   tag->buffer = NULL;
256 }
257
258 #define FLAC_MAGIC "fLaC"
259 #define FLAC_MAGIC_SIZE (sizeof (FLAC_MAGIC) - 1)
260
261 static void
262 gst_flac_tag_chain (GstPad * pad, GstData * data)
263 {
264   GstBuffer *buffer;
265   GstFlacTag *tag;
266
267   if (GST_IS_EVENT (data)) {
268     g_print ("Unhandled event\n");
269     return;
270   }
271
272   buffer = GST_BUFFER (data);
273   tag = GST_FLAC_TAG (gst_pad_get_parent (pad));
274
275   if (tag->buffer) {
276     GstBuffer *merge;
277
278     merge = gst_buffer_merge (tag->buffer, buffer);
279     gst_buffer_unref (buffer);
280     gst_buffer_unref (tag->buffer);
281     tag->buffer = merge;
282   } else {
283     tag->buffer = buffer;
284   }
285
286
287   /* Initial state, we don't even know if we are dealing with a flac file */
288   if (tag->state == GST_FLAC_TAG_STATE_INIT) {
289     if (!caps_nego (tag)) {
290       return;
291     }
292
293     if (GST_BUFFER_SIZE (tag->buffer) < sizeof (FLAC_MAGIC)) {
294       return;
295     }
296
297     if (strncmp (GST_BUFFER_DATA (tag->buffer), FLAC_MAGIC,
298             FLAC_MAGIC_SIZE) == 0) {
299       GstBuffer *sub;
300
301       tag->state = GST_FLAC_TAG_STATE_METADATA_BLOCKS;
302       sub = gst_buffer_create_sub (tag->buffer, 0, FLAC_MAGIC_SIZE);
303
304       gst_pad_push (tag->srcpad, GST_DATA (sub));
305       sub =
306           gst_buffer_create_sub (tag->buffer, FLAC_MAGIC_SIZE,
307           GST_BUFFER_SIZE (tag->buffer) - FLAC_MAGIC_SIZE);
308       gst_buffer_unref (tag->buffer);
309       /* We do a copy because we need a writable buffer, and _create_sub
310        * sets the buffer it uses to read-only
311        */
312       tag->buffer = gst_buffer_copy (sub);
313       gst_buffer_unref (sub);
314     } else {
315       /* FIXME: does that work well with FLAC files containing ID3v2 tags ? */
316       GST_ELEMENT_ERROR (tag, STREAM, WRONG_TYPE, (NULL), (NULL));
317     }
318   }
319
320
321   /* The fLaC magic string has been skipped, try to detect the beginning
322    * of a metadata block
323    */
324   if (tag->state == GST_FLAC_TAG_STATE_METADATA_BLOCKS) {
325     guint size;
326     guint type;
327     gboolean is_last;
328
329     g_assert (tag->metadata_bytes_remaining == 0);
330     g_assert (tag->metadata_last_block == FALSE);
331
332     /* The header of a flac metadata block is 4 bytes long:
333      * 1st bit: indicates whether this is the last metadata info block
334      * 7 next bits: 4 if vorbis comment block
335      * 24 next bits: size of the metadata to follow (big endian)
336      */
337     if (GST_BUFFER_SIZE (tag->buffer) < 4) {
338       return;
339     }
340     is_last = (((GST_BUFFER_DATA (tag->buffer)[0]) & 0x80) == 0x80);
341     /* If we have metadata set on the element, the last metadata block 
342      * will be the vorbis comment block which we will build ourselves
343      */
344     if (is_last) {
345       (GST_BUFFER_DATA (tag->buffer)[0]) &= (~0x80);
346     }
347
348     type = (GST_BUFFER_DATA (tag->buffer)[0]) & 0x7F;
349     size = ((GST_BUFFER_DATA (tag->buffer)[1]) << 16)
350         | ((GST_BUFFER_DATA (tag->buffer)[2]) << 8)
351         | (GST_BUFFER_DATA (tag->buffer)[3]);
352
353     /* The 4 bytes long header isn't included in the metadata size */
354     tag->metadata_bytes_remaining = size + 4;
355     tag->metadata_last_block = is_last;
356
357     /* Metadata blocks of type 4 are vorbis comment blocks */
358     if (type == 0x04) {
359       tag->state = GST_FLAC_TAG_STATE_VC_METADATA_BLOCK;
360     } else {
361       tag->state = GST_FLAC_TAG_STATE_WRITING_METADATA_BLOCK;
362     }
363   }
364
365
366   /* Reads a metadata block */
367   if ((tag->state == GST_FLAC_TAG_STATE_WRITING_METADATA_BLOCK) ||
368       (tag->state == GST_FLAC_TAG_STATE_VC_METADATA_BLOCK)) {
369     GstBuffer *sub;
370     guint bytes_to_push;
371
372     g_assert (tag->metadata_bytes_remaining != 0);
373
374     bytes_to_push = min (tag->metadata_bytes_remaining,
375         GST_BUFFER_SIZE (tag->buffer));
376
377     sub = gst_buffer_create_sub (tag->buffer, 0, bytes_to_push);
378
379     if (tag->state == GST_FLAC_TAG_STATE_WRITING_METADATA_BLOCK) {
380       gst_pad_push (tag->srcpad, GST_DATA (sub));
381     } else {
382       if (tag->vorbiscomment == NULL) {
383         tag->vorbiscomment = sub;
384       } else {
385         GstBuffer *merge;
386
387         merge = gst_buffer_merge (tag->vorbiscomment, sub);
388         gst_buffer_unref (tag->vorbiscomment);
389         gst_buffer_unref (sub);
390         tag->vorbiscomment = merge;
391       }
392     }
393
394     tag->metadata_bytes_remaining -= (bytes_to_push);
395
396     if (GST_BUFFER_SIZE (tag->buffer) > bytes_to_push) {
397       GstBuffer *sub;
398
399       sub = gst_buffer_create_sub (tag->buffer, bytes_to_push,
400           GST_BUFFER_SIZE (tag->buffer) - bytes_to_push);
401       gst_buffer_unref (tag->buffer);
402
403       /* We make a copy because we need a writable buffer, and _create_sub
404        * sets the buffer it uses to read-only
405        */
406       tag->buffer = gst_buffer_copy (sub);
407       gst_buffer_unref (sub);
408
409       tag->state = GST_FLAC_TAG_STATE_METADATA_NEXT_BLOCK;
410     } else if (tag->metadata_bytes_remaining == 0) {
411       gst_buffer_unref (tag->buffer);
412       tag->buffer = NULL;
413       tag->state = GST_FLAC_TAG_STATE_METADATA_NEXT_BLOCK;
414       tag->buffer = NULL;
415     } else {
416       tag->state = GST_FLAC_TAG_STATE_WRITING_METADATA_BLOCK;
417       tag->buffer = NULL;
418     }
419   }
420
421   /* This state is mainly used to be able to stop as soon as we read
422    * a vorbiscomment block from the flac file if we are in an only output
423    * tags mode
424    */
425   if (tag->state == GST_FLAC_TAG_STATE_METADATA_NEXT_BLOCK) {
426     /* Check if in the previous iteration we read a vorbis comment metadata 
427      * block, and stop now if the user only wants to read tags
428      */
429     if (tag->vorbiscomment != NULL) {
430       /* We found some tags, try to parse them and notify the other elements
431        * that we encoutered some tags
432        */
433       tag->tags = gst_tag_list_from_vorbiscomment_buffer (tag->vorbiscomment,
434           GST_BUFFER_DATA (tag->vorbiscomment), 4, NULL);
435       if (tag->tags != NULL) {
436         gst_element_found_tags (GST_ELEMENT (tag), tag->tags);
437       }
438
439       gst_buffer_unref (tag->vorbiscomment);
440       tag->vorbiscomment = NULL;
441
442       if (tag->only_output_tags) {
443         send_eos (tag);
444         return;
445       }
446     }
447
448     /* Skip to next state */
449     if (tag->metadata_last_block == FALSE) {
450       tag->state = GST_FLAC_TAG_STATE_METADATA_BLOCKS;
451     } else {
452       if (tag->only_output_tags) {
453         /* If we finished parsing the metadata blocks, we will never find any
454          * metadata, so just stop now
455          */
456         send_eos (tag);
457         return;
458       } else {
459         tag->state = GST_FLAC_TAG_STATE_ADD_VORBIS_COMMENT;
460       }
461     }
462   }
463
464
465   /* Creates a vorbis comment block from the metadata which was set
466    * on the gstreamer element, and add it to the flac stream
467    */
468   if (tag->state == GST_FLAC_TAG_STATE_ADD_VORBIS_COMMENT) {
469     GstBuffer *buffer;
470     gint size;
471     const GstTagList *user_tags;
472     GstTagList *merged_tags;
473
474     g_assert (tag->only_output_tags == FALSE);
475
476     user_tags = gst_tag_setter_get_list (GST_TAG_SETTER (tag));
477     merged_tags = gst_tag_list_merge (tag->tags, user_tags,
478         gst_tag_setter_get_merge_mode (GST_TAG_SETTER (tag)));
479
480     if (merged_tags == NULL) {
481       /* If we get a NULL list of tags, we must generate a padding block
482        * which is marked as the last metadata block, otherwise we'll
483        * end up with a corrupted flac file.
484        */
485       g_warning ("No tags found\n");
486       buffer = gst_buffer_new_and_alloc (12);
487       if (buffer == NULL) {
488         GST_ELEMENT_ERROR (tag, CORE, TOO_LAZY, (NULL),
489             ("Error creating 12-byte buffer for padding block"));
490       }
491       memset (GST_BUFFER_DATA (buffer), 0, GST_BUFFER_SIZE (buffer));
492       GST_BUFFER_DATA (buffer)[0] = 0x81;       /* 0x80 = Last metadata block, 
493                                                  * 0x01 = padding block
494                                                  */
495     } else {
496       guchar header[4];
497
498       memset (header, 0, sizeof (header));
499       header[0] = 0x84;         /* 0x80 = Last metadata block, 
500                                  * 0x04 = vorbiscomment block
501                                  */
502       buffer = gst_tag_list_to_vorbiscomment_buffer (merged_tags, header,
503           sizeof (header), NULL);
504       gst_tag_list_free (merged_tags);
505       if (buffer == NULL) {
506         GST_ELEMENT_ERROR (tag, CORE, TAG, (NULL),
507             ("Error converting tag list to vorbiscomment buffer"));
508         return;
509       }
510       size = GST_BUFFER_SIZE (buffer) - 4;
511       if ((size > 0xFFFFFF) || (size < 0)) {
512         /* FLAC vorbis comment blocks are limited to 2^24 bytes, 
513          * while the vorbis specs allow more than that. Shouldn't 
514          * be a real world problem though
515          */
516         GST_ELEMENT_ERROR (tag, CORE, TAG, (NULL),
517             ("Vorbis comment of size %d too long", size));
518         return;
519       }
520     }
521
522     /* The 4 byte metadata block header isn't accounted for in the total
523      * size of the metadata block
524      */
525     size = GST_BUFFER_SIZE (buffer) - 4;
526
527     GST_BUFFER_DATA (buffer)[1] = ((size & 0xFF0000) >> 16);
528     GST_BUFFER_DATA (buffer)[2] = ((size & 0x00FF00) >> 8);
529     GST_BUFFER_DATA (buffer)[3] = (size & 0x0000FF);
530     gst_pad_push (tag->srcpad, GST_DATA (buffer));
531     tag->state = GST_FLAC_TAG_STATE_AUDIO_DATA;
532   }
533
534
535   /* The metadata blocks have been read, now we are reading audio data */
536   if (tag->state == GST_FLAC_TAG_STATE_AUDIO_DATA) {
537     gst_pad_push (tag->srcpad, GST_DATA (tag->buffer));
538     tag->buffer = NULL;
539   }
540 }
541
542
543 static GstElementStateReturn
544 gst_flac_tag_change_state (GstElement * element)
545 {
546   GstFlacTag *tag;
547
548   tag = GST_FLAC_TAG (element);
549
550   switch (GST_STATE_TRANSITION (element)) {
551     case GST_STATE_NULL_TO_READY:
552       break;
553     case GST_STATE_READY_TO_PAUSED:
554       break;
555     case GST_STATE_PAUSED_TO_PLAYING:
556       /* do something to get out of the chain function faster */
557       break;
558     case GST_STATE_PLAYING_TO_PAUSED:
559       break;
560     case GST_STATE_PAUSED_TO_READY:
561       if (tag->buffer) {
562         gst_buffer_unref (tag->buffer);
563         tag->buffer = NULL;
564       }
565       if (tag->vorbiscomment) {
566         gst_buffer_unref (tag->vorbiscomment);
567         tag->vorbiscomment = NULL;
568       }
569       if (tag->tags) {
570         gst_tag_list_free (tag->tags);
571       }
572       tag->state = GST_FLAC_TAG_STATE_INIT;
573       break;
574     case GST_STATE_READY_TO_NULL:
575       break;
576   }
577
578   return parent_class->change_state (element);
579 }