Source file: /~heha/hsn/puh.zip/id3tag.cpp

/* id3tag.c -- Write ID3 version 1 and 2 tags.
 * Copyright 2000 Don Melton
 * Copyright 2011-2017 Robert Hegemann
 */

/*
 * 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.
 */
#include "lame.h"

#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 "machine.h"
#include "encoder.h"
#include "id3tag.h"
#include "lame_global_flags.h"
#include "util.h"
#include "bitstream.h"


static const char genre_names[] = {
/* NOTE: The spelling of these genre names is identical to those found in Winamp and mp3info. */
    "Blues\0" "Classic Rock\0" "Country\0" "Dance\0" "Disco\0" "Funk\0" "Grunge\0"
    "Hip-Hop\0" "Jazz\0" "Metal\0" "New Age\0" "Oldies\0" "Other\0" "Pop\0" "R&B\0"
    "Rap\0" "Reggae\0" "Rock\0" "Techno\0" "Industrial\0" "Alternative\0" "Ska\0"
    "Death Metal\0" "Pranks\0" "Soundtrack\0" "Euro-Techno\0" "Ambient\0" "Trip-Hop\0"
    "Vocal\0" "Jazz+Funk\0" "Fusion\0" "Trance\0" "Classical\0" "Instrumental\0"
    "Acid\0" "House\0" "Game\0" "Sound Clip\0" "Gospel\0" "Noise\0" "Alternative Rock\0"
    "Bass\0" "Soul\0" "Punk\0" "Space\0" "Meditative\0" "Instrumental Pop\0"
    "Instrumental Rock\0" "Ethnic\0" "Gothic\0" "Darkwave\0" "Techno-Industrial\0"
    "Electronic\0" "Pop-Folk\0" "Eurodance\0" "Dream\0" "Southern Rock\0" "Comedy\0"
    "Cult\0" "Gangsta\0" "Top 40\0" "Christian Rap\0" "Pop/Funk\0" "Jungle\0"
    "Native US\0" "Cabaret\0" "New Wave\0" "Psychedelic\0" "Rave\0"
    "Showtunes\0" "Trailer\0" "Lo-Fi\0" "Tribal\0" "Acid Punk\0" "Acid Jazz\0"
    "Polka\0" "Retro\0" "Musical\0" "Rock & Roll\0" "Hard Rock\0" "Folk\0"
    "Folk-Rock\0" "National Folk\0" "Swing\0" "Fast Fusion\0" "Bebob\0" "Latin\0"
    "Revival\0" "Celtic\0" "Bluegrass\0" "Avantgarde\0" "Gothic Rock\0"
    "Progressive Rock\0" "Psychedelic Rock\0" "Symphonic Rock\0" "Slow Rock\0"
    "Big Band\0" "Chorus\0" "Easy Listening\0" "Acoustic\0" "Humour\0" "Speech\0"
    "Chanson\0" "Opera\0" "Chamber Music\0" "Sonata\0" "Symphony\0" "Booty Bass\0"
    "Primus\0" "Porn Groove\0" "Satire\0" "Slow Jam\0" "Club\0" "Tango\0" "Samba\0"
    "Folklore\0" "Ballad\0" "Power Ballad\0" "Rhythmic Soul\0" "Freestyle\0" "Duet\0"
    "Punk Rock\0" "Drum Solo\0" "A Cappella\0" "Euro-House\0" "Dance Hall\0"
    "Goa\0" "Drum & Bass\0" "Club-House\0" "Hardcore\0" "Terror\0" "Indie\0"
    "BritPop\0" "Negerpunk\0" "Polsk Punk\0" "Beat\0" "Christian Gangsta\0"
    "Heavy Metal\0" "Black Metal\0" "Crossover\0" "Contemporary Christian\0"
    "Christian Rock\0" "Merengue\0" "Salsa\0" "Thrash Metal\0" "Anime\0" "JPop\0"
    "SynthPop\0"
};

enum{
  GENRE_NAME_COUNT=148
};

