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