|
Packit |
c32a2d |
/*
|
|
Packit |
c32a2d |
id3print: display routines for ID3 tags (including filtering of UTF8 to ASCII)
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
copyright 2006-2016 by the mpg123 project - free software under the terms of the LGPL 2.1
|
|
Packit |
c32a2d |
see COPYING and AUTHORS files in distribution or http://mpg123.org
|
|
Packit |
c32a2d |
initially written by Thomas Orgis
|
|
Packit |
c32a2d |
*/
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
/* Need snprintf(). */
|
|
Packit |
c32a2d |
#define _DEFAULT_SOURCE
|
|
Packit |
c32a2d |
#define _BSD_SOURCE
|
|
Packit |
c32a2d |
#include "mpg123app.h"
|
|
Packit |
c32a2d |
#include "common.h"
|
|
Packit |
c32a2d |
#include "genre.h"
|
|
Packit |
c32a2d |
#include "debug.h"
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
static const char joker_symbol = '*';
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
/* Metadata name field texts with index enumeration. */
|
|
Packit |
c32a2d |
enum tagcode { TITLE=0, ARTIST, ALBUM, COMMENT, YEAR, GENRE, FIELDS };
|
|
Packit |
c32a2d |
static const char* name[FIELDS] =
|
|
Packit |
c32a2d |
{
|
|
Packit |
c32a2d |
"Title"
|
|
Packit |
c32a2d |
, "Artist"
|
|
Packit |
c32a2d |
, "Album"
|
|
Packit |
c32a2d |
, "Comment"
|
|
Packit |
c32a2d |
, "Year"
|
|
Packit |
c32a2d |
, "Genre"
|
|
Packit |
c32a2d |
};
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
/* Two-column printing: max length of left and right name strings.
|
|
Packit |
c32a2d |
see print_id3 for what goes left or right.
|
|
Packit |
c32a2d |
Choose namelen[0] >= namelen[1]! */
|
|
Packit |
c32a2d |
static const int namelen[2] = {7, 6};
|
|
Packit |
c32a2d |
/* Overhead is Name + ": " and also plus " " for right column. */
|
|
Packit |
c32a2d |
/* pedantic C89 does not like:
|
|
Packit |
c32a2d |
const int overhead[2] = { namelen[0]+2, namelen[1]+4 }; */
|
|
Packit |
c32a2d |
static const int overhead[2] = { 9, 10 };
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
static void utf8_ascii(mpg123_string *dest, mpg123_string *source);
|
|
Packit |
c32a2d |
/* Copy UTF-8 string or melt it down to ASCII, also returning the character length. */
|
|
Packit |
c32a2d |
static size_t transform(mpg123_string *dest, mpg123_string *source)
|
|
Packit |
c32a2d |
{
|
|
Packit |
c32a2d |
debug("transform!");
|
|
Packit |
c32a2d |
if(source == NULL) return 0;
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
if(utf8env) mpg123_copy_string(source, dest);
|
|
Packit |
c32a2d |
else utf8_ascii(dest, source);
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
return mpg123_strlen(dest, utf8env);
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
static void id3_gap(mpg123_string *dest, size_t count, char *v1, size_t *len)
|
|
Packit |
c32a2d |
{
|
|
Packit |
c32a2d |
if(!dest->fill)
|
|
Packit |
c32a2d |
{
|
|
Packit |
c32a2d |
if(dest->size >= count+1 || mpg123_resize_string(dest, count+1))
|
|
Packit |
c32a2d |
{
|
|
Packit |
c32a2d |
strncpy(dest->p,v1,count);
|
|
Packit |
c32a2d |
dest->p[count] = 0;
|
|
Packit |
c32a2d |
*len = strlen(dest->p);
|
|
Packit |
c32a2d |
dest->fill = *len + 1;
|
|
Packit |
c32a2d |
/* We have no idea what encoding this is.
|
|
Packit |
c32a2d |
So, to prevent mess up of our UTF-8 display, filter anything above ASCII.
|
|
Packit |
c32a2d |
But in non-UTF-8 mode, we pray that the verbatim contents are meaningful to the user. Might filter non-printable characters, though. */
|
|
Packit |
c32a2d |
if(utf8env)
|
|
Packit |
c32a2d |
{
|
|
Packit |
c32a2d |
size_t i;
|
|
Packit |
c32a2d |
for(i=0; i<dest->fill-1; ++i)
|
|
Packit |
c32a2d |
if(dest->p[i] & 0x80) dest->p[i] = joker_symbol;
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
/* Print one metadata entry on a line, aligning the beginning. */
|
|
Packit |
c32a2d |
static void print_oneline( FILE* out
|
|
Packit |
c32a2d |
, const mpg123_string *tag, enum tagcode fi, int long_mode )
|
|
Packit |
c32a2d |
{
|
|
Packit |
c32a2d |
char fmt[14]; /* "%s:%-XXXs%s\n" plus one null */
|
|
Packit |
c32a2d |
if(!tag[fi].fill && !long_mode)
|
|
Packit |
c32a2d |
return;
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
if(long_mode)
|
|
Packit |
c32a2d |
fprintf(out, "\t");
|
|
Packit |
c32a2d |
snprintf( fmt, sizeof(fmt)-1, "%%s:%%-%ds%%s\n"
|
|
Packit |
c32a2d |
, 1+namelen[0]-(int)strlen(name[fi]) );
|
|
Packit |
c32a2d |
fprintf(out, fmt, name[fi], " ", tag[fi].fill ? tag[fi].p : "");
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
/*
|
|
Packit |
c32a2d |
Print a pair of tag name-value pairs along each other in two columns or
|
|
Packit |
c32a2d |
each on a line if that is not sensible.
|
|
Packit |
c32a2d |
This takes a given length (in columns) into account, not just bytes.
|
|
Packit |
c32a2d |
If that length would be computed taking grapheme clusters into account, things
|
|
Packit |
c32a2d |
could be fine for the whole world of Unicode. So far we ride only on counting
|
|
Packit |
c32a2d |
possibly multibyte characters (unless mpg123_strlen() got adapted meanwhile).
|
|
Packit |
c32a2d |
*/
|
|
Packit |
c32a2d |
static void print_pair
|
|
Packit |
c32a2d |
(
|
|
Packit |
c32a2d |
FILE* out /* Output stream. */
|
|
Packit |
c32a2d |
, const int *climit /* Maximum width of columns (two values). */
|
|
Packit |
c32a2d |
, const mpg123_string *tag /* array of tag value strings */
|
|
Packit |
c32a2d |
, const size_t *len /* array of character/column lengths */
|
|
Packit |
c32a2d |
, enum tagcode f0, enum tagcode f1 /* field indices for column 0 and 1 */
|
|
Packit |
c32a2d |
){
|
|
Packit |
c32a2d |
/* Two-column printout if things match, dumb printout otherwise. */
|
|
Packit |
c32a2d |
if( tag[f0].fill && tag[f1].fill
|
|
Packit |
c32a2d |
&& len[f0] <= (size_t)climit[0] && len[f1] <= (size_t)climit[1] )
|
|
Packit |
c32a2d |
{
|
|
Packit |
c32a2d |
char cfmt[35]; /* "%s:%-XXXs%-XXXs %s:%-XXXs%-XXXs\n" plus one extra null from snprintf */
|
|
Packit |
c32a2d |
int chardiff[2];
|
|
Packit |
c32a2d |
size_t bytelen;
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
/* difference between character length and byte length */
|
|
Packit |
c32a2d |
bytelen = strlen(tag[f0].p);
|
|
Packit |
c32a2d |
chardiff[0] = len[f0] < bytelen ? bytelen-len[f0] : 0;
|
|
Packit |
c32a2d |
bytelen = strlen(tag[f1].p);
|
|
Packit |
c32a2d |
chardiff[1] = len[f1] < bytelen ? bytelen-len[f1] : 0;
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
/* Two-column format string with added padding for multibyte chars. */
|
|
Packit |
c32a2d |
snprintf( cfmt, sizeof(cfmt)-1, "%%s:%%-%ds%%-%ds %%s:%%-%ds%%-%ds\n"
|
|
Packit |
c32a2d |
, 1+namelen[0]-(int)strlen(name[f0]), climit[0]+chardiff[0]
|
|
Packit |
c32a2d |
, 1+namelen[1]-(int)strlen(name[f1]), climit[1]+chardiff[1] );
|
|
Packit |
c32a2d |
/* Actual printout of name and value pairs. */
|
|
Packit |
c32a2d |
fprintf(out, cfmt, name[f0], " ", tag[f0].p, name[f1], " ", tag[f1].p);
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
else
|
|
Packit |
c32a2d |
{
|
|
Packit |
c32a2d |
print_oneline(out, tag, f0, FALSE);
|
|
Packit |
c32a2d |
print_oneline(out, tag, f1, FALSE);
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
/* Print tags... limiting the UTF-8 to ASCII, if necessary. */
|
|
Packit |
c32a2d |
void print_id3_tag(mpg123_handle *mh, int long_id3, FILE *out)
|
|
Packit |
c32a2d |
{
|
|
Packit |
c32a2d |
char genre_from_v1 = 0;
|
|
Packit |
c32a2d |
enum tagcode ti;
|
|
Packit |
c32a2d |
mpg123_string tag[FIELDS];
|
|
Packit |
c32a2d |
size_t len[FIELDS];
|
|
Packit |
c32a2d |
mpg123_id3v1 *v1;
|
|
Packit |
c32a2d |
mpg123_id3v2 *v2;
|
|
Packit |
c32a2d |
/* no memory allocated here, so return is safe */
|
|
Packit |
c32a2d |
for(ti=0; ti
|
|
Packit |
c32a2d |
/* extract the data */
|
|
Packit |
c32a2d |
mpg123_id3(mh, &v1, &v2;;
|
|
Packit |
c32a2d |
/* Only work if something there... */
|
|
Packit |
c32a2d |
if(v1 == NULL && v2 == NULL) return;
|
|
Packit |
c32a2d |
if(v2 != NULL) /* fill from ID3v2 data */
|
|
Packit |
c32a2d |
{
|
|
Packit |
c32a2d |
len[TITLE] = transform(&tag[TITLE], v2->title);
|
|
Packit |
c32a2d |
len[ARTIST] = transform(&tag[ARTIST], v2->artist);
|
|
Packit |
c32a2d |
len[ALBUM] = transform(&tag[ALBUM], v2->album);
|
|
Packit |
c32a2d |
len[COMMENT] = transform(&tag[COMMENT], v2->comment);
|
|
Packit |
c32a2d |
len[YEAR] = transform(&tag[YEAR], v2->year);
|
|
Packit |
c32a2d |
len[GENRE] = transform(&tag[GENRE], v2->genre);
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
if(v1 != NULL) /* fill gaps with ID3v1 data */
|
|
Packit |
c32a2d |
{
|
|
Packit |
c32a2d |
/* I _could_ skip the recalculation of fill ... */
|
|
Packit |
c32a2d |
id3_gap(&tag[TITLE], 30, v1->title, &len[TITLE]);
|
|
Packit |
c32a2d |
id3_gap(&tag[ARTIST], 30, v1->artist, &len[ARTIST]);
|
|
Packit |
c32a2d |
id3_gap(&tag[ALBUM], 30, v1->album, &len[ALBUM]);
|
|
Packit |
c32a2d |
id3_gap(&tag[COMMENT], 30, v1->comment, &len[COMMENT]);
|
|
Packit |
c32a2d |
id3_gap(&tag[YEAR], 4, v1->year, &len[YEAR]);
|
|
Packit |
c32a2d |
/*
|
|
Packit |
c32a2d |
genre is special... v1->genre holds an index, id3v2 genre may contain indices in textual form and raw textual genres...
|
|
Packit |
c32a2d |
*/
|
|
Packit |
c32a2d |
if(!tag[GENRE].fill)
|
|
Packit |
c32a2d |
{
|
|
Packit |
c32a2d |
if(tag[GENRE].size >= 31 || mpg123_resize_string(&tag[GENRE],31))
|
|
Packit |
c32a2d |
{
|
|
Packit |
c32a2d |
if(v1->genre <= genre_count)
|
|
Packit |
c32a2d |
{
|
|
Packit |
c32a2d |
strncpy(tag[GENRE].p, genre_table[v1->genre], 30);
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
else
|
|
Packit |
c32a2d |
{
|
|
Packit |
c32a2d |
strncpy(tag[GENRE].p,"Unknown",30);
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
tag[GENRE].p[30] = 0;
|
|
Packit |
c32a2d |
/* V1 was plain ASCII, so strlen is fine. */
|
|
Packit |
c32a2d |
len[GENRE] = strlen(tag[GENRE].p);
|
|
Packit |
c32a2d |
tag[GENRE].fill = len[GENRE] + 1;
|
|
Packit |
c32a2d |
genre_from_v1 = 1;
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
if(tag[GENRE].fill && !genre_from_v1)
|
|
Packit |
c32a2d |
{
|
|
Packit |
c32a2d |
/*
|
|
Packit |
c32a2d |
id3v2.3 says (id)(id)blabla and in case you want ot have (blabla) write ((blabla)
|
|
Packit |
c32a2d |
also, there is
|
|
Packit |
c32a2d |
(RX) Remix
|
|
Packit |
c32a2d |
(CR) Cover
|
|
Packit |
c32a2d |
id3v2.4 says
|
|
Packit |
c32a2d |
"one or several of the ID3v1 types as numerical strings"
|
|
Packit |
c32a2d |
or define your own (write strings), RX and CR
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
Now I am very sure that I'll encounter hellishly mixed up id3v2 frames, so try to parse both at once.
|
|
Packit |
c32a2d |
*/
|
|
Packit |
c32a2d |
mpg123_string tmp;
|
|
Packit |
c32a2d |
mpg123_init_string(&tmp);
|
|
Packit |
c32a2d |
debug1("interpreting genre: %s\n", tag[GENRE].p);
|
|
Packit |
c32a2d |
if(mpg123_copy_string(&tag[GENRE], &tmp))
|
|
Packit |
c32a2d |
{
|
|
Packit |
c32a2d |
size_t num = 0;
|
|
Packit |
c32a2d |
size_t nonum = 0;
|
|
Packit |
c32a2d |
size_t i;
|
|
Packit |
c32a2d |
enum { nothing, number, outtahere } state = nothing;
|
|
Packit |
c32a2d |
tag[GENRE].fill = 0; /* going to be refilled */
|
|
Packit |
c32a2d |
/* number\n -> id3v1 genre */
|
|
Packit |
c32a2d |
/* (number) -> id3v1 genre */
|
|
Packit |
c32a2d |
/* (( -> ( */
|
|
Packit |
c32a2d |
for(i = 0; i < tmp.fill; ++i)
|
|
Packit |
c32a2d |
{
|
|
Packit |
c32a2d |
debug1("i=%lu", (unsigned long) i);
|
|
Packit |
c32a2d |
switch(state)
|
|
Packit |
c32a2d |
{
|
|
Packit |
c32a2d |
case nothing:
|
|
Packit |
c32a2d |
nonum = i;
|
|
Packit |
c32a2d |
if(tmp.p[i] == '(')
|
|
Packit |
c32a2d |
{
|
|
Packit |
c32a2d |
num = i+1; /* number starting as next? */
|
|
Packit |
c32a2d |
state = number;
|
|
Packit |
c32a2d |
debug1("( before number at %lu?", (unsigned long) num);
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
/* you know an encoding where this doesn't work? */
|
|
Packit |
c32a2d |
else if(tmp.p[i] >= '0' && tmp.p[i] <= '9')
|
|
Packit |
c32a2d |
{
|
|
Packit |
c32a2d |
num = i;
|
|
Packit |
c32a2d |
state = number;
|
|
Packit |
c32a2d |
debug1("direct number at %lu", (unsigned long) num);
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
else state = outtahere;
|
|
Packit |
c32a2d |
break;
|
|
Packit |
c32a2d |
case number:
|
|
Packit |
c32a2d |
/* fake number alert: (( -> ( */
|
|
Packit |
c32a2d |
if(tmp.p[i] == '(')
|
|
Packit |
c32a2d |
{
|
|
Packit |
c32a2d |
nonum = i;
|
|
Packit |
c32a2d |
state = outtahere;
|
|
Packit |
c32a2d |
debug("no, it was ((");
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
else if(tmp.p[i] == ')' || tmp.p[i] == '\n' || tmp.p[i] == 0)
|
|
Packit |
c32a2d |
{
|
|
Packit |
c32a2d |
if(i-num > 0)
|
|
Packit |
c32a2d |
{
|
|
Packit |
c32a2d |
/* we really have a number */
|
|
Packit |
c32a2d |
int gid;
|
|
Packit |
c32a2d |
char* genre = "Unknown";
|
|
Packit |
c32a2d |
tmp.p[i] = 0;
|
|
Packit |
c32a2d |
gid = atoi(tmp.p+num);
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
/* get that genre */
|
|
Packit |
c32a2d |
if(gid >= 0 && gid <= genre_count) genre = genre_table[gid];
|
|
Packit |
c32a2d |
debug1("found genre: %s", genre);
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
if(tag[GENRE].fill) mpg123_add_string(&tag[GENRE], ", ");
|
|
Packit |
c32a2d |
mpg123_add_string(&tag[GENRE], genre);
|
|
Packit |
c32a2d |
nonum = i+1; /* next possible stuff */
|
|
Packit |
c32a2d |
state = nothing;
|
|
Packit |
c32a2d |
debug1("had a number: %i", gid);
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
else
|
|
Packit |
c32a2d |
{
|
|
Packit |
c32a2d |
/* wasn't a number, nonum is set */
|
|
Packit |
c32a2d |
state = outtahere;
|
|
Packit |
c32a2d |
debug("no (num) thing...");
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
else if(!(tmp.p[i] >= '0' && tmp.p[i] <= '9'))
|
|
Packit |
c32a2d |
{
|
|
Packit |
c32a2d |
/* no number at last... */
|
|
Packit |
c32a2d |
state = outtahere;
|
|
Packit |
c32a2d |
debug("nothing numeric here");
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
else
|
|
Packit |
c32a2d |
{
|
|
Packit |
c32a2d |
debug("still number...");
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
break;
|
|
Packit |
c32a2d |
default: break;
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
if(state == outtahere) break;
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
/* Small hack: Avoid repeating genre in case of stuff like
|
|
Packit |
c32a2d |
(144)Thrash Metal being given. The simple cases. */
|
|
Packit |
c32a2d |
if(
|
|
Packit |
c32a2d |
nonum < tmp.fill-1 &&
|
|
Packit |
c32a2d |
(!tag[GENRE].fill || strncmp(tag[GENRE].p, tmp.p+nonum, tag[GENRE].fill))
|
|
Packit |
c32a2d |
)
|
|
Packit |
c32a2d |
{
|
|
Packit |
c32a2d |
if(tag[GENRE].fill) mpg123_add_string(&tag[GENRE], ", ");
|
|
Packit |
c32a2d |
mpg123_add_string(&tag[GENRE], tmp.p+nonum);
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
/* Do not like that ... assumes plain ASCII ... */
|
|
Packit |
c32a2d |
len[GENRE] = strlen(tag[GENRE].p);
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
mpg123_free_string(&tmp);
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
if(long_id3)
|
|
Packit |
c32a2d |
{
|
|
Packit |
c32a2d |
fprintf(out,"\n");
|
|
Packit |
c32a2d |
/* print id3v2 */
|
|
Packit |
c32a2d |
print_oneline(out, tag, TITLE, TRUE);
|
|
Packit |
c32a2d |
print_oneline(out, tag, ARTIST, TRUE);
|
|
Packit |
c32a2d |
print_oneline(out, tag, ALBUM, TRUE);
|
|
Packit |
c32a2d |
print_oneline(out, tag, YEAR, TRUE);
|
|
Packit |
c32a2d |
print_oneline(out, tag, GENRE, TRUE);
|
|
Packit |
c32a2d |
print_oneline(out, tag, COMMENT, TRUE);
|
|
Packit |
c32a2d |
fprintf(out,"\n");
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
else
|
|
Packit |
c32a2d |
{
|
|
Packit |
c32a2d |
/* We are trying to be smart here and conserve some vertical space.
|
|
Packit |
c32a2d |
So we will skip tags not set, and try to show them in two parallel
|
|
Packit |
c32a2d |
columns if they are short, which is by far the most common case. */
|
|
Packit |
c32a2d |
int linelimit;
|
|
Packit |
c32a2d |
int climit[2];
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
/* Adapt formatting width to terminal if possible. */
|
|
Packit |
c32a2d |
linelimit = term_width(fileno(out));
|
|
Packit |
c32a2d |
if(linelimit < 0)
|
|
Packit |
c32a2d |
linelimit=overhead[0]+30+overhead[1]+30; /* the old style, based on ID3v1 */
|
|
Packit |
c32a2d |
if(linelimit > 200)
|
|
Packit |
c32a2d |
linelimit = 200; /* Not too wide. Also for format string safety. */
|
|
Packit |
c32a2d |
/* Divide the space between the two columns, not wasting any. */
|
|
Packit |
c32a2d |
climit[1] = linelimit/2-overhead[0];
|
|
Packit |
c32a2d |
climit[0] = linelimit-linelimit/2-overhead[1];
|
|
Packit |
c32a2d |
debug3("linelimits: %i < %i | %i >", linelimit, climit[0], climit[1]);
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
if(climit[0] <= 0 || climit[1] <= 0)
|
|
Packit |
c32a2d |
{
|
|
Packit |
c32a2d |
/* Ensure disabled column printing, no play with signedness in comparisons. */
|
|
Packit |
c32a2d |
climit[0] = 0;
|
|
Packit |
c32a2d |
climit[1] = 0;
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
fprintf(out,"\n"); /* Still use one separator line. Too ugly without. */
|
|
Packit |
c32a2d |
print_pair(out, climit, tag, len, TITLE, ARTIST);
|
|
Packit |
c32a2d |
print_pair(out, climit, tag, len, COMMENT, ALBUM );
|
|
Packit |
c32a2d |
print_pair(out, climit, tag, len, YEAR, GENRE );
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
for(ti=0; ti
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
if(v2 != NULL && APPFLAG(MPG123APP_LYRICS))
|
|
Packit |
c32a2d |
{
|
|
Packit |
c32a2d |
/* find and print texts that have USLT IDs */
|
|
Packit |
c32a2d |
size_t i;
|
|
Packit |
c32a2d |
for(i=0; i<v2->texts; ++i)
|
|
Packit |
c32a2d |
{
|
|
Packit |
c32a2d |
if(!memcmp(v2->text[i].id, "USLT", 4))
|
|
Packit |
c32a2d |
{
|
|
Packit |
c32a2d |
/* split into lines, ensure usage of proper local line end */
|
|
Packit |
c32a2d |
size_t a=0;
|
|
Packit |
c32a2d |
size_t b=0;
|
|
Packit |
c32a2d |
char lang[4]; /* just a 3-letter ASCII code, no fancy encoding */
|
|
Packit |
c32a2d |
mpg123_string innline;
|
|
Packit |
c32a2d |
mpg123_string outline;
|
|
Packit |
c32a2d |
mpg123_string *uslt = &v2->text[i].text;
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
memcpy(lang, &v2->text[i].lang, 3);
|
|
Packit |
c32a2d |
lang[3] = 0;
|
|
Packit |
c32a2d |
printf("Lyrics begin, language: %s; %s\n\n", lang, v2->text[i].description.fill ? v2->text[i].description.p : "");
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
mpg123_init_string(&innline);
|
|
Packit |
c32a2d |
mpg123_init_string(&outline);
|
|
Packit |
c32a2d |
while(a < uslt->fill)
|
|
Packit |
c32a2d |
{
|
|
Packit |
c32a2d |
b = a;
|
|
Packit |
c32a2d |
while(b < uslt->fill && uslt->p[b] != '\n' && uslt->p[b] != '\r') ++b;
|
|
Packit |
c32a2d |
/* Either found end of a line or end of the string (null byte) */
|
|
Packit |
c32a2d |
mpg123_set_substring(&innline, uslt->p, a, b-a);
|
|
Packit |
c32a2d |
transform(&outline, &innline);
|
|
Packit |
c32a2d |
printf(" %s\n", outline.p);
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
if(uslt->p[b] == uslt->fill) break; /* nothing more */
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
/* Swallow CRLF */
|
|
Packit |
c32a2d |
if(uslt->fill-b > 1 && uslt->p[b] == '\r' && uslt->p[b+1] == '\n') ++b;
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
a = b + 1; /* next line beginning */
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
mpg123_free_string(&innline);
|
|
Packit |
c32a2d |
mpg123_free_string(&outline);
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
printf("\nLyrics end.\n");
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
void print_icy(mpg123_handle *mh, FILE *outstream)
|
|
Packit |
c32a2d |
{
|
|
Packit |
c32a2d |
char* icy;
|
|
Packit |
c32a2d |
if(MPG123_OK == mpg123_icy(mh, &icy))
|
|
Packit |
c32a2d |
{
|
|
Packit |
c32a2d |
mpg123_string in;
|
|
Packit |
c32a2d |
mpg123_init_string(&in);
|
|
Packit |
c32a2d |
if(mpg123_store_utf8(&in, mpg123_text_icy, (unsigned char*)icy, strlen(icy)+1))
|
|
Packit |
c32a2d |
{
|
|
Packit |
c32a2d |
mpg123_string out;
|
|
Packit |
c32a2d |
mpg123_init_string(&out;;
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
transform(&out, &in);
|
|
Packit |
c32a2d |
if(out.fill)
|
|
Packit |
c32a2d |
fprintf(outstream, "\nICY-META: %s\n", out.p);
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
mpg123_free_string(&out;;
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
mpg123_free_string(&in);
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
static void utf8_ascii(mpg123_string *dest, mpg123_string *source)
|
|
Packit |
c32a2d |
{
|
|
Packit |
c32a2d |
size_t spos = 0;
|
|
Packit |
c32a2d |
size_t dlen = 0;
|
|
Packit |
c32a2d |
char *p;
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
/* Find length of ASCII string (count non-continuation bytes).
|
|
Packit |
c32a2d |
Do _not_ change this to mpg123_strlen()!
|
|
Packit |
c32a2d |
It needs to match the loop below. Especially dlen should not stop at embedded null bytes. You can get any trash from ID3! */
|
|
Packit |
c32a2d |
for(spos=0; spos < source->fill; ++spos)
|
|
Packit |
c32a2d |
if((source->p[spos] & 0xc0) == 0x80) continue;
|
|
Packit |
c32a2d |
else ++dlen;
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
/* The trailing zero is included in dlen; if there is none, one character will be cut. Bad input -> bad output. */
|
|
Packit |
c32a2d |
if(!mpg123_resize_string(dest, dlen)){ mpg123_free_string(dest); return; }
|
|
Packit |
c32a2d |
/* Just ASCII, we take it easy. */
|
|
Packit |
c32a2d |
p = dest->p;
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
for(spos=0; spos < source->fill; ++spos)
|
|
Packit |
c32a2d |
{
|
|
Packit |
c32a2d |
/* UTF-8 continuation byte 0x10?????? */
|
|
Packit |
c32a2d |
if((source->p[spos] & 0xc0) == 0x80) continue;
|
|
Packit |
c32a2d |
/* UTF-8 lead byte 0x11?????? */
|
|
Packit |
c32a2d |
else if(source->p[spos] & 0x80) *p = joker_symbol;
|
|
Packit |
c32a2d |
/* just ASCII, 0x0??????? */
|
|
Packit |
c32a2d |
else *p = source->p[spos];
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
++p; /* next output char */
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
/* Always close the string, trailing zero might be missing. */
|
|
Packit |
c32a2d |
if(dest->size) dest->p[dest->size-1] = 0;
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
dest->fill = dest->size;
|
|
Packit |
c32a2d |
}
|