static char const*genre_name(int idx) {
  static const char*p=genre_names;
  for(;idx&&*p;p+=strlen(p)+1,--idx);
  return p;
}


static const unsigned char 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_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
//id3v2_add_ucs2_lng(lame_global_flags & gfp, uint32_t frame_id, unsigned short const *desc, unsigned short const *text);
//static int
//id3v2_add_latin1_lng(lame_global_flags & gfp, uint32_t frame_id, char const *desc, char const *text);


void lame_global_flags::copyV1ToV2(int frame_id, char const *s) {
  if (!is_valid()) return;
  lame_internal_flags & gfc = *internal_flags;
  unsigned int flags = gfc.tag_spec.flags;
  id3v2_add_lngA(frame_id, 0, s);
  gfc.tag_spec.flags = flags;
#if 0
  debug_tag_spec_flags(gfc, "copyV1ToV2");
#endif
}


void lame_global_flags::id3v2AddLameVersion() {
    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(ID_ENCODER, buffer);
}

void lame_global_flags::id3v2AddAudioDuration(double ms) {
  SessionConfig_t const & cfg = 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(ID_PLAYLENGTH, buffer);
}

void id3tag_genre_list(void (*handler) (int, const char *, void *), void *cookie) {
  if (!handler) return;
  for (int i = 0; i < sizeof genre_alpha_map; ++i) {
    int j = genre_alpha_map[i];
    handler(j,genre_name(j),cookie);
  }
}

#define GENRE_NUM_UNKNOWN 255

void lame_global_flags::id3tag_init() {
  if (!is_valid()) return;
  lame_internal_flags&gfc = *internal_flags;
  gfc.free_id3tag();
  memset(&gfc.tag_spec, 0, sizeof gfc.tag_spec);
  gfc.tag_spec.genre_id3v1 = GENRE_NUM_UNKNOWN;
  gfc.tag_spec.padding_size = 128;
  id3v2AddLameVersion();
}

void lame_global_flags::id3tag_add_v2() {
  if (!is_valid()) return;
  lame_internal_flags&gfc = *internal_flags;
  gfc.tag_spec.flags &= ~V1_ONLY_FLAG;
  gfc.tag_spec.flags |= ADD_V2_FLAG;
}

void id3tag_v1_only(lame_global_flags & gfp) {
  if (!gfp.is_valid()) return;
  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) {
  if (!gfp.is_valid()) return;
  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) {
  if (!gfp.is_valid()) return;
  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) {
  if (!gfp.is_valid()) return;
  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 = (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 = new 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 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 bool 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 true;
    if (p[1] == '.') { /* some abbrevation */
      while (*q && *q++ != ' ');
    }
    p = nextUpperAlpha(p, cp);
    q = nextUpperAlpha(q, cq);
    cp = toupper(*p);
    cq = toupper(*q);
  }
  return false;
}


static int sloppySearchGenre(const char *genre) {
  char const*p=genre_names;
  for (int i=0;*p;p+=strlen(p)+1,++i) {
    if (sloppyCompared(genre,p)) return i;
  }
  return -1;
}

