mirror of
				https://github.com/cookiengineer/audacity
				synced 2025-10-31 14:13:50 +01:00 
			
		
		
		
	Using LAME 3.10 Windows project files substantially changed from original, and included into audacity solution.
		
			
				
	
	
		
			1927 lines
		
	
	
		
			52 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			1927 lines
		
	
	
		
			52 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /*
 | |
|  * 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: id3tag.c,v 1.80 2017/08/28 15:39:51 robert Exp $ */
 | |
| 
 | |
| #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 */
 | |
| }
 |