2
* mp3check - check mp3 file for consistency and print infos
4
* Copyright (C) 1998-2005 by Johannes Overmann <Johannes.Overmann@gmx.de>
6
* This program is free software; you can redistribute it and/or modify
7
* it under the terms of the GNU General Public License as published by
8
* the Free Software Foundation; either version 2 of the License, or
9
* (at your option) any later version.
11
* This program is distributed in the hope that it will be useful,
12
* but WITHOUT ANY WARRANTY; without even the implied warranty of
13
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
* GNU General Public License for more details.
16
* You should have received a copy of the GNU General Public License
17
* along with this program; if not, write to the Free Software
18
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
24
#include <sys/ioctl.h>
29
#include "tappconfig.h"
32
#include "tfiletools.h"
36
#define ONLY_MP3 "mp3,MP3,Mp3,mP3"
38
// please update also HISTORY
39
#define VERSION "0.8.4"
42
const char *options[] = {
43
"#usage='Usage: [OPTIONS, FILES AND DIRECTORIES] [--] [FILES AND DIRECTORIES]\n\n"
44
"this program checks audio mpeg layer 1,2 and 3 (*.mp3) files for\n"
45
"consistency (headers and crc) and anomalies'",
46
"#trailer='\n%n version %v *** (C) 1998-2003,2005,2008 by Johannes Overmann\ncomments, bugs and suggestions welcome: %e\n%gpl'",
50
"name=list , type=switch, char=l, help='list parameters by examining the first valid header and size', headline=mode:",
51
"name=compact-list , type=switch, char=c, help='list parameters of one file per line in a very compact format: "
52
"version (l=1.0, L=2.0), layer, sampling frequency [kHz] (44=44.1), bitrate [kbit/s], mode (js=joint stereo, st=stereo, sc=single channel, dc=dual channel), "
53
"emphasis (n=none, 5=50/15 usecs, J=CCITT J.17), COY (has [C]rc, [O]riginal, cop[Y]right), length [min:sec], filename (poss. truncated)'",
54
"name=error-check , type=switch, char=e, help='check crc and headers for consistency and print several error messages'",
55
"name=max-errors , type=int , char=m, param=N, lower=0, help='with -e: set maximum number of errors N to print per file (default 0==infinity)'",
56
"name=anomaly-check , type=switch, char=a, help='report all differences from these parameters: layer 3, 44.1kHz, 128kB, joint stereo, no emphasis, has crc'",
57
"name=dump-header , type=switch, char=d, help='dump all possible header with sync=0xfff'",
58
"name=dump-tag , type=switch, char=t, help='dump all possible tags of known version'",
59
"name=raw-list , type=switch, , help='list parameters in raw output format for use with external programs'",
60
"name=raw-elem-sep , type=string, , default=0x09, param=N, help='separate elements in one line by char N (numerical ASCII code)'",
61
"name=raw-line-sep , type=string, , default=0x0a, param=N, help='separate lines by char N (numerical ASCII code)'",
62
"name=edit-frame-b , type=string, , param=P, help='modify a single byte of a specific frame at a specific offset; B has the format \\'frame,offset,byteval\\', (use 0xff for hex or 255 for dec or 0377 for octal); this mode operates on all given files and is useful for your own experiment with broken streams or while testing this tool ;-)'",
64
"name=cut-junk-start , type=switch, , help='remove junk before first frame', headline='fix errors:'",
65
"name=cut-junk-end , type=switch, , help='remove junk after last frame'",
66
"name=cut-tag-end , type=switch, , help='remove trailing tag'",
67
"name=fix-headers , type=switch, , help='fix invalid headers (prevent constant parameter switching), implies -e, use with care'",
68
"name=fix-crc , type=switch, , help='fix crc (set crc to the calculated one), implies -e, use with care\n(note: it is not possible to add crc to files which have been created without crc)'",
69
"name=add-tag , type=switch, , help='add ID3 v1.1 tag calculated from filename and path using simple heuristics if a file does not already have a tag'",
71
"name=ign-tag128 , type=switch, char=G, help='ignore 128 byte TAG after last frame', headline='disable error messages for -e --error-check:'",
72
"name=ign-resync , type=switch, char=Y, help='ignore synchronization errors (invalid frame header/frame too long/short)'",
73
"name=ign-junk-end , type=switch, char=E, help='ignore junk after last frame'",
74
"name=ign-crc-error , type=switch, char=Z, help='ignore crc errors'",
75
"name=ign-non-ampeg , type=switch, char=N, help='ignore non audio mpeg streams'",
76
"name=ign-truncated , type=switch, char=T, help='ignore truncated last frames'",
77
"name=ign-junk-start , type=switch, char=S, help='ignore junk before first frame'",
78
"name=ign-bitrate-sw , type=switch, char=B, help='ignore bitrate switching and enable VBR support'",
79
"name=ign-constant-sw , type=switch, char=W, help='ignore switching of constant parameters, such as sampling frequency'",
80
"name=show-valid , type=switch, , help='print the message \\'valid audio mpeg stream\\' for all files which appear to be error free (after ignoring errors)",
82
"name=any-crc , type=switch, char=C, help='ignore crc anomalies', headline='disable anomaly messages for -a --anomaly-check'",
83
"name=any-mode , type=switch, char=M, help='ignore mode anomalies'",
84
"name=any-layer , type=switch, char=L, help='ignore layer anomalies'",
85
"name=any-bitrate , type=switch, char=K, help='ignore bitrate anomalies'",
86
"name=any-version , type=switch, char=I, help='ignore version anomalies'",
87
"name=any-sampling , type=switch, char=F, help='ignore sampling frequency anomalies'",
88
"name=any-emphasis , type=switch, char=P, help='ignore emphasis anomalies'",
90
"name=recursive , type=switch, char=r, help='process any given directories recursively (the default is to ignore all directories specified on the command line)', headline='file options:'",
91
"name=filelist , type=string, char=f, param=FILE, help='process all files specified in FILE (one filename per line) in addition to the command line'",
92
"name=accept , type=string, char=A, param=LIST, help='process only files with filename extensions specified by comma separated LIST'",
93
"name=reject , type=string, char=R, param=LIST, help='do not process files with a filename extension specified by comma separated LIST'",
94
"name=only-mp3 , type=switch, char=3, help='same as --accept " ONLY_MP3 "'",
96
"name=xdev , type=switch, help='do not descend into other filesystems when recursing directories'",
98
"name=print-files , type=switch, help='just print all filenames without processing them, then exit (for debugging purposes, also useful to create files for --filelist)'",
100
"name=single-line , type=switch, char=s, help='print one line per file and message instead of splitting into several lines', headline='output options:'",
101
"name=no-summary , type=switch, , help='suppress the summary printed below all messages if multiple files are given'",
102
"name=log-file , type=string, char=g, param=FILE, help='print names of erroneous files to FILE, one per line'",
103
"name=quiet , type=switch, char=q, help='quiet mode, hide messages about directories, non-regular or non-existing files'",
104
"name=color , type=switch, char=o, help='colorize output with ANSI sequences'",
105
"name=alt-color , type=switch, char=b, help='colorize: do not use bold ANSI sequences'",
106
"name=ascii-only , type=switch, help='replace the range of ASCII chars 160-255 (which is usually printable: e.g. ISO-8859) by \\'?\\''",
107
"name=progress , type=switch, char=p, help='show progress information on stderr'",
108
"name=verbose , type=switch, char=v, help='be more verbose'",
109
"name=no-mmap , type=switch, , help='do not use mmap (e.g. when you get \\'mmap: No such device\\')'",
110
"name=dummy , type=switch, char=0, help='do not write/modify anything other than the logfile', headline=common options:",
114
// default value for systems which do not define O_BINARY
130
const char *cfil = "\033[1;37m";
131
const char *cano = "\033[1;33m";
132
const char *cerror = "\033[1;31m";
133
const char *cval = "\033[1;34m";
134
const char *cok = "\033[0;32m";
135
const char *cnor = "\033[0m";
137
const char *c_fil = "\033[37m";
138
const char *c_ano = "\033[33m";
139
const char *c_err = "\033[31m";
140
const char *c_val = "\033[34m";
141
const char *c_ok = "\033[32m";
142
const char *c_nor = "\033[0m";
144
// minimum number of sequential valid and constant frame headers to validate header
145
const int MIN_VALID = 6;
149
bool progress = false;
150
bool single_line = false;
153
unsigned int columns = 0;
154
bool show_valid_files = false;
156
bool only_ascii = false;
158
bool ano_any_crc = false;
159
bool ano_any_bit = false;
160
bool ano_any_emp = false;
161
bool ano_any_rate = false;
162
bool ano_any_mode = false;
163
bool ano_any_layer = false;
164
bool ano_any_ver = false;
166
bool ign_crc = false;
167
bool ign_start = false;
168
bool ign_end = false;
169
bool ign_tag = false;
170
bool ign_bit = false;
171
bool ign_const = false;
172
bool ign_trunc = false;
173
bool ign_noamp = false;
174
bool ign_sync = false;
179
int layer_tab[4]= {0, 3, 2, 1};
181
const int FREEFORMAT = 0;
182
const int FORBIDDEN = -1;
183
int bitrate1_tab[16][3] = {
184
{FREEFORMAT, FREEFORMAT, FREEFORMAT},
199
{FORBIDDEN, FORBIDDEN, FORBIDDEN}
202
int bitrate2_tab[16][3] = {
203
{FREEFORMAT, FREEFORMAT, FREEFORMAT},
218
{FORBIDDEN, FORBIDDEN, FORBIDDEN}
222
double sampd1_tab[4]={44.1, 48.0, 32.0, 0.0};
223
int samp_1_tab[4]={44100, 48000, 32000, 50000};
224
double sampd2_tab[4]={22.05, 24.0, 16.0, 0.0};
225
int samp_2_tab[4]={22050, 24000, 16000, 50000};
227
const unsigned int CONST_MASK = 0xffffffff;
229
#ifdef WORDS_BIGENDIAN
231
syncword: 12, // fix must 0xfff
232
ID: 1, // fix 1==mpeg1.0 0==mpeg2.0
233
layer_index: 2, // fix 0 reserved
234
protection_bit: 1, // fix
235
bitrate_index: 4, // 15 forbidden
236
sampling_frequency: 2,// fix 3 reserved
240
mode_extension: 2, // (not fix!)
243
emphasis: 2; // fix 2 reserved
246
emphasis: 2, // fix 2 reserved
249
mode_extension: 2, // (not fix!)
253
sampling_frequency: 2,// fix 3 reserved
254
bitrate_index: 4, // 15 forbidden
255
protection_bit: 1, // fix
256
layer_index: 2, // fix 0 reserved
257
ID: 1, // fix 1==mpeg1.0 0==mpeg2.0
258
syncword: 12; // fix must 0xfff
259
#endif // ifdef BIGENDIAN
261
bool isValid() const {
262
if(syncword!=0xfff) return false;
263
if(ID==1) { // mpeg 1.0
264
if((layer_index!=0) && (bitrate_index!=15) &&
265
(sampling_frequency!=3) && (emphasis!=2)) return true;
268
if((layer_index!=0) && (bitrate_index!=15) &&
269
(sampling_frequency!=3) && (emphasis!=2)) return true;
274
bool sameConstant(Header h) const {
275
if((*(unsigned int*)this) == (*(unsigned int*)&h)) return true;
276
if((syncword ==h.syncword ) &&
278
(layer_index ==h.layer_index ) &&
279
(protection_bit ==h.protection_bit ) &&
280
// (bitrate_index ==h.bitrate_index ) &&
281
(sampling_frequency==h.sampling_frequency) &&
283
// (mode_extension ==h.mode_extension ) &&
284
(copyright ==h.copyright ) &&
285
(original ==h.original ) &&
286
(emphasis ==h.emphasis ) &&
291
int bitrate() const {
293
return bitrate1_tab[bitrate_index][layer()-1];
295
return bitrate2_tab[bitrate_index][layer()-1];
297
int layer() const {return layer_tab[layer_index];}
299
tstring print() const {
302
s.sprintf("(%03x,ID%d,l%d,prot%d,%2d,%4.1fkHz,pad%d,priv%d,mode%d,ext%d,copy%d,orig%d,emp%d)",
303
syncword, ID, layer(), protection_bit, bitrate_index,
304
samp_rate(), padding_bit, private_bit, mode,
305
mode_extension, copyright, original, emphasis);
308
double version() const {
312
enum {STEREO, JOINT_STEREO, DUAL_CHANNEL, SINGLE_CHANNEL};
313
const char *mode_str() const {
315
case STEREO: return "stereo";
316
case JOINT_STEREO: return "joint stereo";
317
case DUAL_CHANNEL: return "dual channel";
318
case SINGLE_CHANNEL: return "single chann";
322
const char *short_mode_str() const {
324
case STEREO: return "st";
325
case JOINT_STEREO: return "js";
326
case DUAL_CHANNEL: return "dc";
327
case SINGLE_CHANNEL: return "sc";
331
enum {emp_NONE, emp_50_15_MICROSECONDS, emp_RESERVED, emp_CCITT_J_17};
332
const char *emphasis_str() const {
334
case emp_NONE: return "no emph";
335
case emp_50_15_MICROSECONDS: return "50/15us";
336
case emp_RESERVED: return "reservd";
337
case emp_CCITT_J_17: return "C. J.17";
341
const char *short_emphasis_str() const {
343
case emp_NONE: return "n";
344
case emp_50_15_MICROSECONDS: return "5";
345
case emp_RESERVED: return "!";
346
case emp_CCITT_J_17: return "J";
350
double samp_rate() const {
352
return sampd1_tab[sampling_frequency];
354
return sampd2_tab[sampling_frequency];
356
int samp_int_rate() const {
358
return samp_1_tab[sampling_frequency];
360
return samp_2_tab[sampling_frequency];
362
// this should be not affected by endianess
363
int get_int() const {return *((const int *)this);}
367
// get header from pointer
368
inline Header get_header(const unsigned char *p) {
370
unsigned char *q = (unsigned char *)&h;
371
#ifdef WORDS_BIGENDIAN
386
// set header to pointer
387
inline void set_header(unsigned char *p, Header h) {
388
unsigned char *q = (unsigned char *)&h;
389
#ifdef WORDS_BIGENDIAN
403
// set header from header
404
// preserves padding bit and mode extension (under conditions),
405
// among other things
406
void set_header(Header &to, Header from) {
407
if(to.mode!=from.mode)
410
to.mode_extension = from.mode_extension;
413
to.layer_index = from.layer_index;
414
to.protection_bit = from.protection_bit;
415
to.sampling_frequency = from.sampling_frequency;
416
to.copyright = from.copyright;
417
to.original = from.original;
418
to.emphasis = from.emphasis;
422
// set crc to pointer
423
inline bool set_crc_value(unsigned char *p, unsigned short c) {
424
p[1] = (unsigned char) c;
425
p[0] = (unsigned char) (c>>8);
430
// return length of frame in bytes
431
inline int frame_length(Header h) {
432
if(h.version() == 1.0) {
435
return (((12000*h.bitrate()) / h.samp_int_rate()) + h.padding_bit) * 4;
437
return ((144000*h.bitrate()) / h.samp_int_rate()) + h.padding_bit;
442
return (((6000*h.bitrate()) / h.samp_int_rate()) + h.padding_bit) * 4;
444
return ((72000*h.bitrate()) / h.samp_int_rate()) + h.padding_bit;
450
// return duration of frame in ms
451
inline double frame_duration(Header h) {
452
return (((double)frame_length(h)*8) / h.bitrate());
456
// return next pos of min_valid sequential valid and constant header
457
// or -1 if not found
458
inline int find_next_header(const unsigned char *p, int len, int min_valid) {
460
const unsigned char *q = p;
461
const unsigned char *t;
465
for(i=0; i < len-3; i++, q++) {
469
if(h.isValid() && (l>=21)) {
472
for(k=1; (k < min_valid) && (rest >= 4); k++) {
474
if(!h2.isValid()) break;
475
if(!h2.sameConstant(h)) break;
476
l = frame_length(h2);
481
if(k == min_valid) return i;
486
return -1; // not found
489
// return pointer to beginning of nth frame from start (0 is start) or 0 if frame not found
490
const unsigned char *skip_n_frames(const unsigned char *start, int len, int n) {
493
int s = find_next_header(start, len, MIN_VALID);
497
Header h = get_header(start);
499
if(len < 4) return 0;
500
h = get_header(start);
502
if(n <= 0) return start;
503
int l = frame_length(h);
516
void fmes(const char *name, const char *format, ...) __attribute__ ((format(printf,2,3)));
519
void fmes(const char *name, const char *format, ...) {
522
static tstring lastname;
523
tstring pname = name;
524
pname.replaceUnprintable(only_ascii);
526
if(progress) putc('\r', stderr);
527
if(strcmp(format, "\n") == 0) {
528
printf("%s%s%s:\n", cfil, pname.c_str(), cnor);
532
printf("%s%s%s: ", cfil, pname.c_str(), cnor);
534
if(name != lastname) {
536
tstring s = pname.shortFilename(columns-1);
537
printf("%s%s%s:\n", cfil, s.c_str(), cnor);
540
va_start(ap, format);
546
// returns true on error
547
bool error_check(const char *name, const unsigned char *stream, int len, CRC16& crc, bool fix_headers, bool fix_crc) {
549
const unsigned char *p = stream;
550
int start = find_next_header(p, len, MIN_VALID);
559
fmes(name, "%s%s%s\n", cerror, (len?"not an audio mpeg stream":"empty file"), cnor);
564
// check for junk at beginning
568
fmes(name, "%s%d%s %sbyte%s of junk before first frame header%s\n",
569
cval, start, cnor, cerror, (start>1)?"s":"", cnor);
571
// check for possible id3 tags within the junk
572
while(start-pos >= 128)
574
int offset=Tagv1::find_next_tag(p+pos, start-pos);
577
tag=new Tagv1(p+pos);
578
fmes(name, "in leading junk: %spossible %s id3 tag v%u.%u%s at %s0x%08x%s\n",
579
cerror, (tag->isValidSpecs()?"valid":"invalid"),
580
(tag->version())>>8, (tag->version())&0xff, cnor,
591
// check for TAG trailers
592
// Note that we emit a warning if we found more than one tag, even
593
// if ign_tag is set, unless ign_end is also set.
594
tag=new Tagv1(p+rest-128);
596
while((rest>=128)&&tag->isValid()) {
598
if((!ign_tag)||((tag_counter>1)&&!ign_end)) {
599
fmes(name, "%s%s%s id3 tag trailer v%u.%u found%s\n",
600
cerror, (tag_counter>1?"another ":""), (tag->isValidSpecs()?"valid":"invalid"),
601
(tag->version())>>8, (tag->version())&0xff, cnor);
605
tag->setTarget(p+rest-128);
612
Header head = get_header(p);
614
Header h = get_header(p);
616
if((frame%1000)==0) {
621
if(!(h.isValid()&&(frame_length(h)>=21))) {
624
// search for next valid header
626
printf("ERROR! Invalid header with no previous frame. Needs debuging.\n");
628
// search within previous frame
633
// first look for any isolated frame with the same header
634
s = find_next_header(p, rest, 1);
635
if(s<0) { // error: junk at eof
642
// else look for a regular stream
644
if(!head.sameConstant(h))
645
s = find_next_header(p, rest, MIN_VALID);
646
if(s<0) { // error: junk at eof
655
fmes(name, "frame %s%5d%s/%s%2u:%02u%s: %ssync error (frame too short)%s at %s0x%08x%s, %s%d%s byte%s mising\n",
656
cval, frame - 1, cnor,
657
cval, (unsigned int)(time/1000)/60, (unsigned int)(time/1000)%60, cnor,
658
cerror, cnor, cval, start - 4, cnor,
659
cval, l-4-s, cnor, (l-4-s>1)?"s":"");
662
fmes(name, "frame %s%5d%s/%s%2u:%02u%s: %ssync error (frame too long)%s at %s0x%08x%s, skipping %s%d%s byte%s at %s0x%08x%s\n",
663
cval, frame - 1, cnor,
664
cval, (unsigned int)(time/1000)/60, (unsigned int)(time/1000)%60, cnor,
665
cerror, cnor, cval, start - 4, cnor,
666
cval, s-l+4, cnor, (s-l+4>1)?"s":"",
667
cval, start - 4 + l, cnor);
672
// try to fix header including sync information
673
if(fix_headers && (s>l-4)) {
674
unsigned int old_padding_bit = head.padding_bit;
675
head.padding_bit = 0;
676
if(s-l+4 == frame_length(head)) {
677
fmes(name, "frame %s%5d%s/%s%2u:%02u%s: %sfixing header (including sync)%s\n",
679
cval, (unsigned int)(time/1000)/60, (unsigned int)(time/1000)%60, cnor,
681
set_header((unsigned char *)(p+l-4), head);
682
frame++; // we just created a new frame
683
time+=frame_duration(head);
686
head.padding_bit = 1;
687
if(s-l+4 == frame_length(head)) {
688
fmes(name, "frame %s%5d%s/%s%2u:%02u%s: %sfixing header (including sync)%s\n",
690
cval, (unsigned int)(time/1000)/60, (unsigned int)(time/1000)%60, cnor,
692
set_header((unsigned char *)(p+l-4), head);
693
frame++; // we just created a new frame
694
time+=frame_duration(head);
697
// prevent possible side effect
698
head.padding_bit = old_padding_bit;
703
// position on next frame
710
// check for constant parameters
711
if(!head.sameConstant(h)) {
713
fmes(name, "frame %s%5d%s/%s%2u:%02u%s: %sconstant parameter switching%s at %s0x%08x%s (%s0x%08x%s -> %s0x%08x%s)\n",
715
cval, (unsigned int)(time/1000)/60, (unsigned int)(time/1000)%60, cnor,
718
cval, head.get_int()&CONST_MASK, cnor,
719
cval, h.get_int()&CONST_MASK, cnor);
721
printf("frame %s%5d%s/%s%2u:%02u%s: %sMPEG version switching%s (MPEG %s%1.1f%s -> MPEG %s%1.1f%s)\n",
723
cval, (unsigned int)(time/1000)/60, (unsigned int)(time/1000)%60, cnor,
725
cval, head.version(), cnor,
726
cval, h.version(), cnor);
727
if(h.layer_index!=head.layer_index)
728
printf("frame %s%5d%s/%s%2u:%02u%s: %sMPEG layer switching%s (layer %s%1d%s -> layer %s%1d%s)\n",
730
cval, (unsigned int)(time/1000)/60, (unsigned int)(time/1000)%60, cnor,
732
cval, head.layer(), cnor,
733
cval, h.layer(), cnor);
734
if(h.samp_rate()!=head.samp_rate())
735
printf("frame %s%5d%s/%s%2u:%02u%s: %ssampling frequency switching%s (%s%f%skHz -> %s%f%skHz)\n",
737
cval, (unsigned int)(time/1000)/60, (unsigned int)(time/1000)%60, cnor,
739
cval, head.samp_rate(), cnor,
740
cval, h.samp_rate(), cnor);
741
if(h.mode!=head.mode)
742
printf("frame %s%5d%s/%s%2u:%02u%s: %smode switching%s (%s%s%s -> %s%s%s)\n",
744
cval, (unsigned int)(time/1000)/60, (unsigned int)(time/1000)%60, cnor,
746
cval, head.mode_str(), cnor,
747
cval, h.mode_str(), cnor);
748
if(h.protection_bit!=head.protection_bit)
749
printf("frame %s%5d%s/%s%2u:%02u%s: %sprotection bit switching%s (%s%s%s -> %s%s%s)\n",
751
cval, (unsigned int)(time/1000)/60, (unsigned int)(time/1000)%60, cnor,
753
cval, head.protection_bit?"no crc":"crc", cnor,
754
cval, h.protection_bit?"no crc":"crc", cnor);
755
if(h.copyright!=head.copyright)
756
printf("frame %s%5d%s/%s%2u:%02u%s: %scopyright bit switching%s (%s%s%s -> %s%s%s)\n",
758
cval, (unsigned int)(time/1000)/60, (unsigned int)(time/1000)%60, cnor,
760
cval, head.copyright?"copyright":"no copyright", cnor,
761
cval, h.copyright?"copyright":"no copyright", cnor);
762
if(h.original!=head.original)
763
printf("frame %s%5d%s/%s%2u:%02u%s: %soriginal bit switching%s (%s%s%s -> %s%s%s)\n",
765
cval, (unsigned int)(time/1000)/60, (unsigned int)(time/1000)%60, cnor,
767
cval, head.original?"original":"not original", cnor,
768
cval, h.original?"original":"not original", cnor);
772
fmes(name, "frame %s%5d%s/%s%2u:%02u%s: %sfixing header%s\n",
774
cval, (unsigned int)(time/1000)/60, (unsigned int)(time/1000)%60, cnor,
776
// fix only what should be
778
set_header((unsigned char *)p, h);
781
if(head.bitrate_index != h.bitrate_index) {
783
fmes(name, "frame %s%5d%s/%s%2u:%02u%s: %sbitrate switching%s (%s%d%s -> %s%d%s)\n",
785
cval, (unsigned int)(time/1000)/60, (unsigned int)(time/1000)%60, cnor,
787
cval, head.bitrate(), cnor,
788
cval, h.bitrate(), cnor);
791
fmes(name, "frame %s%5d%s/%s%2u:%02u%s: %sfixing header%s\n",
793
cval, (unsigned int)(time/1000)/60, (unsigned int)(time/1000)%60, cnor,
795
// fix only what should be
796
h.bitrate_index=head.bitrate_index;
797
set_header((unsigned char *)p, h);
804
if((!ign_crc)&&(h.protection_bit==0)&&(rest>=32+6)) {
807
// get length of side info
809
if(h.version()==1.0) { // mpeg 1.0
812
if(h.mode==Header::SINGLE_CHANNEL) s = 17;
818
case Header::SINGLE_CHANNEL: s = 16; break;
819
case Header::DUAL_CHANNEL: s = 32; break;
820
case Header::STEREO: s = 32; break;
821
case Header::JOINT_STEREO: s = 18+h.mode_extension*2; break;
826
s = 0; // mpeg 1.0 layer 2 not yet supported
829
} else { // mpeg 2.0 or 2.5
830
if(h.layer()==3) { // layer 3
831
if(h.mode==Header::SINGLE_CHANNEL) s = 9;
834
s = 0; // mpeg 2.0 or 2.5 layer 1 and 2 not yet supported
841
for(int i=0; i < s; i++) crc.add(p[i+6]);
843
unsigned short c = p[5] | ((unsigned short)(p[4])<<8);
847
fixed_crc = set_crc_value((unsigned char *) &(p[4]), crc.crc());
849
fmes(name, "frame %s%5d%s/%s%2u:%02u%s: %scrc error%s (%s0x%04x%s!=%s0x%04x%s)%s\n",
851
cval, (unsigned int)(time/1000)/60, (unsigned int)(time/1000)%60, cnor,
853
cval, c, cnor, cval, crc.crc(), cnor, fixed_crc ? " fixed" : "");
856
// printf("frame=%d, pos=%d, s=%d, c=%04x crc.crc()=%04x\n", frame, start, s, c, crc.crc());
860
// skip to next frame
866
time+=frame_duration(h);
869
// maximum number of error reached?
870
if(max_errors && (errors >= max_errors))
872
fmes(name, "frame %s%5d%s/%s%2u:%02u%s: %smaximum number of errors exceeded%s\n",
874
cval, (unsigned int)(time/1000)/60, (unsigned int)(time/1000)%60, cnor,
881
// check for truncated file
884
fmes(name, "frame %s%5d%s/%s%2u:%02u%s: %sfile truncated%s, %s%d%s byte%s missing for last frame\n",
886
cval, (unsigned int)(time/1000)/60, (unsigned int)(time/1000)%60, cnor,
887
cerror, cnor, cval, -rest, cnor, (-rest)>1?"s":"");
892
// check for trailing junk
895
fmes(name, "frame %s%5d%s/%s%2u:%02u%s: %s%d%s %sbyte%s of junk after last frame%s at %s0x%08x%s\n",
897
cval, (unsigned int)(time/1000)/60, (unsigned int)(time/1000)%60, cnor,
898
cval, rest, cnor, cerror, (rest>1)?"s":"", cnor, cval, start, cnor);
900
// check for possible id3 tags within the junk
903
int offset=Tagv1::find_next_tag(p, rest);
908
fmes(name, "in trailing junk: %spossible %s id3 tag v%u.%u%s at %s0x%08x%s\n",
909
cerror, (tag->isValidSpecs()?"valid":"invalid"),
910
(tag->version())>>8, (tag->version())&0xff, cnor,
926
fputs("\r \r", stderr);
929
if((errors == 0) && show_valid_files)
930
fmes(name, "%svalid audio mpeg stream%s\n", cok, cnor);
935
// returns true on anomaly
936
bool anomaly_check(const char *name, const unsigned char *p, int len, bool err_check, int& err) {
937
bool had_ano = false;
938
int start = find_next_header(p, len, MIN_VALID);
940
Header h = get_header(p+start);
942
if(h.version()!=1.0) {
943
fmes(name, "%sanomaly%s: audio mpeg version %s%3.1f%s stream\n",
944
cano, cnor, cval, h.version(), cnor);
950
fmes(name, "%sanomaly%s: audio mpeg %slayer %d%s stream\n",
951
cano, cnor, cval, h.layer(), cnor);
956
if(h.samp_rate()!=44.1) {
957
fmes(name, "%sanomaly%s: sampling rate %s%4.1fkHz%s\n",
958
cano, cnor, cval, h.samp_rate(), cnor);
963
if(h.bitrate()!=128) {
964
fmes(name, "%sanomaly%s: bitrate %s%3dkbit/s%s\n",
965
cano, cnor, cval, h.bitrate(), cnor);
970
if(h.mode!=Header::JOINT_STEREO) {
971
fmes(name, "%sanomaly%s: mode %s%s%s\n",
972
cano, cnor, cval, h.mode_str(), cnor);
977
if(h.protection_bit==1) {
978
fmes(name, "%sanomaly%s: %sno crc%s\n",
979
cano, cnor, cval, cnor);
984
if(h.emphasis!=Header::emp_NONE) {
985
fmes(name, "%sanomaly%s: emphasis %s%s%s\n",
986
cano, cnor, cval, h.emphasis_str(), cnor);
991
if((!err_check)&&(!ign_noamp)) {
992
fmes(name, "%s%s%s\n", cerror, (len?"not an audio mpeg stream":"empty file"), cnor);
1000
// returns the stream duration in ms
1001
// also returns minimum, maximum and average bitrates if told to
1002
unsigned int stream_duration(const unsigned char *p, int len, unsigned short int *minbr, unsigned short int *maxbr, unsigned short int *avgbr) {
1003
int next = find_next_header(p, len, MIN_VALID);
1004
int rest = len - next;
1005
double duration = 0.0;
1006
unsigned short int min = 2048, max = 0;
1007
unsigned long int bytes = 0;
1009
if(next<0) return 0;
1012
Header h=get_header(p+next);
1015
next = find_next_header(p+old, rest, MIN_VALID);
1022
int l=frame_length(h);
1029
duration+=frame_duration(h);
1033
if(minbr!=NULL) { *minbr = min; }
1034
if(maxbr!=NULL) { *maxbr = max; }
1035
if(avgbr!=NULL) { *avgbr = (bytes * 8) / (unsigned int)duration; }
1036
return (unsigned int)duration;
1040
// return true if junk was found and cut
1041
bool cut_junk_end(const char *name, const unsigned char *p, int len, const unsigned char *free_p, int fd, int& err) {
1042
// this is basically the error_check routine which treats only the trainling junk case
1043
// (implemented by Pollyanna Lindgren <jtlindgr@cs.helsinki.fi>)
1044
int start = find_next_header(p, len, MIN_VALID);
1049
bool have_a_tag = false;
1053
fmes(name, "%s%s%s\n", cerror, (len?"not an audio mpeg stream":"empty file"), cnor);
1059
// check for TAG trailer
1061
tag = new Tagv1(p + rest - 128);
1062
if(tag->isValid()) {
1063
if(progress) putc('\r', stderr);
1066
fmes(name, "%scut-junk-end: %s id3 tag trailer v%u.%u found, protecting it%s\n",
1067
cok, (tag->isValidSpecs()?"valid":"invalid"),
1068
(tag->version())>>8, (tag->version())&0xff, cnor);
1073
fmes(name, "%scut-junk-end: %s id3 tag trailer v%u.%u found, could not protect it, sorry%s\n",
1074
cok, (tag->isValidSpecs()?"valid":"invalid"),
1075
(tag->version())>>8, (tag->version())&0xff, cnor);
1083
Header head = get_header(p);
1084
l = frame_length(head);
1089
Header h = get_header(p);
1092
if((frame%1000)==0) {
1097
if(!(h.isValid()&&(frame_length(h)>=21))) {
1100
// search for next valid header
1101
s = find_next_header(p, rest, MIN_VALID);
1103
if(s<0) break; // error: junk at eof
1105
// skip s invalid bytes
1114
// skip to next frame
1115
l = frame_length(h);
1122
// in case we had found a tag
1123
if((rest >= 128) && have_a_tag)
1128
// remove incomplete last frames
1129
//if((rest < 0) && remove_truncated_last_frame)
1134
// remove trailing junk
1136
fmes(name, "%scut-junk-end: removing last %s%d%s byte%s, %sretrying%s\n",
1137
cok, cval, rest, cok, (rest>1)?"s":"", dummy?"not (due to dummy) ":"", cnor);
1142
if(tag->restore((unsigned char*)p)) {
1143
fmes(name, "%scut-junk-end: tag successfully restored%s\n", cok, cnor);
1145
fmes(name, "%scut-junk-end: unable to restore tag, sorry%s\n", cok, cnor);
1151
if(munmap((char*)free_p, len)) {
1153
userError("can't unmap file '%s'!\n", name);
1155
// truncate file and close
1157
#ifndef __STRICT_ANSI__
1158
if(ftruncate(fd, len) < 0) {
1159
perror("ftruncate");
1162
userError("cannot truncate file since this executable was compiled with __STRICT_ANSI__ defined!\n");
1168
fmes(name, "%scut-junk-end: no junk found%s\n", cok, cnor);
1175
// return true if trailing tag was found and cut
1176
bool cut_tag_end(const char *name, const unsigned char *p, int len, int fd, int& err) {
1177
// this is basically the cut_junk_end routine that only looks for a 128 bytes trailing tag
1178
// (implemented by Jean Delvare <delvare@ensicaen.ismra.fr>)
1179
int start = find_next_header(p, len, MIN_VALID);
1184
fmes(name, "%s%s%s\n", cerror, (len?"not an audio mpeg stream":"empty file"), cnor);
1190
// check for TAG trailer
1192
tag=new Tagv1(p+len-128);
1193
if(tag->isValid()) {
1194
if(progress) putc('\r', stderr);
1195
fmes(name, "%scut-tag-end: %s id3 tag trailer v%u.%u found and removed, %sretrying%s\n",
1196
cok, (tag->isValidSpecs()?"valid":"invalid"),
1197
(tag->version())>>8, (tag->version())&0xff, dummy?"not (due to dummy) ":"", cnor);
1201
if(munmap((char*)p, len)) {
1203
userError("can't unmap file '%s'!\n", name);
1206
// truncate file and close
1208
#ifndef __STRICT_ANSI__
1209
if(ftruncate(fd, len) < 0) {
1210
perror("ftruncate");
1213
userError("cannot truncate file since this executable was compiled with __STRICT_ANSI__ defined!\n");
1222
fmes(name, "%scut-tag-end: no tag found%s\n", cok, cnor);
1227
// check for ID3 v1.x tag
1228
bool checkForID3V1(const unsigned char *p, size_t len)
1232
return memcmp(p + len - 128, "TAG", 3) == 0;
1235
// check for ID3 v2.x tag
1236
bool checkForID3V2(const unsigned char *p, size_t len)
1240
return (memcmp(p + len - 10, "3DI", 3) == 0) || (memcmp(p, "ID3", 3) == 0);
1243
// check for plausible string
1244
bool isValidStr(const unsigned char *p, size_t len)
1246
for(size_t i = 0; (i < len) && (i < 30); i++)
1256
// split artist and title
1257
void splitArtistTitle(const tstring& title, tstring &artistOut, tstring &titleOut)
1259
tstring origtitle = title;
1260
const char *start = title.c_str();
1262
const char *p = strstr(start, " - ");
1266
p = strstr(start, "-");
1270
size_t len = p - start;
1271
artistOut = title.substr(0, len);
1272
titleOut = title.substr(len + patlen);
1273
if((strcasecmp(titleOut.substr(0, 3).c_str(), "Vol") == 0) ||
1274
(strcasecmp(titleOut.substr(0, 3).c_str(), "CD1") == 0) ||
1275
(strcasecmp(titleOut.substr(0, 3).c_str(), "CD3") == 0) ||
1276
(strcasecmp(titleOut.substr(0, 3).c_str(), "CD4") == 0) ||
1277
(strcasecmp(titleOut.substr(0, 3).c_str(), "CD2") == 0))
1279
titleOut = origtitle;
1282
if(artistOut == "Various")
1284
if(artistOut == "Sampler")
1290
titleOut = origtitle;
1295
// check for possible tags more sloppy
1296
bool checkForTagsSloppy(const unsigned char *p, size_t len)
1300
size_t max = 1024*1024*1024;
1303
for(size_t i = 0; i <= max; i++)
1306
((memcmp(p + i, "TAG", 3) == 0) && (isValidStr(p + i + 3, len - i - 3))) ||
1309
(memcmp(p + i, "ID3", 3) == 0) ||
1310
(memcmp(p + i, "3DI", 3) == 0)
1312
(p[i + 3] < 10) && (p[i + 4] < 10) && ((p[i + 5] & 0xf) == 0) &&
1313
(p[i + 6] < 128) && (p[i + 7] < 128) && (p[i + 8] < 128) && (p[i + 9] < 128)))
1315
printf("%swarning: possible %c%c%c tag found at offset %d (%d from end) (%02x %02x %02x %02x %02x %02x %02x)%s\n",
1316
cano, p[i+0], p[i+1], p[i+2], int(i), int(len - 3 - i), p[i+3]&255, p[i+4]&255, p[i+5]&255,
1317
p[i+6]&255, p[i+7]&255, p[i+8]&255, p[i+9]&255, cnor);
1324
// capitalize string
1325
void capitalize(tstring &str)
1327
bool lastAlpha = false;
1328
for(size_t i = 0; i < str.length(); i++)
1330
if((!lastAlpha) && islower(str[i]))
1331
str[i] = toupper(str[i]);
1332
lastAlpha = isalpha(str[i]);
1337
int main(int argc, char *argv[]) {
1340
TAppConfig ac(options, "options", argc, argv, 0, 0, VERSION);
1342
// get the terminal width if available
1344
if(ioctl(1, TIOCGWINSZ, &win) == 0)
1345
columns = win.ws_col;
1346
const char* scolumns=getenv("COLUMNS");
1348
columns=atoi(scolumns)-1;
1353
quiet = ac("quiet");
1354
dummy = ac("dummy");
1355
progress = ac("progress");
1356
max_errors = ac.getInt("max-errors");
1357
show_valid_files = ac("show-valid");
1358
bool nommap = ac("no-mmap");
1361
if(ac("error-check")) opt=1;
1362
if(ac("fix-headers")) opt=1;
1363
if(ac("fix-crc")) opt=1;
1364
if(ac("add-tag")) opt=1;
1365
if(ac("anomaly-check")) opt=1;
1366
if(ac("cut-junk-start")) opt=1;
1367
if(ac("cut-junk-end")) opt=1;
1368
if(ac("cut-tag-end")) opt=1;
1369
if(!ac.getString("edit-frame-b").empty()) opt=1;
1371
if(ac("dump-header")) opt++;
1372
if(ac("dump-tag")) opt++;
1373
if(ac("list")) opt++;
1374
if(ac("compact-list")) opt++;
1375
if(ac("raw-list")) opt++;
1376
if(ac("print-files")) opt=1;
1379
userError("you must specify the mode of operation! (try --help for more info)\n");
1381
userError("incompatible modes specified! (try --help for more info)\n");
1384
cval = cnor = cano = cerror = cfil = cok = "";
1385
if(ac("alt-color")) {
1393
tstring rawsepstr = ac.getString("raw-elem-sep");
1394
char rawsep = strtol(rawsepstr.c_str(), 0, 0);
1395
if(!rawsepstr.empty()) if(!isdigit(rawsepstr[0])) rawsep = rawsepstr[0];
1396
tstring rawlinesepstr = ac.getString("raw-line-sep");
1397
char rawlinesep = strtol(rawlinesepstr.c_str(), 0, 0);
1398
if(!rawlinesepstr.c_str()) if(!isdigit(rawlinesepstr[0])) rawsep = rawlinesepstr[0];
1399
if(ac("raw-list")) {
1403
bool edit_frame_byte = !ac.getString("edit-frame-b").empty();
1407
if(edit_frame_byte) {
1408
tvector<tstring> a = split(ac.getString("edit-frame-b"), ",");
1409
if((a.size() != 3) || !a[0].toInt(efb_frame) || !a[1].toInt(efb_offset) || !a[2].toInt(efb_value))
1410
userError("format of parameter for --edit-frame-b is 'frame,offset,byteval'!\n");
1412
bool recursive = ac("recursive");
1414
bool cross_filesystems = true;
1416
bool cross_filesystems = !ac("xdev");
1418
tstring extensions = ac.getString("accept");
1419
tstring reject_extensions = ac.getString("reject");
1420
if(ac("only-mp3")) {
1421
if(extensions.empty()) extensions = ONLY_MP3;
1422
else extensions += "," ONLY_MP3;
1424
single_line = ac("single-line");
1425
only_ascii = ac("ascii-only");
1429
if(ac.numParam()==0)
1430
userError("need at least one file or directory! (try --help for more info)\n");
1432
// setup ignores/anys
1433
ano_any_crc = ac("any-crc");
1434
ano_any_mode = ac("any-mode");
1435
ano_any_layer = ac("any-layer");
1436
ano_any_bit = ac("any-bitrate");
1437
ano_any_emp = ac("any-emphasis");
1438
ano_any_rate = ac("any-sampling");
1439
ano_any_ver = ac("any-version");
1440
ign_crc = ac("ign-crc-error");
1441
ign_start = ac("ign-junk-start");
1442
ign_end = ac("ign-junk-end");
1443
ign_tag = ac("ign-tag128");
1444
ign_bit = ac("ign-bitrate-sw");
1445
ign_const = ac("ign-constant-sw");
1446
ign_trunc = ac("ign-truncated");
1447
ign_noamp = ac("ign-non-ampeg");
1448
ign_sync = ac("ign-resync");
1451
tvector<tstring> filelist;
1452
// from command line (perhaps recurse directories)
1453
for(size_t i = 0; i < ac.numParam(); i++) {
1456
TFile f(ac.param(i));
1458
TSubTreeContext context(cross_filesystems);
1460
filelist += findFilesRecursive(d);
1466
filelist.push_back(ac.param(i));
1468
// read filenames from text file
1469
tstring filelistfile = ac.getString("filelist");
1470
if(!filelistfile.empty()) {
1472
filelist += loadTextFile(filelistfile.c_str());
1475
userError("cannot open file '%s' for reading!\n", filelistfile.c_str());
1479
if(!extensions.empty())
1480
filelist = filterExtensions(filelist, split(extensions, ",;:"));
1481
if(!reject_extensions.empty())
1482
filelist = filterExtensions(filelist, split(reject_extensions, ",;:"), true);
1485
if(ac("print-files")) {
1486
for(size_t i = 0; i < filelist.size(); i++)
1487
printf("%s\n", filelist[i].c_str());
1495
int num_tagsadded = 0;
1496
CRC16 crc(CRC16::CRC_16);
1498
if(!ac.getString("log-file").empty()) {
1499
log = fopen(ac.getString("log-file").c_str(), "a");
1501
userError("can't open logfile '%s'!\n", ac.getString("log-file").c_str());
1503
for(size_t i = 0; i < filelist.size(); i++) {
1504
const char *name = filelist[i].c_str();
1505
// ignore all files starting with ._ which are apple metafiles
1508
t.extractFilename();
1509
if((t[0] == '.') && (t[1] == '_'))
1515
if(stat(name, &buf)) {
1516
fmes(name, "%scan't stat file (dangling symbolic link?)%s\n", cerror, cnor);
1519
if(S_ISDIR(buf.st_mode)) {
1520
fmes(name, "%signoring directory%s\n", cerror, cnor);
1523
if(!S_ISREG(buf.st_mode)) {
1524
fmes(name, "%signoring non regular file%s\n", cerror, cnor);
1527
off_t len = buf.st_size;
1530
int flags = O_RDONLY;
1531
int prot = PROT_READ;
1533
if(ac("fix-headers")||ac("cut-junk-start")||ac("fix-crc")||ac("cut-junk-end")||ac("cut-tag-end")||edit_frame_byte) {
1537
userError("option --no-mmap does not yet support options which may modify a file (e.g --fix-crc)!\n");
1541
int fd = open(name, flags);
1544
userError("can't open file '%s' for reading!\n", name);
1547
// mmap or read file
1548
const unsigned char *p;
1549
const unsigned char *free_p = 0;
1552
free_p = p = new unsigned char[len];
1553
if(read(fd, (void*)p, len) != len) {
1555
userError("error while reading file '%s'!\n", name);
1560
free_p = p = (const unsigned char *) mmap(0, len, prot, MAP_SHARED, fd, 0);
1564
if(p==(const unsigned char *)MAP_FAILED) {
1566
userError("can't map file '%s'!\n", name);
1570
// edit single byte of a frame
1571
if(edit_frame_byte) {
1573
tstring s = tstring(name).shortFilename(79);
1574
fprintf(stderr, "%-79.79s\r", s.c_str());
1577
unsigned char *pp = const_cast<unsigned char *>(skip_n_frames(free_p, len, efb_frame));
1579
if(!dummy) pp[efb_offset] = efb_value;
1581
fmes(name, "%sframe %s%d%s not found%s\n", cerror, cval, efb_frame, cerror, cnor);
1587
if(ac("list")||ac("compact-list")||ac("raw-list")) {
1588
// speed up list of very large files (like *.wav)
1589
int maxl = 128*1024; // search max 128k
1590
int start = find_next_header(p, len<maxl?len:maxl, MIN_VALID);
1593
if(ac("raw-list")) {
1594
printf("%s%c%s%c%c", (len?"invalid_stream":"empty_stream"), rawsep, name, rawsep, rawlinesep);
1595
} else if(ac("compact-list")) {
1596
printf("%s%-25s%s%s %s%s%s\n", cerror, (len?"not an audio mpeg stream":"empty file"), cnor, (columns>=82?" ":""), cfil, name, cnor);
1598
fmes(name, "%s%s%s\n", cerror, (len?"not an audio mpeg stream":"empty file"), cnor);
1603
Header h = get_header(p+start);
1604
unsigned short int minbr = 0, maxbr = 0, avgbr;
1605
unsigned int l_min = (ign_bit?stream_duration(p, len, &minbr, &maxbr, &avgbr):len/(h.bitrate()/8));
1606
unsigned int l_mil = l_min%1000;
1608
unsigned int l_sec = l_min%60;
1612
l_str.sprintf("%2u:%02u", l_min/60, l_min%60);
1614
l_str.sprintf(" %2u", l_min);
1615
Tagv1 *tag=new Tagv1(p+len-128);
1616
unsigned short int tag_version=0;
1618
tag_version=tag->version();
1620
unsigned int xwidth = 0;
1621
tstring n = single_line?tstring(name):tstring(name).shortFilename(columns-1);
1622
fmes(name, "mpeg %s%3.1f%s layer %s%d%s %s%2.1f%skHz %s%3d%skbps",
1623
h.version()==1.0?cval:cano, h.version(), cnor,
1624
h.layer()==3?cval:cano, h.layer(), cnor,
1625
h.samp_rate()==44.1?cval:cano, h.samp_rate(), cnor,
1626
(h.bitrate()==128&&minbr==maxbr)?cval:cano, minbr!=maxbr?avgbr:h.bitrate(), cnor);
1627
if(ign_bit && columns>=83) {
1629
minbr!=maxbr?cano:cval,minbr!=maxbr?"VBR":"CBR",cnor);
1632
printf(" %s%-12.12s%s %s%-7.7s%s %s%s%s %s%s%s %s%s%s %s%s:%02u.%02u%s",
1633
h.mode==Header::JOINT_STEREO?cval:cano, h.mode_str(), cnor,
1634
h.emphasis==Header::emp_NONE?cval:cano, h.emphasis_str(), cnor,
1635
h.protection_bit?cano:cval, h.protection_bit?"---":"crc", cnor,
1636
h.original?cval:cano, h.original?"orig":"----", cnor,
1637
cval, h.copyright?"copy":"----", cnor,
1638
cval, l_str.c_str(), l_sec, l_mil/10, cnor);
1639
if(columns>=87+xwidth) {
1641
printf(" id3 %s%1u.%1u%s", cval, tag_version>>8,
1642
tag_version&0xff,cnor);
1646
} else if(ac("compact-list")) {
1647
unsigned int xwidth = 0;
1648
printf("%s%c%s%s%d%s %s%2.0f%s %s%3d%s",
1649
h.version()==1.0?cval:cano, h.version()==1.0?'l':'L', cnor,
1650
h.layer()==3?cval:cano, h.layer(), cnor,
1651
h.samp_rate()==44.1?cval:cano, h.samp_rate(), cnor,
1652
(h.bitrate()==128&&minbr==maxbr)?cval:cano, minbr!=maxbr?avgbr:h.bitrate(), cnor);
1653
if(ign_bit && columns>=80) {
1655
minbr==maxbr?cval:cano, minbr==maxbr?' ':'V', cnor);
1658
printf(" %s%s%s %s%s%s %s%s%s%s%s%s%s%s%s",
1659
h.mode==Header::JOINT_STEREO?cval:cano, h.short_mode_str(), cnor,
1660
h.emphasis==Header::emp_NONE?cval:cano, h.short_emphasis_str(), cnor,
1661
h.protection_bit?cano:cval, h.protection_bit?"-":"C", cnor,
1662
h.original?cval:cano, h.original?"O":"-", cnor,
1663
cval, h.copyright?"Y":"-", cnor);
1664
if(columns>=81+xwidth) {
1666
printf(" %s%1u%s",cval,tag_version>>8,cnor);
1668
printf(" %s-%s",cval,cnor);
1671
tstring n = tstring(name).shortFilename(columns-(26+xwidth));
1672
n.replaceUnprintable(only_ascii);
1673
printf(" %s%3u:%02u%s %s%s%s\n",
1674
cval, l_min, l_sec, cnor, cfil, n.c_str(), cnor);
1675
} else if(ac("raw-list")) {
1676
printf("valid_stream%c%.1f%c%d%c%.1f%c%d%c%s%c%s%c%s%c%s%c%s%c%s%c%u%c%u%c%u%c%s%c%c",
1678
h.version(), rawsep,
1680
h.samp_rate(), rawsep,
1681
minbr!=maxbr?avgbr:h.bitrate(), rawsep,
1682
ign_bit?(minbr!=maxbr?"VBR":"CBR"):"?", rawsep,
1683
h.mode_str(), rawsep,
1684
h.emphasis_str(), rawsep,
1685
h.protection_bit?"---":"crc", rawsep,
1686
h.original?"orig":"copy", rawsep,
1687
h.copyright?"cprgt":"-----", rawsep,
1691
name, rawsep, rawlinesep);
1698
if(ac("cut-junk-start")) {
1699
int start = find_next_header(p, len, MIN_VALID);
1701
fmes(name, "%s%s%s\n", cerror, (len?"not an audio mpeg stream":"empty file"), cnor);
1703
} else if(start==0) {
1704
fmes(name, "%scut-junk-start: no junk found%s\n", cok, cnor);
1706
fmes(name, "%scut-junk-start: removing first %s%d%s byte%s, %sretrying%s\n",
1707
cok, cval, start, cok, (start>1)?"s":"", dummy?"not (due to dummy) ":"", cnor);
1709
// move start to begining and truncate the file
1710
memmove((char*)free_p, free_p + start, len - start);
1711
if(munmap((char*)free_p, len)) {
1713
userError("can't unmap file '%s'!\n", name);
1716
#ifndef __STRICT_ANSI__
1717
if(ftruncate(fd, len) < 0) {
1718
perror("ftruncate");
1721
userError("cannot truncate file since this executable was compiled with __STRICT_ANSI__ defined!\n");
1733
if(ac("cut-tag-end")) {
1734
// lots of side effects: perhaps unmaps and closes file
1735
if(cut_tag_end(name, p, len, fd, err)) {
1736
// retry this file if not dummy
1745
if(ac("cut-junk-end")) {
1746
// lots of side effects: perhaps unmaps and closes file
1747
if(cut_junk_end(name, p, len, free_p, fd, err)) {
1748
// retry this file if not dummy
1757
if(ac("error-check") || ac("fix-headers") || ac("fix-crc")) {
1759
tstring s = tstring(name).shortFilename(79);
1760
fprintf(stderr, "%-79.79s\r", s.c_str());
1763
if(error_check(name, p, len, crc, ac("fix-headers"), ac("fix-crc"))) {
1764
if(log) fprintf(log, "%s\n", name);
1769
// check for anomalies
1770
if(ac("anomaly-check")) {
1772
tstring s = tstring(name).shortFilename(79);
1773
fprintf(stderr, "%-79.79s\r", s.c_str());
1776
if(anomaly_check(name, p, len, ac("error-check"), err)) ++num_ano;
1780
if(ac("dump-header")) {
1782
for(int k=0; k<len-3; p++, k++) {
1786
if(h.syncword==0xfff) {
1787
tstring s=h.print();
1788
printf("%7d %s\n", k, s.c_str());
1789
int l = frame_length(h);
1802
if(ac("dump-tag")) {
1803
unsigned int err_thisfile=0;
1805
Tagv1 *tag=new Tagv1;
1806
for(int k=0; k<len-127; k++) {
1807
tag->setTarget(p+k);
1808
if(tag->isValidGuess()) {
1809
printf(" Found at: %s0x%08x%s (%s%s%s)\n", cval, k, cnor,
1810
(k==len-128?cok:cerror),
1811
((k==len-128)||!(++err_thisfile)?"end":"in the stream"), cnor);
1813
printf(" Version: %s%u.%u%s\n", cval, tag->fields_version>>8,
1814
tag->fields_version&0xff, cnor);
1815
printf(" Conforms to specification: %s%s%s\n",
1816
(tag->isValidSpecs()&&!tag->fields_spacefilled?cok:cerror),
1817
(tag->isValidSpecs()||!(++err_thisfile)?(tag->fields_spacefilled?"space filled":"yes"):"no"),
1819
printf(" Title: \"%s%s%s\"\n", cval, tag->field_title, cnor);
1820
printf(" Artist: \"%s%s%s\"\n", cval, tag->field_artist, cnor);
1821
printf(" Album: \"%s%s%s\"\n", cval, tag->field_album, cnor);
1822
printf(" Year: \"%s%s%s\"\n", cval, tag->field_year, cnor);
1823
printf(" Comment: \"%s%s%s\"\n", cval, tag->field_comment, cnor);
1824
if(tag->field_genre==0xff) // not set
1825
printf(" Genre: not set\n");
1828
printf(" Genre: %s%s%s\n", ((tag->field_genre>=Tagv1::genres_count)?cerror:cval),
1829
((tag->field_genre<Tagv1::genres_count)?(Tagv1::id3_genres[tag->field_genre]):"unknown"),
1832
if(tag->field_track)
1833
printf(" Track: %s%u%s\n", cval, tag->field_track, cnor);
1838
if(err_thisfile) ++err;
1842
bool addTag = false;
1846
tstring s = tstring(name).shortFilename(79);
1847
fprintf(stderr, "%-79.79s\r", s.c_str());
1850
// check for existing tag
1851
if(checkForID3V1(p, len))
1854
fmes(name, "id3 tag v1.x found, not adding anything\n");
1856
else if(checkForID3V2(p, len))
1859
fmes(name, "id3 tag v2.x found, not adding anything\n");
1862
// this wqs just here to make sure we do not mis something
1863
else if(checkForTagsSloppy(p, len))
1865
fmes(name, "some tag found\n");
1870
// no tag found: add tag
1879
// unmap file and close
1880
if((free_p!=NULL)&&munmap((char*)free_p, len)) {
1882
userError("can't unmap file '%s'!\n", name);
1888
// add tag? (see above)
1891
// extract data from filename
1892
tstring title; // 30
1893
tstring artist; // 30
1894
tstring album; // 30
1895
tstring comment;// 28
1896
char track = 0; // 1
1897
tstring fname = name;
1898
fname.translateChar('_', ' ');
1899
fname.searchReplace("cd 1", "cd1");
1900
fname.searchReplace("cd 2", "cd2");
1901
fname.searchReplace("cd 3", "cd3");
1902
fname.searchReplace("cd 4", "cd4");
1903
fname.searchReplace(" - ms/disc1/", "/cd1 - ");
1904
fname.searchReplace(" - ms/disc2/", "/cd2 - ");
1905
fname.searchReplace("/cd1/", " - cd1/");
1906
fname.searchReplace("/cd2/", " - cd2/");
1907
fname.searchReplace("/cd3/", " - cd3/");
1908
fname.searchReplace("/cd4/", " - cd4/");
1909
fname.searchReplace("zance - a decade of dance from ztt", "zance decade of dance from ztt");
1910
fname.searchReplace("/captain future soundtrack - ", "/");
1911
fname.searchReplace("-ms/", "/");
1912
fname.collapseSpace();
1916
title.extractFilename();
1917
album.extractPath();
1918
album.removeDirSlash();
1919
album.extractFilename();
1920
comment.extractPath();
1921
comment.removeDirSlash();
1922
comment.extractPath();
1923
comment.removeDirSlash();
1924
comment.extractFilename();
1929
title.searchReplace(album, "");
1931
if((title.length() >= 4) && strcasecmp(title.c_str() + title.length(), ".mp3"))
1932
title.truncate(title.length() - 4);
1934
if((strcasecmp(title.substr(0, 3).c_str(), "CD1") == 0) ||
1935
(strcasecmp(title.substr(0, 3).c_str(), "CD1") == 0) ||
1936
(strcasecmp(title.substr(0, 3).c_str(), "CD2") == 0))
1938
album += " " + title.substr(0,3);
1939
title = title.substr(3);
1941
while(strchr(" -.", title.c_str()[0]))
1942
title = title.substr(1);
1944
// check for AA-TT format
1945
if(isdigit(title[0]) && isdigit(title[1]) && isdigit(title[3]) && isdigit(title[4]) && (title[2] == '-'))
1947
album += " cd" + title.substr(1,2);
1948
title = title.substr(3);
1950
// check for 'audio '
1951
tstring ltitle = title;
1953
if(ltitle.substr(0, 5) == "audio")
1954
title = title.substr(5) + " " + title.substr(0, 5);
1955
else if(ltitle.substr(0, 10) == "track cd -")
1957
title = title.substr(10);
1958
album += " track cd";
1960
else if(ltitle.substr(0, 8) == "mix cd -")
1962
title = title.substr(8);
1965
else if(ltitle.substr(0, 6) == "track-")
1966
title = title.substr(6) + " " + title.substr(0, 6);
1967
else if(ltitle.substr(0, 5) == "track")
1968
title = title.substr(5) + " " + title.substr(0, 5);
1970
title.collapseSpace();
1974
while(isdigit(title.c_str()[pos])) pos++;
1975
if((pos >= 1) && (pos <=2))
1978
title.substr(0, pos).toInt(t, 10);
1984
while(strchr(" -.", title.c_str()[pos])) pos++;
1985
title = title.substr(pos);
1986
// split artist - title
1987
splitArtistTitle(title, artist, title);
1992
// split artist - album
1993
if((album == "alben") ||
1994
(album == "cdrom2") ||
1995
(album == "cdrom") ||
1996
(album == "misc1") ||
2001
splitArtistTitle(album, ar, album);
2002
if((!artist.empty()) && (!ar.empty()))
2006
title = artist + "-" + title;
2010
else if(artist.empty())
2022
if(artist == "diverse")
2026
comment.cropSpace();
2027
if((comment == "alben") ||
2028
(comment == "cdrom2") ||
2029
(comment == "cdrom") ||
2030
(comment == "mp3") ||
2031
(comment == "ov") ||
2032
(comment == "chr music") ||
2033
(comment == "mp3 dl") ||
2034
(comment == "diverse") ||
2035
(comment.substr(0, 2) == "M0") ||
2036
(comment.substr(0, 7) == "Unknown") ||
2037
(comment.substr(0, 7) == "Various") ||
2041
// capitalize strings
2045
capitalize(comment);
2047
// move rest of long strings into comment
2048
if(title.length() > 30)
2049
title.searchReplace(" - ", "-");
2050
if(artist.length() > 30)
2051
artist.searchReplace(" - ", "-");
2052
if(album.length() > 30)
2053
album.searchReplace(" - ", "-");
2054
if(title.length() > 30)
2056
comment = "T:" + title.substr(30);
2059
if(artist.length() > 30)
2061
comment = "R:" + artist.substr(30);
2062
artist.truncate(30);
2064
if(album.length() > 30)
2066
comment = "L:" + album.substr(30);
2070
// print and check tag
2071
// fmes(name, "appending id3 tag v1.1:\ntitle= '%s%s%s'\nartist= '%s%s%s'\nalbum= '%s%s%s'\ncomment='%s%s%s'\ntrack= %s%d%s\n",
2072
// cval, title.c_str(), cnor, cval, artist.c_str(), cnor, cval, album.c_str(), cnor, cval, comment.c_str(), cnor, cval, track, cnor);
2073
fmes(name, "'%s%-30.30s%s' '%s%-30.30s%s' '%s%-30.30s%s' '%s%-28.28s%s' %s%d%s\n",
2074
cval, title.c_str(), cnor, cval, artist.c_str(), cnor, cval, album.c_str(), cnor, cval, comment.c_str(), cnor, cval, track, cnor);
2075
if(title.length() > 30)
2076
printf("%swarning%s: title > 30 chars\n", cerror, cnor);
2077
if(artist.length() > 30)
2078
printf("%swarning%s: artist > 30 chars\n", cerror, cnor);
2079
if(album.length() > 30)
2080
printf("%swarning%s: album > 30 chars\n", cerror, cnor);
2081
if(comment.length() > 28)
2082
printf("%swarning%s: comment > 28\n", cerror, cnor);
2084
printf("%swarning%s: no track\n", cerror, cnor);
2088
memset(tag, 0, 128);
2092
strncpy(tag + 3, title.c_str(), 30);
2093
strncpy(tag + 3 + 30, artist.c_str(), 30);
2094
strncpy(tag + 3 + 60, album.c_str(), 30);
2095
strncpy(tag + 3 + 90 + 4, comment.c_str(), 28);
2103
FILE *f = fopen(name, "ab");
2105
userError("can't open file '%s' for writing!\n", name);
2106
if(fwrite(tag, 1, 128, f) != 128)
2107
userError("error while writing to file '%s'\n", name);
2116
// print final statistics
2117
if((filelist.size()>1)&&(!ac("raw-list")) && (!ac("no-summary")) && (!quiet)) {
2119
"%s%d%s file%s %s, %s%d%s erroneous file%s found\n",
2120
cval, checked, cnor, checked==1?"":"s",
2121
ac("list")?"listed":"checked", cval, err, cnor, err==1?"":"s");
2122
if(num_ano) printf("(%s%d%s %sanomal%s%s found)\n",
2123
cval, num_ano, cnor, cano, num_ano==1?"y":"ies", cnor);
2125
printf("(%s%d%s tags added)\n",
2126
cval, num_tagsadded, cnor);
2130
if(log!=NULL) fclose(log);
2132
return (err || num_ano)?1:0;