/* 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 int searchGenre(const char* genre) {
  char const*p=genre_names;
  for (int i=0;*p;p+=strlen(p)+1,++i) {
    if (!local_strcasecmp(genre,p)) return i;
  }
  return -1;
}

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 <0) num = sloppySearchGenre(genre);
    if (num <0) 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;
}


int lame_global_flags::id3tag_set_genreW(const wchar_t*text) {
  lame_internal_flags& gfc = *internal_flags;
  int   ret;
  if (!text) 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(ID_GENRE, genre_name(num));
            return 0;
        }
    }
    ret = id3v2_add_lngW(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 lame_global_flags::id3tag_set_albumart(const char *image, size_t size) {
  if (!is_valid()) return 0;
  lame_internal_flags&gfc = *internal_flags;

  int     mimetype = MIMETYPE_NONE;
  if (image) {
        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) {
        delete[]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 = new 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();
    }
    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;
}

int lame_global_flags::id3v2_addW(uint32_t frame_id, char const *lng, wchar_t const *desc, wchar_t const *text) {
  lame_internal_flags &gfc = *internal_flags;
  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) {
    node = new FrameDataNode;
    if (!node) 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;
}

int lame_global_flags::id3v2_addA(uint32_t frame_id, char const *lng, char const *desc, char const *text) {
  lame_internal_flags &gfc = *internal_flags;
  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) {
    node = new FrameDataNode;
    if (!node) 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;
}

char const* lame_global_flags::id3v2_get_language()const{
  return internal_flags->tag_spec.language;
}

int lame_global_flags::id3v2_add_lngW(uint32_t frame_id, unsigned short const *desc, unsigned short const *text) {
  char const* lang = id3v2_get_language();
  return id3v2_addW(frame_id, lang, desc, text);
}

int lame_global_flags::id3v2_add_lngA(uint32_t frame_id, char const *desc, char const *text) {
  char const* lang = id3v2_get_language();
  return id3v2_addA(frame_id, lang, desc, text);
}

int lame_global_flags::id3tag_set_userinfoA(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_lngA(id, dup, dup+a+1);
    free(dup);
  }
  return rc;
}

int lame_global_flags::id3tag_set_userinfoW(uint32_t id, const wchar_t*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_lngW(id, dsc, val);
    delete[]dsc;
    delete[]val;
  }
  return rc;
}

int lame_global_flags::id3tag_set_textinfoW(char const *id, unsigned short const *text) {
  uint32_t const frame_id = toID3v2TagId(id);
  if (!frame_id) return -1;
  if (!is_valid()) return 0;
  if (!text) 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_userinfoW(frame_id, text);
  }
  if (frame_id == ID_GENRE) return id3tag_set_genreW(text);
  if (frame_id == ID_PCST) return id3v2_add_lngW(frame_id, 0, text);
  if (frame_id == ID_USER) return id3v2_add_lngW(frame_id, text, 0);
  if (frame_id == ID_WFED) return id3v2_add_lngW(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_lngW(frame_id, 0, text);
  }
  return -255;        /* not supported by now */
}

int lame_global_flags::id3tag_set_textinfoA(char const *id, char const *text) {
  uint32_t const frame_id = toID3v2TagId(id);
  if (frame_id == 0) return -1;
  if (!is_valid()) return 0;
  if (!text) return 0;
  if (frame_id == ID_TXXX || frame_id == ID_WXXX || frame_id == ID_COMMENT) {
    return id3tag_set_userinfoA(frame_id, text);
  }
  if (frame_id == ID_GENRE) return id3tag_set_genre(text);
  if (frame_id == ID_PCST) return id3v2_add_lngA(frame_id, 0, text);
  if (frame_id == ID_USER) return id3v2_add_lngA(frame_id, text, 0);
  if (frame_id == ID_WFED) return id3v2_add_lngA(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_lngA(frame_id, 0, text);
  }
  return -255;        /* not supported by now */
}


int lame_global_flags::id3tag_set_commentA(char const *lang, char const *desc, char const *text) {
  return id3v2_addA(ID_COMMENT, lang, desc, text);
}

int lame_global_flags::id3tag_set_commentW(char const *lang, unsigned short const *desc, unsigned short const *text) {
  return id3v2_addW(ID_COMMENT, lang, desc, text);
}

void lame_global_flags::id3tag_set_title(const char *title) {
  if (!is_valid()) return;
  lame_internal_flags & gfc = *internal_flags;
  if (title && *title) {
    local_strdup(&gfc.tag_spec.title, title);
    gfc.tag_spec.flags |= CHANGED_FLAG;
    copyV1ToV2(ID_TITLE, title);
  }
}

void lame_global_flags::id3tag_set_artist(const char *artist) {
  if (!is_valid()) return;
  lame_internal_flags & gfc = *internal_flags;
  if (artist && *artist) {
    local_strdup(&gfc.tag_spec.artist, artist);
    gfc.tag_spec.flags |= CHANGED_FLAG;
    copyV1ToV2(ID_ARTIST, artist);
  }
}

