Blame src/mpg123-id3dump.c

Packit c32a2d
/*
Packit c32a2d
	id3dump: Print ID3 tags of files, scanned using libmpg123.
Packit c32a2d
Packit c32a2d
	copyright 2007 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 "config.h"
Packit c32a2d
#include "compat.h"
Packit c32a2d
#include "mpg123.h"
Packit c32a2d
#include "getlopt.h"
Packit c32a2d
#include <errno.h>
Packit c32a2d
#include <ctype.h>
Packit c32a2d
#include "debug.h"
Packit c32a2d
#include "win32_support.h"
Packit c32a2d
Packit c32a2d
static int errors = 0;
Packit c32a2d
Packit c32a2d
static struct
Packit c32a2d
{
Packit c32a2d
	int store_pics;
Packit c32a2d
	int do_scan;
Packit c32a2d
} param =
Packit c32a2d
{
Packit c32a2d
	  FALSE
Packit c32a2d
	, TRUE
Packit c32a2d
};
Packit c32a2d
Packit c32a2d
static const char* progname;
Packit c32a2d
Packit c32a2d
static void usage(int err)
Packit c32a2d
{
Packit c32a2d
	FILE* o = stdout;
Packit c32a2d
	if(err)
Packit c32a2d
	{
Packit c32a2d
		o = stderr; 
Packit c32a2d
		fprintf(o, "You made some mistake in program usage... let me briefly remind you:\n\n");
Packit c32a2d
	}
Packit c32a2d
	fprintf(o, "Tool to dump ID3 meta data from MPEG audio files using libmpg123\n");
Packit c32a2d
	fprintf(o, "\tversion %s; written and copyright by Thomas Orgis and the mpg123 project\n", PACKAGE_VERSION);
Packit c32a2d
	fprintf(o,"\nusage: %s [option(s)] file(s)\n", progname);
Packit c32a2d
	fprintf(o,"\noptions:\n");
Packit c32a2d
	fprintf(o," -h     --help              give usage help\n");
Packit c32a2d
	fprintf(o," -n     --no-scan           do not scan entire file (just beginning)\n");
Packit c32a2d
	fprintf(o," -p     --store-pics        write APIC frames (album art pictures) to files\n");
Packit c32a2d
	fprintf(o,"                            file names using whole input file name as prefix\n");
Packit c32a2d
	fprintf(o,"\nNote that text output will always be in UTF-8, regardless of locale.\n");
Packit c32a2d
	exit(err);
Packit c32a2d
}
Packit c32a2d
static void want_usage(char* bla)
Packit c32a2d
{
Packit c32a2d
	usage(0);
Packit c32a2d
}
Packit c32a2d
Packit c32a2d
static topt opts[] =
Packit c32a2d
{
Packit c32a2d
	 {'h', "help",         0,       want_usage, 0,                 0}
Packit c32a2d
	,{'n', "no-scan",      GLO_INT, 0,          &param.do_scan,    FALSE}
Packit c32a2d
	,{'p', "store-pics",   GLO_INT, 0,          &param.store_pics, TRUE}
Packit c32a2d
	,{0, 0, 0, 0, 0, 0}
Packit c32a2d
};
Packit c32a2d
Packit c32a2d
/* Helper for v1 printing, get these strings their zero byte. */
Packit c32a2d
void safe_print(char* name, char *data, size_t size)
Packit c32a2d
{
Packit c32a2d
	char safe[31];
Packit c32a2d
	if(size>30) return;
Packit c32a2d
Packit c32a2d
	memcpy(safe, data, size);
Packit c32a2d
	safe[size] = 0;
Packit c32a2d
	printf("%s: %s\n", name, safe);
Packit c32a2d
}
Packit c32a2d
Packit c32a2d
/* Print out ID3v1 info. */
Packit c32a2d
void print_v1(mpg123_id3v1 *v1)
Packit c32a2d
{
Packit c32a2d
	safe_print("Title",   v1->title,   sizeof(v1->title));
Packit c32a2d
	safe_print("Artist",  v1->artist,  sizeof(v1->artist));
Packit c32a2d
	safe_print("Album",   v1->album,   sizeof(v1->album));
Packit c32a2d
	safe_print("Year",    v1->year,    sizeof(v1->year));
Packit c32a2d
	safe_print("Comment", v1->comment, sizeof(v1->comment));
Packit c32a2d
	printf("Genre: %i", v1->genre);
Packit c32a2d
}
Packit c32a2d
Packit c32a2d
/* Split up a number of lines separated by \n, \r, both or just zero byte
Packit c32a2d
   and print out each line with specified prefix. */
