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