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