1 /* -*- Mode: C; tab-width: 2; indent-tabs-mode: t; c-basic-offset: 2 -*- */
2 /* GStreamer ID3 tag demuxer
3 * Copyright (C) 2005 Jan Schmidt <thaytan@mad.scientist.com>
4 * Copyright (C) 2003-2004 Benjamin Otte <otte@gnome.org>
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-id3demux
24 * @title: id3demux
25 *
26 * id3demux accepts data streams with either (or both) ID3v2 regions at the
27 * start, or ID3v1 at the end. The mime type of the data between the tag blocks
28 * is detected using typefind functions, and the appropriate output mime type
29 * set on outgoing buffers.
30 *
31 * The element is only able to read ID3v1 tags from a seekable stream, because
32 * they are at the end of the stream. That is, when get_range mode is supported
33 * by the upstream elements. If get_range operation is available, id3demux makes
34 * it available downstream. This means that elements which require get_range
35 * mode, such as wavparse, can operate on files containing ID3 tag information.
36 *
37 * This id3demux element replaced an older element with the same name which
38 * relied on libid3tag from the MAD project.
39 *
40 * ## Example launch line
41 * |[
42 * gst-launch-1.0 filesrc location=file.mp3 ! id3demux ! fakesink -t
43 * ]| This pipeline should read any available ID3 tag information and output it.
44 * The contents of the file inside the ID3 tag regions should be detected, and
45 * the appropriate mime type set on buffers produced from id3demux.
46 *
47 */
48 #ifdef HAVE_CONFIG_H
49 #include "config.h"
50 #endif
51 #include <gst/gst.h>
52 #include <gst/gst-i18n-plugin.h>
53 #include <gst/tag/tag.h>
54 #include <gst/pbutils/pbutils.h>
55 #include <string.h>
56
57 #include "gstid3demux.h"
58
59 enum
60 {
61 PROP_0,
62 PROP_PREFER_V1
63 };
64
65 #define DEFAULT_PREFER_V1 FALSE
66
67 GST_DEBUG_CATEGORY (id3demux_debug);
68 #define GST_CAT_DEFAULT (id3demux_debug)
69
70 #define ID3V1_TAG_SIZE 128
71 #define ID3V2_HDR_SIZE GST_TAG_ID3V2_HEADER_SIZE
72
73 static GstStaticPadTemplate sink_factory = GST_STATIC_PAD_TEMPLATE ("sink",
74 GST_PAD_SINK,
75 GST_PAD_ALWAYS,
76 GST_STATIC_CAPS ("application/x-id3")
77 );
78
79 static gboolean gst_id3demux_identify_tag (GstTagDemux * demux,
80 GstBuffer * buffer, gboolean start_tag, guint * tag_size);
81 static GstTagDemuxResult gst_id3demux_parse_tag (GstTagDemux * demux,
82 GstBuffer * buffer, gboolean start_tag, guint * tag_size,
83 GstTagList ** tags);
84 static GstTagList *gst_id3demux_merge_tags (GstTagDemux * tagdemux,
85 const GstTagList * start_tags, const GstTagList * end_tags);
86
87 static void gst_id3demux_set_property (GObject * object, guint prop_id,
88 const GValue * value, GParamSpec * pspec);
89 static void gst_id3demux_get_property (GObject * object, guint prop_id,
90 GValue * value, GParamSpec * pspec);
91
92 #define gst_id3demux_parent_class parent_class
93 G_DEFINE_TYPE (GstID3Demux, gst_id3demux, GST_TYPE_TAG_DEMUX);
94 #define _do_init \
95 GST_DEBUG_CATEGORY_INIT (id3demux_debug, "id3demux", 0, \
96 "GStreamer ID3 tag demuxer"); \
97 gst_tag_register_musicbrainz_tags ();
98 GST_ELEMENT_REGISTER_DEFINE_WITH_CODE (id3demux, "id3demux",
99 GST_RANK_PRIMARY, GST_TYPE_ID3DEMUX, _do_init);
100
101 static void
gst_id3demux_class_init(GstID3DemuxClass * klass)102 gst_id3demux_class_init (GstID3DemuxClass * klass)
103 {
104 GObjectClass *gobject_class = (GObjectClass *) klass;
105 GstElementClass *gstelement_class = (GstElementClass *) klass;
106 GstTagDemuxClass *tagdemux_class = (GstTagDemuxClass *) klass;
107
108 gobject_class->set_property = gst_id3demux_set_property;
109 gobject_class->get_property = gst_id3demux_get_property;
110
111 g_object_class_install_property (gobject_class, PROP_PREFER_V1,
112 g_param_spec_boolean ("prefer-v1", "Prefer version 1 tag",
113 "Prefer tags from ID3v1 tag at end of file when both ID3v1 "
114 "and ID3v2 tags are present", DEFAULT_PREFER_V1,
115 G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS));
116
117 gst_element_class_add_static_pad_template (gstelement_class, &sink_factory);
118
119 gst_element_class_set_static_metadata (gstelement_class, "ID3 tag demuxer",
120 "Codec/Demuxer/Metadata",
121 "Read and output ID3v1 and ID3v2 tags while demuxing the contents",
122 "Jan Schmidt <thaytan@mad.scientist.com>");
123
124 tagdemux_class->identify_tag = GST_DEBUG_FUNCPTR (gst_id3demux_identify_tag);
125 tagdemux_class->parse_tag = GST_DEBUG_FUNCPTR (gst_id3demux_parse_tag);
126 tagdemux_class->merge_tags = GST_DEBUG_FUNCPTR (gst_id3demux_merge_tags);
127
128 tagdemux_class->min_start_size = ID3V2_HDR_SIZE;
129 tagdemux_class->min_end_size = ID3V1_TAG_SIZE;
130 }
131
132 static void
gst_id3demux_init(GstID3Demux * id3demux)133 gst_id3demux_init (GstID3Demux * id3demux)
134 {
135 id3demux->prefer_v1 = DEFAULT_PREFER_V1;
136 }
137
138 static gboolean
gst_id3demux_identify_tag(GstTagDemux * demux,GstBuffer * buf,gboolean start_tag,guint * tag_size)139 gst_id3demux_identify_tag (GstTagDemux * demux, GstBuffer * buf,
140 gboolean start_tag, guint * tag_size)
141 {
142 guint8 data[3];
143
144 gst_buffer_extract (buf, 0, data, 3);
145
146 if (start_tag) {
147 if (data[0] != 'I' || data[1] != 'D' || data[2] != '3')
148 goto no_marker;
149
150 *tag_size = gst_tag_get_id3v2_tag_size (buf);
151 } else {
152 if (data[0] != 'T' || data[1] != 'A' || data[2] != 'G')
153 goto no_marker;
154
155 *tag_size = ID3V1_TAG_SIZE;
156 }
157
158 GST_INFO_OBJECT (demux, "Found ID3v%u marker, tag_size = %u",
159 (start_tag) ? 2 : 1, *tag_size);
160
161 return TRUE;
162
163 no_marker:
164 {
165 GST_DEBUG_OBJECT (demux, "No ID3v%u marker found", (start_tag) ? 2 : 1);
166 return FALSE;
167 }
168 }
169
170 static void
gst_id3demux_add_container_format(GstTagList * tags)171 gst_id3demux_add_container_format (GstTagList * tags)
172 {
173 GstCaps *sink_caps;
174
175 sink_caps = gst_static_pad_template_get_caps (&sink_factory);
176 gst_pb_utils_add_codec_description_to_tag_list (tags,
177 GST_TAG_CONTAINER_FORMAT, sink_caps);
178 gst_caps_unref (sink_caps);
179 }
180
181 static GstTagDemuxResult
gst_id3demux_parse_tag(GstTagDemux * demux,GstBuffer * buffer,gboolean start_tag,guint * tag_size,GstTagList ** tags)182 gst_id3demux_parse_tag (GstTagDemux * demux, GstBuffer * buffer,
183 gboolean start_tag, guint * tag_size, GstTagList ** tags)
184 {
185 if (start_tag) {
186 *tag_size = gst_tag_get_id3v2_tag_size (buffer);
187 *tags = gst_tag_list_from_id3v2_tag (buffer);
188
189 if (G_LIKELY (*tags != NULL)) {
190 gst_id3demux_add_container_format (*tags);
191 return GST_TAG_DEMUX_RESULT_OK;
192 } else {
193 return GST_TAG_DEMUX_RESULT_BROKEN_TAG;
194 }
195 } else {
196 GstMapInfo map;
197
198 gst_buffer_map (buffer, &map, GST_MAP_READ);
199 *tags = gst_tag_list_new_from_id3v1 (map.data);
200 gst_buffer_unmap (buffer, &map);
201
202 if (G_UNLIKELY (*tags == NULL))
203 return GST_TAG_DEMUX_RESULT_BROKEN_TAG;
204
205 gst_id3demux_add_container_format (*tags);
206 *tag_size = ID3V1_TAG_SIZE;
207 return GST_TAG_DEMUX_RESULT_OK;
208 }
209 }
210
211 static GstTagList *
gst_id3demux_merge_tags(GstTagDemux * tagdemux,const GstTagList * start_tags,const GstTagList * end_tags)212 gst_id3demux_merge_tags (GstTagDemux * tagdemux, const GstTagList * start_tags,
213 const GstTagList * end_tags)
214 {
215 GstID3Demux *id3demux;
216 GstTagList *merged;
217 gboolean prefer_v1;
218
219 id3demux = GST_ID3DEMUX (tagdemux);
220
221 GST_OBJECT_LOCK (id3demux);
222 prefer_v1 = id3demux->prefer_v1;
223 GST_OBJECT_UNLOCK (id3demux);
224
225 /* we merge in REPLACE mode, so put the less important tags first */
226 if (prefer_v1)
227 merged = gst_tag_list_merge (start_tags, end_tags, GST_TAG_MERGE_REPLACE);
228 else
229 merged = gst_tag_list_merge (end_tags, start_tags, GST_TAG_MERGE_REPLACE);
230
231 GST_LOG_OBJECT (id3demux, "start tags: %" GST_PTR_FORMAT, start_tags);
232 GST_LOG_OBJECT (id3demux, "end tags: %" GST_PTR_FORMAT, end_tags);
233 GST_LOG_OBJECT (id3demux, "merged tags: %" GST_PTR_FORMAT, merged);
234
235 return merged;
236 }
237
238 static void
gst_id3demux_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)239 gst_id3demux_set_property (GObject * object, guint prop_id,
240 const GValue * value, GParamSpec * pspec)
241 {
242 GstID3Demux *id3demux;
243
244 id3demux = GST_ID3DEMUX (object);
245
246 switch (prop_id) {
247 case PROP_PREFER_V1:{
248 GST_OBJECT_LOCK (id3demux);
249 id3demux->prefer_v1 = g_value_get_boolean (value);
250 GST_OBJECT_UNLOCK (id3demux);
251 break;
252 }
253 default:
254 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
255 break;
256 }
257 }
258
259 static void
gst_id3demux_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)260 gst_id3demux_get_property (GObject * object, guint prop_id,
261 GValue * value, GParamSpec * pspec)
262 {
263 GstID3Demux *id3demux;
264
265 id3demux = GST_ID3DEMUX (object);
266
267 switch (prop_id) {
268 case PROP_PREFER_V1:
269 GST_OBJECT_LOCK (id3demux);
270 g_value_set_boolean (value, id3demux->prefer_v1);
271 GST_OBJECT_UNLOCK (id3demux);
272 break;
273 default:
274 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
275 break;
276 }
277 }
278
279 static gboolean
plugin_init(GstPlugin * plugin)280 plugin_init (GstPlugin * plugin)
281 {
282
283 return GST_ELEMENT_REGISTER (id3demux, plugin);
284
285
286 }
287
288 GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
289 GST_VERSION_MINOR,
290 id3demux,
291 "Demux ID3v1 and ID3v2 tags from a file",
292 plugin_init, VERSION, GST_LICENSE, GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN)
293