chiark / gitweb /
exclude nonsense finkbindir
[disorder] / plugins / tracklength.c
1 /*
2  * This file is part of DisOrder.
3  * Portions copyright (C) 2004, 2005 Richard Kettlewell (see also below)
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful, but
11  * WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
18  * USA
19  */
20
21 #include <config.h>
22
23 #include <string.h>
24 #include <stdio.h>
25 #include <math.h>
26 #include <sys/types.h>
27 #include <sys/stat.h>
28 #include <unistd.h>
29 #include <fcntl.h>
30 #include <sys/mman.h>
31 #include <errno.h>
32
33 #include <vorbis/vorbisfile.h>
34 #include <mad.h>
35
36 #include <disorder.h>
37
38 #include "madshim.h"
39
40 static void *mmap_file(const char *path, size_t *lengthp) {
41   int fd;
42   void *base;
43   struct stat sb;
44   
45   if((fd = open(path, O_RDONLY)) < 0) {
46     disorder_error(errno, "error opening %s", path);
47     return 0;
48   }
49   if(fstat(fd, &sb) < 0) {
50     disorder_error(errno, "error calling stat on %s", path);
51     goto error;
52   }
53   if(sb.st_size == 0)                   /* can't map 0-length files */
54     goto error;
55   if((base = mmap(0, sb.st_size, PROT_READ,
56                   MAP_SHARED, fd, 0)) == (void *)-1) {
57     disorder_error(errno, "error calling mmap on %s", path);
58     goto error;
59   }
60   *lengthp = sb.st_size;
61   close(fd);
62   return base;
63 error:
64   close(fd);
65   return 0;
66 }
67
68 static long tl_mp3(const char *path) {
69   size_t length;
70   void *base;
71   buffer b;
72
73   if(!(base = mmap_file(path, &length))) return -1;
74   b.duration = mad_timer_zero;
75   scan_mp3(base, length, &b);
76   munmap(base, length);
77   return b.duration.seconds + !!b.duration.fraction;
78 }
79
80 static long tl_ogg(const char *path) {
81   OggVorbis_File vf;
82   FILE *fp = 0;
83   double length;
84
85   if(!path) goto error;
86   if(!(fp = fopen(path, "rb"))) goto error;
87   if(ov_open(fp, &vf, 0, 0)) goto error;
88   fp = 0;
89   length = ov_time_total(&vf, -1);
90   ov_clear(&vf);
91   return ceil(length);
92 error:
93   if(fp) fclose(fp);
94   return -1;
95 }
96
97 static long tl_wav(const char *path) {
98   size_t length;
99   void *base;
100   long duration = -1;
101   unsigned char *ptr;
102   unsigned n, m, data_bytes = 0, samples_per_second = 0;
103   unsigned n_channels = 0, bits_per_sample = 0, sample_point_size;
104   unsigned sample_frame_size, n_samples;
105
106   /* Sources:
107    *
108    * http://www.technology.niagarac.on.ca/courses/comp530/WavFileFormat.html
109    * http://www.borg.com/~jglatt/tech/wave.htm
110    * http://www.borg.com/~jglatt/tech/aboutiff.htm
111    *
112    * These files consists of a header followed by chunks.
113    * Multibyte values are little-endian.
114    *
115    * 12 byte file header:
116    *  offset  size  meaning
117    *  00      4     'RIFF'
118    *  04      4     length of rest of file
119    *  08      4     'WAVE'
120    *
121    * The length includes 'WAVE' but excludes the 1st 8 bytes.
122    *
123    * Chunk header:
124    *  00      4     chunk ID
125    *  04      4     length of rest of chunk
126    *
127    * The stated length may be odd, if so then there is an implicit padding byte
128    * appended to the chunk to make it up to an even length (someone wasn't
129    * think about 32/64-bit worlds).
130    *
131    * Also some files seem to have extra stuff at the end of chunks that nobody
132    * I know of documents.  Go figure, but check the length field rather than
133    * deducing the length from the ID.
134    *
135    * Format chunk:
136    *  00      4     'fmt'
137    *  04      4     length of rest of chunk
138    *  08      2     compression (1 = none)
139    *  0a      2     number of channels
140    *  0c      4     samples/second
141    *  10      4     average bytes/second, = (samples/sec) * (bytes/sample)
142    *  14      2     bytes/sample
143    *  16      2     bits/sample point
144    *
145    * 'sample' means 'sample frame' above, i.e. a sample point for each channel.
146    *
147    * Data chunk:
148    *  00      4     'data'
149    *  04      4     length of rest of chunk
150    *  08      ...   data
151    *
152    * There is only allowed to be one data chunk.  Some people violate this; we
153    * shall encourage people to fix their broken WAV files by not supporting
154    * this violation and because it's easier.
155    *
156    * As to the encoding of the data:
157    *
158    * Firstly, samples up to 8 bits in size are unsigned, larger samples are
159    * signed.  Madness.
160    *
161    * Secondly sample points are stored rounded up to a multiple of 8 bits in
162    * size.  Marginally saner.
163    *
164    * Written as a single word (of 8, 16, 24, whatever bits) the padding to
165    * implement this happens at the right hand (least significant) end.
166    * e.g. assuming a 9 bit sample:
167    *
168    * |                 padded sample word              |
169    * | 15 14 13 12 11 10  9  8  7  6  5  4  3  2  1  0 |
170    * |  8  7  6  5  4  3  2  1  0  -  -  -  -  -  -  - |
171    * 
172    * But this is a little-endian file format so the least significant byte is
173    * the first, which means that the padding is "between" the bits if you
174    * imagine them in their usual order:
175    *
176    *  |     first byte         |     second byte        |
177    *  | 7  6  5  4  3  2  1  0 | 7  6  5  4  3  2  1  0 |
178    *  | 0  -  -  -  -  -  -  - | 8  7  6  5  4  3  2  1 |
179    *
180    * Sample points are grouped into sample frames, consisting of as many
181    * samples points as their are channels.  It seems that there are standard
182    * orderings of different channels.
183    *
184    * Given all of the above all we need to do is pick up some numbers from the
185    * format chunk, and the length of the data chunk, and do some arithmetic.
186    */
187   if(!(base = mmap_file(path, &length))) return -1;
188 #define get16(p) ((p)[0] + 256 * (p)[1])
189 #define get32(p) ((p)[0] + 256 * ((p)[1] + 256 * ((p)[2] + 256 * (p)[3])))
190   ptr = base;
191   if(length < 12) goto out;
192   if(strncmp((char *)ptr, "RIFF", 4)) goto out; /* wrong type */
193   n = get32(ptr + 4);                   /* file length */
194   if(n > length - 8) goto out;          /* truncated */
195   ptr += 8;                             /* skip file header */
196   if(n < 4 || strncmp((char *)ptr, "WAVE", 4)) goto out; /* wrong type */
197   ptr += 4;                             /* skip 'WAVE' */
198   n -= 4;
199   while(n >= 8) {
200     m = get32(ptr + 4);                 /* chunk length */
201     if(m > n - 8) goto out;             /* truncated */
202     if(!strncmp((char *)ptr, "fmt ", 4)) {
203       if(samples_per_second) goto out;  /* duplicate format chunk! */
204       n_channels = get16(ptr + 0x0a);
205       samples_per_second = get32(ptr + 0x0c);
206       bits_per_sample = get16(ptr + 0x16);
207       if(!samples_per_second) goto out; /* bogus! */
208     } else if(!strncmp((char *)ptr, "data", 4)) {
209       if(data_bytes) goto out;          /* multiple data chunks! */
210       data_bytes = m;                   /* remember data size */
211     }
212     m += 8;                             /* include chunk header */
213     ptr += m;                           /* skip chunk */
214     n -= m;
215   }
216   sample_point_size = (bits_per_sample + 7) / 8;
217   sample_frame_size = sample_point_size * n_channels;
218   if(!sample_frame_size) goto out;      /* bogus or overflow */
219   n_samples = data_bytes / sample_frame_size;
220   duration = (n_samples + samples_per_second - 1) / samples_per_second;
221 out:
222   munmap(base, length);
223   return duration;
224 }
225
226 static const struct {
227   const char *ext;
228   long (*fn)(const char *path);
229 } file_formats[] = {
230   { ".MP3", tl_mp3 },
231   { ".OGG", tl_ogg },
232   { ".WAV", tl_wav },
233   { ".mp3", tl_mp3 },
234   { ".ogg", tl_ogg },
235   { ".wav", tl_wav }
236 };
237 #define N_FILE_FORMATS (int)(sizeof file_formats / sizeof *file_formats)
238
239 long disorder_tracklength(const char attribute((unused)) *track,
240                           const char *path) {
241   const char *ext = strrchr(path, '.');
242   int l, r, m = 0, c = 0;               /* quieten compiler */
243
244   if(ext) {
245     l = 0;
246     r = N_FILE_FORMATS - 1;
247     while(l <= r && (c = strcmp(ext, file_formats[m = (l + r) / 2].ext)))
248       if(c < 0)
249         r = m - 1;
250       else
251         l = m + 1;
252     if(!c)
253       return file_formats[m].fn(path);
254   }
255   return 0;
256 }
257
258 /*
259 Local Variables:
260 c-basic-offset:2
261 comment-column:40
262 fill-column:79
263 End:
264 */