void lame_global_flags::id3tag_set_album(const char *album) {
  if (!is_valid()) return;
  lame_internal_flags & gfc = *internal_flags;
  if (album && *album) {
    local_strdup(&gfc.tag_spec.album, album);
    gfc.tag_spec.flags |= CHANGED_FLAG;
    copyV1ToV2(ID_ALBUM, album);
  }
}

void lame_global_flags::id3tag_set_year(const char *year) {
  if (!is_valid()) return;
  lame_internal_flags & gfc = *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(ID_YEAR, year);
  }
}

void lame_global_flags::id3tag_set_comment(const char *comment) {
  if (!is_valid()) return;
  lame_internal_flags & gfc = *internal_flags;
  if (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_lngA(ID_COMMENT, "", comment);
    gfc.tag_spec.flags = flags;
  }
}

int lame_global_flags::id3tag_set_track(const char *track) {
  if (!is_valid()) return 0;
  lame_internal_flags & gfc = *internal_flags;
  char const *trackcount;
  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(ID_TRACK, track);
  }
  return ret;
}

int lame_global_flags::id3tag_set_genre(const char *genre) {
  if (!is_valid()) return 0;
  lame_internal_flags & gfc = *internal_flags;
  int     ret = 0;
  if (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_name(num);
    }else{
      gfc.tag_spec.genre_id3v1 = GENRE_INDEX_OTHER;
      gfc.tag_spec.flags |= ADD_V2_FLAG;
    }
    copyV1ToV2(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 lame_global_flags::id3tag_set_fieldvalue(const char *fieldvalue) {
  if (!is_valid()) return 0;
  if (fieldvalue && *fieldvalue) {
    if (strlen(fieldvalue) < 5 || fieldvalue[4] != '=') return -1;
    return id3tag_set_textinfoA(fieldvalue, &fieldvalue[5]);
  }
  return 0;
}

int lame_global_flags::id3tag_set_fieldvalueW(const wchar_t*fieldvalue) {
  if (!is_valid()) 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_textinfoW(fid, txt);
      delete[]txt;
      return rc;
    }
  }
  return -1;
}

size_t lame_global_flags::lame_get_id3v2_tag(unsigned char *buffer, size_t size) {
  if (!is_valid()) return 0;
  lame_internal_flags & gfc = *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 (num_samples != MAX_U_32_NUM) {
                id3v2AddAudioDuration(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 lame_global_flags::id3tag_write_v2() {
  if (!is_valid()) return 0;
  lame_internal_flags & gfc = *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(0, 0);
        tag = new unsigned char[n];
        if (tag == 0) {
            return -1;
        }
        tag_size = lame_get_id3v2_tag(tag, n);
        if (tag_size > n) {
            delete[]tag;
            return -1;
        }
        else {
            size_t  i;
            /* write tag directly into bitstream at current position */
            for (i = 0; i < tag_size; ++i) {
                gfc.add_dummy_byte(tag[i], 1);
            }
        }
        delete[]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_global_flags::lame_get_id3v1_tag(unsigned char *buffer, size_t size) {
  size_t const tag_size = 128;
  if (!is_valid()) return 0;
  if (size < tag_size) return tag_size;
  lame_internal_flags &gfc = *internal_flags;
  if (!buffer) 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 lame_global_struct::id3tag_write_v1() {
  size_t  i, n, m;
  unsigned char tag[128];
  if (!is_valid()) return 0;
  lame_internal_flags & gfc = *internal_flags;

    m = sizeof(tag);
    n = lame_get_id3v1_tag( tag, m);
    if (n > m) {
        return 0;
    }
    /* write tag directly into bitstream at current position */
    for (i = 0; i < n; ++i) {
      gfc.add_dummy_byte(tag[i], 1);
    }
    return (int) n;     /* ok, tag has fixed size of 128 bytes, well below 2GB */
}
Detected encoding: ASCII (7 bit)2