]> mj.ucw.cz Git - vcut.git/blob - vorbiscut.c
Fixed a cupola bugs.
[vcut.git] / vorbiscut.c
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <stdarg.h>
4 #include <termios.h>
5 #include <unistd.h>
6 #include <fcntl.h>
7 #include <sys/wait.h>
8 #include <vorbis/vorbisfile.h>
9 #include <alsa/asoundlib.h>
10 #include <sndfile.h>
11
12 static void __attribute__((noreturn)) die(char *msg, ...)
13 {
14   va_list args;
15   va_start(args, msg);
16   vfprintf(stderr, msg, args);
17   fputc('\n', stderr);
18   exit(1);
19 }
20
21 typedef long long s64;
22 typedef short s16;
23 #define MIN(a,b) ((a)<(b) ? (a) : (b))
24 #define MAX(a,b) ((a)>(b) ? (a) : (b))
25 #define TRIPLE(pos) (int)(((pos)/rate)/60), (int)(((pos)/rate)%60), (int)((pos)%rate)
26
27 static struct termios tios, tios_old;
28
29 static void key_init(void)
30 {
31   if (tcgetattr(0, &tios_old) < 0)
32     die("tcgetattr failed: %m");
33   tios = tios_old;
34   tios.c_iflag = IGNBRK;
35   tios.c_lflag = 0;
36   tios.c_cc[VTIME] = 0;
37   tios.c_cc[VMIN] = 1;
38   if (tcsetattr(0, 0, &tios) < 0)
39     die("tcsetattr failed: %m");
40   fcntl(0, F_SETFL, O_NONBLOCK);
41 }
42
43 static void key_cleanup(void)
44 {
45   tcsetattr(0, 0, &tios_old);
46 }
47
48 static int key_get(void)
49 {
50   char keybuf[1];
51   static int esc_state, esc_num;
52
53   for (;;)
54     {
55       int e = read(0, keybuf, 1);
56       if (e != 1)
57         return 0;
58
59       int key = keybuf[0];
60       switch (esc_state)
61         {
62         case 0:
63           if (key != '\e')
64             return key;
65           esc_state = 1;
66           esc_num = 0;
67           break;
68         case 1:
69           if (key == '[')
70             esc_state = 2;
71           else
72             {
73               esc_state = 0;
74               return 0x0100 + key;
75             }
76           break;
77         case 2:
78           if (key >= '0' && key <= '9')
79             esc_num = 10*esc_num + key - '0';
80           else
81             {
82               esc_state = 0;
83               return (esc_num << 16) + 0x200 + key;
84             }
85           break;
86         }
87     }
88 }
89
90 static FILE *infile;
91 enum { IN_WAV, IN_OGG } inmode;
92 static OggVorbis_File vf;
93 static SNDFILE *sndf;
94 static unsigned int rate;
95 static char *find_title = "Toulky ceskou minulosti";
96 static s64 total_samples = 0;
97 static s64 start_pos = -1;
98 static s64 end_pos = -1;
99 double prefade = 1, postfade = 1;
100
101 static void scan_streams(void)
102 {
103   int nstr = ov_streams(&vf);
104   printf("OGG: Scanning %d logical streams:\n", nstr);
105   if (!nstr)
106     die("No streams found");
107   for (int i=0; i<nstr; i++)
108     {
109       vorbis_info *vi;
110       vi = ov_info(&vf, i);
111       if (!vi)
112         die("ov_info failed");
113       if (vi->channels != 2)
114         die("Stream %d has %d channels, which is not supported", i, vi->channels);
115       if ((unsigned int) vi->rate != rate)
116         {
117           if (rate)
118             die("Stream %d has sample rate %d, while the previous had %d", i, vi->rate, rate);
119           else
120             rate = vi->rate;
121         }
122
123       vorbis_comment *vc;
124       vc = ov_comment(&vf, i);
125       if (!vc)
126         die("ov_comment failed");
127       char title[1024];
128       title[0] = 0;
129       for (int j=0; j<vc->comments; j++)
130         if (vc->comment_lengths[j] > 6 && !strncasecmp(vc->user_comments[j], "title=", 6))
131           {
132             int l = vc->comment_lengths[j] - 6;
133             memcpy(title, vc->user_comments[j]+6, l);
134             title[l] = 0;
135           }
136       if (!title[0])
137         strcpy(title, "<none>");
138
139       s64 samples = ov_pcm_total(&vf, i);
140       int sec = (samples + rate - 1) / rate;
141       printf("  %d: `%s' (%d:%02d, %d bits/sec) @%Ld\n", i, title, sec/60, sec%60, (int)vi->bitrate_nominal, total_samples);
142
143       if (find_title && strstr(title, find_title))
144         {
145           if (start_pos < 0)
146             start_pos = total_samples;
147           else if (end_pos != total_samples)
148             printf("WARNING: Gap encountered!\n");
149           end_pos = total_samples + samples;
150         }
151
152       total_samples += samples;
153     }
154   if (ov_pcm_total(&vf, -1) != total_samples)
155     die("ov_pcm_total mismatch");
156
157   if (start_pos < 0)
158     {
159       if (find_title)
160         printf("WARNING: Title not found, marking whole file");
161       start_pos = 0;
162       end_pos = total_samples;
163     }
164 }
165
166 static void in_open(char *name)
167 {
168   infile = fopen(name, "r");
169   if (!infile)
170     die("Cannot open %s: %m", name);
171
172   char s[4];
173   if (fread(s, 1, 4, infile) != 4)
174     die("Input file too short");
175   rewind(infile);
176   if (!memcmp(s, "RIFF", 4))
177     {
178       puts("INPUT: WAV file detected");
179       inmode = IN_WAV;
180     }
181   else if (!memcmp(s, "OggS", 4))
182     {
183       puts("INPUT: OGG file detected");
184       inmode = IN_OGG;
185     }
186   else if (!memcmp(s, "HTTP", 4))
187     {
188       puts("INPUT: HTTP header detected, expecting OGG inside");
189       inmode = IN_OGG;
190     }
191   else
192     die("Unable to identify input format");
193
194   if (inmode == IN_WAV)
195     {
196       SF_INFO si;
197       bzero(&si, sizeof(si));
198       lseek(fileno(infile), 0, SEEK_SET);
199       sndf = sf_open_fd(fileno(infile), O_RDONLY, &si, 0);
200       if (!sndf)
201         die("sf_open_fd() failed: %s", sf_strerror(NULL));
202       total_samples = si.frames;
203       rate = si.samplerate;
204       if (si.channels != 2)
205         die("Got %d channels instead of 2", si.channels);
206       if (si.sections != 1)
207         die("Found %d sections, what does it mean?", si.sections);
208       printf("WAV: format id=%08x\n", si.format);
209       start_pos = 0;
210       end_pos = total_samples;
211     }
212   else
213     {
214       int err = ov_open(infile, &vf, NULL, 0);
215       if (err)
216         die("ov_open: error %d", err);
217       if (!ov_seekable(&vf))
218         die("Input is not seekable, how come?");
219       scan_streams();
220     }
221
222   printf("INPUT: length %3d:%02d.%05d, %Ld samples at rate %d\n", TRIPLE(total_samples), total_samples, rate);
223 }
224
225 static void in_goto(s64 go)
226 {
227   if (inmode == IN_OGG)
228     ov_pcm_seek(&vf, go);
229   else
230     sf_seek(sndf, go, SEEK_SET);
231 }
232
233 static int in_read(s16 *buf, s64 pos, int nsamp)
234 {
235   if (inmode == IN_OGG)
236     {
237       if (ov_pcm_tell(&vf) != pos)
238         {
239           printf("!!! CONFUSED POSITION\n");
240           ov_pcm_seek(&vf, pos);
241           if (ov_pcm_tell(&vf) != pos)
242             printf(">>> unable to correct :(\n");
243         }
244       for (;;)
245         {
246           int bp;
247           int e = ov_read(&vf, (char*)buf, 4*nsamp, 0, 2, 1, &bp);
248           if (e == OV_HOLE)
249             printf("!!! HOLE DETECTED\n");
250           else if (e == OV_EBADLINK)
251             printf("!!! BAD LINK\n");
252           else if (e % 4)
253             die("ov_read returned %d bytes, which means non-integer number of samples. Huh.", e);
254           else if (e < 0)
255             die("ov_read returned %d. Huh!", e);
256           else if (e/4 <= nsamp)
257             return e/4;
258           else
259             die("ov_read returned %d samples, although we wanted %d", e/4, nsamp);
260         }
261     }
262   else
263     {
264       int e = sf_readf_short(sndf, buf, nsamp);
265       if (e != nsamp)
266         printf("!!!! sf_readf_short mismatch: %d != %d\n", e, nsamp);
267       return e;
268     }
269 }
270
271 static void in_close(void)
272 {
273   if (inmode == IN_OGG)
274     ov_clear(&vf);
275   else
276     sf_close(sndf);
277 }
278
279 static s16 *prefader, *postfader;
280
281 static s16 *calc_fader(int len, int rev)
282 {
283   if (!len)
284     return NULL;
285   s16 *x = malloc(2*len);
286   for (int i=0; i<len; i++)
287     x[rev ? len-1-i : i] = (s64)i*32767/len;
288   return x;
289 }
290
291 static void recalc_faders(void)
292 {
293   // printf("FADERS: set to %g pre, %g post\n", prefade, postfade);
294   free(prefader);
295   prefader = calc_fader(prefade*rate, 0);
296   free(postfader);
297   postfader = calc_fader(postfade*rate, 1);
298 }
299
300 static void apply_fader(s16 *sig, int nsamp, s64 pos, s64 fstart, int flen, s16 *fader)
301 {
302   s64 relpos = pos - fstart;
303   if (relpos <= -nsamp || relpos >= flen)
304     return;
305   int rel = relpos;
306   if (rel < 0)
307     {
308       rel = -rel;
309       sig += 2*rel;
310       nsamp -= rel;
311       rel = 0;
312     }
313   while (nsamp > 0 && rel < flen)
314     {
315       sig[0] = (sig[0] * fader[rel]) >> 15;
316       sig[1] = (sig[1] * fader[rel]) >> 15;
317       sig += 2;
318       nsamp--;
319       rel++;
320     }
321 }
322
323 static int cooked_read(s16 *buf, s64 pos, int nsamp, int apply_pre, int apply_post)
324 {
325   nsamp = in_read(buf, pos, nsamp);
326   int presamp = prefade * rate;
327   int postsamp = postfade * rate;
328   if (apply_pre)
329     apply_fader(buf, nsamp, pos, start_pos, presamp, prefader);
330   if (apply_post)
331     apply_fader(buf, nsamp, pos, end_pos-postsamp, postsamp, postfader);
332   return nsamp;
333 }
334
335 static char *cuename;
336
337 static int cue_load(void)
338 {
339   if (cuename)
340     {
341       FILE *f = fopen(cuename, "r");
342       if (f)
343         {
344           if (fscanf(f, "%Ld%lf%Ld%lf", &start_pos, &prefade, &end_pos, &postfade) != 4)
345             die("CUE: Invalid syntax");
346           fclose(f);
347           printf("CUE: Loaded\n");
348           return 1;
349         }
350     }
351   return 0;
352 }
353
354 static void cue_save(void)
355 {
356   if (cuename)
357     {
358       FILE *f = fopen(cuename, "w");
359       if (!f)
360         die("CUE: Cannot write");
361       fprintf(f, "%Ld %g %Ld %g\n", start_pos, prefade, end_pos, postfade);
362       fclose(f);
363     }
364 }
365
366 static void editor(void)
367 {
368   printf("Initializing ALSA\n");
369   snd_pcm_t *pcm;
370   snd_pcm_hw_params_t *apars;
371   int err;
372 #define ALSACALL(f,args) if ((err = f args) < 0) die(#f " failed: %s", snd_strerror(err))
373   ALSACALL(snd_pcm_open, (&pcm, "default", SND_PCM_STREAM_PLAYBACK, 0));
374   ALSACALL(snd_pcm_hw_params_malloc, (&apars));
375   ALSACALL(snd_pcm_hw_params_any, (pcm, apars));
376   ALSACALL(snd_pcm_hw_params_set_access, (pcm, apars, SND_PCM_ACCESS_RW_INTERLEAVED));
377   ALSACALL(snd_pcm_hw_params_set_format, (pcm, apars, SND_PCM_FORMAT_S16_LE));
378   unsigned int xrate = rate;
379   int dir = 0;
380   ALSACALL(snd_pcm_hw_params_set_rate_near, (pcm, apars, &xrate, &dir));
381   if (xrate != rate)
382     printf("WARNING: Rate set to %d instead of %d\n", xrate, rate);
383   ALSACALL(snd_pcm_hw_params_set_channels, (pcm, apars, 2));
384   ALSACALL(snd_pcm_hw_params, (pcm, apars));
385   snd_pcm_hw_params_free(apars);
386   ALSACALL(snd_pcm_prepare, (pcm));
387
388   s64 go = start_pos;
389   s64 pos = -1;
390
391 #define CLAMP(x) (((x) < 0) ? 0 : ((x) >= total_samples) ? total_samples-1 : (x))
392
393   s16 buf[2048];
394   key_init();
395
396   enum {
397     M_START,
398     M_END,
399     M_PRE,
400     M_POST,
401   } mode = M_START;
402   int silence = 0;
403   int step = rate*4;
404   double fst = 4;
405   double fsil = 1;
406   int lback = 3*rate;
407   for(;;)
408     {
409       if (go >= 0)
410         {
411           if (pos != go)
412             {
413               in_goto(go);
414               pos = go;
415             }
416           go = -1;
417         }
418
419       int key = key_get();
420       switch (key)
421         {
422         case ' ':
423           if (mode == M_START || mode == M_PRE)
424             {
425               start_pos = pos;
426               go = start_pos;
427               mode = M_PRE;
428             }
429           else if (mode == M_END || mode == M_POST)
430             {
431               end_pos = pos;
432               go = CLAMP(end_pos - lback);
433               mode = M_POST;
434             }
435           break;
436         case 0x241:
437           fst *= 2;
438           step = rate * fst;
439           break;
440         case 0x242:
441           fst /= 2;
442           step = rate * fst;
443           break;
444         case '1' ... '4':
445           fst = 1. / (16 >> (key - '0'));
446           step = rate * fst;
447           break;
448         case '5' ... '9':
449           fst = 1 << (key - '4');
450           step = rate * fst;
451           break;
452         case 0x244:
453           if (mode == M_PRE)
454             {
455               start_pos = CLAMP(start_pos - step);
456               go = start_pos;
457             }
458           else if (mode == M_POST)
459             {
460               end_pos = CLAMP(end_pos - step);
461               go = CLAMP(end_pos - lback);
462             }
463           else
464             go = CLAMP(pos - step);
465           break;
466         case 0x243:
467           if (mode == M_PRE)
468             {
469               start_pos = CLAMP(start_pos + step);
470               go = start_pos;
471             }
472           else if (mode == M_POST)
473             {
474               end_pos = CLAMP(end_pos + step);
475               go = CLAMP(end_pos - lback);
476             }
477           else
478             go = CLAMP(pos + step);
479           break;
480         case '\r':
481         case '\n':
482           if (mode == M_PRE)
483             {
484               go = start_pos;
485               silence = fsil*rate;
486             }
487           else if (mode == M_POST)
488             {
489               go = CLAMP(end_pos - lback);
490               silence = fsil*rate;
491             }
492           else
493             silence = 0;
494           break;
495         case 0x7027e:   // Home
496           if (mode <= M_END)
497             go = 0;
498           break;
499         case 0x8027e:   // End
500           if (mode <= M_END)
501             go = CLAMP(total_samples - lback);
502           break;
503         case 0x5027e:   // PgUp
504           if (mode <= M_END)
505             go = CLAMP(pos - 60*rate);
506           break;
507         case 0x6027e:   // PgUp
508           if (mode <= M_END)
509             go = CLAMP(pos + 60*rate);
510           break;
511         case 0x2027e:   // Insert
512           mode = M_START;
513           break;
514         case 0x3027e:   // Delete
515           mode = M_END;
516           go = end_pos - lback;
517           break;
518         case '[':
519           mode = M_PRE;
520           go = start_pos;
521           break;
522         case ']':
523           mode = M_POST;
524           go = CLAMP(end_pos - lback);
525           break;
526         case 0x7f:
527           silence = -1;
528           break;
529         case '+':
530         case '-':
531         case '/':
532         case '*':
533           if (mode == M_PRE || mode == M_POST)
534             {
535               double *f = (mode == M_PRE ? &prefade : &postfade);
536               if (key == '/')
537                 *f = 0;
538               else if (key == '*')
539                 *f = MIN(fst, 10);
540               else if (key == '+')
541                 *f = MIN(*f + fst, 10);
542               else
543                 *f = MAX(*f - fst, 0);
544               recalc_faders();
545               if (mode == 1)
546                 go = start_pos;
547               else
548                 go = CLAMP(end_pos - lback);
549             }
550           break;
551         case 'q':
552           cue_save();
553           /* fall-thru */
554         case 'Q':
555         case 3:
556           goto done;
557         default:
558           printf("KEY <%x>\n", key);
559         case 0: ;
560         }
561       if (go >= 0)
562         continue;
563
564       printf("%3d:%02d.%05d  [%s %3d:%02d.%05d/%g -> %s %3d:%02d.%05d\\%g] step=%g%s\e[K\r",
565              TRIPLE(pos),
566              (mode == M_START) ? "START" : (mode == M_PRE) ? "START>" : "start", TRIPLE(start_pos), prefade,
567              (mode == M_END) ? "END" : (mode == M_POST) ? ">END" : "end", TRIPLE(end_pos), postfade,
568              fst,
569              (silence < 0) ? " [STOP]" : "");
570       fflush(stdout);
571
572       s64 end = (mode == M_POST) ? end_pos : total_samples;
573       int nsamp, nread;
574       if (pos >= end || silence)
575         {
576           bzero(buf, 1024);
577           nsamp = 1024/4;
578           if (silence > 0)
579             {
580               nsamp = MIN(nsamp, silence);
581               silence -= nsamp;
582             }
583           nread = 0;
584         }
585       else
586         {
587           nsamp = sizeof(buf)/4;
588           if (pos + nsamp > pos)
589             nsamp = end - pos;
590           nsamp = cooked_read(buf, pos, nsamp, (mode == M_PRE), (mode == M_POST));
591           nread = nsamp;
592         }
593
594       int err = snd_pcm_writei(pcm, buf, nsamp);
595       if (err == -EPIPE)
596         {
597           puts("[xrun detected]");
598           err = snd_pcm_prepare(pcm);
599           if (err < 0)
600             die("xrun recovery failed: error %d", err); 
601         }
602       if (err < 0)
603         die("snd_pcm_writei failed: error %d", err);
604       pos += nread;
605       if (pos > end)
606         pos = end;
607     }
608
609  done:
610   key_cleanup();
611 }
612
613 static int nchildren;
614
615 static int add_filter(int fd, char **args)
616 {
617   int p[2];
618   if (pipe(p) < 0)
619     die("pipe: %m");
620   pid_t pid = fork();
621   if (pid < 0)
622     die("fork: %m");
623   if (!pid)
624     {
625       close(p[1]);
626       dup2(p[0], 0);
627       close(p[0]);
628       dup2(fd, 1);
629       close(fd);
630       execvp(args[0], args);
631       die("execvp(%s) failed: %m", args[0]);
632     }
633   else
634     {
635       nchildren++;
636       printf("FILTER: Forked pid %d for %s\n", pid, args[0]);
637       close(p[0]);
638       close(fd);
639       return p[1];
640     }
641 }
642
643 static int orig_stdout;
644
645 static void render(char *name)
646 {
647   char *sfix;
648   int fd;
649   if (!strcmp(name, "-"))
650     {
651       fd = orig_stdout;
652       sfix = ".wav";
653     }
654   else
655     {
656       sfix = strrchr(name, '.') ? : "";
657       fd = open(name, O_WRONLY | O_CREAT | O_TRUNC, 0666);
658       if (fd < 0)
659         die("Unable to create %s: %m", name);
660     }
661   if (!strcmp(sfix, ".WAV"))
662     printf("RENDER: WAV output without sample rate conversion\n");
663   else if (!strcmp(sfix, ".wav"))
664     {
665       printf("RENDER: WAV output\n");
666       if (rate != 44100)
667         {
668           printf("RENDER: Adding resample filter\n");
669           char *f[] = { "sox", "-twav", "-", "-r44100", "-twav", "-", "vol", "0.9", "resample", NULL };
670           fd = add_filter(fd, f);
671         }
672     }
673   else
674     die("Unknown output file suffix");
675
676   s64 outlen = end_pos - start_pos;
677
678 #if 0
679   /* GRRRR! libsndfile is unable to write WAV files to pipes! */
680   SF_INFO si = {
681     .frames = 0,
682     .samplerate = rate,
683     .channels = 2,
684     .format = SF_FORMAT_WAV | SF_FORMAT_PCM_16,
685     .sections = 1,
686     .seekable = 0
687   };
688   SNDFILE *sf = sf_open_fd(fd, SFM_WRITE, &si, 0);
689   if (!sf)
690     die("Output sf_open_fd() failed: %s", sf_strerror(NULL));
691 #else
692   FILE *of = fdopen(fd, "w");
693   void wt(char *x) { fwrite(x, 4, 1, of); }
694   void wr(unsigned int x) { fwrite(&x, sizeof(x), 1, of); }
695   wt("RIFF");
696   wr(8 + 0x10 + 8 + 4*outlen);
697   wt("WAVE");
698   wt("fmt ");
699   wr(0x10);
700   wr(0x00020001);       // 2 channels, not compressed
701   wr(rate);
702   wr(4*rate);
703   wr(0x00100004);       // 16-bit
704   wt("data");
705   wr(4*outlen);
706 #endif
707
708   s64 pos = start_pos;
709   s64 outpos = 0;
710   s16 buf[65536];
711   in_goto(pos);
712   printf("RENDER: Generating %d:%02d.%05d of output\n", TRIPLE(outlen));
713   for (;;)
714     {
715       int n = sizeof(buf) / 4;
716       if (pos + n > end_pos)
717         n = end_pos - pos;
718       if (!n)
719         break;
720       n = cooked_read(buf, pos, n, 1, 1);
721       pos += n;
722       outpos += n;
723 #if 0
724       if (sf_writef_short(sf, buf, n) != n)
725 #else
726       if ((int)fwrite(buf, 4, n, of) != n)
727 #endif
728         die("Short write, oops!");
729       printf("RENDER: %3d:%02d.%05d\r", TRIPLE(outpos));
730       fflush(stdout);
731     }
732
733 #if 0
734   sf_close(sf);
735   close(fd);
736 #else
737   fclose(of);
738 #endif
739   while (nchildren)
740     {
741       int st;
742       pid_t p = wait(&st);
743       if (p < 0)
744         printf("wait(): %m\n");
745       else
746         {
747           nchildren--;
748           if (WIFEXITED(st))
749             {
750               if (WEXITSTATUS(st))
751                 printf("!!! pid %d failed with exit code %d\n", p, WEXITSTATUS(st));
752               else
753                 printf("FILTER: pid %d finished OK\n", p);
754             }
755           else
756             printf("!!! pid %d failed with status %x\n", p, st);
757         }
758     }
759   printf("RENDER: Rendering successfully completed.\n");
760 }
761
762 int main(int argc, char **argv)
763 {
764   if (argc < 2 || argc > 4)
765     die("Usage: vorbiscut <infile> [<cuefile> [<outfile>|-]]");
766
767   if (argc >= 4)
768     {
769       orig_stdout = dup(1);
770       dup2(2, 1);
771     }
772
773   in_open(argv[1]);
774   if (argc >= 3)
775     cuename = argv[2];
776   int cueok = cue_load();
777   recalc_faders();
778   if (argc >= 4)
779     {
780       if (!cueok)
781         printf("!!!!! Cue sheet not present, rendering with defaults !!!!!\n");
782       render(argv[3]);
783     }
784   else
785     {
786       editor();
787     }
788   in_close();
789   return 0;
790 }