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