/* * id3tag.c -- Write ID3 version 1 and 2 tags. * * Copyright (C) 2000 Don Melton. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. */ /* * HISTORY: This source file is part of LAME (see http://www.mp3dev.org) * and was originally adapted by Conrad Sanderson * from mp3info by Ricardo Cerqueira to write only ID3 version 1 * tags. Don Melton COMPLETELY rewrote it to support version * 2 tags and be more conformant to other standards while remaining flexible. * * NOTE: See http://id3.org/ for more information about ID3 tag formats. */ /* $Id: id3tag.c,v 1.53.2.5 2010/02/19 00:44:37 robert Exp $ */ #ifdef HAVE_CONFIG_H #include #endif #ifdef STDC_HEADERS # include # include # include # include #else # ifndef HAVE_STRCHR # define strchr index # define strrchr rindex # endif char *strchr(), *strrchr(); # ifndef HAVE_MEMCPY # define memcpy(d, s, n) bcopy ((s), (d), (n)) # define memmove(d, s, n) bcopy ((s), (d), (n)) # endif #endif #ifdef _MSC_VER #define snprintf _snprintf #endif #include "lame.h" #include "machine.h" #include "encoder.h" #include "id3tag.h" #include "lame_global_flags.h" #include "util.h" #include "bitstream.h" static const char *const genre_names[] = { /* * NOTE: The spelling of these genre names is identical to those found in * Winamp and mp3info. */ "Blues", "Classic Rock", "Country", "Dance", "Disco", "Funk", "Grunge", "Hip-Hop", "Jazz", "Metal", "New Age", "Oldies", "Other", "Pop", "R&B", "Rap", "Reggae", "Rock", "Techno", "Industrial", "Alternative", "Ska", "Death Metal", "Pranks", "Soundtrack", "Euro-Techno", "Ambient", "Trip-Hop", "Vocal", "Jazz+Funk", "Fusion", "Trance", "Classical", "Instrumental", "Acid", "House", "Game", "Sound Clip", "Gospel", "Noise", "Alternative Rock", "Bass", "Soul", "Punk", "Space", "Meditative", "Instrumental Pop", "Instrumental Rock", "Ethnic", "Gothic", "Darkwave", "Techno-Industrial", "Electronic", "Pop-Folk", "Eurodance", "Dream", "Southern Rock", "Comedy", "Cult", "Gangsta", "Top 40", "Christian Rap", "Pop/Funk", "Jungle", "Native US", "Cabaret", "New Wave", "Psychedelic", "Rave", "Showtunes", "Trailer", "Lo-Fi", "Tribal", "Acid Punk", "Acid Jazz", "Polka", "Retro", "Musical", "Rock & Roll", "Hard Rock", "Folk", "Folk-Rock", "National Folk", "Swing", "Fast Fusion", "Bebob", "Latin", "Revival", "Celtic", "Bluegrass", "Avantgarde", "Gothic Rock", "Progressive Rock", "Psychedelic Rock", "Symphonic Rock", "Slow Rock", "Big Band", "Chorus", "Easy Listening", "Acoustic", "Humour", "Speech", "Chanson", "Opera", "Chamber Music", "Sonata", "Symphony", "Booty Bass", "Primus", "Porn Groove", "Satire", "Slow Jam", "Club", "Tango", "Samba", "Folklore", "Ballad", "Power Ballad", "Rhythmic Soul", "Freestyle", "Duet", "Punk Rock", "Drum Solo", "A Cappella", "Euro-House", "Dance Hall", "Goa", "Drum & Bass", "Club-House", "Hardcore", "Terror", "Indie", "BritPop", "Negerpunk", "Polsk Punk", "Beat", "Christian Gangsta", "Heavy Metal", "Black Metal", "Crossover", "Contemporary Christian", "Christian Rock", "Merengue", "Salsa", "Thrash Metal", "Anime", "JPop", "SynthPop" }; #define GENRE_NAME_COUNT \ ((int)(sizeof genre_names / sizeof (const char *const))) static const int genre_alpha_map[] = { 123, 34, 74, 73, 99, 20, 40, 26, 145, 90, 116, 41, 135, 85, 96, 138, 89, 0, 107, 132, 65, 88, 104, 102, 97, 136, 61, 141, 32, 1, 112, 128, 57, 140, 2, 139, 58, 3, 125, 50, 22, 4, 55, 127, 122, 120, 98, 52, 48, 54, 124, 25, 84, 80, 115, 81, 119, 5, 30, 36, 59, 126, 38, 49, 91, 6, 129, 79, 137, 7, 35, 100, 131, 19, 33, 46, 47, 8, 29, 146, 63, 86, 71, 45, 142, 9, 77, 82, 64, 133, 10, 66, 39, 11, 103, 12, 75, 134, 13, 53, 62, 109, 117, 23, 108, 92, 67, 93, 43, 121, 15, 68, 14, 16, 76, 87, 118, 17, 78, 143, 114, 110, 69, 21, 111, 95, 105, 42, 37, 24, 56, 44, 101, 83, 94, 106, 147, 113, 18, 51, 130, 144, 60, 70, 31, 72, 27, 28 }; #define GENRE_ALPHA_COUNT ((int)(sizeof genre_alpha_map / sizeof (int))) #define GENRE_INDEX_OTHER 12 #define FRAME_ID(a, b, c, d) \ ( ((unsigned long)(a) << 24) \ | ((unsigned long)(b) << 16) \ | ((unsigned long)(c) << 8) \ | ((unsigned long)(d) << 0) ) typedef enum UsualStringIDs { ID_TITLE = FRAME_ID('T', 'I', 'T', '2') , ID_ARTIST = FRAME_ID('T', 'P', 'E', '1') , ID_ALBUM = FRAME_ID('T', 'A', 'L', 'B') , ID_GENRE = FRAME_ID('T', 'C', 'O', 'N') , ID_ENCODER = FRAME_ID('T', 'S', 'S', 'E') , ID_PLAYLENGTH = FRAME_ID('T', 'L', 'E', 'N') , ID_COMMENT = FRAME_ID('C', 'O', 'M', 'M') /* full text string */ } UsualStringIDs; typedef enum NumericStringIDs { ID_DATE = FRAME_ID('T', 'D', 'A', 'T') /* "ddMM" */ , ID_TIME = FRAME_ID('T', 'I', 'M', 'E') /* "hhmm" */ , ID_TPOS = FRAME_ID('T', 'P', 'O', 'S') /* '0'-'9' and '/' allowed */ , ID_TRACK = FRAME_ID('T', 'R', 'C', 'K') /* '0'-'9' and '/' allowed */ , ID_YEAR = FRAME_ID('T', 'Y', 'E', 'R') /* "yyyy" */ } NumericStringIDs; typedef enum MiscIDs { ID_TXXX = FRAME_ID('T', 'X', 'X', 'X') , ID_WXXX = FRAME_ID('W', 'X', 'X', 'X') , ID_SYLT = FRAME_ID('S', 'Y', 'L', 'T') , ID_APIC = FRAME_ID('A', 'P', 'I', 'C') , ID_GEOB = FRAME_ID('G', 'E', 'O', 'B') , ID_PCNT = FRAME_ID('P', 'C', 'N', 'T') , ID_AENC = FRAME_ID('A', 'E', 'N', 'C') , ID_LINK = FRAME_ID('L', 'I', 'N', 'K') , ID_ENCR = FRAME_ID('E', 'N', 'C', 'R') , ID_GRID = FRAME_ID('G', 'R', 'I', 'D') , ID_PRIV = FRAME_ID('P', 'R', 'I', 'V') , ID_VSLT = FRAME_ID('V', 'S', 'L', 'T') /* full text string */ } MiscIDs; static int id3v2_add_ucs2(lame_global_flags * gfp, int frame_id, char const *lang, unsigned short const *desc, unsigned short const *text); static int id3v2_add_latin1(lame_global_flags * gfp, int frame_id, char const *lang, char const *desc, char const *text); static void copyV1ToV2(lame_global_flags * gfp, int frame_id, char const *s) { lame_internal_flags *gfc = gfp->internal_flags; int flags = gfc->tag_spec.flags; id3v2_add_latin1(gfp, frame_id, 0, 0, s); gfc->tag_spec.flags = flags; } static void id3v2AddLameVersion(lame_global_flags * gfp) { char buffer[1024]; const char *b = get_lame_os_bitness(); const char *v = get_lame_version(); const char *u = get_lame_url(); const size_t lenb = strlen(b); if (lenb > 0) { sprintf(buffer, "LAME %s version %s (%s)", b, v, u); } else { sprintf(buffer, "LAME version %s (%s)", v, u); } copyV1ToV2(gfp, ID_ENCODER, buffer); } static void id3v2AddAudioDuration(lame_global_flags * gfp) { if (gfp->num_samples != MAX_U_32_NUM) { char buffer[1024]; double const max_ulong = MAX_U_32_NUM; double ms = gfp->num_samples; unsigned long playlength_ms; ms *= 1000; ms /= gfp->in_samplerate; if (ms > max_ulong) { playlength_ms = max_ulong; } else if (ms < 0) { playlength_ms = 0; } else { playlength_ms = ms; } snprintf(buffer, sizeof(buffer), "%lu", playlength_ms); copyV1ToV2(gfp, ID_PLAYLENGTH, buffer); } } void id3tag_genre_list(void (*handler) (int, const char *, void *), void *cookie) { if (handler) { int i; for (i = 0; i < GENRE_NAME_COUNT; ++i) { if (i < GENRE_ALPHA_COUNT) { int j = genre_alpha_map[i]; handler(j, genre_names[j], cookie); } } } } #define GENRE_NUM_UNKNOWN 255 void id3tag_init(lame_global_flags * gfp) { lame_internal_flags *gfc = gfp->internal_flags; free_id3tag(gfc); memset(&gfc->tag_spec, 0, sizeof gfc->tag_spec); gfc->tag_spec.genre_id3v1 = GENRE_NUM_UNKNOWN; gfc->tag_spec.padding_size = 128; id3v2AddLameVersion(gfp); } void id3tag_add_v2(lame_global_flags * gfp) { lame_internal_flags *gfc = gfp->internal_flags; gfc->tag_spec.flags &= ~V1_ONLY_FLAG; gfc->tag_spec.flags |= ADD_V2_FLAG; } void id3tag_v1_only(lame_global_flags * gfp) { lame_internal_flags *gfc = gfp->internal_flags; gfc->tag_spec.flags &= ~(ADD_V2_FLAG | V2_ONLY_FLAG); gfc->tag_spec.flags |= V1_ONLY_FLAG; } void id3tag_v2_only(lame_global_flags * gfp) { lame_internal_flags *gfc = gfp->internal_flags; gfc->tag_spec.flags &= ~V1_ONLY_FLAG; gfc->tag_spec.flags |= V2_ONLY_FLAG; } void id3tag_space_v1(lame_global_flags * gfp) { lame_internal_flags *gfc = gfp->internal_flags; gfc->tag_spec.flags &= ~V2_ONLY_FLAG; gfc->tag_spec.flags |= SPACE_V1_FLAG; } void id3tag_pad_v2(lame_global_flags * gfp) { id3tag_set_pad(gfp, 128); } void id3tag_set_pad(lame_global_flags * gfp, size_t n) { lame_internal_flags *gfc = gfp->internal_flags; gfc->tag_spec.flags &= ~V1_ONLY_FLAG; gfc->tag_spec.flags |= PAD_V2_FLAG; gfc->tag_spec.flags |= ADD_V2_FLAG; gfc->tag_spec.padding_size = n; } static size_t local_strdup(char **dst, const char *src) { if (dst == 0) { return 0; } free(*dst); *dst = 0; if (src != 0) { size_t n; for (n = 0; src[n] != 0; ++n) { /* calc src string length */ } if (n > 0) { /* string length without zero termination */ assert(sizeof(*src) == sizeof(**dst)); *dst = malloc((n + 1) * sizeof(**dst)); if (*dst != 0) { memcpy(*dst, src, n * sizeof(**dst)); (*dst)[n] = 0; return n; } } } return 0; } static size_t local_ucs2_strdup(unsigned short **dst, unsigned short const *src) { if (dst == 0) { return 0; } free(*dst); /* free old string pointer */ *dst = 0; if (src != 0) { size_t n; for (n = 0; src[n] != 0; ++n) { /* calc src string length */ } if (n > 0) { /* string length without zero termination */ assert(sizeof(*src) >= 2); assert(sizeof(*src) == sizeof(**dst)); *dst = malloc((n + 1) * sizeof(**dst)); if (*dst != 0) { memcpy(*dst, src, n * sizeof(**dst)); (*dst)[n] = 0; return n; } } } return 0; } #if 0 static size_t local_ucs2_strlen(unsigned short const *s) { size_t n = 0; if (s != 0) { while (*s++) { ++n; } } return n; } #endif /* Some existing options for ID3 tag can be specified by --tv option as follows. --tt , --tv TIT2=value --ta , --tv TPE1=value --tl , --tv TALB=value --ty , --tv TYER=value --tn , --tv TRCK=value --tg , --tv TCON=value (although some are not exactly same)*/ int id3tag_set_albumart(lame_global_flags * gfp, const char *image, size_t size) { int mimetype = 0; unsigned char const *data = (unsigned char const *) image; lame_internal_flags *gfc = gfp->internal_flags; /* make sure the image size is no larger than the maximum value */ if (LAME_MAXALBUMART < size) { return -1; } /* determine MIME type from the actual image data */ if (2 < size && data[0] == 0xFF && data[1] == 0xD8) { mimetype = MIMETYPE_JPEG; } else if (4 < size && data[0] == 0x89 && strncmp((const char *) &data[1], "PNG", 3) == 0) { mimetype = MIMETYPE_PNG; } else if (4 < size && strncmp((const char *) data, "GIF8", 4) == 0) { mimetype = MIMETYPE_GIF; } else { return -1; } if (gfc->tag_spec.albumart != 0) { free(gfc->tag_spec.albumart); gfc->tag_spec.albumart = 0; gfc->tag_spec.albumart_size = 0; gfc->tag_spec.albumart_mimetype = MIMETYPE_NONE; } if (size < 1) { return 0; } gfc->tag_spec.albumart = malloc(size); if (gfc->tag_spec.albumart != 0) { memcpy(gfc->tag_spec.albumart, image, size); gfc->tag_spec.albumart_size = size; gfc->tag_spec.albumart_mimetype = mimetype; gfc->tag_spec.flags |= CHANGED_FLAG; id3tag_add_v2(gfp); } return 0; } static unsigned char * set_4_byte_value(unsigned char *bytes, uint32_t value) { int i; for (i = 3; i >= 0; --i) { bytes[i] = value & 0xffUL; value >>= 8; } return bytes + 4; } static int toID3v2TagId(char const *s) { int i, x = 0; if (s == 0) { return 0; } for (i = 0; i < 4 && s[i] != 0; ++i) { char const c = s[i]; unsigned int const u = 0x0ff & c; x <<= 8; x |= u; if (c < 'A' || 'Z' < c) { if (c < '0' || '9' < c) { return 0; } } } return x; } static int isNumericString(int frame_id) { switch (frame_id) { case ID_DATE: case ID_TIME: case ID_TPOS: case ID_TRACK: case ID_YEAR: return 1; } return 0; } static int isMultiFrame(int frame_id) { switch (frame_id) { case ID_TXXX: case ID_WXXX: case ID_COMMENT: case ID_SYLT: case ID_APIC: case ID_GEOB: case ID_PCNT: case ID_AENC: case ID_LINK: case ID_ENCR: case ID_GRID: case ID_PRIV: return 1; } return 0; } #if 0 static int isFullTextString(int frame_id) { switch (frame_id) { case ID_VSLT: case ID_COMMENT: return 1; } return 0; } #endif static int hasUcs2ByteOrderMarker(unsigned short bom) { if (bom == 0xFFFEu || bom == 0xFEFFu) { return 1; } return 0; } static FrameDataNode * findNode(id3tag_spec const *tag, int frame_id, FrameDataNode * last) { FrameDataNode *node = last ? last->nxt : tag->v2_head; while (node != 0) { if (node->fid == frame_id) { return node; } node = node->nxt; } return 0; } static void appendNode(id3tag_spec * tag, FrameDataNode * node) { if (tag->v2_tail == 0 || tag->v2_head == 0) { tag->v2_head = node; tag->v2_tail = node; } else { tag->v2_tail->nxt = node; tag->v2_tail = node; } } static void setLang(char *dst, char const *src) { int i; if (src == 0 || src[0] == 0) { dst[0] = 'X'; dst[1] = 'X'; dst[2] = 'X'; } else { for (i = 0; i < 3 && src && *src; ++i) { dst[i] = src[i]; } for (; i < 3; ++i) { dst[i] = ' '; } } } static int isSameLang(char const *l1, char const *l2) { char d[3]; int i; setLang(d, l2); for (i = 0; i < 3; ++i) { char a = tolower(l1[i]); char b = tolower(d[i]); if (a < ' ') a = ' '; if (b < ' ') b = ' '; if (a != b) { return 0; } } return 1; } static int isSameDescriptor(FrameDataNode * node, char const *dsc) { size_t i; if (node->dsc.enc == 1 && node->dsc.dim > 0) { return 0; } for (i = 0; i < node->dsc.dim; ++i) { if (!dsc || node->dsc.ptr.l[i] != dsc[i]) { return 0; } } return 1; } static int isSameDescriptorUcs2(FrameDataNode const *node, unsigned short const *dsc) { size_t i; if (node->dsc.enc != 1 && node->dsc.dim > 0) { return 0; } for (i = 0; i < node->dsc.dim; ++i) { if (!dsc || node->dsc.ptr.u[i] != dsc[i]) { return 0; } } return 1; } static int id3v2_add_ucs2(lame_global_flags * gfp, int frame_id, char const *lang, unsigned short const *desc, unsigned short const *text) { lame_internal_flags *gfc = gfp->internal_flags; if (gfc != 0) { FrameDataNode *node = 0; node = findNode(&gfc->tag_spec, frame_id, 0); if (isMultiFrame(frame_id)) { while (node) { if (isSameLang(node->lng, lang)) { if (isSameDescriptorUcs2(node, desc)) { break; } } node = findNode(&gfc->tag_spec, frame_id, node); } } if (node == 0) { node = calloc(1, sizeof(FrameDataNode)); if (node == 0) { return -254; /* memory problem */ } appendNode(&gfc->tag_spec, node); } node->fid = frame_id; setLang(node->lng, lang); node->dsc.dim = local_ucs2_strdup(&node->dsc.ptr.u, desc); node->dsc.enc = 1; node->txt.dim = local_ucs2_strdup(&node->txt.ptr.u, text); node->txt.enc = 1; gfc->tag_spec.flags |= (CHANGED_FLAG | ADD_V2_FLAG); } return 0; } static int id3v2_add_latin1(lame_global_flags * gfp, int frame_id, char const *lang, char const *desc, char const *text) { lame_internal_flags *gfc = gfp->internal_flags; if (gfc != 0) { FrameDataNode *node = 0; node = findNode(&gfc->tag_spec, frame_id, 0); if (isMultiFrame(frame_id)) { while (node) { if (isSameLang(node->lng, lang)) { if (isSameDescriptor(node, desc)) { break; } } node = findNode(&gfc->tag_spec, frame_id, node); } } if (node == 0) { node = calloc(1, sizeof(FrameDataNode)); if (node == 0) { return -254; /* memory problem */ } appendNode(&gfc->tag_spec, node); } node->fid = frame_id; setLang(node->lng, lang); node->dsc.dim = local_strdup(&node->dsc.ptr.l, desc); node->dsc.enc = 0; node->txt.dim = local_strdup(&node->txt.ptr.l, text); node->txt.enc = 0; gfc->tag_spec.flags |= (CHANGED_FLAG | ADD_V2_FLAG); } return 0; } int id3tag_set_textinfo_ucs2(lame_global_flags * gfp, char const *id, unsigned short const *text) { int const t_mask = FRAME_ID('T', 0, 0, 0); int const frame_id = toID3v2TagId(id); if (frame_id == 0) { return -1; } if ((frame_id & t_mask) == t_mask) { if (isNumericString(frame_id)) { return -2; /* must be Latin-1 encoded */ } if (text == 0) { return 0; } if (!hasUcs2ByteOrderMarker(text[0])) { return -3; /* BOM missing */ } if (gfp != 0) { return id3v2_add_ucs2(gfp, frame_id, 0, 0, text); } } return -255; /* not supported by now */ } int id3tag_set_textinfo_latin1(lame_global_flags * gfp, char const *id, char const *text) { int const t_mask = FRAME_ID('T', 0, 0, 0); int const frame_id = toID3v2TagId(id); if (frame_id == 0) { return -1; } if ((frame_id & t_mask) == t_mask) { if (text == 0) { return 0; } if (gfp != 0) { return id3v2_add_latin1(gfp, frame_id, 0, 0, text); } } return -255; /* not supported by now */ } int id3tag_set_comment_latin1(lame_global_flags * gfp, char const *lang, char const *desc, char const *text) { if (gfp != 0) { return id3v2_add_latin1(gfp, ID_COMMENT, lang, desc, text); } return -255; } int id3tag_set_comment_ucs2(lame_global_flags * gfp, char const *lang, unsigned short const *desc, unsigned short const *text) { if (gfp != 0) { return id3v2_add_ucs2(gfp, ID_COMMENT, lang, desc, text); } return -255; } void id3tag_set_title(lame_global_flags * gfp, const char *title) { lame_internal_flags *gfc = gfp->internal_flags; if (title && *title) { local_strdup(&gfc->tag_spec.title, title); gfc->tag_spec.flags |= CHANGED_FLAG; copyV1ToV2(gfp, ID_TITLE, title); } } void id3tag_set_artist(lame_global_flags * gfp, const char *artist) { lame_internal_flags *gfc = gfp->internal_flags; if (artist && *artist) { local_strdup(&gfc->tag_spec.artist, artist); gfc->tag_spec.flags |= CHANGED_FLAG; copyV1ToV2(gfp, ID_ARTIST, artist); } } void id3tag_set_album(lame_global_flags * gfp, const char *album) { lame_internal_flags *gfc = gfp->internal_flags; if (album && *album) { local_strdup(&gfc->tag_spec.album, album); gfc->tag_spec.flags |= CHANGED_FLAG; copyV1ToV2(gfp, ID_ALBUM, album); } } void id3tag_set_year(lame_global_flags * gfp, const char *year) { lame_internal_flags *gfc = gfp->internal_flags; if (year && *year) { int num = atoi(year); if (num < 0) { num = 0; } /* limit a year to 4 digits so it fits in a version 1 tag */ if (num > 9999) { num = 9999; } if (num) { gfc->tag_spec.year = num; gfc->tag_spec.flags |= CHANGED_FLAG; } copyV1ToV2(gfp, ID_YEAR, year); } } void id3tag_set_comment(lame_global_flags * gfp, const char *comment) { lame_internal_flags *gfc = gfp->internal_flags; if (comment && *comment) { local_strdup(&gfc->tag_spec.comment, comment); gfc->tag_spec.flags |= CHANGED_FLAG; { int const flags = gfc->tag_spec.flags; id3v2_add_latin1(gfp, ID_COMMENT, "XXX", "", comment); gfc->tag_spec.flags = flags; } } } int id3tag_set_track(lame_global_flags * gfp, const char *track) { char const *trackcount; lame_internal_flags *gfc = gfp->internal_flags; int ret = 0; if (track && *track) { int num = atoi(track); /* check for valid ID3v1 track number range */ if (num < 1 || num > 255) { num = 0; ret = -1; /* track number out of ID3v1 range, ignored for ID3v1 */ gfc->tag_spec.flags |= (CHANGED_FLAG | ADD_V2_FLAG); } if (num) { gfc->tag_spec.track_id3v1 = num; gfc->tag_spec.flags |= CHANGED_FLAG; } /* Look for the total track count after a "/", same restrictions */ trackcount = strchr(track, '/'); if (trackcount && *trackcount) { gfc->tag_spec.flags |= (CHANGED_FLAG | ADD_V2_FLAG); } copyV1ToV2(gfp, ID_TRACK, track); } return ret; } /* would use real "strcasecmp" but it isn't portable */ static int local_strcasecmp(const char *s1, const char *s2) { unsigned char c1; unsigned char c2; do { c1 = tolower(*s1); c2 = tolower(*s2); if (!c1) { break; } ++s1; ++s2; } while (c1 == c2); return c1 - c2; } static const char* nextUpperAlpha(const char* p, char x) { char c; for(c = toupper(*p); *p != 0; c = toupper(*++p)) { if ('A' <= c && c <= 'Z') { if (c != x) { return p; } } } return p; } static int sloppyCompared(const char* p, const char* q) { char cp, cq; p = nextUpperAlpha(p, 0); q = nextUpperAlpha(q, 0); cp = toupper(*p); cq = toupper(*q); while (cp == cq) { if (cp == 0) { return 1; } if (p[1] == '.') { /* some abbrevation */ while (*q && *q++ != ' ') { } } p = nextUpperAlpha(p, cp); q = nextUpperAlpha(q, cq); cp = toupper(*p); cq = toupper(*q); } return 0; } static int sloppySearchGenre(const char *genre) { int i; for (i = 0; i < GENRE_NAME_COUNT; ++i) { if (sloppyCompared(genre, genre_names[i])) { return i; } } return GENRE_NAME_COUNT; } static int searchGenre(const char* genre) { int i; for (i = 0; i < GENRE_NAME_COUNT; ++i) { if (!local_strcasecmp(genre, genre_names[i])) { return i; } } return GENRE_NAME_COUNT; } int id3tag_set_genre(lame_global_flags * gfp, const char *genre) { lame_internal_flags *gfc = gfp->internal_flags; int ret = 0; if (genre && *genre) { char *str; int num = strtol(genre, &str, 10); /* is the input a string or a valid number? */ if (*str) { num = searchGenre(genre); if (num == GENRE_NAME_COUNT) { num = sloppySearchGenre(genre); } if (num == GENRE_NAME_COUNT) { num = GENRE_INDEX_OTHER; ret = -2; } else { genre = genre_names[num]; } } else { if ((num < 0) || (num >= GENRE_NAME_COUNT)) { return -1; } genre = genre_names[num]; } gfc->tag_spec.genre_id3v1 = num; gfc->tag_spec.flags |= CHANGED_FLAG; if (ret) { gfc->tag_spec.flags |= ADD_V2_FLAG; } copyV1ToV2(gfp, ID_GENRE, genre); } return ret; } static unsigned char * set_frame_custom(unsigned char *frame, const char *fieldvalue) { if (fieldvalue && *fieldvalue) { const char *value = fieldvalue + 5; size_t length = strlen(value); *frame++ = *fieldvalue++; *frame++ = *fieldvalue++; *frame++ = *fieldvalue++; *frame++ = *fieldvalue++; frame = set_4_byte_value(frame, (unsigned long) (strlen(value) + 1)); /* clear 2-byte header flags */ *frame++ = 0; *frame++ = 0; /* clear 1 encoding descriptor byte to indicate ISO-8859-1 format */ *frame++ = 0; while (length--) { *frame++ = *value++; } } return frame; } static size_t sizeOfNode(FrameDataNode const *node) { size_t n = 0; if (node) { n = 10; /* header size */ n += 1; /* text encoding flag */ switch (node->txt.enc) { default: case 0: n += node->txt.dim; break; case 1: n += node->txt.dim * 2; break; } } return n; } static size_t sizeOfCommentNode(FrameDataNode const *node) { size_t n = 0; if (node) { n = 10; /* header size */ n += 1; /* text encoding flag */ n += 3; /* language */ switch (node->dsc.enc) { default: case 0: n += 1 + node->dsc.dim; break; case 1: n += 2 + node->dsc.dim * 2; break; } switch (node->txt.enc) { default: case 0: n += node->txt.dim; break; case 1: n += node->txt.dim * 2; break; } } return n; } static unsigned char * writeChars(unsigned char *frame, char const *str, size_t n) { while (n--) { *frame++ = *str++; } return frame; } static unsigned char * writeUcs2s(unsigned char *frame, unsigned short *str, size_t n) { while (n--) { *frame++ = 0xff & (*str >> 8); *frame++ = 0xff & (*str++); } return frame; } static unsigned char * set_frame_comment(unsigned char *frame, FrameDataNode const *node) { size_t const n = sizeOfCommentNode(node); if (n > 10) { frame = set_4_byte_value(frame, ID_COMMENT); frame = set_4_byte_value(frame, (uint32_t) (n - 10)); /* clear 2-byte header flags */ *frame++ = 0; *frame++ = 0; /* encoding descriptor byte */ *frame++ = node->txt.enc == 1 ? 1 : 0; /* 3 bytes language */ *frame++ = node->lng[0]; *frame++ = node->lng[1]; *frame++ = node->lng[2]; /* descriptor with zero byte(s) separator */ if (node->dsc.enc != 1) { frame = writeChars(frame, node->dsc.ptr.l, node->dsc.dim); *frame++ = 0; } else { frame = writeUcs2s(frame, node->dsc.ptr.u, node->dsc.dim); *frame++ = 0; *frame++ = 0; } /* comment full text */ if (node->txt.enc != 1) { frame = writeChars(frame, node->txt.ptr.l, node->txt.dim); } else { frame = writeUcs2s(frame, node->txt.ptr.u, node->txt.dim); } } return frame; } static unsigned char * set_frame_custom2(unsigned char *frame, FrameDataNode const *node) { size_t const n = sizeOfNode(node); if (n > 10) { frame = set_4_byte_value(frame, node->fid); frame = set_4_byte_value(frame, (unsigned long) (n - 10)); /* clear 2-byte header flags */ *frame++ = 0; *frame++ = 0; /* clear 1 encoding descriptor byte to indicate ISO-8859-1 format */ *frame++ = node->txt.enc == 1 ? 1 : 0; if (node->txt.enc != 1) { frame = writeChars(frame, node->txt.ptr.l, node->txt.dim); } else { frame = writeUcs2s(frame, node->txt.ptr.u, node->txt.dim); } } return frame; } static unsigned char * set_frame_apic(unsigned char *frame, const char *mimetype, const unsigned char *data, size_t size) { /* ID3v2.3 standard APIC frame: *
* Text encoding $xx * MIME type $00 * Picture type $xx * Description $00 (00) * Picture data */ if (mimetype && data && size) { frame = set_4_byte_value(frame, FRAME_ID('A', 'P', 'I', 'C')); frame = set_4_byte_value(frame, (unsigned long) (4 + strlen(mimetype) + size)); /* clear 2-byte header flags */ *frame++ = 0; *frame++ = 0; /* clear 1 encoding descriptor byte to indicate ISO-8859-1 format */ *frame++ = 0; /* copy mime_type */ while (*mimetype) { *frame++ = *mimetype++; } *frame++ = 0; /* set picture type to 0 */ *frame++ = 0; /* empty description field */ *frame++ = 0; /* copy the image data */ while (size--) { *frame++ = *data++; } } return frame; } int id3tag_set_fieldvalue(lame_global_flags * gfp, const char *fieldvalue) { lame_internal_flags *gfc = gfp->internal_flags; if (fieldvalue && *fieldvalue) { int const frame_id = toID3v2TagId(fieldvalue); char **p = NULL; if (strlen(fieldvalue) < 5 || fieldvalue[4] != '=') { return -1; } if (frame_id != 0) { if (id3tag_set_textinfo_latin1(gfp, fieldvalue, &fieldvalue[5])) { p = (char **) realloc(gfc->tag_spec.values, sizeof(char *) * (gfc->tag_spec.num_values + 1)); if (!p) { return -1; } gfc->tag_spec.values = (char **) p; gfc->tag_spec.values[gfc->tag_spec.num_values++] = strdup(fieldvalue); } } gfc->tag_spec.flags |= CHANGED_FLAG; } id3tag_add_v2(gfp); return 0; } size_t lame_get_id3v2_tag(lame_global_flags * gfp, unsigned char *buffer, size_t size) { lame_internal_flags *gfc; if (gfp == 0) { return 0; } gfc = gfp->internal_flags; if (gfc == 0) { return 0; } if (gfc->tag_spec.flags & V1_ONLY_FLAG) { return 0; } { /* calculate length of four fields which may not fit in verion 1 tag */ size_t title_length = gfc->tag_spec.title ? strlen(gfc->tag_spec.title) : 0; size_t artist_length = gfc->tag_spec.artist ? strlen(gfc->tag_spec.artist) : 0; size_t album_length = gfc->tag_spec.album ? strlen(gfc->tag_spec.album) : 0; size_t comment_length = gfc->tag_spec.comment ? strlen(gfc->tag_spec.comment) : 0; /* write tag if explicitly requested or if fields overflow */ if ((gfc->tag_spec.flags & (ADD_V2_FLAG | V2_ONLY_FLAG)) || (title_length > 30) || (artist_length > 30) || (album_length > 30) || (comment_length > 30) || (gfc->tag_spec.track_id3v1 && (comment_length > 28))) { size_t tag_size; unsigned char *p; size_t adjusted_tag_size; unsigned int i; const char *albumart_mime = NULL; static const char *mime_jpeg = "image/jpeg"; static const char *mime_png = "image/png"; static const char *mime_gif = "image/gif"; id3v2AddAudioDuration(gfp); /* calulate size of tag starting with 10-byte tag header */ tag_size = 10; for (i = 0; i < gfc->tag_spec.num_values; ++i) { tag_size += 6 + strlen(gfc->tag_spec.values[i]); } if (gfc->tag_spec.albumart && gfc->tag_spec.albumart_size) { switch (gfc->tag_spec.albumart_mimetype) { case MIMETYPE_JPEG: albumart_mime = mime_jpeg; break; case MIMETYPE_PNG: albumart_mime = mime_png; break; case MIMETYPE_GIF: albumart_mime = mime_gif; break; } if (albumart_mime) { tag_size += 10 + 4 + strlen(albumart_mime) + gfc->tag_spec.albumart_size; } } { id3tag_spec *tag = &gfc->tag_spec; if (tag->v2_head != 0) { FrameDataNode *node; for (node = tag->v2_head; node != 0; node = node->nxt) { switch (node->fid) { case ID_COMMENT: tag_size += sizeOfCommentNode(node); break; default: tag_size += sizeOfNode(node); break; } } } } if (gfc->tag_spec.flags & PAD_V2_FLAG) { /* add some bytes of padding */ tag_size += gfc->tag_spec.padding_size; } if (size < tag_size) { return tag_size; } if (buffer == 0) { return 0; } p = buffer; /* set tag header starting with file identifier */ *p++ = 'I'; *p++ = 'D'; *p++ = '3'; /* set version number word */ *p++ = 3; *p++ = 0; /* clear flags byte */ *p++ = 0; /* calculate and set tag size = total size - header size */ adjusted_tag_size = tag_size - 10; /* encode adjusted size into four bytes where most significant * bit is clear in each byte, for 28-bit total */ *p++ = (unsigned char) ((adjusted_tag_size >> 21) & 0x7fu); *p++ = (unsigned char) ((adjusted_tag_size >> 14) & 0x7fu); *p++ = (unsigned char) ((adjusted_tag_size >> 7) & 0x7fu); *p++ = (unsigned char) (adjusted_tag_size & 0x7fu); /* * NOTE: The remainder of the tag (frames and padding, if any) * are not "unsynchronized" to prevent false MPEG audio headers * from appearing in the bitstream. Why? Well, most players * and utilities know how to skip the ID3 version 2 tag by now * even if they don't read its contents, and it's actually * very unlikely that such a false "sync" pattern would occur * in just the simple text frames added here. */ /* set each frame in tag */ { id3tag_spec *tag = &gfc->tag_spec; if (tag->v2_head != 0) { FrameDataNode *node; for (node = tag->v2_head; node != 0; node = node->nxt) { switch (node->fid) { case ID_COMMENT: p = set_frame_comment(p, node); break; default: p = set_frame_custom2(p, node); break; } } } } for (i = 0; i < gfc->tag_spec.num_values; ++i) { p = set_frame_custom(p, gfc->tag_spec.values[i]); } if (albumart_mime) { p = set_frame_apic(p, albumart_mime, gfc->tag_spec.albumart, gfc->tag_spec.albumart_size); } /* clear any padding bytes */ memset(p, 0, tag_size - (p - buffer)); return tag_size; } } return 0; } int id3tag_write_v2(lame_global_flags * gfp) { lame_internal_flags *gfc = gfp->internal_flags; if ((gfc->tag_spec.flags & CHANGED_FLAG) && !(gfc->tag_spec.flags & V1_ONLY_FLAG)) { unsigned char *tag = 0; size_t tag_size, n; n = lame_get_id3v2_tag(gfp, 0, 0); tag = malloc(n); if (tag == 0) { return -1; } tag_size = lame_get_id3v2_tag(gfp, tag, n); if (tag_size > n) { free(tag); return -1; } else { size_t i; /* write tag directly into bitstream at current position */ for (i = 0; i < tag_size; ++i) { add_dummy_byte(gfp, tag[i], 1); } } free(tag); return (int) tag_size; /* ok, tag should not exceed 2GB */ } return 0; } static unsigned char * set_text_field(unsigned char *field, const char *text, size_t size, int pad) { while (size--) { if (text && *text) { *field++ = *text++; } else { *field++ = pad; } } return field; } size_t lame_get_id3v1_tag(lame_global_flags * gfp, unsigned char *buffer, size_t size) { size_t const tag_size = 128; lame_internal_flags *gfc; if (gfp == 0) { return 0; } if (size < tag_size) { return tag_size; } gfc = gfp->internal_flags; if (gfc == 0) { return 0; } if (buffer == 0) { return 0; } if ((gfc->tag_spec.flags & CHANGED_FLAG) && !(gfc->tag_spec.flags & V2_ONLY_FLAG)) { unsigned char *p = buffer; int pad = (gfc->tag_spec.flags & SPACE_V1_FLAG) ? ' ' : 0; char year[5]; /* set tag identifier */ *p++ = 'T'; *p++ = 'A'; *p++ = 'G'; /* set each field in tag */ p = set_text_field(p, gfc->tag_spec.title, 30, pad); p = set_text_field(p, gfc->tag_spec.artist, 30, pad); p = set_text_field(p, gfc->tag_spec.album, 30, pad); snprintf(year, sizeof(year), "%d", gfc->tag_spec.year); p = set_text_field(p, gfc->tag_spec.year ? year : NULL, 4, pad); /* limit comment field to 28 bytes if a track is specified */ p = set_text_field(p, gfc->tag_spec.comment, gfc->tag_spec.track_id3v1 ? 28 : 30, pad); if (gfc->tag_spec.track_id3v1) { /* clear the next byte to indicate a version 1.1 tag */ *p++ = 0; *p++ = gfc->tag_spec.track_id3v1; } *p++ = gfc->tag_spec.genre_id3v1; return tag_size; } return 0; } int id3tag_write_v1(lame_global_flags * gfp) { size_t i, n, m; unsigned char tag[128]; m = sizeof(tag); n = lame_get_id3v1_tag(gfp, tag, m); if (n > m) { return 0; } /* write tag directly into bitstream at current position */ for (i = 0; i < n; ++i) { add_dummy_byte(gfp, tag[i], 1); } return (int) n; /* ok, tag has fixed size of 128 bytes, well below 2GB */ }