1 /* GStreamer ID3v2 tag writer
2  *
3  * Copyright (C) 2006 Christophe Fergeau <teuf@gnome.org>
4  * Copyright (C) 2006-2009 Tim-Philipp Müller <tim centricular net>
5  * Copyright (C) 2009 Pioneers of the Inevitable <songbird@songbirdnest.com>
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., 51 Franklin St, Fifth Floor,
20  * Boston, MA 02110-1301, USA.
21  */
22 
23 #include "id3tag.h"
24 #include <string.h>
25 
26 #include <gst/tag/tag.h>
27 
28 GST_DEBUG_CATEGORY_EXTERN (gst_id3_mux_debug);
29 #define GST_CAT_DEFAULT gst_id3_mux_debug
30 
31 #define ID3V2_APIC_PICTURE_OTHER 0
32 #define ID3V2_APIC_PICTURE_FILE_ICON 1
33 
34 /* ======================================================================== */
35 
36 typedef GString GstByteWriter;
37 
38 static inline GstByteWriter *
gst_byte_writer_new(guint size)39 gst_byte_writer_new (guint size)
40 {
41   return (GstByteWriter *) g_string_sized_new (size);
42 }
43 
44 static inline guint
gst_byte_writer_get_length(GstByteWriter * w)45 gst_byte_writer_get_length (GstByteWriter * w)
46 {
47   return ((GString *) w)->len;
48 }
49 
50 static inline void
gst_byte_writer_write_bytes(GstByteWriter * w,const guint8 * data,guint len)51 gst_byte_writer_write_bytes (GstByteWriter * w, const guint8 * data, guint len)
52 {
53   g_string_append_len ((GString *) w, (const gchar *) data, len);
54 }
55 
56 static inline void
gst_byte_writer_write_uint8(GstByteWriter * w,guint8 val)57 gst_byte_writer_write_uint8 (GstByteWriter * w, guint8 val)
58 {
59   guint8 data[1];
60 
61   GST_WRITE_UINT8 (data, val);
62   gst_byte_writer_write_bytes (w, data, 1);
63 }
64 
65 static inline void
gst_byte_writer_write_uint16(GstByteWriter * w,guint16 val)66 gst_byte_writer_write_uint16 (GstByteWriter * w, guint16 val)
67 {
68   guint8 data[2];
69 
70   GST_WRITE_UINT16_BE (data, val);
71   gst_byte_writer_write_bytes (w, data, 2);
72 }
73 
74 static inline void
gst_byte_writer_write_uint32(GstByteWriter * w,guint32 val)75 gst_byte_writer_write_uint32 (GstByteWriter * w, guint32 val)
76 {
77   guint8 data[4];
78 
79   GST_WRITE_UINT32_BE (data, val);
80   gst_byte_writer_write_bytes (w, data, 4);
81 }
82 
83 static inline void
gst_byte_writer_write_uint32_syncsafe(GstByteWriter * w,guint32 val)84 gst_byte_writer_write_uint32_syncsafe (GstByteWriter * w, guint32 val)
85 {
86   guint8 data[4];
87 
88   data[0] = (guint8) ((val >> 21) & 0x7f);
89   data[1] = (guint8) ((val >> 14) & 0x7f);
90   data[2] = (guint8) ((val >> 7) & 0x7f);
91   data[3] = (guint8) ((val >> 0) & 0x7f);
92   gst_byte_writer_write_bytes (w, data, 4);
93 }
94 
95 static void
gst_byte_writer_copy_bytes(GstByteWriter * w,guint8 * dest,guint offset,gint size)96 gst_byte_writer_copy_bytes (GstByteWriter * w, guint8 * dest, guint offset,
97     gint size)
98 {
99   guint length;
100 
101   length = gst_byte_writer_get_length (w);
102 
103   if (size == -1)
104     size = length - offset;
105 
106   g_warn_if_fail (length >= (offset + size));
107 
108   memcpy (dest, w->str + offset, MIN (size, length - offset));
109 }
110 
111 static inline void
gst_byte_writer_free(GstByteWriter * w)112 gst_byte_writer_free (GstByteWriter * w)
113 {
114   g_string_free (w, TRUE);
115 }
116 
117 /* ======================================================================== */
118 
119 /*
120 typedef enum {
121   GST_ID3V2_FRAME_FLAG_NONE = 0,
122   GST_ID3V2_FRAME_FLAG_
123 } GstID3v2FrameMsgFlags;
124 */
125 
126 typedef struct
127 {
128   gchar id[5];
129   guint32 len;                  /* Length encoded in the header; this is the
130                                    total length - header size */
131   guint16 flags;
132   GstByteWriter *writer;
133   gboolean dirty;               /* TRUE if frame header needs updating */
134 } GstId3v2Frame;
135 
136 typedef struct
137 {
138   GArray *frames;
139   guint major_version;          /* The 3 in v2.3.0 */
140 } GstId3v2Tag;
141 
142 typedef void (*GstId3v2AddTagFunc) (GstId3v2Tag * tag, const GstTagList * list,
143     const gchar * gst_tag, guint num_tags, const gchar * data);
144 
145 #define ID3V2_ENCODING_ISO_8859_1    0x00
146 #define ID3V2_ENCODING_UTF16_BOM     0x01
147 #define ID3V2_ENCODING_UTF8          0x03
148 
149 static gboolean id3v2_tag_init (GstId3v2Tag * tag, guint major_version);
150 static void id3v2_tag_unset (GstId3v2Tag * tag);
151 
152 static void id3v2_frame_init (GstId3v2Frame * frame,
153     const gchar * frame_id, guint16 flags);
154 static void id3v2_frame_unset (GstId3v2Frame * frame);
155 static void id3v2_frame_finish (GstId3v2Tag * tag, GstId3v2Frame * frame);
156 static guint id3v2_frame_get_size (GstId3v2Tag * tag, GstId3v2Frame * frame);
157 
158 static void id3v2_tag_add_text_frame (GstId3v2Tag * tag,
159     const gchar * frame_id, const gchar ** strings, int num_strings);
160 static void id3v2_tag_add_simple_text_frame (GstId3v2Tag * tag,
161     const gchar * frame_id, const gchar * string);
162 
163 static gboolean
id3v2_tag_init(GstId3v2Tag * tag,guint major_version)164 id3v2_tag_init (GstId3v2Tag * tag, guint major_version)
165 {
166   if (major_version != 3 && major_version != 4)
167     return FALSE;
168 
169   tag->major_version = major_version;
170   tag->frames = g_array_new (TRUE, TRUE, sizeof (GstId3v2Frame));
171 
172   return TRUE;
173 }
174 
175 static void
id3v2_tag_unset(GstId3v2Tag * tag)176 id3v2_tag_unset (GstId3v2Tag * tag)
177 {
178   guint i;
179 
180   for (i = 0; i < tag->frames->len; ++i)
181     id3v2_frame_unset (&g_array_index (tag->frames, GstId3v2Frame, i));
182 
183   g_array_free (tag->frames, TRUE);
184   memset (tag, 0, sizeof (GstId3v2Tag));
185 }
186 
187 #ifndef GST_ROUND_UP_1024
188 #define GST_ROUND_UP_1024(num) (((num)+1023)&~1023)
189 #endif
190 
191 static GstBuffer *
id3v2_tag_to_buffer(GstId3v2Tag * tag)192 id3v2_tag_to_buffer (GstId3v2Tag * tag)
193 {
194   GstByteWriter *w;
195   GstMapInfo info;
196   GstBuffer *buf;
197   guint8 *dest;
198   guint i, size, offset, size_frames = 0;
199 
200   GST_DEBUG ("Creating buffer for ID3v2 tag containing %d frames",
201       tag->frames->len);
202 
203   for (i = 0; i < tag->frames->len; ++i) {
204     GstId3v2Frame *frame = &g_array_index (tag->frames, GstId3v2Frame, i);
205 
206     id3v2_frame_finish (tag, frame);
207     size_frames += id3v2_frame_get_size (tag, frame);
208   }
209 
210   size = GST_ROUND_UP_1024 (10 + size_frames);
211 
212   w = gst_byte_writer_new (10);
213   gst_byte_writer_write_uint8 (w, 'I');
214   gst_byte_writer_write_uint8 (w, 'D');
215   gst_byte_writer_write_uint8 (w, '3');
216   gst_byte_writer_write_uint8 (w, tag->major_version);
217   gst_byte_writer_write_uint8 (w, 0);   /* micro version */
218   gst_byte_writer_write_uint8 (w, 0);   /* flags */
219   gst_byte_writer_write_uint32_syncsafe (w, size - 10);
220 
221   buf = gst_buffer_new_allocate (NULL, size, NULL);
222   gst_buffer_map (buf, &info, GST_MAP_WRITE);
223   dest = info.data;
224   gst_byte_writer_copy_bytes (w, dest, 0, 10);
225   offset = 10;
226 
227   for (i = 0; i < tag->frames->len; ++i) {
228     GstId3v2Frame *frame = &g_array_index (tag->frames, GstId3v2Frame, i);
229 
230     gst_byte_writer_copy_bytes (frame->writer, dest + offset, 0, -1);
231     offset += id3v2_frame_get_size (tag, frame);
232   }
233 
234   /* Zero out any additional space in our buffer as padding. */
235   memset (dest + offset, 0, size - offset);
236 
237   gst_byte_writer_free (w);
238   gst_buffer_unmap (buf, &info);
239 
240   return buf;
241 }
242 
243 static inline void
id3v2_frame_write_bytes(GstId3v2Frame * frame,const guint8 * data,guint len)244 id3v2_frame_write_bytes (GstId3v2Frame * frame, const guint8 * data, guint len)
245 {
246   gst_byte_writer_write_bytes (frame->writer, data, len);
247   frame->dirty = TRUE;
248 }
249 
250 static inline void
id3v2_frame_write_uint8(GstId3v2Frame * frame,guint8 val)251 id3v2_frame_write_uint8 (GstId3v2Frame * frame, guint8 val)
252 {
253   gst_byte_writer_write_uint8 (frame->writer, val);
254   frame->dirty = TRUE;
255 }
256 
257 static inline void
id3v2_frame_write_uint16(GstId3v2Frame * frame,guint16 val)258 id3v2_frame_write_uint16 (GstId3v2Frame * frame, guint16 val)
259 {
260   gst_byte_writer_write_uint16 (frame->writer, val);
261   frame->dirty = TRUE;
262 }
263 
264 static inline void
id3v2_frame_write_uint32(GstId3v2Frame * frame,guint32 val)265 id3v2_frame_write_uint32 (GstId3v2Frame * frame, guint32 val)
266 {
267   gst_byte_writer_write_uint32 (frame->writer, val);
268   frame->dirty = TRUE;
269 }
270 
271 static void
id3v2_frame_init(GstId3v2Frame * frame,const gchar * frame_id,guint16 flags)272 id3v2_frame_init (GstId3v2Frame * frame, const gchar * frame_id, guint16 flags)
273 {
274   g_assert (strlen (frame_id) == 4);    /* we only handle 2.3.0/2.4.0 */
275   memcpy (frame->id, frame_id, 4 + 1);
276   frame->flags = flags;
277   frame->len = 0;
278   frame->writer = gst_byte_writer_new (64);
279   id3v2_frame_write_bytes (frame, (const guint8 *) frame->id, 4);
280   id3v2_frame_write_uint32 (frame, 0);  /* size, set later */
281   id3v2_frame_write_uint16 (frame, frame->flags);
282 }
283 
284 static void
id3v2_frame_finish(GstId3v2Tag * tag,GstId3v2Frame * frame)285 id3v2_frame_finish (GstId3v2Tag * tag, GstId3v2Frame * frame)
286 {
287   if (frame->dirty) {
288     frame->len = frame->writer->len - 10;
289     GST_LOG ("[%s] %u bytes", frame->id, frame->len);
290     if (tag->major_version == 3) {
291       GST_WRITE_UINT32_BE (frame->writer->str + 4, frame->len);
292     } else {
293       /* Version 4 uses a syncsafe int here */
294       GST_WRITE_UINT8 (frame->writer->str + 4, (frame->len >> 21) & 0x7f);
295       GST_WRITE_UINT8 (frame->writer->str + 5, (frame->len >> 14) & 0x7f);
296       GST_WRITE_UINT8 (frame->writer->str + 6, (frame->len >> 7) & 0x7f);
297       GST_WRITE_UINT8 (frame->writer->str + 7, (frame->len >> 0) & 0x7f);
298     }
299     frame->dirty = FALSE;
300   }
301 }
302 
303 static guint
id3v2_frame_get_size(GstId3v2Tag * tag,GstId3v2Frame * frame)304 id3v2_frame_get_size (GstId3v2Tag * tag, GstId3v2Frame * frame)
305 {
306   id3v2_frame_finish (tag, frame);
307   return gst_byte_writer_get_length (frame->writer);
308 }
309 
310 static void
id3v2_frame_unset(GstId3v2Frame * frame)311 id3v2_frame_unset (GstId3v2Frame * frame)
312 {
313   gst_byte_writer_free (frame->writer);
314   memset (frame, 0, sizeof (GstId3v2Frame));
315 }
316 
317 static gboolean
id3v2_string_is_ascii(const gchar * string)318 id3v2_string_is_ascii (const gchar * string)
319 {
320   while (*string) {
321     if (!g_ascii_isprint (*string++))
322       return FALSE;
323   }
324 
325   return TRUE;
326 }
327 
328 static int
id3v2_tag_string_encoding(GstId3v2Tag * tag,const gchar * string)329 id3v2_tag_string_encoding (GstId3v2Tag * tag, const gchar * string)
330 {
331   int encoding;
332   if (tag->major_version == 4) {
333     /* ID3v2.4 supports UTF8, use it unconditionally as it's really the only
334        sensible encoding. */
335     encoding = ID3V2_ENCODING_UTF8;
336   } else {
337     /* If we're not writing v2.4, then check to see if it's ASCII.
338        If it is, write ISO-8859-1 (compatible with ASCII).
339        Otherwise, write UTF-16-LE with a byte order marker.
340        Note that we don't write arbitrary ISO-8859-1 as ISO-8859-1, because much
341        software misuses this - and non-ASCII might confuse it. */
342     if (id3v2_string_is_ascii (string))
343       encoding = ID3V2_ENCODING_ISO_8859_1;
344     else
345       encoding = ID3V2_ENCODING_UTF16_BOM;
346   }
347 
348   return encoding;
349 }
350 
351 static void
id3v2_frame_write_string(GstId3v2Frame * frame,int encoding,const gchar * string,gboolean null_terminate)352 id3v2_frame_write_string (GstId3v2Frame * frame, int encoding,
353     const gchar * string, gboolean null_terminate)
354 {
355   int terminator_length;
356   if (encoding == ID3V2_ENCODING_UTF16_BOM) {
357     gsize utf16len;
358     const guint8 bom[] = { 0xFF, 0xFE };
359     /* This converts to little-endian UTF-16 */
360     gchar *utf16 = g_convert (string, -1, "UTF-16LE", "UTF-8",
361         NULL, &utf16len, NULL);
362     if (!utf16) {
363       GST_WARNING ("Failed to convert UTF-8 to UTF-16LE");
364       return;
365     }
366 
367     /* Write the BOM */
368     id3v2_frame_write_bytes (frame, (const guint8 *) bom, 2);
369     id3v2_frame_write_bytes (frame, (const guint8 *) utf16, utf16len);
370     if (null_terminate) {
371       /* NUL terminator is 2 bytes, if present. */
372       id3v2_frame_write_uint16 (frame, 0);
373     }
374 
375     g_free (utf16);
376   } else {
377     /* write NUL terminator as well if requested */
378     terminator_length = null_terminate ? 1 : 0;
379     id3v2_frame_write_bytes (frame, (const guint8 *) string,
380         strlen (string) + terminator_length);
381   }
382 }
383 
384 static void
id3v2_tag_add_text_frame(GstId3v2Tag * tag,const gchar * frame_id,const gchar ** strings_utf8,int num_strings)385 id3v2_tag_add_text_frame (GstId3v2Tag * tag, const gchar * frame_id,
386     const gchar ** strings_utf8, int num_strings)
387 {
388   GstId3v2Frame frame;
389   guint len, i;
390   int encoding;
391 
392   if (num_strings < 1 || strings_utf8 == NULL || strings_utf8[0] == NULL) {
393     GST_LOG ("Not adding text frame, no strings");
394     return;
395   }
396 
397   id3v2_frame_init (&frame, frame_id, 0);
398 
399   encoding = id3v2_tag_string_encoding (tag, strings_utf8[0]);
400   id3v2_frame_write_uint8 (&frame, encoding);
401 
402   GST_LOG ("Adding text frame %s with %d strings", frame_id, num_strings);
403 
404   for (i = 0; i < num_strings; ++i) {
405     len = strlen (strings_utf8[i]);
406     g_return_if_fail (g_utf8_validate (strings_utf8[i], len, NULL));
407 
408     id3v2_frame_write_string (&frame, encoding, strings_utf8[i],
409         i != num_strings - 1);
410 
411     /* only v2.4.0 supports multiple strings per frame (according to the
412      * earlier specs tag readers should just ignore everything after the first
413      * string, but we probably shouldn't write anything there, just in case
414      * tag readers that only support the old version are not expecting
415      * more data after the first string) */
416     if (tag->major_version < 4)
417       break;
418   }
419 
420   if (i < num_strings - 1) {
421     GST_WARNING ("Only wrote one of multiple string values for text frame %s "
422         "- ID3v2 supports multiple string values only since v2.4.0, but writing"
423         "v2.%u.0 tag", frame_id, tag->major_version);
424   }
425 
426   g_array_append_val (tag->frames, frame);
427 }
428 
429 static void
id3v2_tag_add_simple_text_frame(GstId3v2Tag * tag,const gchar * frame_id,const gchar * string)430 id3v2_tag_add_simple_text_frame (GstId3v2Tag * tag, const gchar * frame_id,
431     const gchar * string)
432 {
433   id3v2_tag_add_text_frame (tag, frame_id, (const gchar **) &string, 1);
434 }
435 
436 /* ====================================================================== */
437 
438 static void
add_text_tag(GstId3v2Tag * id3v2tag,const GstTagList * list,const gchar * tag,guint num_tags,const gchar * frame_id)439 add_text_tag (GstId3v2Tag * id3v2tag, const GstTagList * list,
440     const gchar * tag, guint num_tags, const gchar * frame_id)
441 {
442   const gchar **strings;
443   guint n, i;
444 
445   GST_LOG ("Adding '%s' frame", frame_id);
446 
447   strings = g_new0 (const gchar *, num_tags + 1);
448   for (n = 0, i = 0; n < num_tags; ++n) {
449     if (gst_tag_list_peek_string_index (list, tag, n, &strings[i]) &&
450         strings[i] != NULL) {
451       GST_LOG ("%s: %s[%u] = '%s'", frame_id, tag, i, strings[i]);
452       ++i;
453     }
454   }
455 
456   if (strings[0] != NULL) {
457     id3v2_tag_add_text_frame (id3v2tag, frame_id, strings, i);
458   } else {
459     GST_WARNING ("Empty list for tag %s, skipping", tag);
460   }
461 
462   g_free ((gchar **) strings);
463 }
464 
465 static void
add_private_data_tag(GstId3v2Tag * id3v2tag,const GstTagList * list,const gchar * tag,guint num_tags,const gchar * frame_id)466 add_private_data_tag (GstId3v2Tag * id3v2tag, const GstTagList * list,
467     const gchar * tag, guint num_tags, const gchar * frame_id)
468 {
469   gint n;
470 
471   for (n = 0; n < num_tags; ++n) {
472     GstId3v2Frame frame;
473     GstSample *sample = NULL;
474     const GstStructure *structure = NULL;
475     GstBuffer *binary = NULL;
476     GstBuffer *priv_frame = NULL;
477     const gchar *owner_str = NULL;
478     guint owner_len = 0;
479     GstMapInfo mapinfo;
480 
481     if (!gst_tag_list_get_sample_index (list, tag, n, &sample))
482       continue;
483 
484     structure = gst_sample_get_info (sample);
485     if (structure != NULL
486         && !strcmp (gst_structure_get_name (structure), "ID3PrivateFrame")) {
487       owner_str = gst_structure_get_string (structure, "owner");
488 
489       if (owner_str != NULL) {
490         owner_len = strlen (owner_str) + 1;
491         priv_frame = gst_buffer_new_and_alloc (owner_len);
492         gst_buffer_fill (priv_frame, 0, owner_str, owner_len);
493 
494         binary = gst_buffer_ref (gst_sample_get_buffer (sample));
495         priv_frame = gst_buffer_append (priv_frame, binary);
496 
497         id3v2_frame_init (&frame, frame_id, 0);
498 
499         if (gst_buffer_map (priv_frame, &mapinfo, GST_MAP_READ)) {
500           id3v2_frame_write_bytes (&frame, mapinfo.data, mapinfo.size);
501           g_array_append_val (id3v2tag->frames, frame);
502           gst_buffer_unmap (priv_frame, &mapinfo);
503         } else {
504           GST_WARNING ("Couldn't map priv frame tag buffer");
505           id3v2_frame_unset (&frame);
506         }
507 
508         gst_buffer_unref (priv_frame);
509         gst_sample_unref (sample);
510       }
511     } else {
512       GST_WARNING ("Couldn't find ID3PrivateFrame structure");
513     }
514   }
515 }
516 
517 static void
add_id3v2frame_tag(GstId3v2Tag * id3v2tag,const GstTagList * list,const gchar * tag,guint num_tags,const gchar * unused)518 add_id3v2frame_tag (GstId3v2Tag * id3v2tag, const GstTagList * list,
519     const gchar * tag, guint num_tags, const gchar * unused)
520 {
521   guint i;
522 
523   for (i = 0; i < num_tags; ++i) {
524     GstSample *sample;
525     GstBuffer *buf;
526     GstCaps *caps;
527 
528     if (!gst_tag_list_get_sample_index (list, tag, i, &sample))
529       continue;
530 
531     buf = gst_sample_get_buffer (sample);
532 
533     /* FIXME: should use auxiliary sample struct instead of caps for this */
534     caps = gst_sample_get_caps (sample);
535 
536     if (buf && caps) {
537       GstStructure *s;
538       gint version = 0;
539 
540       s = gst_caps_get_structure (caps, 0);
541       /* We can only add it if this private buffer is for the same ID3 version,
542          because we don't understand the contents at all. */
543       if (s && gst_structure_get_int (s, "version", &version) &&
544           version == id3v2tag->major_version) {
545         GstId3v2Frame frame;
546         GstMapInfo mapinfo;
547         gchar frame_id[5];
548         guint16 flags;
549         guint8 *data;
550         gint size;
551 
552         if (!gst_buffer_map (buf, &mapinfo, GST_MAP_READ)) {
553           gst_sample_unref (sample);
554           continue;
555         }
556 
557         size = mapinfo.size;
558         data = mapinfo.data;
559 
560         if (size >= 10) {       /* header size */
561           /* We only get here if the frame version matches the muxer. Since the
562            * muxer only does v2.3 or v2.4, the frame must be one of those - and
563            * so the frame header is the same format */
564           memcpy (frame_id, data, 4);
565           frame_id[4] = 0;
566           flags = GST_READ_UINT16_BE (data + 8);
567 
568           id3v2_frame_init (&frame, frame_id, flags);
569           id3v2_frame_write_bytes (&frame, data + 10, size - 10);
570 
571           g_array_append_val (id3v2tag->frames, frame);
572           GST_DEBUG ("Added unparsed tag with %d bytes", size);
573           gst_buffer_unmap (buf, &mapinfo);
574         } else {
575           GST_WARNING ("Short ID3v2 frame");
576         }
577       } else {
578         GST_WARNING ("Discarding unrecognised ID3 tag for different ID3 "
579             "version");
580       }
581     }
582     gst_sample_unref (sample);
583   }
584 }
585 
586 static void
add_text_tag_v4(GstId3v2Tag * id3v2tag,const GstTagList * list,const gchar * tag,guint num_tags,const gchar * frame_id)587 add_text_tag_v4 (GstId3v2Tag * id3v2tag, const GstTagList * list,
588     const gchar * tag, guint num_tags, const gchar * frame_id)
589 {
590   if (id3v2tag->major_version == 4)
591     add_text_tag (id3v2tag, list, tag, num_tags, frame_id);
592   else {
593     GST_WARNING ("Cannot serialise tag '%s' in ID3v2.%d", frame_id,
594         id3v2tag->major_version);
595   }
596 }
597 
598 static void
add_count_or_num_tag(GstId3v2Tag * id3v2tag,const GstTagList * list,const gchar * tag,guint num_tags,const gchar * frame_id)599 add_count_or_num_tag (GstId3v2Tag * id3v2tag, const GstTagList * list,
600     const gchar * tag, guint num_tags, const gchar * frame_id)
601 {
602   static const struct
603   {
604     const gchar *gst_tag;
605     const gchar *corr_count;    /* corresponding COUNT tag (if number) */
606     const gchar *corr_num;      /* corresponding NUMBER tag (if count) */
607   } corr[] = {
608     {
609     GST_TAG_TRACK_NUMBER, GST_TAG_TRACK_COUNT, NULL}, {
610     GST_TAG_TRACK_COUNT, NULL, GST_TAG_TRACK_NUMBER}, {
611     GST_TAG_ALBUM_VOLUME_NUMBER, GST_TAG_ALBUM_VOLUME_COUNT, NULL}, {
612     GST_TAG_ALBUM_VOLUME_COUNT, NULL, GST_TAG_ALBUM_VOLUME_NUMBER}
613   };
614   guint idx;
615 
616   for (idx = 0; idx < G_N_ELEMENTS (corr); ++idx) {
617     if (strcmp (corr[idx].gst_tag, tag) == 0)
618       break;
619   }
620 
621   g_assert (idx < G_N_ELEMENTS (corr));
622   g_assert (frame_id && strlen (frame_id) == 4);
623 
624   if (corr[idx].corr_num == NULL) {
625     guint number;
626 
627     /* number tag */
628     if (gst_tag_list_get_uint_index (list, tag, 0, &number)) {
629       gchar *tag_str;
630       guint count;
631 
632       if (gst_tag_list_get_uint_index (list, corr[idx].corr_count, 0, &count))
633         tag_str = g_strdup_printf ("%u/%u", number, count);
634       else
635         tag_str = g_strdup_printf ("%u", number);
636 
637       GST_DEBUG ("Setting %s to %s (frame_id = %s)", tag, tag_str, frame_id);
638 
639       id3v2_tag_add_simple_text_frame (id3v2tag, frame_id, tag_str);
640       g_free (tag_str);
641     }
642   } else if (corr[idx].corr_count == NULL) {
643     guint count;
644 
645     /* count tag */
646     if (gst_tag_list_get_uint_index (list, corr[idx].corr_num, 0, &count)) {
647       GST_DEBUG ("%s handled with %s, skipping", tag, corr[idx].corr_num);
648     } else if (gst_tag_list_get_uint_index (list, tag, 0, &count)) {
649       gchar *tag_str = g_strdup_printf ("0/%u", count);
650       GST_DEBUG ("Setting %s to %s (frame_id = %s)", tag, tag_str, frame_id);
651 
652       id3v2_tag_add_simple_text_frame (id3v2tag, frame_id, tag_str);
653       g_free (tag_str);
654     }
655   }
656 
657   if (num_tags > 1) {
658     GST_WARNING ("more than one %s, can only handle one", tag);
659   }
660 }
661 
662 static void
add_bpm_tag(GstId3v2Tag * id3v2tag,const GstTagList * list,const gchar * tag,guint num_tags,const gchar * unused)663 add_bpm_tag (GstId3v2Tag * id3v2tag, const GstTagList * list,
664     const gchar * tag, guint num_tags, const gchar * unused)
665 {
666   gdouble bpm;
667 
668   GST_LOG ("Adding BPM frame");
669 
670   if (gst_tag_list_get_double (list, tag, &bpm)) {
671     gchar *tag_str;
672 
673     /* bpm is stored as an integer in id3 tags, but is a double in
674      * tag lists.
675      */
676     tag_str = g_strdup_printf ("%u", (guint) bpm);
677     GST_DEBUG ("Setting %s to %s", tag, tag_str);
678     id3v2_tag_add_simple_text_frame (id3v2tag, "TBPM", tag_str);
679     g_free (tag_str);
680   }
681 
682   if (num_tags > 1) {
683     GST_WARNING ("more than one %s, can only handle one", tag);
684   }
685 }
686 
687 static void
add_comment_tag(GstId3v2Tag * id3v2tag,const GstTagList * list,const gchar * tag,guint num_tags,const gchar * unused)688 add_comment_tag (GstId3v2Tag * id3v2tag, const GstTagList * list,
689     const gchar * tag, guint num_tags, const gchar * unused)
690 {
691   guint n;
692 
693   GST_LOG ("Adding comment frames");
694   for (n = 0; n < num_tags; ++n) {
695     const gchar *s = NULL;
696 
697     if (gst_tag_list_peek_string_index (list, tag, n, &s) && s != NULL) {
698       gchar *desc = NULL, *val = NULL, *lang = NULL;
699       int desclen, vallen, encoding1, encoding2, encoding;
700       GstId3v2Frame frame;
701 
702       id3v2_frame_init (&frame, "COMM", 0);
703 
704       if (strcmp (tag, GST_TAG_COMMENT) == 0 ||
705           !gst_tag_parse_extended_comment (s, &desc, &lang, &val, TRUE)) {
706         /* create dummy description fields */
707         desc = g_strdup ("Comment");
708         val = g_strdup (s);
709       }
710 
711       /* If we don't have a valid language, match what taglib does for
712          unknown languages */
713       if (!lang || strlen (lang) < 3)
714         lang = g_strdup ("XXX");
715 
716       desclen = strlen (desc);
717       g_return_if_fail (g_utf8_validate (desc, desclen, NULL));
718       vallen = strlen (val);
719       g_return_if_fail (g_utf8_validate (val, vallen, NULL));
720 
721       GST_LOG ("%s[%u] = '%s' (%s|%s|%s)", tag, n, s, GST_STR_NULL (desc),
722           GST_STR_NULL (lang), GST_STR_NULL (val));
723 
724       encoding1 = id3v2_tag_string_encoding (id3v2tag, desc);
725       encoding2 = id3v2_tag_string_encoding (id3v2tag, val);
726       encoding = MAX (encoding1, encoding2);
727 
728       id3v2_frame_write_uint8 (&frame, encoding);
729       id3v2_frame_write_bytes (&frame, (const guint8 *) lang, 3);
730       /* write description and value */
731       id3v2_frame_write_string (&frame, encoding, desc, TRUE);
732       id3v2_frame_write_string (&frame, encoding, val, FALSE);
733 
734       g_free (lang);
735       g_free (desc);
736       g_free (val);
737 
738       g_array_append_val (id3v2tag->frames, frame);
739     }
740   }
741 }
742 
743 static void
add_image_tag(GstId3v2Tag * id3v2tag,const GstTagList * list,const gchar * tag,guint num_tags,const gchar * unused)744 add_image_tag (GstId3v2Tag * id3v2tag, const GstTagList * list,
745     const gchar * tag, guint num_tags, const gchar * unused)
746 {
747   guint n;
748 
749   for (n = 0; n < num_tags; ++n) {
750     GstSample *sample;
751     GstBuffer *image;
752     GstCaps *caps;
753 
754     GST_DEBUG ("image %u/%u", n + 1, num_tags);
755 
756     if (!gst_tag_list_get_sample_index (list, tag, n, &sample))
757       continue;
758 
759     image = gst_sample_get_buffer (sample);
760     caps = gst_sample_get_caps (sample);
761 
762     if (image != NULL && gst_buffer_get_size (image) > 0 &&
763         caps != NULL && !gst_caps_is_empty (caps)) {
764       const gchar *mime_type;
765       GstStructure *s;
766 
767       s = gst_caps_get_structure (caps, 0);
768       mime_type = gst_structure_get_name (s);
769       if (mime_type != NULL) {
770         const gchar *desc = NULL;
771         GstId3v2Frame frame;
772         GstMapInfo mapinfo;
773         int encoding;
774         const GstStructure *info_struct;
775 
776         info_struct = gst_sample_get_info (sample);
777         if (!info_struct
778             || !gst_structure_has_name (info_struct, "GstTagImageInfo"))
779           info_struct = NULL;
780 
781         /* APIC frame specifies "-->" if we're providing a URL to the image
782            rather than directly embedding it */
783         if (strcmp (mime_type, "text/uri-list") == 0)
784           mime_type = "-->";
785 
786         GST_DEBUG ("Attaching picture of %" G_GSIZE_FORMAT " bytes and "
787             "mime type %s", gst_buffer_get_size (image), mime_type);
788 
789         id3v2_frame_init (&frame, "APIC", 0);
790 
791         if (info_struct)
792           desc = gst_structure_get_string (info_struct, "image-description");
793         if (!desc)
794           desc = "";
795         encoding = id3v2_tag_string_encoding (id3v2tag, desc);
796         id3v2_frame_write_uint8 (&frame, encoding);
797 
798         id3v2_frame_write_string (&frame, encoding, mime_type, TRUE);
799 
800         if (strcmp (tag, GST_TAG_PREVIEW_IMAGE) == 0) {
801           id3v2_frame_write_uint8 (&frame, ID3V2_APIC_PICTURE_FILE_ICON);
802         } else {
803           int image_type = ID3V2_APIC_PICTURE_OTHER;
804 
805           if (info_struct) {
806             if (gst_structure_get (info_struct, "image-type",
807                     GST_TYPE_TAG_IMAGE_TYPE, &image_type, NULL)) {
808               if (image_type > 0 && image_type <= 18) {
809                 image_type += 2;
810               } else {
811                 image_type = ID3V2_APIC_PICTURE_OTHER;
812               }
813             } else {
814               image_type = ID3V2_APIC_PICTURE_OTHER;
815             }
816           }
817           id3v2_frame_write_uint8 (&frame, image_type);
818         }
819 
820         id3v2_frame_write_string (&frame, encoding, desc, TRUE);
821 
822         if (gst_buffer_map (image, &mapinfo, GST_MAP_READ)) {
823           id3v2_frame_write_bytes (&frame, mapinfo.data, mapinfo.size);
824           g_array_append_val (id3v2tag->frames, frame);
825           gst_buffer_unmap (image, &mapinfo);
826         } else {
827           GST_WARNING ("Couldn't map image tag buffer");
828           id3v2_frame_unset (&frame);
829         }
830       }
831     } else {
832       GST_WARNING ("no image or caps: %p, caps=%" GST_PTR_FORMAT, image, caps);
833     }
834     gst_sample_unref (sample);
835   }
836 }
837 
838 static void
add_musicbrainz_tag(GstId3v2Tag * id3v2tag,const GstTagList * list,const gchar * tag,guint num_tags,const gchar * data)839 add_musicbrainz_tag (GstId3v2Tag * id3v2tag, const GstTagList * list,
840     const gchar * tag, guint num_tags, const gchar * data)
841 {
842   static const struct
843   {
844     const gchar gst_tag[28];
845     const gchar spec_id[28];
846     const gchar realworld_id[28];
847   } mb_ids[] = {
848     {
849     GST_TAG_MUSICBRAINZ_ARTISTID, "MusicBrainz Artist Id",
850           "musicbrainz_artistid"}, {
851     GST_TAG_MUSICBRAINZ_ALBUMID, "MusicBrainz Album Id", "musicbrainz_albumid"}, {
852     GST_TAG_MUSICBRAINZ_ALBUMARTISTID, "MusicBrainz Album Artist Id",
853           "musicbrainz_albumartistid"}, {
854     GST_TAG_MUSICBRAINZ_TRMID, "MusicBrainz TRM Id", "musicbrainz_trmid"}, {
855     GST_TAG_CDDA_MUSICBRAINZ_DISCID, "MusicBrainz DiscID",
856           "musicbrainz_discid"}, {
857       /* the following one is more or less made up, there seems to be little
858        * evidence that any popular application is actually putting this info
859        * into TXXX frames; the first one comes from a musicbrainz wiki 'proposed
860        * tags' page, the second one is analogue to the vorbis/ape/flac tag. */
861     GST_TAG_CDDA_CDDB_DISCID, "CDDB DiscID", "discid"}
862   };
863   guint i, idx;
864 
865   idx = (guint8) data[0];
866   g_assert (idx < G_N_ELEMENTS (mb_ids));
867 
868   for (i = 0; i < num_tags; ++i) {
869     const gchar *id_str;
870 
871     if (gst_tag_list_peek_string_index (list, tag, 0, &id_str) && id_str) {
872       /* add two frames, one with the ID the musicbrainz.org spec mentions
873        * and one with the ID that applications use in the real world */
874       GstId3v2Frame frame1, frame2;
875       int encoding;
876 
877       GST_DEBUG ("Setting '%s' to '%s'", mb_ids[idx].spec_id, id_str);
878       encoding = id3v2_tag_string_encoding (id3v2tag, id_str);
879 
880       id3v2_frame_init (&frame1, "TXXX", 0);
881       id3v2_frame_write_uint8 (&frame1, encoding);
882       id3v2_frame_write_string (&frame1, encoding, mb_ids[idx].spec_id, TRUE);
883       id3v2_frame_write_string (&frame1, encoding, id_str, FALSE);
884       g_array_append_val (id3v2tag->frames, frame1);
885 
886       id3v2_frame_init (&frame2, "TXXX", 0);
887       id3v2_frame_write_uint8 (&frame2, encoding);
888       id3v2_frame_write_string (&frame2, encoding,
889           mb_ids[idx].realworld_id, TRUE);
890       id3v2_frame_write_string (&frame2, encoding, id_str, FALSE);
891       g_array_append_val (id3v2tag->frames, frame2);
892     }
893   }
894 }
895 
896 static void
add_unique_file_id_tag(GstId3v2Tag * id3v2tag,const GstTagList * list,const gchar * tag,guint num_tags,const gchar * unused)897 add_unique_file_id_tag (GstId3v2Tag * id3v2tag, const GstTagList * list,
898     const gchar * tag, guint num_tags, const gchar * unused)
899 {
900   const gchar *origin = "http://musicbrainz.org";
901   const gchar *id_str = NULL;
902 
903   if (gst_tag_list_peek_string_index (list, tag, 0, &id_str) && id_str) {
904     GstId3v2Frame frame;
905 
906     GST_LOG ("Adding %s (%s): %s", tag, origin, id_str);
907 
908     id3v2_frame_init (&frame, "UFID", 0);
909     id3v2_frame_write_bytes (&frame, (const guint8 *) origin,
910         strlen (origin) + 1);
911     id3v2_frame_write_bytes (&frame, (const guint8 *) id_str,
912         strlen (id_str) + 1);
913     g_array_append_val (id3v2tag->frames, frame);
914   }
915 }
916 
917 static void
add_date_tag(GstId3v2Tag * id3v2tag,const GstTagList * list,const gchar * tag,guint num_tags,const gchar * unused)918 add_date_tag (GstId3v2Tag * id3v2tag, const GstTagList * list,
919     const gchar * tag, guint num_tags, const gchar * unused)
920 {
921   guint n;
922   guint i = 0;
923   const gchar *frame_id;
924   gchar **strings;
925 
926   if (id3v2tag->major_version == 3)
927     frame_id = "TYER";
928   else
929     frame_id = "TDRC";
930 
931   GST_LOG ("Adding date time frame");
932 
933   strings = g_new0 (gchar *, num_tags + 1);
934   for (n = 0; n < num_tags; ++n) {
935     GstDateTime *dt = NULL;
936     guint year;
937     gchar *s;
938 
939     if (!gst_tag_list_get_date_time_index (list, tag, n, &dt) || dt == NULL)
940       continue;
941 
942     year = gst_date_time_get_year (dt);
943     if (year > 500 && year < 2100) {
944       s = g_strdup_printf ("%u", year);
945       GST_LOG ("%s[%u] = '%s'", tag, n, s);
946       strings[i] = s;
947       i++;
948     } else {
949       GST_WARNING ("invalid year %u, skipping", year);
950     }
951 
952     if (gst_date_time_has_month (dt)) {
953       if (id3v2tag->major_version == 3)
954         GST_FIXME ("write TDAT and possibly also TIME frame");
955     }
956     gst_date_time_unref (dt);
957   }
958 
959   if (strings[0] != NULL) {
960     id3v2_tag_add_text_frame (id3v2tag, frame_id, (const gchar **) strings, i);
961   } else {
962     GST_WARNING ("Empty list for tag %s, skipping", tag);
963   }
964 
965   g_strfreev (strings);
966 }
967 
968 static void
add_encoder_tag(GstId3v2Tag * id3v2tag,const GstTagList * list,const gchar * tag,guint num_tags,const gchar * unused)969 add_encoder_tag (GstId3v2Tag * id3v2tag, const GstTagList * list,
970     const gchar * tag, guint num_tags, const gchar * unused)
971 {
972   guint n;
973   gchar **strings;
974   int i = 0;
975 
976   /* ENCODER_VERSION is either handled with the ENCODER tag or not at all */
977   if (strcmp (tag, GST_TAG_ENCODER_VERSION) == 0)
978     return;
979 
980   strings = g_new0 (gchar *, num_tags + 1);
981   for (n = 0; n < num_tags; ++n) {
982     const gchar *encoder = NULL;
983 
984     if (gst_tag_list_peek_string_index (list, tag, n, &encoder) && encoder) {
985       guint encoder_version;
986       gchar *s;
987 
988       if (gst_tag_list_get_uint_index (list, GST_TAG_ENCODER_VERSION, n,
989               &encoder_version) && encoder_version > 0) {
990         s = g_strdup_printf ("%s %u", encoder, encoder_version);
991       } else {
992         s = g_strdup (encoder);
993       }
994 
995       GST_LOG ("encoder[%u] = '%s'", n, s);
996       strings[i] = s;
997       i++;
998     }
999   }
1000 
1001   if (strings[0] != NULL) {
1002     id3v2_tag_add_text_frame (id3v2tag, "TSSE", (const gchar **) strings, i);
1003   } else {
1004     GST_WARNING ("Empty list for tag %s, skipping", tag);
1005   }
1006 
1007   g_strfreev (strings);
1008 }
1009 
1010 static void
add_uri_tag(GstId3v2Tag * id3v2tag,const GstTagList * list,const gchar * tag,guint num_tags,const gchar * frame_id)1011 add_uri_tag (GstId3v2Tag * id3v2tag, const GstTagList * list,
1012     const gchar * tag, guint num_tags, const gchar * frame_id)
1013 {
1014   const gchar *url = NULL;
1015 
1016   g_assert (frame_id != NULL);
1017 
1018   /* URI tags are limited to one of each per taglist */
1019   if (gst_tag_list_peek_string_index (list, tag, 0, &url) && url != NULL) {
1020     guint url_len;
1021 
1022     url_len = strlen (url);
1023     if (url_len > 0 && gst_uri_is_valid (url)) {
1024       GstId3v2Frame frame;
1025 
1026       id3v2_frame_init (&frame, frame_id, 0);
1027       id3v2_frame_write_bytes (&frame, (const guint8 *) url, strlen (url) + 1);
1028       g_array_append_val (id3v2tag->frames, frame);
1029     } else {
1030       GST_WARNING ("Tag %s does not contain a valid URI (%s)", tag, url);
1031     }
1032   }
1033 }
1034 
1035 static void
add_relative_volume_tag(GstId3v2Tag * id3v2tag,const GstTagList * list,const gchar * tag,guint num_tags,const gchar * unused)1036 add_relative_volume_tag (GstId3v2Tag * id3v2tag, const GstTagList * list,
1037     const gchar * tag, guint num_tags, const gchar * unused)
1038 {
1039   const char *gain_tag_name;
1040   const char *peak_tag_name;
1041   gdouble peak_val;
1042   gdouble gain_val;
1043   const char *identification;
1044   guint16 peak_int;
1045   gint16 gain_int;
1046   guint8 peak_bits;
1047   GstId3v2Frame frame;
1048   const gchar *frame_id;
1049 
1050   /* figure out tag names and the identification string to use */
1051   if (strcmp (tag, GST_TAG_TRACK_PEAK) == 0 ||
1052       strcmp (tag, GST_TAG_TRACK_GAIN) == 0) {
1053     gain_tag_name = GST_TAG_TRACK_GAIN;
1054     peak_tag_name = GST_TAG_TRACK_PEAK;
1055     identification = "track";
1056     GST_DEBUG ("adding track relative-volume frame");
1057   } else {
1058     gain_tag_name = GST_TAG_ALBUM_GAIN;
1059     peak_tag_name = GST_TAG_ALBUM_PEAK;
1060     identification = "album";
1061 
1062     if (id3v2tag->major_version == 3) {
1063       GST_WARNING ("Cannot store replaygain album gain data in ID3v2.3");
1064       return;
1065     }
1066     GST_DEBUG ("adding album relative-volume frame");
1067   }
1068 
1069   /* find the value for the paired tag (gain, if this is peak, and
1070    * vice versa).  if both tags exist, only write the frame when
1071    * we're processing the peak tag.
1072    */
1073   if (strcmp (tag, GST_TAG_TRACK_PEAK) == 0 ||
1074       strcmp (tag, GST_TAG_ALBUM_PEAK) == 0) {
1075 
1076     gst_tag_list_get_double (list, tag, &peak_val);
1077 
1078     if (gst_tag_list_get_tag_size (list, gain_tag_name) > 0) {
1079       gst_tag_list_get_double (list, gain_tag_name, &gain_val);
1080       GST_DEBUG ("setting volume adjustment %g", gain_val);
1081       gain_int = (gint16) (gain_val * 512.0);
1082     } else
1083       gain_int = 0;
1084 
1085     /* copying mutagen: always write as 16 bits for sanity. */
1086     peak_int = (short) (peak_val * G_MAXSHORT);
1087     peak_bits = 16;
1088   } else {
1089     gst_tag_list_get_double (list, tag, &gain_val);
1090     GST_DEBUG ("setting volume adjustment %g", gain_val);
1091 
1092     gain_int = (gint16) (gain_val * 512.0);
1093     peak_bits = 0;
1094     peak_int = 0;
1095 
1096     if (gst_tag_list_get_tag_size (list, peak_tag_name) != 0) {
1097       GST_DEBUG
1098           ("both gain and peak tags exist, not adding frame this time around");
1099       return;
1100     }
1101   }
1102 
1103   if (id3v2tag->major_version == 4) {
1104     /* 2.4: Use RVA2 tag */
1105     frame_id = "RVA2";
1106   } else {
1107     /* 2.3: Use XRVA tag - this is experimental, but useful in the real world.
1108        This version only officially supports the 'RVAD' tag, but that appears
1109        to not be widely implemented in reality. */
1110     frame_id = "XRVA";
1111   }
1112 
1113   id3v2_frame_init (&frame, frame_id, 0);
1114   id3v2_frame_write_bytes (&frame, (const guint8 *) identification,
1115       strlen (identification) + 1);
1116   id3v2_frame_write_uint8 (&frame, 0x01);       /* Master volume */
1117   id3v2_frame_write_uint16 (&frame, gain_int);
1118   id3v2_frame_write_uint8 (&frame, peak_bits);
1119   if (peak_bits)
1120     id3v2_frame_write_uint16 (&frame, peak_int);
1121 
1122   g_array_append_val (id3v2tag->frames, frame);
1123 }
1124 
1125 /* id3demux produces these for frames it cannot parse */
1126 #define GST_ID3_DEMUX_TAG_ID3V2_FRAME "private-id3v2-frame"
1127 
1128 static const struct
1129 {
1130   const gchar *gst_tag;
1131   const GstId3v2AddTagFunc func;
1132   const gchar *data;
1133 } add_funcs[] = {
1134   {
1135     /* Simple text tags */
1136   GST_TAG_ARTIST, add_text_tag, "TPE1"}, {
1137   GST_TAG_ALBUM_ARTIST, add_text_tag, "TPE2"}, {
1138   GST_TAG_TITLE, add_text_tag, "TIT2"}, {
1139   GST_TAG_ALBUM, add_text_tag, "TALB"}, {
1140   GST_TAG_COPYRIGHT, add_text_tag, "TCOP"}, {
1141   GST_TAG_COMPOSER, add_text_tag, "TCOM"}, {
1142   GST_TAG_GENRE, add_text_tag, "TCON"}, {
1143   GST_TAG_ENCODED_BY, add_text_tag, "TENC"}, {
1144   GST_TAG_PUBLISHER, add_text_tag, "TPUB"}, {
1145   GST_TAG_INTERPRETED_BY, add_text_tag, "TPE4"}, {
1146   GST_TAG_MUSICAL_KEY, add_text_tag, "TKEY"}, {
1147 
1148     /* Private frames */
1149   GST_TAG_PRIVATE_DATA, add_private_data_tag, "PRIV"}, {
1150   GST_ID3_DEMUX_TAG_ID3V2_FRAME, add_id3v2frame_tag, NULL}, {
1151 
1152     /* Track and album numbers */
1153   GST_TAG_TRACK_NUMBER, add_count_or_num_tag, "TRCK"}, {
1154   GST_TAG_TRACK_COUNT, add_count_or_num_tag, "TRCK"}, {
1155   GST_TAG_ALBUM_VOLUME_NUMBER, add_count_or_num_tag, "TPOS"}, {
1156   GST_TAG_ALBUM_VOLUME_COUNT, add_count_or_num_tag, "TPOS"}, {
1157 
1158     /* Comment tags */
1159   GST_TAG_COMMENT, add_comment_tag, NULL}, {
1160   GST_TAG_EXTENDED_COMMENT, add_comment_tag, NULL}, {
1161 
1162     /* BPM tag */
1163   GST_TAG_BEATS_PER_MINUTE, add_bpm_tag, NULL}, {
1164 
1165     /* Images */
1166   GST_TAG_IMAGE, add_image_tag, NULL}, {
1167   GST_TAG_PREVIEW_IMAGE, add_image_tag, NULL}, {
1168 
1169     /* Misc user-defined text tags for IDs (and UFID frame) */
1170   GST_TAG_MUSICBRAINZ_ARTISTID, add_musicbrainz_tag, "\000"}, {
1171   GST_TAG_MUSICBRAINZ_ALBUMID, add_musicbrainz_tag, "\001"}, {
1172   GST_TAG_MUSICBRAINZ_ALBUMARTISTID, add_musicbrainz_tag, "\002"}, {
1173   GST_TAG_MUSICBRAINZ_TRMID, add_musicbrainz_tag, "\003"}, {
1174   GST_TAG_CDDA_MUSICBRAINZ_DISCID, add_musicbrainz_tag, "\004"}, {
1175   GST_TAG_CDDA_CDDB_DISCID, add_musicbrainz_tag, "\005"}, {
1176   GST_TAG_MUSICBRAINZ_TRACKID, add_unique_file_id_tag, NULL}, {
1177 
1178     /* Info about encoder */
1179   GST_TAG_ENCODER, add_encoder_tag, NULL}, {
1180   GST_TAG_ENCODER_VERSION, add_encoder_tag, NULL}, {
1181 
1182     /* URIs */
1183   GST_TAG_COPYRIGHT_URI, add_uri_tag, "WCOP"}, {
1184   GST_TAG_LICENSE_URI, add_uri_tag, "WCOP"}, {
1185 
1186     /* Up to here, all the frame ids and contents have been the same between
1187        versions 2.3 and 2.4. The rest of them differ... */
1188     /* Date (in ID3v2.3, this is a TYER tag. In v2.4, it's a TDRC tag */
1189   GST_TAG_DATE_TIME, add_date_tag, NULL}, {
1190 
1191     /* Replaygain data (not really supported in 2.3, we use an experimental
1192        tag there) */
1193   GST_TAG_TRACK_PEAK, add_relative_volume_tag, NULL}, {
1194   GST_TAG_TRACK_GAIN, add_relative_volume_tag, NULL}, {
1195   GST_TAG_ALBUM_PEAK, add_relative_volume_tag, NULL}, {
1196   GST_TAG_ALBUM_GAIN, add_relative_volume_tag, NULL}, {
1197 
1198     /* Sortable version of various tags. These are all v2.4 ONLY */
1199   GST_TAG_ARTIST_SORTNAME, add_text_tag_v4, "TSOP"}, {
1200   GST_TAG_ALBUM_SORTNAME, add_text_tag_v4, "TSOA"}, {
1201   GST_TAG_TITLE_SORTNAME, add_text_tag_v4, "TSOT"}
1202 };
1203 
1204 static void
foreach_add_tag(const GstTagList * list,const gchar * tag,gpointer userdata)1205 foreach_add_tag (const GstTagList * list, const gchar * tag, gpointer userdata)
1206 {
1207   GstId3v2Tag *id3v2tag = (GstId3v2Tag *) userdata;
1208   guint num_tags, i;
1209 
1210   num_tags = gst_tag_list_get_tag_size (list, tag);
1211 
1212   GST_LOG ("Processing tag %s (num=%u)", tag, num_tags);
1213 
1214   if (num_tags > 1 && gst_tag_is_fixed (tag)) {
1215     GST_WARNING ("Multiple occurences of fixed tag '%s', ignoring some", tag);
1216     num_tags = 1;
1217   }
1218 
1219   for (i = 0; i < G_N_ELEMENTS (add_funcs); ++i) {
1220     if (strcmp (add_funcs[i].gst_tag, tag) == 0) {
1221       add_funcs[i].func (id3v2tag, list, tag, num_tags, add_funcs[i].data);
1222       break;
1223     }
1224   }
1225 
1226   if (i == G_N_ELEMENTS (add_funcs)) {
1227     GST_WARNING ("Unsupported tag '%s' - not written", tag);
1228   }
1229 }
1230 
1231 GstBuffer *
id3_mux_render_v2_tag(GstTagMux * mux,const GstTagList * taglist,int version)1232 id3_mux_render_v2_tag (GstTagMux * mux, const GstTagList * taglist, int version)
1233 {
1234   GstId3v2Tag tag;
1235   GstBuffer *buf;
1236 
1237   if (!id3v2_tag_init (&tag, version)) {
1238     GST_WARNING_OBJECT (mux, "Unsupported version %d", version);
1239     return NULL;
1240   }
1241 
1242   /* Render the tag */
1243   gst_tag_list_foreach (taglist, foreach_add_tag, &tag);
1244 
1245 #if 0
1246   /* Do we want to add our own signature to the tag somewhere? */
1247   {
1248     gchar *tag_producer_str;
1249 
1250     tag_producer_str = g_strdup_printf ("(GStreamer id3v2mux %s, using "
1251         "taglib %u.%u)", VERSION, TAGLIB_MAJOR_VERSION, TAGLIB_MINOR_VERSION);
1252     add_one_txxx_tag (id3v2tag, "tag_encoder", tag_producer_str);
1253     g_free (tag_producer_str);
1254   }
1255 #endif
1256 
1257   /* Create buffer with tag */
1258   buf = id3v2_tag_to_buffer (&tag);
1259   GST_LOG_OBJECT (mux, "tag size = %d bytes", (int) gst_buffer_get_size (buf));
1260 
1261   id3v2_tag_unset (&tag);
1262 
1263   return buf;
1264 }
1265 
1266 #define ID3_V1_TAG_SIZE 128
1267 
1268 typedef void (*GstId3v1WriteFunc) (const GstTagList * list,
1269     const gchar * gst_tag, guint8 * dst, int len, gboolean * wrote_tag);
1270 
1271 static void
latin1_convert(const GstTagList * list,const gchar * tag,guint8 * dst,int maxlen,gboolean * wrote_tag)1272 latin1_convert (const GstTagList * list, const gchar * tag,
1273     guint8 * dst, int maxlen, gboolean * wrote_tag)
1274 {
1275   gchar *str;
1276   gsize len;
1277   gchar *latin1;
1278 
1279   if (!gst_tag_list_get_string (list, tag, &str) || str == NULL)
1280     return;
1281 
1282   /* Convert to Latin-1 (ISO-8859-1), replacing unrepresentable characters
1283      with '?' */
1284   latin1 =
1285       g_convert_with_fallback (str, -1, "ISO-8859-1", "UTF-8", (char *) "?",
1286       NULL, &len, NULL);
1287 
1288   if (latin1 != NULL && *latin1 != '\0') {
1289     len = MIN (len, maxlen);
1290     memcpy (dst, latin1, len);
1291     *wrote_tag = TRUE;
1292     g_free (latin1);
1293   }
1294 
1295   g_free (str);
1296 }
1297 
1298 static void
date_v1_convert(const GstTagList * list,const gchar * tag,guint8 * dst,int maxlen,gboolean * wrote_tag)1299 date_v1_convert (const GstTagList * list, const gchar * tag,
1300     guint8 * dst, int maxlen, gboolean * wrote_tag)
1301 {
1302   GstDateTime *dt;
1303 
1304   /* Only one date supported */
1305   if (gst_tag_list_get_date_time_index (list, tag, 0, &dt)) {
1306     guint year = gst_date_time_get_year (dt);
1307     /* Check for plausible year */
1308     if (year > 500 && year < 2100) {
1309       gchar str[5];
1310       g_snprintf (str, 5, "%.4u", year);
1311       *wrote_tag = TRUE;
1312       memcpy (dst, str, 4);
1313     } else {
1314       GST_WARNING ("invalid year %u, skipping", year);
1315     }
1316 
1317     gst_date_time_unref (dt);
1318   }
1319 }
1320 
1321 static void
genre_v1_convert(const GstTagList * list,const gchar * tag,guint8 * dst,int maxlen,gboolean * wrote_tag)1322 genre_v1_convert (const GstTagList * list, const gchar * tag,
1323     guint8 * dst, int maxlen, gboolean * wrote_tag)
1324 {
1325   const gchar *str;
1326   int genreidx = -1;
1327   guint i, max;
1328 
1329   /* We only support one genre */
1330   if (!gst_tag_list_peek_string_index (list, tag, 0, &str) || str == NULL)
1331     return;
1332 
1333   max = gst_tag_id3_genre_count ();
1334 
1335   for (i = 0; i < max; i++) {
1336     const gchar *genre = gst_tag_id3_genre_get (i);
1337     if (g_str_equal (str, genre)) {
1338       genreidx = i;
1339       break;
1340     }
1341   }
1342 
1343   if (genreidx >= 0 && genreidx <= 127) {
1344     *dst = (guint8) genreidx;
1345     *wrote_tag = TRUE;
1346   }
1347 }
1348 
1349 static void
track_number_convert(const GstTagList * list,const gchar * tag,guint8 * dst,int maxlen,gboolean * wrote_tag)1350 track_number_convert (const GstTagList * list, const gchar * tag,
1351     guint8 * dst, int maxlen, gboolean * wrote_tag)
1352 {
1353   guint tracknum;
1354 
1355   /* We only support one track number */
1356   if (!gst_tag_list_get_uint_index (list, tag, 0, &tracknum))
1357     return;
1358 
1359   if (tracknum <= 127) {
1360     *dst = (guint8) tracknum;
1361     *wrote_tag = TRUE;
1362   }
1363 }
1364 
1365 /* FIXME: get rid of silly table */
1366 static const struct
1367 {
1368   const gchar *gst_tag;
1369   const gint offset;
1370   const gint length;
1371   const GstId3v1WriteFunc func;
1372 } v1_funcs[] = {
1373   {
1374   GST_TAG_TITLE, 3, 30, latin1_convert}, {
1375   GST_TAG_ARTIST, 33, 30, latin1_convert}, {
1376   GST_TAG_ALBUM, 63, 30, latin1_convert}, {
1377   GST_TAG_DATE_TIME, 93, 4, date_v1_convert}, {
1378   GST_TAG_COMMENT, 97, 28, latin1_convert}, {
1379     /* Note: one-byte gap here */
1380   GST_TAG_TRACK_NUMBER, 126, 1, track_number_convert}, {
1381   GST_TAG_GENRE, 127, 1, genre_v1_convert}
1382 };
1383 
1384 GstBuffer *
id3_mux_render_v1_tag(GstTagMux * mux,const GstTagList * taglist)1385 id3_mux_render_v1_tag (GstTagMux * mux, const GstTagList * taglist)
1386 {
1387   GstMapInfo info;
1388   GstBuffer *buf;
1389   guint8 *data;
1390   gboolean wrote_tag = FALSE;
1391   int i;
1392 
1393   buf = gst_buffer_new_allocate (NULL, ID3_V1_TAG_SIZE, NULL);
1394   gst_buffer_map (buf, &info, GST_MAP_WRITE);
1395   data = info.data;
1396   memset (data, 0, ID3_V1_TAG_SIZE);
1397 
1398   data[0] = 'T';
1399   data[1] = 'A';
1400   data[2] = 'G';
1401 
1402   /* Genre #0 stands for 'Blues', so init genre field to an invalid number */
1403   data[127] = 255;
1404 
1405   for (i = 0; i < G_N_ELEMENTS (v1_funcs); i++) {
1406     v1_funcs[i].func (taglist, v1_funcs[i].gst_tag, data + v1_funcs[i].offset,
1407         v1_funcs[i].length, &wrote_tag);
1408   }
1409 
1410   gst_buffer_unmap (buf, &info);
1411 
1412   if (!wrote_tag) {
1413     GST_WARNING_OBJECT (mux, "no ID3v1 tag written (no suitable tags found)");
1414     gst_buffer_unref (buf);
1415     return NULL;
1416   }
1417 
1418   return buf;
1419 }
1420