| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926 |
- /*
- * id3tag.c -- Write ID3 version 1 and 2 tags.
- *
- * Copyright (C) 2000 Don Melton
- * Copyright (C) 2011-2017 Robert Hegemann
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Library 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 Library 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 <c.sanderson@me.gu.edu.au>
- * from mp3info by Ricardo Cerqueira <rmc@rccn.net> to write only ID3 version 1
- * tags. Don Melton <don@blivet.com> 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$ */
- #ifdef HAVE_CONFIG_H
- #include <config.h>
- #endif
- #ifdef STDC_HEADERS
- # include <stddef.h>
- # include <stdlib.h>
- # include <string.h>
- # include <ctype.h>
- #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))
- # endif
- #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_USLT = FRAME_ID('U', 'S', 'L', 'T') /* full text string */
- , ID_USER = FRAME_ID('U', 'S', 'E', 'R') /* full text string */
- , ID_PCST = FRAME_ID('P', 'C', 'S', 'T') /* iTunes Podcast indicator, only presence important */
- , ID_WFED = FRAME_ID('W', 'F', 'E', 'D') /* iTunes Podcast URL as TEXT FRAME !!! violates standard */
- } MiscIDs;
- static int
- frame_id_matches(int id, int mask)
- {
- int result = 0, i, window = 0xff;
- for (i = 0; i < 4; ++i, window <<= 8) {
- int const mw = (mask & window);
- int const iw = (id & window);
- if (mw != 0 && mw != iw) {
- result |= iw;
- }
- }
- return result;
- }
- static int
- isFrameIdMatching(int id, int mask)
- {
- return frame_id_matches(id, mask) == 0 ? 1 : 0;
- }
- static int
- test_tag_spec_flags(lame_internal_flags const *gfc, unsigned int tst)
- {
- return (gfc->tag_spec.flags & tst) != 0u ? 1 : 0;
- }
- #if 0
- static void
- debug_tag_spec_flags(lame_internal_flags * gfc, const char* info)
- {
- MSGF(gfc, "%s\n", info);
- MSGF(gfc, "CHANGED_FLAG : %d\n", test_tag_spec_flags(gfc, CHANGED_FLAG ));
- MSGF(gfc, "ADD_V2_FLAG : %d\n", test_tag_spec_flags(gfc, ADD_V2_FLAG ));
- MSGF(gfc, "V1_ONLY_FLAG : %d\n", test_tag_spec_flags(gfc, V1_ONLY_FLAG ));
- MSGF(gfc, "V2_ONLY_FLAG : %d\n", test_tag_spec_flags(gfc, V2_ONLY_FLAG ));
- MSGF(gfc, "SPACE_V1_FLAG : %d\n", test_tag_spec_flags(gfc, SPACE_V1_FLAG));
- MSGF(gfc, "PAD_V2_FLAG : %d\n", test_tag_spec_flags(gfc, PAD_V2_FLAG ));
- }
- #endif
- static int
- is_lame_internal_flags_null(lame_t gfp)
- {
- return (gfp && gfp->internal_flags) ? 0 : 1;
- }
- static int
- id3v2_add_ucs2_lng(lame_t gfp, uint32_t frame_id, unsigned short const *desc, unsigned short const *text);
- static int
- id3v2_add_latin1_lng(lame_t gfp, uint32_t frame_id, char const *desc, char const *text);
- static void
- copyV1ToV2(lame_t gfp, int frame_id, char const *s)
- {
- lame_internal_flags *gfc = gfp != 0 ? gfp->internal_flags : 0;
- if (gfc != 0) {
- unsigned int flags = gfc->tag_spec.flags;
- id3v2_add_latin1_lng(gfp, frame_id, 0, s);
- gfc->tag_spec.flags = flags;
- #if 0
- debug_tag_spec_flags(gfc, "copyV1ToV2");
- #endif
- }
- }
- static void
- id3v2AddLameVersion(lame_t 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_t gfp, double ms)
- {
- SessionConfig_t const *const cfg = &gfp->internal_flags->cfg; /* caller checked pointers */
- char buffer[1024];
- double const max_ulong = MAX_U_32_NUM;
- unsigned long playlength_ms;
- ms *= 1000;
- ms /= cfg->samplerate_in;
- if (ms > max_ulong) {
- playlength_ms = max_ulong;
- }
- else if (ms < 0) {
- playlength_ms = 0;
- }
- else {
- playlength_ms = ms;
- }
- sprintf(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_t gfp)
- {
- lame_internal_flags *gfc = 0;
- if (is_lame_internal_flags_null(gfp)) {
- return;
- }
- 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_t gfp)
- {
- lame_internal_flags *gfc = 0;
- if (is_lame_internal_flags_null(gfp)) {
- return;
- }
- gfc = gfp->internal_flags;
- gfc->tag_spec.flags &= ~V1_ONLY_FLAG;
- gfc->tag_spec.flags |= ADD_V2_FLAG;
- }
- void
- id3tag_v1_only(lame_t gfp)
- {
- lame_internal_flags *gfc = 0;
- if (is_lame_internal_flags_null(gfp)) {
- return;
- }
- 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_t gfp)
- {
- lame_internal_flags *gfc = 0;
- if (is_lame_internal_flags_null(gfp)) {
- return;
- }
- gfc = gfp->internal_flags;
- gfc->tag_spec.flags &= ~V1_ONLY_FLAG;
- gfc->tag_spec.flags |= V2_ONLY_FLAG;
- }
- void
- id3tag_space_v1(lame_t gfp)
- {
- lame_internal_flags *gfc = 0;
- if (is_lame_internal_flags_null(gfp)) {
- return;
- }
- gfc = gfp->internal_flags;
- gfc->tag_spec.flags &= ~V2_ONLY_FLAG;
- gfc->tag_spec.flags |= SPACE_V1_FLAG;
- }
- void
- id3tag_pad_v2(lame_t gfp)
- {
- id3tag_set_pad(gfp, 128);
- }
- void
- id3tag_set_pad(lame_t gfp, size_t n)
- {
- lame_internal_flags *gfc = 0;
- if (is_lame_internal_flags_null(gfp)) {
- return;
- }
- 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 = (unsigned int)n;
- }
- static int
- hasUcs2ByteOrderMarker(unsigned short bom)
- {
- if (bom == 0xFFFEu || bom == 0xFEFFu) {
- return 1;
- }
- return 0;
- }
- static unsigned short
- swap_bytes(unsigned short w)
- {
- return (0xff00u & (w << 8)) | (0x00ffu & (w >> 8));
- }
- static unsigned short
- toLittleEndian(unsigned short bom, unsigned short c)
- {
- if (bom == 0xFFFEu) {
- return swap_bytes(c);
- }
- return c;
- }
- static unsigned short
- fromLatin1Char(const unsigned short* s, unsigned short c)
- {
- if (s[0] == 0xFFFEu) {
- return swap_bytes(c);
- }
- return c;
- }
- 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 = lame_calloc(char, n + 1);
- 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 = lame_calloc(unsigned short, n + 1);
- if (*dst != 0) {
- memcpy(*dst, src, n * sizeof(**dst));
- (*dst)[n] = 0;
- return n;
- }
- }
- }
- return 0;
- }
- static size_t
- local_ucs2_strlen(unsigned short const *s)
- {
- size_t n = 0;
- if (s != 0) {
- while (*s++) {
- ++n;
- }
- }
- return n;
- }
- static size_t
- local_ucs2_substr(unsigned short** dst, unsigned short const* src, size_t start, size_t end)
- {
- size_t const len = 1 + 1 + ((start < end) ? (end - start) : 0);
- size_t n = 0;
- unsigned short *ptr = lame_calloc(unsigned short, len);
- *dst = ptr;
- if (ptr == 0 || src == 0) {
- return 0;
- }
- if (hasUcs2ByteOrderMarker(src[0])) {
- ptr[n++] = src[0];
- if (start == 0) {
- ++start;
- }
- }
- while (start < end) {
- ptr[n++] = src[start++];
- }
- ptr[n] = 0;
- return n;
- }
- static int
- local_ucs2_pos(unsigned short const* str, unsigned short c)
- {
- int i;
- for (i = 0; str != 0 && str[i] != 0; ++i) {
- if (str[i] == c) {
- return i;
- }
- }
- return -1;
- }
- static int
- local_char_pos(char const* str, char c)
- {
- int i;
- for (i = 0; str != 0 && str[i] != 0; ++i) {
- if (str[i] == c) {
- return i;
- }
- }
- return -1;
- }
- static int
- maybeLatin1(unsigned short const* text)
- {
- if (text) {
- unsigned short bom = *text++;
- while (*text) {
- unsigned short c = toLittleEndian(bom, *text++);
- if (c > 0x00fe) return 0;
- }
- }
- return 1;
- }
- static int searchGenre(char const* genre);
- static int sloppySearchGenre(char const* genre);
- static int
- lookupGenre(char const* 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) {
- return -2; /* no common genre text found */
- }
- }
- else {
- if ((num < 0) || (num >= GENRE_NAME_COUNT)) {
- return -1; /* number unknown */
- }
- }
- return num;
- }
- static unsigned char *
- writeLoBytes(unsigned char *frame, unsigned short const *str, size_t n);
- static char*
- local_strdup_utf16_to_latin1(unsigned short const* utf16)
- {
- size_t len = local_ucs2_strlen(utf16);
- unsigned char* latin1 = lame_calloc(unsigned char, len+1);
- writeLoBytes(latin1, utf16, len);
- return (char*)latin1;
- }
- static int
- id3tag_set_genre_utf16(lame_t gfp, unsigned short const* text)
- {
- lame_internal_flags* gfc = gfp->internal_flags;
- int ret;
- if (text == 0) {
- return -3;
- }
- if (!hasUcs2ByteOrderMarker(text[0])) {
- return -3;
- }
- if (maybeLatin1(text)) {
- char* latin1 = local_strdup_utf16_to_latin1(text);
- int num = lookupGenre(latin1);
- free(latin1);
- if (num == -1) return -1; /* number out of range */
- if (num >= 0) { /* common genre found */
- gfc->tag_spec.flags |= CHANGED_FLAG;
- gfc->tag_spec.genre_id3v1 = num;
- copyV1ToV2(gfp, ID_GENRE, genre_names[num]);
- return 0;
- }
- }
- ret = id3v2_add_ucs2_lng(gfp, ID_GENRE, 0, text);
- if (ret == 0) {
- gfc->tag_spec.flags |= CHANGED_FLAG;
- gfc->tag_spec.genre_id3v1 = GENRE_INDEX_OTHER;
- }
- return ret;
- }
- /*
- Some existing options for ID3 tag can be specified by --tv option
- as follows.
- --tt <value>, --tv TIT2=value
- --ta <value>, --tv TPE1=value
- --tl <value>, --tv TALB=value
- --ty <value>, --tv TYER=value
- --tn <value>, --tv TRCK=value
- --tg <value>, --tv TCON=value
- (although some are not exactly same)*/
- int
- id3tag_set_albumart(lame_t gfp, const char *image, size_t size)
- {
- int mimetype = MIMETYPE_NONE;
- lame_internal_flags *gfc = 0;
- if (is_lame_internal_flags_null(gfp)) {
- return 0;
- }
- gfc = gfp->internal_flags;
- if (image != 0) {
- unsigned char const *data = (unsigned char const *) image;
- /* 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 || mimetype == MIMETYPE_NONE) {
- return 0;
- }
- gfc->tag_spec.albumart = lame_calloc(unsigned char, size);
- if (gfc->tag_spec.albumart != 0) {
- memcpy(gfc->tag_spec.albumart, image, size);
- gfc->tag_spec.albumart_size = (unsigned int)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 uint32_t
- toID3v2TagId(char const *s)
- {
- unsigned 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 uint32_t
- toID3v2TagId_ucs2(unsigned short const *s)
- {
- unsigned int i, x = 0;
- unsigned short bom = 0;
- if (s == 0) {
- return 0;
- }
- bom = s[0];
- if (hasUcs2ByteOrderMarker(bom)) {
- ++s;
- }
- for (i = 0; i < 4 && s[i] != 0; ++i) {
- unsigned short const c = toLittleEndian(bom, s[i]);
- if (c < 'A' || 'Z' < c) {
- if (c < '0' || '9' < c) {
- return 0;
- }
- }
- x <<= 8;
- x |= c;
- }
- return x;
- }
- #if 0
- static int
- isNumericString(uint32_t frame_id)
- {
- switch (frame_id) {
- case ID_DATE:
- case ID_TIME:
- case ID_TPOS:
- case ID_TRACK:
- case ID_YEAR:
- return 1;
- }
- return 0;
- }
- #endif
- static int
- isMultiFrame(uint32_t 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 FrameDataNode *
- findNode(id3tag_spec const *tag, uint32_t frame_id, FrameDataNode const *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] = 'e';
- dst[1] = 'n';
- dst[2] = 'g';
- }
- 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 const *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_t gfp, uint32_t frame_id, char const *lng, unsigned short const *desc, unsigned short const *text)
- {
- lame_internal_flags *gfc = gfp != 0 ? gfp->internal_flags : 0;
- if (gfc != 0) {
- FrameDataNode *node = findNode(&gfc->tag_spec, frame_id, 0);
- char lang[4];
- setLang(lang, lng);
- 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 = lame_calloc(FrameDataNode, 1);
- 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;
- }
- return -255;
- }
- static int
- id3v2_add_latin1(lame_t gfp, uint32_t frame_id, char const *lng, char const *desc, char const *text)
- {
- lame_internal_flags *gfc = gfp != 0 ? gfp->internal_flags : 0;
- if (gfc != 0) {
- FrameDataNode *node = findNode(&gfc->tag_spec, frame_id, 0);
- char lang[4];
- setLang(lang, lng);
- 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 = lame_calloc(FrameDataNode, 1);
- 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;
- }
- return -255;
- }
- static char const*
- id3v2_get_language(lame_t gfp)
- {
- lame_internal_flags const* gfc = gfp ? gfp->internal_flags : 0;
- if (gfc) return gfc->tag_spec.language;
- return 0;
- }
- static int
- id3v2_add_ucs2_lng(lame_t gfp, uint32_t frame_id, unsigned short const *desc, unsigned short const *text)
- {
- char const* lang = id3v2_get_language(gfp);
- return id3v2_add_ucs2(gfp, frame_id, lang, desc, text);
- }
- static int
- id3v2_add_latin1_lng(lame_t gfp, uint32_t frame_id, char const *desc, char const *text)
- {
- char const* lang = id3v2_get_language(gfp);
- return id3v2_add_latin1(gfp, frame_id, lang, desc, text);
- }
- static int
- id3tag_set_userinfo_latin1(lame_t gfp, uint32_t id, char const *fieldvalue)
- {
- char const separator = '=';
- int rc = -7;
- int a = local_char_pos(fieldvalue, separator);
- if (a >= 0) {
- char* dup = 0;
- local_strdup(&dup, fieldvalue);
- dup[a] = 0;
- rc = id3v2_add_latin1_lng(gfp, id, dup, dup+a+1);
- free(dup);
- }
- return rc;
- }
- static int
- id3tag_set_userinfo_ucs2(lame_t gfp, uint32_t id, unsigned short const *fieldvalue)
- {
- unsigned short const separator = fromLatin1Char(fieldvalue,'=');
- int rc = -7;
- size_t b = local_ucs2_strlen(fieldvalue);
- int a = local_ucs2_pos(fieldvalue, separator);
- if (a >= 0) {
- unsigned short* dsc = 0, *val = 0;
- local_ucs2_substr(&dsc, fieldvalue, 0, a);
- local_ucs2_substr(&val, fieldvalue, a+1, b);
- rc = id3v2_add_ucs2_lng(gfp, id, dsc, val);
- free(dsc);
- free(val);
- }
- return rc;
- }
- int
- id3tag_set_textinfo_utf16(lame_t gfp, char const *id, unsigned short const *text)
- {
- uint32_t const frame_id = toID3v2TagId(id);
- if (frame_id == 0) {
- return -1;
- }
- if (is_lame_internal_flags_null(gfp)) {
- return 0;
- }
- if (text == 0) {
- return 0;
- }
- if (!hasUcs2ByteOrderMarker(text[0])) {
- return -3; /* BOM missing */
- }
- if (frame_id == ID_TXXX || frame_id == ID_WXXX || frame_id == ID_COMMENT) {
- return id3tag_set_userinfo_ucs2(gfp, frame_id, text);
- }
- if (frame_id == ID_GENRE) {
- return id3tag_set_genre_utf16(gfp, text);
- }
- if (frame_id == ID_PCST) {
- return id3v2_add_ucs2_lng(gfp, frame_id, 0, text);
- }
- if (frame_id == ID_USER) {
- return id3v2_add_ucs2_lng(gfp, frame_id, text, 0);
- }
- if (frame_id == ID_WFED) {
- return id3v2_add_ucs2_lng(gfp, frame_id, text, 0); /* iTunes expects WFED to be a text frame */
- }
- if (isFrameIdMatching(frame_id, FRAME_ID('T', 0, 0, 0))
- ||isFrameIdMatching(frame_id, FRAME_ID('W', 0, 0, 0))) {
- #if 0
- if (isNumericString(frame_id)) {
- return -2; /* must be Latin-1 encoded */
- }
- #endif
- return id3v2_add_ucs2_lng(gfp, frame_id, 0, text);
- }
- return -255; /* not supported by now */
- }
- extern int
- id3tag_set_textinfo_ucs2(lame_t gfp, char const *id, unsigned short const *text);
- int
- id3tag_set_textinfo_ucs2(lame_t gfp, char const *id, unsigned short const *text)
- {
- return id3tag_set_textinfo_utf16(gfp, id, text);
- }
- int
- id3tag_set_textinfo_latin1(lame_t gfp, char const *id, char const *text)
- {
- uint32_t const frame_id = toID3v2TagId(id);
- if (frame_id == 0) {
- return -1;
- }
- if (is_lame_internal_flags_null(gfp)) {
- return 0;
- }
- if (text == 0) {
- return 0;
- }
- if (frame_id == ID_TXXX || frame_id == ID_WXXX || frame_id == ID_COMMENT) {
- return id3tag_set_userinfo_latin1(gfp, frame_id, text);
- }
- if (frame_id == ID_GENRE) {
- return id3tag_set_genre(gfp, text);
- }
- if (frame_id == ID_PCST) {
- return id3v2_add_latin1_lng(gfp, frame_id, 0, text);
- }
- if (frame_id == ID_USER) {
- return id3v2_add_latin1_lng(gfp, frame_id, text, 0);
- }
- if (frame_id == ID_WFED) {
- return id3v2_add_latin1_lng(gfp, frame_id, text, 0); /* iTunes expects WFED to be a text frame */
- }
- if (isFrameIdMatching(frame_id, FRAME_ID('T', 0, 0, 0))
- ||isFrameIdMatching(frame_id, FRAME_ID('W', 0, 0, 0))) {
- return id3v2_add_latin1_lng(gfp, frame_id, 0, text);
- }
- return -255; /* not supported by now */
- }
- int
- id3tag_set_comment_latin1(lame_t gfp, char const *lang, char const *desc, char const *text)
- {
- if (is_lame_internal_flags_null(gfp)) {
- return 0;
- }
- return id3v2_add_latin1(gfp, ID_COMMENT, lang, desc, text);
- }
- int
- id3tag_set_comment_utf16(lame_t gfp, char const *lang, unsigned short const *desc, unsigned short const *text)
- {
- if (is_lame_internal_flags_null(gfp)) {
- return 0;
- }
- return id3v2_add_ucs2(gfp, ID_COMMENT, lang, desc, text);
- }
- extern int
- id3tag_set_comment_ucs2(lame_t gfp, char const *lang, unsigned short const *desc, unsigned short const *text);
- int
- id3tag_set_comment_ucs2(lame_t gfp, char const *lang, unsigned short const *desc, unsigned short const *text)
- {
- if (is_lame_internal_flags_null(gfp)) {
- return 0;
- }
- return id3tag_set_comment_utf16(gfp, lang, desc, text);
- }
- void
- id3tag_set_title(lame_t gfp, const char *title)
- {
- lame_internal_flags *gfc = gfp != 0 ? gfp->internal_flags : 0;
- if (gfc && 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_t gfp, const char *artist)
- {
- lame_internal_flags *gfc = gfp != 0 ? gfp->internal_flags : 0;
- if (gfc && 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_t gfp, const char *album)
- {
- lame_internal_flags *gfc = gfp != 0 ? gfp->internal_flags : 0;
- if (gfc && 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_t gfp, const char *year)
- {
- lame_internal_flags *gfc = gfp != 0 ? gfp->internal_flags : 0;
- if (gfc && 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_t gfp, const char *comment)
- {
- lame_internal_flags *gfc = gfp != 0 ? gfp->internal_flags : 0;
- if (gfc && comment && *comment) {
- local_strdup(&gfc->tag_spec.comment, comment);
- gfc->tag_spec.flags |= CHANGED_FLAG;
- {
- uint32_t const flags = gfc->tag_spec.flags;
- id3v2_add_latin1_lng(gfp, ID_COMMENT, "", comment);
- gfc->tag_spec.flags = flags;
- }
- }
- }
- int
- id3tag_set_track(lame_t gfp, const char *track)
- {
- char const *trackcount;
- lame_internal_flags *gfc = gfp != 0 ? gfp->internal_flags : 0;
- int ret = 0;
- if (gfc && 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_t gfp, const char *genre)
- {
- lame_internal_flags *gfc = gfp != 0 ? gfp->internal_flags : 0;
- int ret = 0;
- if (gfc && genre && *genre) {
- int const num = lookupGenre(genre);
- if (num == -1) return num;
- gfc->tag_spec.flags |= CHANGED_FLAG;
- if (num >= 0) {
- gfc->tag_spec.genre_id3v1 = num;
- genre = genre_names[num];
- }
- else {
- gfc->tag_spec.genre_id3v1 = GENRE_INDEX_OTHER;
- gfc->tag_spec.flags |= ADD_V2_FLAG;
- }
- copyV1ToV2(gfp, ID_GENRE, genre);
- }
- return ret;
- }
- 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:
- if (node->dsc.dim > 0) {
- n += node->dsc.dim + 1;
- }
- n += node->txt.dim;
- break;
- case 1:
- if (node->dsc.dim > 0) {
- n += (node->dsc.dim+1) * 2;
- }
- 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 size_t
- sizeOfWxxxNode(FrameDataNode const *node)
- {
- size_t n = 0;
- if (node) {
- n = 10; /* header size */
- if (node->dsc.dim > 0) {
- n += 1; /* text encoding flag */
- switch (node->dsc.enc) {
- default:
- case 0:
- n += 1 + node->dsc.dim;
- break;
- case 1:
- n += 2 + node->dsc.dim * 2;
- break;
- }
- }
- if (node->txt.dim > 0) {
- switch (node->txt.enc) {
- default:
- case 0:
- n += node->txt.dim;
- break;
- case 1:
- n += node->txt.dim - 1; /* UCS2 -> Latin1, skip BOM */
- 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 const *str, size_t n)
- {
- if (n > 0) {
- unsigned short const bom = *str;
- while (n--) {
- unsigned short const c = toLittleEndian(bom, *str++);
- *frame++ = 0x00ffu & c;
- *frame++ = 0x00ffu & (c >> 8);
- }
- }
- return frame;
- }
- static unsigned char *
- writeLoBytes(unsigned char *frame, unsigned short const *str, size_t n)
- {
- if (n > 0) {
- unsigned short const bom = *str;
- if (hasUcs2ByteOrderMarker(bom)) {
- str++; n--; /* skip BOM */
- }
- while (n--) {
- unsigned short const c = toLittleEndian(bom, *str++);
- if (c < 0x0020u || 0x00ffu < c) {
- *frame++ = 0x0020; /* blank */
- }
- else {
- *frame++ = c;
- }
- }
- }
- 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, node->fid);
- 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->dsc.dim > 0) {
- 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;
- }
- }
- 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_wxxx(unsigned char *frame, FrameDataNode const *node)
- {
- size_t const n = sizeOfWxxxNode(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;
- if (node->dsc.dim > 0) {
- /* clear 1 encoding descriptor byte to indicate ISO-8859-1 format */
- *frame++ = node->dsc.enc == 1 ? 1 : 0;
- 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;
- }
- }
- if (node->txt.enc != 1) {
- frame = writeChars(frame, node->txt.ptr.l, node->txt.dim);
- }
- else {
- frame = writeLoBytes(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:
- * <Header for 'Attached picture', ID: "APIC">
- * Text encoding $xx
- * MIME type <text string> $00
- * Picture type $xx
- * Description <text string according to encoding> $00 (00)
- * Picture data <binary 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_t gfp, const char *fieldvalue)
- {
- if (is_lame_internal_flags_null(gfp)) {
- return 0;
- }
- if (fieldvalue && *fieldvalue) {
- if (strlen(fieldvalue) < 5 || fieldvalue[4] != '=') {
- return -1;
- }
- return id3tag_set_textinfo_latin1(gfp, fieldvalue, &fieldvalue[5]);
- }
- return 0;
- }
- int
- id3tag_set_fieldvalue_utf16(lame_t gfp, const unsigned short *fieldvalue)
- {
- if (is_lame_internal_flags_null(gfp)) {
- return 0;
- }
- if (fieldvalue && *fieldvalue) {
- size_t dx = hasUcs2ByteOrderMarker(fieldvalue[0]);
- unsigned short const separator = fromLatin1Char(fieldvalue, '=');
- char fid[5] = {0,0,0,0,0};
- uint32_t const frame_id = toID3v2TagId_ucs2(fieldvalue);
- if (local_ucs2_strlen(fieldvalue) < (5+dx) || fieldvalue[4+dx] != separator) {
- return -1;
- }
- fid[0] = (frame_id >> 24) & 0x0ff;
- fid[1] = (frame_id >> 16) & 0x0ff;
- fid[2] = (frame_id >> 8) & 0x0ff;
- fid[3] = frame_id & 0x0ff;
- if (frame_id != 0) {
- unsigned short* txt = 0;
- int rc;
- local_ucs2_substr(&txt, fieldvalue, dx+5, local_ucs2_strlen(fieldvalue));
- rc = id3tag_set_textinfo_utf16(gfp, fid, txt);
- free(txt);
- return rc;
- }
- }
- return -1;
- }
- extern int
- id3tag_set_fieldvalue_ucs2(lame_t gfp, const unsigned short *fieldvalue);
- int
- id3tag_set_fieldvalue_ucs2(lame_t gfp, const unsigned short *fieldvalue)
- {
- if (is_lame_internal_flags_null(gfp)) {
- return 0;
- }
- return id3tag_set_fieldvalue_utf16(gfp, fieldvalue);
- }
- size_t
- lame_get_id3v2_tag(lame_t gfp, unsigned char *buffer, size_t size)
- {
- lame_internal_flags *gfc = 0;
- if (is_lame_internal_flags_null(gfp)) {
- return 0;
- }
- gfc = gfp->internal_flags;
- if (test_tag_spec_flags(gfc, V1_ONLY_FLAG)) {
- return 0;
- }
- #if 0
- debug_tag_spec_flags(gfc, "lame_get_id3v2_tag");
- #endif
- {
- int usev2 = test_tag_spec_flags(gfc, ADD_V2_FLAG | V2_ONLY_FLAG);
- /* 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 ((title_length > 30)
- || (artist_length > 30)
- || (album_length > 30)
- || (comment_length > 30)
- || (gfc->tag_spec.track_id3v1 && (comment_length > 28))) {
- usev2 = 1;
- }
- if (usev2) {
- size_t tag_size;
- unsigned char *p;
- size_t adjusted_tag_size;
- 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";
- if (gfp->num_samples != MAX_U_32_NUM) {
- id3v2AddAudioDuration(gfp, gfp->num_samples);
- }
- /* calulate size of tag starting with 10-byte tag header */
- tag_size = 10;
- 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) {
- if (node->fid == ID_COMMENT || node->fid == ID_USER) {
- tag_size += sizeOfCommentNode(node);
- }
- else if (isFrameIdMatching(node->fid, FRAME_ID('W',0,0,0))) {
- tag_size += sizeOfWxxxNode(node);
- }
- else {
- tag_size += sizeOfNode(node);
- }
- }
- }
- }
- if (test_tag_spec_flags(gfc, 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) {
- if (node->fid == ID_COMMENT || node->fid == ID_USER) {
- p = set_frame_comment(p, node);
- }
- else if (isFrameIdMatching(node->fid,FRAME_ID('W',0,0,0))) {
- p = set_frame_wxxx(p, node);
- }
- else {
- p = set_frame_custom2(p, node);
- }
- }
- }
- }
- 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_t gfp)
- {
- lame_internal_flags *gfc = 0;
- if (is_lame_internal_flags_null(gfp)) {
- return 0;
- }
- gfc = gfp->internal_flags;
- #if 0
- debug_tag_spec_flags(gfc, "write v2");
- #endif
- if (test_tag_spec_flags(gfc, V1_ONLY_FLAG)) {
- return 0;
- }
- if (test_tag_spec_flags(gfc, CHANGED_FLAG)) {
- unsigned char *tag = 0;
- size_t tag_size, n;
- n = lame_get_id3v2_tag(gfp, 0, 0);
- tag = lame_calloc(unsigned char, 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(gfc, 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_t 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 (test_tag_spec_flags(gfc, V2_ONLY_FLAG)) {
- return 0;
- }
- if (test_tag_spec_flags(gfc, CHANGED_FLAG)) {
- unsigned char *p = buffer;
- int pad = test_tag_spec_flags(gfc, 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);
- sprintf(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_t gfp)
- {
- lame_internal_flags* gfc = 0;
- size_t i, n, m;
- unsigned char tag[128];
- if (is_lame_internal_flags_null(gfp)) {
- return 0;
- }
- gfc = gfp->internal_flags;
- 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(gfc, tag[i], 1);
- }
- return (int) n; /* ok, tag has fixed size of 128 bytes, well below 2GB */
- }
|