1 /*********************************************************************
2 original base program http://oku.edu.mie-u.ac.jp/~okumura/dumpwave.c
6 Usage: cmcheckwave filename.wav
7 *********************************************************************/
17 static void usage(char *cmd){
18 fprintf(stderr,"Check CM from wavefile(output cmd stdout)\n");
19 fprintf(stderr,"%s: filename.wav\n\n",cmd);
20 fprintf(stderr,"Check CM from mp4file(require ffmpeg cmd)\n");
21 fprintf(stderr,"%s: filename.mp4\n\n",cmd);
22 fprintf(stderr,"Check CM from mp4file and execute cutcmd(create new filename.mp4-new.mp4) \n");
23 fprintf(stderr,"%s: -x filename.mp4\n\n",cmd);
24 fprintf(stderr,"Check CM and manual edit\n");
25 fprintf(stderr,"%s: -b filename.mp4 filename.mp4 > filename-sh\n",cmd);
26 fprintf(stderr,"Edit filename-sh for mis detection and re execute next cmd\n",cmd);
27 fprintf(stderr,"%s: -x filename-sh\n",cmd);
31 unsigned char *get_bytes(FILE *f, int n)
33 static unsigned char s[16];
35 assert (n <= sizeof s);
36 if (fread(s, n, 1, f) != 1) {
37 fprintf(stderr, "Read error\n");
43 unsigned long get_ulong(FILE *f)
45 unsigned char *s = get_bytes(f, 4);
46 return s[0] + 256LU * (s[1] + 256LU * (s[2] + 256LU * s[3]));
49 unsigned get_ushort(FILE *f)
51 unsigned char *s = get_bytes(f, 2);
52 return s[0] + 256U * s[1];
64 static muonst m[1000];
67 static int noaudioencode=0;
68 static char *wkfilename=NULL;
69 static int defmuon=250;
72 static int txtrecheck=0;
73 static int cmdexecute=0;
74 static int checkcomplete=0;
76 static char *MP4BOXCMDRAPSTR="Adjusting chunk start time to previous random access at ";
78 static char *MP4BOXCMD="/usr/local/bin/MP4Box";
79 static char *SOXCMD="/usr/local/bin/sox";
80 static char *FFMPEGCMD="/usr/local/bin/ffmpeg";
81 static char *MPLAYERCMD="/usr/local/bin/mplayer";
82 static char *AACENCCMD="/usr/local/bin/aacplusenc";
83 static char *AACENCOPT="%s '%s.wav' '%s' 60";
84 static char *FAADCMD=NULL;
86 static char *MP4BOXCMD="mp4box";
87 static char *SOXCMD="sox";
88 static char *FFMPEGCMD="ffmpeg";
89 static char *MPLAYERCMD="mplayer";
90 static char *AACENCCMD="neroAacEnc";
91 static char *AACENCOPT="%s -hev2 -br 60 -if '%s.wav' -of '%s'";
92 static char *FAADCMD=NULL;
95 FILE *checkMP4(FILE *f,char *filename)
101 memset(readbuf,0,sizeof(readbuf));
102 fread(readbuf,sizeof(readbuf),1,f);
104 if (!strstr(readbuf+4,"ftypisom")) {
107 // mp4ファイルだったら、ffmpegでwaveに変換し読み込む。
108 asprintf(&cmdbuf,"%s -v 0 -i '%s' -f wav pipe: 2>/dev/null",FFMPEGCMD,filename);
110 // FFMPEG-> aac-> faad ?
111 asprintf(&cmdbuf,"%s -d -w -q '%s%'",FAADCMD,filename);
113 pp = popen(cmdbuf,"r");
114 if (pp == NULL) return NULL;
116 wkfilename = strdup(filename);
120 FILE *openpipeffmpeg(char *filename)
129 int checkMP4RAP(int stsec,int edsec)
136 if (wkfilename==NULL) return stsec;
138 sprintf(cmdbuf,"%s -quiet -noprog -splitx %.2f:%.2f '%s' -out /dev/null",MP4BOXCMD,edsec/1000.0,(edsec+10000)/1000.0,wkfilename);
141 pp = popen(cmdbuf,"r");
142 if (pp == NULL) return stsec;
143 while(fgets(pbuf,1024,pp)!=NULL){
144 if (strstr(pbuf,MP4BOXCMDRAPSTR)) {
145 sscanf(pbuf+strlen(MP4BOXCMDRAPSTR),"%f",&rap);
146 //TODO rapはCM開始フレームの秒数なのでちょっと戻す。
147 // フレームレートとか調べないとだめだな・・・
152 if (rap > 0 && stsec < rap*1000.0) return rap*1000.0;
156 int cmpinfo(int mcnt)
159 char *cmdptr,readbuf[20];
162 if (!wkfilename) return 0;
164 cmdlist = tclistnew();
166 asprintf(&cmdptr,"#!/bin/sh");
167 tclistpush2(cmdlist,cmdptr);
171 for(i=0;i<mcnt;i++) {
173 asprintf(&cmdptr,"rm -f '%s-%d.png'",wkfilename,i);
174 tclistpush2(cmdlist,cmdptr);
179 asprintf(&cmdptr,"%s-new.mp4",wkfilename);
181 fp = fopen(cmdptr,"rb");
186 fread(readbuf,sizeof(readbuf),1,fp);
187 if (!strstr(readbuf+4,"ftypisom")) {
188 //mp4じゃない感じ(置き換えないときは無視)
189 if (checkcomplete == 1) return 0;
194 if (checkcomplete==1) { //元ファイル置き換え
195 asprintf(&cmdptr,"mv '%s-new.mp4' '%s'",wkfilename,wkfilename);
196 tclistpush2(cmdlist,cmdptr);
199 if (checkcomplete==2) { //CMカットファイル削除
200 asprintf(&cmdptr,"rm -f '%s-new.mp4'",wkfilename);
201 tclistpush2(cmdlist,cmdptr);
205 asprintf(&cmdptr,"rm -f '%s-sh'",wkfilename);
206 tclistpush2(cmdlist,cmdptr);
209 asprintf(&cmdptr,"rm -f '%s.split.log'",wkfilename);
210 tclistpush2(cmdlist,cmdptr);
213 for(i=0;i<tclistnum(cmdlist);i++) {
217 pp = popen(tclistval2(cmdlist,i),"r");
218 if (pp==NULL) {continue;}
219 while(fgets(pbuf,1024,pp)!=NULL){
224 printf("%s\n",tclistval2(cmdlist,i));
228 int dumpinfo(int mcnt)
230 int honstart,hcnt,totalsec;
232 char *cptr,*cptr2,*tfptr;
239 printf("#!/bin/sh\n# cmcheckwave %s\n#\n",wkfilename?wkfilename:"");
240 //カットするため、一連のCM,本編時間を結合
241 for(i=0;i<mcnt;i++) {
242 printf("# %.2f %.2f diff %.2f %s\n",m[i].stsec/1000.0,m[i].edsec/1000.0,m[i].diffs/1000.0,m[i].cmflg?"CM":"");
244 if ((m[i].cmflg==0)&&(honstart==0)) {
246 if (i==0) h[hcnt].stsec = 0;
247 else h[hcnt].stsec = m[i-1].edsec+(defmuon*0.5);
251 if ((m[i].cmflg==1)&&(honstart==1)) {
253 //h[hcnt].edsec = m[i-1].stsec;
254 h[hcnt].edsec = checkMP4RAP(m[i-1].stsec,m[i-1].edsec);
255 totalsec += h[hcnt].edsec - h[hcnt].stsec;
261 h[hcnt].edsec = checkMP4RAP(m[i-1].stsec,m[i-1].edsec);
262 totalsec += h[hcnt].edsec - h[hcnt].stsec;
266 printf("# total %.2f\n\n",totalsec/1000.0);
268 cmdlist = tclistnew();
269 tflist = tclistnew();
271 asprintf(&cptr,"rm -f '%s.split.log'",wkfilename);
272 tclistpush2(cmdlist,cptr);
275 for(i=0;i<hcnt;i++) {
277 asprintf(&tfptr,"%s.%d%s",wkfilename,i,noaudioencode?".mp4":"");
278 asprintf(&cptr,"%s -quiet -noprog -splitx %.2f:%.2f '%s' -out '%s' >> '%s.split.log'",MP4BOXCMD,h[i].stsec/1000.0,h[i].edsec/1000.0,wkfilename,tfptr,wkfilename);
279 tclistpush2(cmdlist,cptr);
280 tclistpush2(tflist,tfptr);
284 if (!noaudioencode) {
285 asprintf(&tfptr,"%s.%d.wav",wkfilename,i);
288 asprintf(&cptr,"%s -v 0 -i '%s.%d' -vn -acodec copy '%s.aac';%s -d -q -o '%s' '%s.aac' ",FFMPEGCMD,wkfilename,i,tfptr,FAADCMD,tfptr,tfptr);
290 asprintf(&cptr,"%s -v 0 -i '%s.%d' -vn '%s'",FFMPEGCMD,wkfilename,i,tfptr);
291 tclistpush2(cmdlist,cptr);
292 tclistpush2(tflist,tfptr);
296 asprintf(&tfptr,"%s.%d.wav.aac",wkfilename,i);
297 tclistpush2(tflist,tfptr);
301 asprintf(&tfptr,"%s.%d.mp4",wkfilename,i);
302 asprintf(&cptr,"%s -v 0 -i '%s.%d' -an -vcodec copy '%s'",FFMPEGCMD,wkfilename,i,tfptr);
303 tclistpush2(cmdlist,cptr);
304 tclistpush2(tflist,tfptr);
310 asprintf(&cptr,"# %s -quiet -noprog -splitx %.2f:%.2f ",MP4BOXCMD,h[i].stsec/1000.0,h[i].edsec/1000.0);
311 tclistpush2(cmdlist,cptr);
315 if (!noaudioencode) {
316 asprintf(&cptr2,"%s --norm ",SOXCMD);
317 for(i=0;i<hcnt;i++) {
318 asprintf(&tfptr,"%s.%d.wav",wkfilename,i);
319 asprintf(&cptr,"%s '%s' ",cptr2,tfptr);
322 tclistpush2(tflist,tfptr);
325 asprintf(&tfptr,"%s.wav",wkfilename);
326 asprintf(&cptr,"%s '%s'",cptr2,tfptr);
328 tclistpush2(cmdlist,cptr);
329 tclistpush2(tflist,tfptr);
332 asprintf(&tfptr,"%s.aac",wkfilename);
333 asprintf(&cptr,AACENCOPT,AACENCCMD,wkfilename,tfptr);
334 tclistpush2(cmdlist,cptr);
336 tclistpush2(tflist,tfptr);
339 //ファイル名-new.mp4ファイルを削除する
340 asprintf(&cptr,"rm -f '%s-new.mp4'",wkfilename);
341 tclistpush2(cmdlist,cptr);
344 asprintf(&cptr2,"%s -quiet -noprog ",MP4BOXCMD);
345 for(i=0;i<hcnt;i++) {
346 asprintf(&cptr,"%s -cat '%s.%d.mp4' ",cptr2,wkfilename,i);
350 asprintf(&cptr,"%s '%s-new.mp4'",cptr2,wkfilename);
351 tclistpush2(cmdlist,cptr);
354 if (!noaudioencode) {
355 asprintf(&cptr,"%s -quiet -noprog -add '%s.aac' '%s-new.mp4'",MP4BOXCMD,wkfilename,wkfilename);
356 tclistpush2(cmdlist,cptr);
360 for(i=0;i<tclistnum(tflist);i++) {
361 asprintf(&cptr,"rm -f '%s'",tclistval2(tflist,i));
362 tclistpush2(cmdlist,cptr);
368 for(i=0;i<mcnt;i++) {
369 asprintf(&cptr,"%s -ao null -ss %.2f -frames 1 -vo png:z=9 '%s' ; mv 00000001.png '%s-%d.png'",MPLAYERCMD,(pre + (m[i].stsec-pre)/2)/1000.0,wkfilename,wkfilename,i);
370 tclistpush2(cmdlist,cptr);
376 for (i=0;i<tclistnum(cmdlist);i++) {
377 if (cmdexecute && wkfilename) {
380 pp = popen(tclistval2(cmdlist,i),"r");
381 if (pp==NULL) {continue;}
382 while(fgets(pbuf,1024,pp)!=NULL){
387 printf("%s\n",tclistval2(cmdlist,i));
392 int rechecktext(FILE *f)
411 while(fgets(rbuf,1024,f)!=NULL){
412 if (strstr(rbuf,"# cmcheckwave ")) {
414 if (strlen(rbuf+14)-1>0) {
415 wkfilename = malloc(strlen(rbuf+14));
416 strncpy(wkfilename,rbuf+14,strlen(rbuf+14)-1);
421 if (strstr(rbuf,"# total "))
425 sscanf(rbuf,"# %s %s diff %s %s",wk1,wk2,wk3,wk4);
427 m[cnt].stsec = (int)(atof(wk1)*1000.0);
428 m[cnt].edsec = (int)(atof(wk2)*1000.0);
429 m[cnt].diffs = (int)(atof(wk3)*1000.0);
430 if (strstr(wk4,"CM")) m[cnt].cmflg=1;
437 if (checkcomplete > 0) {
441 return dumpinfo(cnt);
446 int cmcheckwave(FILE *f)
448 int i,j, x, channels, bits;
453 int readed,loop,max,totalsec,muonstartsec,kankaku;
456 int mcnt,hcnt,rcnt,readbufsz;
457 unsigned char *readbuf;
462 if (memcmp(get_bytes(f, 4), "RIFF", 4) != 0) {
463 // fprintf(stderr, "Not a 'RIFF' format\n");
466 //fprintf(stderr, "[RIFF] (%lu bytes)\n", get_ulong(f));
468 if (memcmp(get_bytes(f, 8), "WAVEfmt ", 8) != 0) {
469 // fprintf(stderr, "Not a 'WAVEfmt ' format\n");
473 //fprintf(stderr, "[WAVEfmt ] (%lu bytes)\n", len);
474 //fprintf(stderr, " Data type = %u (1 = PCM)\n", get_ushort(f));
476 channels = get_ushort(f);
477 //fprintf(stderr, " Number of channels = %u (1 = mono, 2 = stereo)\n", channels);
478 //fprintf(stderr, " Sampling rate = %luHz\n", get_ulong(f));
481 //fprintf(stderr, " Bytes / second = %lu\n", bsec);
482 //fprintf(stderr, " Bytes x channels = %u\n", get_ushort(f));
484 bits = get_ushort(f);
485 //fprintf(stderr, " Bits / sample = %u\n", bits);
486 for (i = 16; i < len; i++)
488 while (fread(s, 4, 1, f) == 1) {
491 //fprintf(stderr, "[%s] (%lu bytes)\n", s, len);
492 if (memcmp(s, "data", 4) == 0) break;
493 for (i = 0; i < len; i++)
497 readed=max=totalsec=kankaku=mcnt=0;
501 readbuf=malloc(4096*1000);
502 while(rcnt=fread(readbuf,1,4096*1000,f)) {
503 for(readbufsz=0;readbufsz<rcnt;) {
504 for (i = 0; i < channels; i++) {
506 //if ((x = fgetc(f)) == EOF) {loop=0;break;}
507 x = readbuf[readbufsz];
512 //if (fread(s, 2, 1, f) != 1) {loop=0;break;}
513 //x = (short)(s[0] + 256 * s[1]);
514 x = (short)(readbuf[readbufsz+0] + 256 * readbuf[readbufsz+1]);
518 if (x > max) max = x;
519 if (x > peak) peak = x;
521 //if (i != channels - 1) printf("\t");
524 if (readed % (bsec/100) == 0) {
526 if (muonstartsec==-1 && max < defmax) {
527 muonstartsec = totalsec;
531 if (muonstartsec>=0 && max >= defmax) {
532 // 無音が300(defmuon)msより大きい
533 if (totalsec - muonstartsec > defmuon){
535 m[mcnt].stsec = muonstartsec;
536 m[mcnt].edsec = totalsec;
537 m[mcnt].diffs = totalsec - kankaku;
541 dul = (totalsec-muonstartsec)/1000.0;
542 diffs = (totalsec-kankaku)/1000.0;
544 if ((diffs > 14.5) && (diffs < 15.5)) m[mcnt].cmflg=1;
545 if ((diffs > 29.5) && (diffs < 30.5)) m[mcnt].cmflg=1;
546 if ((diffs > 59.5) && (diffs < 60.5)) m[mcnt].cmflg=1;
564 if ((m[0].cmflg==0) && (m[0].diffs < 15000))
567 for(i=1;i<mcnt-1;i++) {
568 // 本編で31秒以下が連続だったら、次の31秒以上の本編もしくはCMまでの時間をチェック
570 // TODO 28+32で60秒CMとかいうのがあるどうするのがいいだろうか・・・
571 if (m[i].cmflg==0 && m[i].diffs < 31000 && m[i+1].cmflg==0 && m[i+1].diffs < 31000) {
573 for(j=i;j<mcnt;j++) {
574 if (m[j].cmflg==1) break;
575 if (m[j].diffs > 31000) break;
576 if (cmwork + m[j].diffs > 61000) break;
577 cmwork = cmwork + m[j].diffs;
579 //合計時間を15秒で割ってcm時間っぽいならばCMとする。
580 // TODO 30(15)秒以下の条件付けがいる?60秒どうする?
581 if (cmwork%15000>14500 || cmwork%15000<500) {
582 for(j=i;j<mcnt;j++) {
583 if (m[j].cmflg==1) break;
584 if (m[j].diffs > 31000) break;
592 if ((m[mcnt-1].cmflg==0) && (m[mcnt-1].diffs < 15000))
595 for(i=1;i<mcnt-1;i++) {
596 //本編で46秒以下かつ、前後がCMの場合CM14-16,29-31,44-46秒でもCMとする。
597 if (m[i].cmflg==0 && m[i].diffs < 46000 && m[i-1].cmflg==1 && m[i+1].cmflg==1) {
598 if ((m[i].diffs > 14000) && (m[i].diffs < 16000)) m[i].cmflg=1;
599 if ((m[i].diffs > 29000) && (m[i].diffs < 31000)) m[i].cmflg=1;
600 if ((m[i].diffs > 44000) && (m[i].diffs < 46000)) m[i].cmflg=1;
601 // 0.9秒以下(おそらく前後CMのあまり時間)
602 if (m[i].diffs < 900) m[i].cmflg=1;
604 //10,5秒のときは提供とみなし、その前を本編にする。
606 if ((m[i].diffs > 9500) && (m[i].diffs < 10500)) m[i-1].cmflg=0;
607 if ((m[i].diffs > 4500) && (m[i].diffs < 5500)) m[i-1].cmflg=0;
609 // TODO 前後が本編で単独でCMの場合は本編とする?
611 // if (m[i].cmflg==1 && m[i-1].cmflg==0 && m[i+1].cmflg==0) {
617 return dumpinfo(mcnt);
620 int main(int argc, char *argv[])
623 extern int optind, opterr;
631 while ((ch = getopt(argc, argv, "adtxb:m:v:c:")) != -1){
643 defmuon=atoi(optarg);
655 checkcomplete=atoi(optarg);
656 if (checkcomplete <= 0 && checkcomplete > 2) usage(argv0);
669 if (tmpenv=getenv("FFMPEG")) FFMPEGCMD=tmpenv;
670 if (tmpenv=getenv("SOX")) SOXCMD=tmpenv;
671 if (tmpenv=getenv("MP4BOX")) MP4BOXCMD=tmpenv;
672 if (tmpenv=getenv("MP4BOXCMDRAPSTR")) MP4BOXCMDRAPSTR=tmpenv;
673 if (tmpenv=getenv("AACENC")) AACENCCMD=tmpenv;
674 if (tmpenv=getenv("AACENCPOT")) AACENCOPT=tmpenv;
675 if (tmpenv=getenv("MPLAYER")) MPLAYERCMD=tmpenv;
676 if (tmpenv=getenv("FAADCMD")) FAADCMD=tmpenv;
680 if (strcmp(argv[0],"-")==0)
683 f = fopen(argv[0],"rb");
685 p = checkMP4(f,argv[0]);
691 if (p) ret = cmcheckwave(p);
692 else ret = cmcheckwave(f);
693 // -1 のときはテキストとして再チェック
694 if (ret == -1) rechecktext(f);