Packit c32a2d
void print_lines(const char* prefix, mpg123_string *inlines)
Packit c32a2d
{
Packit c32a2d
	size_t i;
Packit c32a2d
	int hadcr = 0, hadlf = 0;
Packit c32a2d
	char *lines = NULL;
Packit c32a2d
	char *line  = NULL;
Packit c32a2d
	size_t len = 0;
Packit c32a2d
Packit c32a2d
	if(inlines != NULL && inlines->fill)
Packit c32a2d
	{
Packit c32a2d
		lines = inlines->p;
Packit c32a2d
		len   = inlines->fill;
Packit c32a2d
	}
Packit c32a2d
	else return;
Packit c32a2d
Packit c32a2d
	line = lines;
Packit c32a2d
	for(i=0; i
Packit c32a2d
	{
Packit c32a2d
		if(lines[i] == '\n' || lines[i] == '\r' || lines[i] == 0)
Packit c32a2d
		{
Packit c32a2d
			char save = lines[i]; /* saving, changing, restoring a byte in the data */
Packit c32a2d
			if(save == '\n') ++hadlf;
Packit c32a2d
			if(save == '\r') ++hadcr;
Packit c32a2d
			if((hadcr || hadlf) && hadlf % 2 == 0 && hadcr % 2 == 0) line = "";
Packit c32a2d
Packit c32a2d
			if(line)
Packit c32a2d
			{
Packit c32a2d
				lines[i] = 0;
Packit c32a2d
				printf("%s%s\n", prefix, line);
Packit c32a2d
				line = NULL;
Packit c32a2d
				lines[i] = save;
Packit c32a2d
			}
Packit c32a2d
		}
Packit c32a2d
		else
Packit c32a2d
		{
Packit c32a2d
			hadlf = hadcr = 0;
Packit c32a2d
			if(line == NULL) line = lines+i;
Packit c32a2d
		}
Packit c32a2d
	}
Packit c32a2d
}
Packit c32a2d
Packit c32a2d
/* Print out the named ID3v2  fields. */
Packit c32a2d
void print_v2(mpg123_id3v2 *v2)
Packit c32a2d
{
Packit c32a2d
	print_lines("Title: ",   v2->title);
Packit c32a2d
	print_lines("Artist: ",  v2->artist);
Packit c32a2d
	print_lines("Album: ",   v2->album);
Packit c32a2d
	print_lines("Year: ",    v2->year);
Packit c32a2d
	print_lines("Comment: ", v2->comment);
Packit c32a2d
	print_lines("Genre: ",   v2->genre);
Packit c32a2d
}
Packit c32a2d
Packit c32a2d
/* Easy conversion to string via lookup. */
Packit c32a2d
const char* pic_types[] = 
Packit c32a2d
{
Packit c32a2d
	 "other"
Packit c32a2d
	,"icon"
Packit c32a2d
	,"other icon"
Packit c32a2d
	,"front cover"
Packit c32a2d
	,"back cover"
Packit c32a2d
	,"leaflet"
Packit c32a2d
	,"media"
Packit c32a2d
	,"lead"
Packit c32a2d
	,"artist"
Packit c32a2d
	,"conductor"
Packit c32a2d
	,"orchestra"
Packit c32a2d
	,"composer"
Packit c32a2d
	,"lyricist"
Packit c32a2d
	,"location"
Packit c32a2d
	,"recording"
Packit c32a2d
	,"performance"
Packit c32a2d
	,"video"
Packit c32a2d
	,"fish"
Packit c32a2d
	,"illustration"
Packit c32a2d
	,"artist logo"
Packit c32a2d
	,"publisher logo"
Packit c32a2d
};
Packit c32a2d
static const char* pic_type(int id)
Packit c32a2d
{
Packit c32a2d
	return (id >= 0 && id < (sizeof(pic_types)/sizeof(char*)))
Packit c32a2d
		? pic_types[id]
Packit c32a2d
		: "invalid type";
Packit c32a2d
}
Packit c32a2d
Packit c32a2d
/* Print out all stored ID3v2 fields with their 4-character IDs. */
Packit c32a2d
void print_raw_v2(mpg123_id3v2 *v2)
Packit c32a2d
{
Packit c32a2d
	size_t i;
Packit c32a2d
	for(i=0; i<v2->texts; ++i)
Packit c32a2d
	{
Packit c32a2d
		char id[5];
Packit c32a2d
		char lang[4];
Packit c32a2d
		memcpy(id, v2->text[i].id, 4);
Packit c32a2d
		id[4] = 0;
Packit c32a2d
		memcpy(lang, v2->text[i].lang, 3);
Packit c32a2d
		lang[3] = 0;
Packit c32a2d
		if(v2->text[i].description.fill)
Packit c32a2d
		printf("%s language(%s) description(%s)\n", id, lang, v2->text[i].description.p);
Packit c32a2d
		else printf("%s language(%s)\n", id, lang);
Packit c32a2d
Packit c32a2d
		print_lines(" ", &v2->text[i].text);
Packit c32a2d
	}
Packit c32a2d
	for(i=0; i<v2->extras; ++i)
Packit c32a2d
	{
Packit c32a2d
		char id[5];
Packit c32a2d
		memcpy(id, v2->extra[i].id, 4);
Packit c32a2d
		id[4] = 0;
Packit c32a2d
		printf( "%s description(%s)\n",
Packit c32a2d
		        id,
Packit c32a2d
		        v2->extra[i].description.fill ? v2->extra[i].description.p : "" );
Packit c32a2d
		print_lines(" ", &v2->extra[i].text);
Packit c32a2d
	}
Packit c32a2d
	for(i=0; i<v2->comments; ++i)
Packit c32a2d
	{
Packit c32a2d
		char id[5];
Packit c32a2d
		char lang[4];
Packit c32a2d
		memcpy(id, v2->comment_list[i].id, 4);
Packit c32a2d
		id[4] = 0;
Packit c32a2d
		memcpy(lang, v2->comment_list[i].lang, 3);
Packit c32a2d
		lang[3] = 0;
Packit c32a2d
		printf( "%s description(%s) language(%s):\n",
Packit c32a2d
		        id,
Packit c32a2d
		        v2->comment_list[i].description.fill ? v2->comment_list[i].description.p : "",
Packit c32a2d
		        lang );
Packit c32a2d
		print_lines(" ", &v2->comment_list[i].text);
Packit c32a2d
	}
Packit c32a2d
	for(i=0; i<v2->pictures; ++i)
Packit c32a2d
	{
Packit c32a2d
		mpg123_picture* pic;
Packit c32a2d
Packit c32a2d
		pic = &v2->picture[i];
Packit c32a2d
		fprintf(stderr, "APIC type(%i, %s) mime(%s) size(%"SIZE_P")\n",
Packit c32a2d
			pic->type, pic_type(pic->type), pic->mime_type.p, (size_p)pic->size);
Packit c32a2d
		print_lines(" ", &pic->description);
Packit c32a2d
	}
Packit c32a2d
}
Packit c32a2d
Packit c32a2d
const char* unknown_end = "picture";
Packit c32a2d
Packit c32a2d
static char* mime2end(mpg123_string* mime)
Packit c32a2d
{
Packit c32a2d
	size_t len;
Packit c32a2d
	char* end;
Packit c32a2d
	if(strncasecmp("image/",mime->p,6))
Packit c32a2d
	{
Packit c32a2d
		len = strlen(unknown_end)+1;
Packit c32a2d
		end = malloc(len);
Packit c32a2d
		memcpy(end, unknown_end, len);
Packit c32a2d
		return end;
Packit c32a2d
	}
Packit c32a2d
Packit c32a2d
	/* Else, use fmt out of image/fmt ... but make sure that usage stops at
Packit c32a2d
	   non-alphabetic character, as MIME can have funny stuff following a ";". */
Packit c32a2d
	for(len=1; len<mime->fill-6; ++len)
Packit c32a2d
	{
Packit c32a2d
		if(!isalnum(mime->p[len-1+6])) break;
Packit c32a2d
	}
Packit c32a2d
	/* len now containing the number of bytes after the "/" up to the next
Packit c32a2d
	   invalid char or null */
Packit c32a2d
	if(len < 1) return "picture";
Packit c32a2d
Packit c32a2d
	end = malloc(len);
Packit c32a2d
	if(!end) exit(11); /* Come on, is it worth wasting lines for a message? 
Packit c32a2d
	                      If we're so broke, fprintf will also likely fail. */
Packit c32a2d
Packit c32a2d
	memcpy(end, mime->p+6,len-1);
Packit c32a2d
	end[len-1] = 0;
Packit c32a2d
	return end;
Packit c32a2d
}
Packit c32a2d
Packit c32a2d
/* Construct a sane file name without introducing spaces, then open.
Packit c32a2d
   Example: /some/where/some.mp3.front_cover.jpeg
Packit c32a2d
   If multiple ones are there: some.mp3.front_cover2.jpeg */
Packit c32a2d
int open_picfile(const char* prefix, mpg123_picture* pic)
Packit c32a2d
{
Packit c32a2d
	char *end, *typestr, *pfn;
Packit c32a2d
	const char* pictype;
Packit c32a2d
	size_t i, len;
Packit c32a2d
	int fd;
Packit c32a2d
	unsigned long count = 1;
Packit c32a2d
Packit c32a2d
	pictype = pic_type(pic->type);
Packit c32a2d
	len = strlen(pictype);
Packit c32a2d
	if(!(typestr = malloc(len+1))) exit(11);
Packit c32a2d
	memcpy(typestr, pictype, len);
Packit c32a2d
	for(i=0; i
Packit c32a2d
Packit c32a2d
	typestr[len] = 0;
Packit c32a2d
	end = mime2end(&pic->mime_type);
Packit c32a2d
	len = strlen(prefix)+1+strlen(typestr)+1+strlen(end);
Packit c32a2d
	if(!(pfn = malloc(len+1))) exit(11);
Packit c32a2d
Packit c32a2d
	sprintf(pfn, "%s.%s.%s", prefix, typestr, end);
Packit c32a2d
	pfn[len] = 0;
Packit c32a2d
Packit c32a2d
	errno = 0;
Packit c32a2d
	fd = compat_open(pfn, O_CREAT|O_WRONLY|O_EXCL);
Packit c32a2d
	while(fd < 0 && errno == EEXIST && ++count < ULONG_MAX)
Packit c32a2d
	{
Packit c32a2d
		char dum;
Packit c32a2d
		size_t digits;
Packit c32a2d
Packit c32a2d
		digits = snprintf(&dum, 1, "%lu", count);
Packit c32a2d
		if(!(pfn=safe_realloc(pfn, len+digits+1))) exit(11);
Packit c32a2d
Packit c32a2d
		sprintf(pfn, "%s.%s%lu.%s", prefix, typestr, count, end);
Packit c32a2d
		pfn[len+digits] = 0;
Packit c32a2d
		errno = 0;		
Packit c32a2d
		fd = compat_open(pfn, O_CREAT|O_WRONLY|O_EXCL);
Packit c32a2d
	}
Packit c32a2d
	printf("writing %s\n", pfn);
Packit c32a2d
	if(fd < 0)
Packit c32a2d
	{
Packit c32a2d
		error("Cannot open for writing (counter exhaust? permissions?).");
Packit c32a2d
		++errors;
Packit c32a2d
	}
Packit c32a2d
Packit c32a2d
	free(end);
Packit c32a2d
	free(typestr);
Packit c32a2d
	free(pfn);
Packit c32a2d
	return fd;
Packit c32a2d
}
Packit c32a2d
Packit c32a2d
static void store_pictures(const char* prefix, mpg123_id3v2 *v2)
Packit c32a2d
{
Packit c32a2d
	int i;
Packit c32a2d
Packit c32a2d
	for(i=0; i<v2->pictures; ++i)
Packit c32a2d
	{
Packit c32a2d
		int fd;
Packit c32a2d
		mpg123_picture* pic;
Packit c32a2d
Packit c32a2d
		pic = &v2->picture[i];
Packit c32a2d
		fd = open_picfile(prefix, pic);
Packit c32a2d
		if(fd >= 0)
Packit c32a2d
		{ /* stream I/O for not having to care about interruptions */
Packit c32a2d
			FILE* picfile = compat_fdopen(fd, "w");
Packit c32a2d
			if(picfile)
Packit c32a2d
			{
Packit c32a2d
				if(fwrite(pic->data, pic->size, 1, picfile) != 1)
Packit c32a2d
				{
Packit c32a2d
					error("Failure to write data.");
Packit c32a2d
					++errors;
Packit c32a2d
				}
Packit c32a2d
				if(fclose(picfile))
Packit c32a2d
				{
Packit c32a2d
					error("Failure to close (flush?).");
Packit c32a2d
					++errors;
Packit c32a2d
				}
Packit c32a2d
			}
Packit c32a2d
			else
Packit c32a2d
			{
Packit c32a2d
				error1("Unable to fdopen output: %s)", strerror(errno));
Packit c32a2d
				++errors;
Packit c32a2d
			}
Packit c32a2d
		}
Packit c32a2d
	}
Packit c32a2d
}
Packit c32a2d
Packit c32a2d
int main(int argc, char **argv)
Packit c32a2d
{
Packit c32a2d
	int i, result;
Packit c32a2d
	mpg123_handle* m;
Packit c32a2d
#if defined(WANT_WIN32_UNICODE)
Packit c32a2d
	win32_cmdline_utf8(&argc,&argv);
Packit c32a2d
#endif
Packit c32a2d
	progname = argv[0];
Packit c32a2d
Packit c32a2d
	while ((result = getlopt(argc, argv, opts)))
Packit c32a2d
	switch (result) {
Packit c32a2d
		case GLO_UNKNOWN:
Packit c32a2d
			fprintf (stderr, "%s: Unknown option \"%s\".\n", 
Packit c32a2d
				progname, loptarg);
Packit c32a2d
			usage(1);
Packit c32a2d
		case GLO_NOARG:
Packit c32a2d
			fprintf (stderr, "%s: Missing argument for option \"%s\".\n",
Packit c32a2d
				progname, loptarg);
Packit c32a2d
			usage(1);
Packit c32a2d
	}
Packit c32a2d
Packit c32a2d
#ifdef WIN32
Packit c32a2d
	fprintf(stderr, "WARNING: This tool is not yet adapted to run on Windows (file I/O, unicode arguments)!\n");
Packit c32a2d
#endif
Packit c32a2d
	if(loptind >= argc) usage(1);
Packit c32a2d
Packit c32a2d
	mpg123_init();
Packit c32a2d
	m = mpg123_new(NULL, NULL);
Packit c32a2d
	mpg123_param(m, MPG123_ADD_FLAGS, MPG123_PICTURE, 0.);
Packit c32a2d
Packit c32a2d
	for(i=loptind; i < argc; ++i)
Packit c32a2d
	{
Packit c32a2d
		mpg123_id3v1 *v1;
Packit c32a2d
		mpg123_id3v2 *v2;
Packit c32a2d
		int meta;
Packit c32a2d
		if(mpg123_open(m, argv[i]) != MPG123_OK)
Packit c32a2d
		{
Packit c32a2d
			fprintf(stderr, "Cannot open %s: %s\n", argv[i], mpg123_strerror(m));
Packit c32a2d
			continue;
Packit c32a2d
		}
Packit c32a2d
		if(param.do_scan) mpg123_scan(m);
Packit c32a2d
		mpg123_seek(m, 0, SEEK_SET);
Packit c32a2d
		meta = mpg123_meta_check(m);
Packit c32a2d
		if(meta & MPG123_ID3 && mpg123_id3(m, &v1, &v2) == MPG123_OK)
Packit c32a2d
		{
Packit c32a2d
			printf("FILE: %s\n", argv[i]);
Packit c32a2d
			printf("\n====      ID3v1       ====\n");
Packit c32a2d
			if(v1 != NULL) print_v1(v1);
Packit c32a2d
Packit c32a2d
			printf("\n====      ID3v2       ====\n");
Packit c32a2d
			if(v2 != NULL) print_v2(v2);
Packit c32a2d
Packit c32a2d
			printf("\n==== ID3v2 Raw frames ====\n");
Packit c32a2d
			if(v2 != NULL)
Packit c32a2d
			{
Packit c32a2d
				print_raw_v2(v2);
Packit c32a2d
				if(param.store_pics)
Packit c32a2d
				store_pictures(argv[i], v2);
Packit c32a2d
			}
Packit c32a2d
		}
Packit c32a2d
		else printf("Nothing found for %s.\n", argv[i]);
Packit c32a2d
Packit c32a2d
		mpg123_close(m);
Packit c32a2d
	}
Packit c32a2d
	mpg123_delete(m);
Packit c32a2d
	mpg123_exit();
Packit c32a2d
Packit c32a2d
	if(errors) error1("Encountered %i errors along the way.", errors);
Packit c32a2d
	return errors != 0;
Packit c32a2d
#if defined(WANT_WIN32_UNICODE)
Packit c32a2d
	win32_cmdline_free(argc,argv);
Packit c32a2d
#endif
Packit c32a2d
}