cmus-remote: Read command results
[cmus.git] / id3.c
blob94112c77ec57992c8659618a46cb31a2f4052c9d
1 /*
2 * Copyright 2005 Timo Hirvonen
3 */
5 #include "id3.h"
6 #include "comment.h"
7 #include "xmalloc.h"
8 #include "utf8_encode.h"
9 #include "uchar.h"
10 #include "options.h"
11 #include "debug.h"
13 #include <unistd.h>
14 #include <inttypes.h>
15 #include <errno.h>
16 #include <stdio.h>
19 * position:
21 * 0 "ID3"
22 * -10 "3DI"
23 * -128 "TAG"
24 * -138 "3DI"
26 * if v2 is at beginning _and_ at end then there must be a seek tag at beginning
29 struct v2_header {
30 unsigned char ver_major;
31 unsigned char ver_minor;
32 unsigned char flags;
33 uint32_t size;
36 struct v2_extended_header {
37 uint32_t size;
40 struct v2_frame_header {
41 char id[4];
42 uint32_t size;
43 uint16_t flags;
46 #define V2_HEADER_UNSYNC (1 << 7)
47 #define V2_HEADER_EXTENDED (1 << 6)
48 #define V2_HEADER_EXPERIMENTAL (1 << 5)
49 #define V2_HEADER_FOOTER (1 << 4)
51 #define V2_FRAME_COMPRESSED (1 << 3) /* great idea!!1 */
52 #define V2_FRAME_ENCRYPTHED (1 << 2) /* wow, this is very neat! */
53 #define V2_FRAME_UNSYNC (1 << 1)
54 #define V2_FRAME_LEN_INDICATOR (1 << 0)
56 #define NR_GENRES 148
57 /* genres {{{ */
58 static const char *genres[NR_GENRES] = {
59 "Blues",
60 "Classic Rock",
61 "Country",
62 "Dance",
63 "Disco",
64 "Funk",
65 "Grunge",
66 "Hip-Hop",
67 "Jazz",
68 "Metal",
69 "New Age",
70 "Oldies",
71 "Other",
72 "Pop",
73 "R&B",
74 "Rap",
75 "Reggae",
76 "Rock",
77 "Techno",
78 "Industrial",
79 "Alternative",
80 "Ska",
81 "Death Metal",
82 "Pranks",
83 "Soundtrack",
84 "Euro-Techno",
85 "Ambient",
86 "Trip-Hop",
87 "Vocal",
88 "Jazz+Funk",
89 "Fusion",
90 "Trance",
91 "Classical",
92 "Instrumental",
93 "Acid",
94 "House",
95 "Game",
96 "Sound Clip",
97 "Gospel",
98 "Noise",
99 "Alt",
100 "Bass",
101 "Soul",
102 "Punk",
103 "Space",
104 "Meditative",
105 "Instrumental Pop",
106 "Instrumental Rock",
107 "Ethnic",
108 "Gothic",
109 "Darkwave",
110 "Techno-Industrial",
111 "Electronic",
112 "Pop-Folk",
113 "Eurodance",
114 "Dream",
115 "Southern Rock",
116 "Comedy",
117 "Cult",
118 "Gangsta Rap",
119 "Top 40",
120 "Christian Rap",
121 "Pop/Funk",
122 "Jungle",
123 "Native American",
124 "Cabaret",
125 "New Wave",
126 "Psychedelic",
127 "Rave",
128 "Showtunes",
129 "Trailer",
130 "Lo-Fi",
131 "Tribal",
132 "Acid Punk",
133 "Acid Jazz",
134 "Polka",
135 "Retro",
136 "Musical",
137 "Rock & Roll",
138 "Hard Rock",
139 "Folk",
140 "Folk/Rock",
141 "National Folk",
142 "Swing",
143 "Fast-Fusion",
144 "Bebob",
145 "Latin",
146 "Revival",
147 "Celtic",
148 "Bluegrass",
149 "Avantgarde",
150 "Gothic Rock",
151 "Progressive Rock",
152 "Psychedelic Rock",
153 "Symphonic Rock",
154 "Slow Rock",
155 "Big Band",
156 "Chorus",
157 "Easy Listening",
158 "Acoustic",
159 "Humour",
160 "Speech",
161 "Chanson",
162 "Opera",
163 "Chamber Music",
164 "Sonata",
165 "Symphony",
166 "Booty Bass",
167 "Primus",
168 "Porn Groove",
169 "Satire",
170 "Slow Jam",
171 "Club",
172 "Tango",
173 "Samba",
174 "Folklore",
175 "Ballad",
176 "Power Ballad",
177 "Rhythmic Soul",
178 "Freestyle",
179 "Duet",
180 "Punk Rock",
181 "Drum Solo",
182 "A Cappella",
183 "Euro-House",
184 "Dance Hall",
185 "Goa",
186 "Drum & Bass",
187 "Club-House",
188 "Hardcore",
189 "Terror",
190 "Indie",
191 "BritPop",
192 "Negerpunk",
193 "Polsk Punk",
194 "Beat",
195 "Christian Gangsta Rap",
196 "Heavy Metal",
197 "Black Metal",
198 "Crossover",
199 "Contemporary Christian",
200 "Christian Rock",
201 "Merengue",
202 "Salsa",
203 "Thrash Metal",
204 "Anime",
205 "JPop",
206 "Synthpop"
208 /* }}} */
210 #if 1
211 #define id3_debug(...) d_print(__VA_ARGS__)
212 #else
213 #define id3_debug(...) do { } while (0)
214 #endif
216 const char * const id3_key_names[NUM_ID3_KEYS] = {
217 "artist",
218 "album",
219 "title",
220 "date",
221 "genre",
222 "discnumber",
223 "tracknumber",
224 "albumartist",
225 "artistsort",
226 "albumartistsort",
227 "compilation",
228 "replaygain_track_gain",
229 "replaygain_track_peak",
230 "replaygain_album_gain",
231 "replaygain_album_peak"
234 static int utf16_is_special(const uchar uch)
236 if (UTF16_IS_HSURROGATE(uch) || UTF16_IS_LSURROGATE(uch) || UTF16_IS_BOM(uch))
237 return -1;
238 return 0;
241 static char *utf16_to_utf8(const unsigned char *buf, int buf_size)
243 char *out;
244 int i, idx;
246 out = xnew(char, (buf_size / 2) * 4 + 1);
247 i = idx = 0;
248 while (buf_size - i >= 2) {
249 uchar u;
251 u = buf[i] + (buf[i + 1] << 8);
252 if (u_is_unicode(u)) {
253 if (utf16_is_special(u) == 0)
254 u_set_char(out, &idx, u);
255 } else {
256 free(out);
257 return NULL;
259 if (u == 0)
260 return out;
261 i += 2;
263 u_set_char(out, &idx, 0);
264 return out;
267 static char *utf16be_to_utf8(const unsigned char *buf, int buf_size)
269 char *out;
270 int i, idx;
272 out = xnew(char, (buf_size / 2) * 4 + 1);
273 i = 0;
274 idx = 0;
275 while (buf_size - i >= 2) {
276 uchar u;
278 u = buf[i + 1] + (buf[i] << 8);
279 if (u_is_unicode(u)) {
280 if (utf16_is_special(u) == 0)
281 u_set_char(out, &idx, u);
282 } else {
283 free(out);
284 return NULL;
286 if (u == 0)
287 return out;
288 i += 2;
290 u_set_char(out, &idx, 0);
291 return out;
294 static int is_v1(const char *buf)
296 return buf[0] == 'T' && buf[1] == 'A' && buf[2] == 'G';
299 static int u32_unsync(const unsigned char *buf, uint32_t *up)
301 uint32_t b, u = 0;
302 int i;
304 for (i = 0; i < 4; i++) {
305 b = buf[i];
306 if (b >= 0x80)
307 return 0;
308 u <<= 7;
309 u |= b;
311 *up = u;
312 return 1;
315 static void get_u32(const unsigned char *buf, uint32_t *up)
317 uint32_t b, u = 0;
318 int i;
320 for (i = 0; i < 4; i++) {
321 b = buf[i];
322 u <<= 8;
323 u |= b;
325 *up = u;
328 static void get_u24(const unsigned char *buf, uint32_t *up)
330 uint32_t b, u = 0;
331 int i;
333 for (i = 0; i < 3; i++) {
334 b = buf[i];
335 u <<= 8;
336 u |= b;
338 *up = u;
341 static int v2_header_footer_parse(struct v2_header *header, const char *buf)
343 const unsigned char *b = (const unsigned char *)buf;
345 header->ver_major = b[3];
346 header->ver_minor = b[4];
347 header->flags = b[5];
348 if (header->ver_major == 0xff || header->ver_minor == 0xff)
349 return 0;
350 return u32_unsync(b + 6, &header->size);
353 static int v2_header_parse(struct v2_header *header, const char *buf)
355 if (buf[0] != 'I' || buf[1] != 'D' || buf[2] != '3')
356 return 0;
357 return v2_header_footer_parse(header, buf);
360 static int v2_footer_parse(struct v2_header *header, const char *buf)
362 if (buf[0] != '3' || buf[1] != 'D' || buf[2] != 'I')
363 return 0;
364 return v2_header_footer_parse(header, buf);
367 static int v2_extended_header_parse(struct v2_extended_header *header, const char *buf)
369 return u32_unsync((const unsigned char *)buf, &header->size);
372 static int is_frame_id_char(char ch)
374 return (ch >= 'A' && ch <= 'Z') || (ch >= '0' && ch <= '9');
377 /* XXXYYY
379 * X = [A-Z0-9]
380 * Y = byte
382 * XXX is frame
383 * YYY is frame size excluding this 6 byte header
385 static int v2_2_0_frame_header_parse(struct v2_frame_header *header, const char *buf)
387 int i;
389 for (i = 0; i < 3; i++) {
390 if (!is_frame_id_char(buf[i]))
391 return 0;
392 header->id[i] = buf[i];
394 header->id[3] = 0;
395 get_u24((const unsigned char *)(buf + 3), &header->size);
396 header->flags = 0;
397 if (header->size == 0)
398 return 0;
399 id3_debug("%c%c%c %d\n", header->id[0], header->id[1], header->id[2], header->size);
400 return 1;
403 /* XXXXYYYYZZ
405 * X = [A-Z0-9]
406 * Y = byte
407 * Z = byte
409 * XXXX is frame
410 * YYYY is frame size excluding this 10 byte header
411 * ZZ is flags
413 static int v2_3_0_frame_header_parse(struct v2_frame_header *header, const char *buf)
415 int i;
417 for (i = 0; i < 4; i++) {
418 if (!is_frame_id_char(buf[i]))
419 return 0;
420 header->id[i] = buf[i];
422 get_u32((const unsigned char *)(buf + 4), &header->size);
423 header->flags = (buf[8] << 8) | buf[9];
424 if (header->size == 0)
425 return 0;
426 id3_debug("%c%c%c%c %d\n", header->id[0], header->id[1], header->id[2],
427 header->id[3], header->size);
428 return 1;
431 /* same as 2.3 but header size is sync safe */
432 static int v2_4_0_frame_header_parse(struct v2_frame_header *header, const char *buf)
434 int i;
436 for (i = 0; i < 4; i++) {
437 if (!is_frame_id_char(buf[i]))
438 return 0;
439 header->id[i] = buf[i];
441 if (!u32_unsync((const unsigned char *)(buf + 4), &header->size))
442 return 0;
443 header->flags = (buf[8] << 8) | buf[9];
444 if (header->size == 0)
445 return 0;
446 id3_debug("%c%c%c%c %d\n", header->id[0], header->id[1], header->id[2],
447 header->id[3], header->size);
448 return 1;
451 static int read_all(int fd, char *buf, size_t size)
453 size_t pos = 0;
455 while (pos < size) {
456 int rc = read(fd, buf + pos, size - pos);
458 if (rc == -1) {
459 if (errno == EINTR || errno == EAGAIN)
460 continue;
461 return -1;
463 pos += rc;
465 return 0;
468 static char *parse_genre(const char *str)
470 int parenthesis = 0;
471 long int idx;
472 char *end;
474 if (strncasecmp(str, "(RX", 3) == 0)
475 return xstrdup("Remix");
477 if (strncasecmp(str, "(CR", 3) == 0)
478 return xstrdup("Cover");
480 if (*str == '(') {
481 parenthesis = 1;
482 str++;
485 idx = strtol(str, &end, 10);
486 if (str != end) {
487 /* Number parsed but there may be some crap after the number.
488 * I don't care, ID3v2 by definition contains crap.
490 if (idx >= 0 && idx < NR_GENRES)
491 return xstrdup(genres[idx]);
494 if (parenthesis) {
495 const char *ptr = strchr(str, ')');
497 if (ptr && ptr[1]) {
498 /* genre name after random crap in parenthesis,
499 * return the genre name */
500 return xstrdup(ptr + 1);
502 str--;
505 /* random crap, just return it and wait for a bug report */
506 return xstrdup(str);
509 /* http://www.id3.org/id3v2.4.0-structure.txt */
510 static struct {
511 const char name[8];
512 enum id3_key key;
513 } frame_tab[] = {
514 /* 2.4.0 */
515 { "TDRC", ID3_DATE }, // recording date
516 { "TDRL", ID3_DATE }, // release date
517 { "TDOR", ID3_DATE }, // original release date
518 { "TSOP", ID3_ARTISTSORT },
520 /* >= 2.3.0 */
521 { "TPE1", ID3_ARTIST },
522 { "TALB", ID3_ALBUM },
523 { "TIT2", ID3_TITLE },
524 { "TYER", ID3_DATE },
525 { "TCON", ID3_GENRE },
526 { "TPOS", ID3_DISC },
527 { "TRCK", ID3_TRACK },
528 { "TPE2", ID3_ALBUMARTIST },
529 { "XSOP", ID3_ARTISTSORT }, // obsolete
530 { "TCMP", ID3_COMPILATION },
532 /* obsolete frames (2.2.0) */
533 { "TP1", ID3_ARTIST },
534 { "TAL", ID3_ALBUM },
535 { "TT2", ID3_TITLE },
536 { "TYE", ID3_DATE },
537 { "TCO", ID3_GENRE },
538 { "TPA", ID3_DISC },
539 { "TRK", ID3_TRACK },
541 { "", -1 }
544 static int frame_tab_index(const char *id)
546 int i;
548 for (i = 0; frame_tab[i].key != -1; i++) {
549 if (!strncmp(id, frame_tab[i].name, 4))
550 return i;
552 return -1;
555 static void fix_date(char *buf)
557 const char *ptr = buf;
558 int ch, len = 0;
560 do {
561 ch = *ptr++;
562 if (ch >= '0' && ch <= '9') {
563 len++;
564 continue;
566 if (len == 4) {
567 // number which length is 4, must be year
568 memmove(buf, ptr - 5, 4);
569 buf[4] = 0;
570 return;
572 len = 0;
573 } while (ch);
574 *buf = 0;
577 static char *decode_str(const char *buf, int len, int encoding)
579 char *in, *out = NULL;
580 int rc = 0;
582 switch (encoding) {
583 case 0x00: /* ISO-8859-1 */
584 in = xstrndup(buf, len);
585 rc = utf8_encode(in, id3_default_charset, &out);
586 free(in);
587 break;
588 case 0x03: /* UTF-8 */
589 in = xstrndup(buf, len);
590 if (u_is_valid(in)) {
591 out = in;
592 } else {
593 rc = utf8_encode(in, id3_default_charset, &out);
594 free(in);
596 break;
597 case 0x01: /* UTF-16 */
598 out = utf16_to_utf8((const unsigned char *)buf, len);
599 break;
600 case 0x02: /* UTF-16BE */
601 out = utf16be_to_utf8((const unsigned char *)buf, len);
602 break;
604 return out;
607 static void v2_add_frame(struct id3tag *id3, struct v2_frame_header *fh, const char *buf)
609 int idx, encoding = *buf++, len = fh->size - 1;
610 enum id3_key key = NUM_ID3_KEYS;
611 char *out;
613 if (encoding > 3)
614 return;
616 idx = frame_tab_index(fh->id);
617 if (idx >= 0) {
618 key = frame_tab[idx].key;
619 out = decode_str(buf, len, encoding);
620 if (!out)
621 return;
623 if (key == ID3_GENRE) {
624 char *tmp;
626 id3_debug("genre before: '%s'\n", out);
627 tmp = parse_genre(out);
628 free(out);
629 out = tmp;
631 if (key == ID3_DATE) {
632 id3_debug("date before: '%s'\n", out);
633 fix_date(out);
634 if (!*out) {
635 id3_debug("date parsing failed\n");
636 free(out);
637 return;
641 id3_debug("%s '%s'\n", frame_tab[idx].name, out);
642 } else if (!strncmp(fh->id, "TXXX", 4)) {
643 int size;
645 id3_debug("TXXX\n");
647 /* TXXX<len><encoding><key><val> */
648 out = decode_str(buf, len, encoding);
649 if (!out)
650 return;
652 id3_debug("TXXX, key = '%s'\n", out);
653 if (!strcasecmp(out, "replaygain_track_gain"))
654 key = ID3_RG_TRACK_GAIN;
655 if (!strcasecmp(out, "replaygain_track_peak"))
656 key = ID3_RG_TRACK_PEAK;
657 if (!strcasecmp(out, "replaygain_album_gain"))
658 key = ID3_RG_ALBUM_GAIN;
659 if (!strcasecmp(out, "replaygain_album_peak"))
660 key = ID3_RG_ALBUM_PEAK;
661 if (!strcasecmp(out, "album artist"))
662 key = ID3_ALBUMARTIST;
663 if (!strcasecmp(out, "albumartistsort"))
664 key = ID3_ALBUMARTISTSORT;
665 if (!strcasecmp(out, "compilation"))
666 key = ID3_COMPILATION;
668 size = strlen(out) + 1;
669 free(out);
671 if (key == NUM_ID3_KEYS)
672 return;
674 buf += size;
675 len -= size;
676 if (len <= 0)
677 return;
679 out = decode_str(buf, len, encoding);
680 if (!out)
681 return;
683 id3_debug("TXXX, val = '%s'\n", out);
684 } else {
685 return;
688 free(id3->v2[key]);
689 id3->v2[key] = out;
690 id3->has_v2 = 1;
693 static void unsync(unsigned char *buf, int *lenp)
695 int len = *lenp;
696 int s, d;
698 s = d = 0;
699 while (s < len - 1) {
700 if (buf[s] == 0xff && buf[s + 1] == 0x00) {
701 /* 0xff 0x00 -> 0xff */
702 buf[d++] = 0xff;
703 s += 2;
705 if (s < len - 2 && buf[s] == 0x00) {
706 /* 0xff 0x00 0x00 -> 0xff 0x00 */
707 buf[d++] = 0x00;
708 s++;
710 continue;
712 buf[d++] = buf[s++];
714 if (s < len)
715 buf[d++] = buf[s++];
717 d_print("unsyncronization removed %d bytes\n", s - d);
718 *lenp = d;
721 static int v2_read(struct id3tag *id3, int fd, const struct v2_header *header)
723 char *buf;
724 int rc, buf_size;
725 int frame_start, i;
726 int frame_header_size;
728 buf_size = header->size;
729 buf = xnew(char, buf_size);
730 rc = read_all(fd, buf, buf_size);
731 if (rc) {
732 free(buf);
733 return rc;
736 frame_start = 0;
737 if (header->flags & V2_HEADER_EXTENDED) {
738 struct v2_extended_header ext;
740 v2_extended_header_parse(&ext, buf);
741 if (ext.size > buf_size) {
742 id3_debug("extended header corrupted\n");
743 free(buf);
744 return -2;
746 frame_start = ext.size;
747 /* should check if update flag is set */
750 if (header->flags & V2_HEADER_UNSYNC) {
751 int len = buf_size - frame_start;
753 unsync((unsigned char *)(buf + frame_start), &len);
754 buf_size = len + frame_start;
757 frame_header_size = 10;
758 if (header->ver_major == 2)
759 frame_header_size = 6;
761 i = frame_start;
762 while (i < buf_size - frame_header_size) {
763 struct v2_frame_header fh;
764 int len;
766 if (header->ver_major == 2) {
767 if (!v2_2_0_frame_header_parse(&fh, buf + i))
768 break;
769 } else if (header->ver_major == 3) {
770 if (!v2_3_0_frame_header_parse(&fh, buf + i))
771 break;
772 } else {
773 /* assume v2.4 */
774 if (!v2_4_0_frame_header_parse(&fh, buf + i))
775 break;
778 i += frame_header_size;
779 if (fh.size > buf_size - i) {
780 id3_debug("frame too big\n");
781 break;
784 len = fh.size;
785 if (fh.flags & V2_FRAME_UNSYNC) {
786 int tmp = len;
788 unsync((unsigned char *)(buf + i), &tmp);
789 fh.size = tmp;
791 v2_add_frame(id3, &fh, buf + i);
792 i += len;
795 free(buf);
796 return 0;
799 int id3_tag_size(const char *buf, int buf_size)
801 struct v2_header header;
803 if (buf_size < 10)
804 return 0;
805 if (v2_header_parse(&header, buf)) {
806 if (header.flags & V2_HEADER_FOOTER) {
807 /* header + data + footer */
808 id3_debug("v2.%d.%d with footer\n", header.ver_major, header.ver_minor);
809 return 10 + header.size + 10;
811 /* header */
812 id3_debug("v2.%d.%d\n", header.ver_major, header.ver_minor);
813 return 10 + header.size;
815 if (buf_size >= 3 && is_v1(buf)) {
816 id3_debug("v1\n");
817 return 128;
819 return 0;
822 void id3_free(struct id3tag *id3)
824 int i;
826 for (i = 0; i < NUM_ID3_KEYS; i++)
827 free(id3->v2[i]);
830 int id3_read_tags(struct id3tag *id3, int fd, unsigned int flags)
832 off_t off;
833 int rc;
835 if (flags & ID3_V2) {
836 struct v2_header header;
837 char buf[138];
839 rc = read_all(fd, buf, 10);
840 if (rc)
841 goto rc_error;
842 if (v2_header_parse(&header, buf)) {
843 rc = v2_read(id3, fd, &header);
844 if (rc)
845 goto rc_error;
846 /* get v1 if needed */
847 } else {
848 /* get v2 from end and optionally v1 */
850 off = lseek(fd, -138, SEEK_END);
851 if (off == -1)
852 goto error;
853 rc = read_all(fd, buf, 138);
854 if (rc)
855 goto rc_error;
857 if (is_v1(buf + 10)) {
858 if (flags & ID3_V1) {
859 memcpy(id3->v1, buf + 10, 128);
860 id3->has_v1 = 1;
862 if (v2_footer_parse(&header, buf)) {
863 /* footer at end of file - 128 */
864 off = lseek(fd, -(header.size + 138), SEEK_END);
865 if (off == -1)
866 goto error;
867 rc = v2_read(id3, fd, &header);
868 if (rc)
869 goto rc_error;
871 } else if (v2_footer_parse(&header, buf + 128)) {
872 /* footer at end of file */
873 off = lseek(fd, -(header.size + 10), SEEK_END);
874 if (off == -1)
875 goto error;
876 rc = v2_read(id3, fd, &header);
877 if (rc)
878 goto rc_error;
880 return 0;
883 if (flags & ID3_V1) {
884 off = lseek(fd, -128, SEEK_END);
885 if (off == -1)
886 goto error;
887 rc = read_all(fd, id3->v1, 128);
888 if (rc)
889 goto rc_error;
890 id3->has_v1 = is_v1(id3->v1);
892 return 0;
893 error:
894 rc = -1;
895 rc_error:
896 return rc;
899 static char *v1_get_str(const char *buf, int len)
901 char in[32];
902 char *out;
903 int i;
905 for (i = len - 1; i >= 0; i--) {
906 if (buf[i] != 0 && buf[i] != ' ')
907 break;
909 if (i == -1)
910 return NULL;
911 memcpy(in, buf, i + 1);
912 in[i + 1] = 0;
913 if (u_is_valid(in))
914 return xstrdup(in);
915 if (utf8_encode(in, id3_default_charset, &out))
916 return NULL;
917 return out;
920 char *id3_get_comment(struct id3tag *id3, enum id3_key key)
922 if (id3->has_v2) {
923 if (id3->v2[key])
924 return xstrdup(id3->v2[key]);
926 if (id3->has_v1) {
927 switch (key) {
928 case ID3_ARTIST:
929 return v1_get_str(id3->v1 + 33, 30);
930 case ID3_ALBUM:
931 return v1_get_str(id3->v1 + 63, 30);
932 case ID3_TITLE:
933 return v1_get_str(id3->v1 + 3, 30);
934 case ID3_DATE:
935 return v1_get_str(id3->v1 + 93, 4);
936 case ID3_GENRE:
938 unsigned char idx = id3->v1[127];
940 if (idx >= NR_GENRES)
941 return NULL;
942 return xstrdup(genres[idx]);
944 case ID3_TRACK:
946 char *t;
948 if (id3->v1[125] != 0)
949 return NULL;
950 t = xnew(char, 4);
951 snprintf(t, 4, "%d", ((unsigned char *)id3->v1)[126]);
952 return t;
954 default:
955 return NULL;
958 return NULL;