Commit | Line | Data |
---|---|---|
460b9539 | 1 | /* |
2 | * This file is part of DisOrder. | |
c57f1201 | 3 | * Copyright (C) 2004, 2005, 2007 Richard Kettlewell |
460b9539 | 4 | * |
e7eb3a27 | 5 | * This program is free software: you can redistribute it and/or modify |
460b9539 | 6 | * it under the terms of the GNU General Public License as published by |
e7eb3a27 | 7 | * the Free Software Foundation, either version 3 of the License, or |
460b9539 | 8 | * (at your option) any later version. |
9 | * | |
e7eb3a27 RK |
10 | * This program is distributed in the hope that it will be useful, |
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
13 | * GNU General Public License for more details. | |
14 | * | |
460b9539 | 15 | * You should have received a copy of the GNU General Public License |
e7eb3a27 | 16 | * along with this program. If not, see <http://www.gnu.org/licenses/>. |
460b9539 | 17 | */ |
132a5a4a RK |
18 | /** @file plugins/tracklength.c |
19 | * @brief Plugin to compute track lengths | |
20 | * | |
21 | * Currently implements MP3, OGG, FLAC and WAV. | |
22 | */ | |
460b9539 | 23 | |
24 | #include <config.h> | |
25 | ||
26 | #include <string.h> | |
27 | #include <stdio.h> | |
28 | #include <math.h> | |
29 | #include <sys/types.h> | |
30 | #include <sys/stat.h> | |
31 | #include <unistd.h> | |
32 | #include <fcntl.h> | |
33 | #include <sys/mman.h> | |
34 | #include <errno.h> | |
35 | ||
36 | #include <vorbis/vorbisfile.h> | |
37 | #include <mad.h> | |
762806f1 RK |
38 | /* libFLAC has had an API change and stupidly taken away the old API */ |
39 | #if HAVE_FLAC_FILE_DECODER_H | |
40 | # include <FLAC/file_decoder.h> | |
41 | #else | |
42 | # include <FLAC/stream_decoder.h> | |
43 | #define FLAC__FileDecoder FLAC__StreamDecoder | |
44 | #define FLAC__FileDecoderState FLAC__StreamDecoderState | |
45 | #endif | |
46 | ||
460b9539 | 47 | |
48 | #include <disorder.h> | |
49 | ||
50 | #include "madshim.h" | |
ce6c36be | 51 | #include "wav.h" |
460b9539 | 52 | |
53 | static void *mmap_file(const char *path, size_t *lengthp) { | |
54 | int fd; | |
55 | void *base; | |
56 | struct stat sb; | |
57 | ||
58 | if((fd = open(path, O_RDONLY)) < 0) { | |
59 | disorder_error(errno, "error opening %s", path); | |
60 | return 0; | |
61 | } | |
62 | if(fstat(fd, &sb) < 0) { | |
63 | disorder_error(errno, "error calling stat on %s", path); | |
64 | goto error; | |
65 | } | |
66 | if(sb.st_size == 0) /* can't map 0-length files */ | |
67 | goto error; | |
68 | if((base = mmap(0, sb.st_size, PROT_READ, | |
69 | MAP_SHARED, fd, 0)) == (void *)-1) { | |
70 | disorder_error(errno, "error calling mmap on %s", path); | |
71 | goto error; | |
72 | } | |
73 | *lengthp = sb.st_size; | |
74 | close(fd); | |
75 | return base; | |
76 | error: | |
77 | close(fd); | |
78 | return 0; | |
79 | } | |
80 | ||
81 | static long tl_mp3(const char *path) { | |
82 | size_t length; | |
83 | void *base; | |
84 | buffer b; | |
85 | ||
86 | if(!(base = mmap_file(path, &length))) return -1; | |
87 | b.duration = mad_timer_zero; | |
88 | scan_mp3(base, length, &b); | |
89 | munmap(base, length); | |
90 | return b.duration.seconds + !!b.duration.fraction; | |
91 | } | |
92 | ||
93 | static long tl_ogg(const char *path) { | |
94 | OggVorbis_File vf; | |
95 | FILE *fp = 0; | |
96 | double length; | |
97 | ||
98 | if(!path) goto error; | |
99 | if(!(fp = fopen(path, "rb"))) goto error; | |
100 | if(ov_open(fp, &vf, 0, 0)) goto error; | |
101 | fp = 0; | |
102 | length = ov_time_total(&vf, -1); | |
103 | ov_clear(&vf); | |
104 | return ceil(length); | |
105 | error: | |
106 | if(fp) fclose(fp); | |
107 | return -1; | |
108 | } | |
109 | ||
110 | static long tl_wav(const char *path) { | |
ce6c36be | 111 | struct wavfile f[1]; |
112 | int err, sample_frame_size; | |
113 | long duration; | |
460b9539 | 114 | |
ce6c36be | 115 | if((err = wav_init(f, path))) { |
116 | disorder_error(err, "error opening %s", path); | |
117 | return -1; | |
460b9539 | 118 | } |
ce6c36be | 119 | sample_frame_size = (f->bits + 7) / 8 * f->channels; |
120 | if(sample_frame_size) { | |
121 | const long long n_samples = f->datasize / sample_frame_size; | |
122 | duration = (n_samples + f->rate - 1) / f->rate; | |
123 | } else | |
124 | duration = -1; | |
125 | wav_destroy(f); | |
460b9539 | 126 | return duration; |
127 | } | |
128 | ||
c57f1201 | 129 | /* libFLAC's "simplified" interface is rather heavyweight... */ |
130 | ||
131 | struct flac_state { | |
132 | long duration; | |
133 | const char *path; | |
134 | }; | |
135 | ||
136 | static void flac_metadata(const FLAC__FileDecoder attribute((unused)) *decoder, | |
137 | const FLAC__StreamMetadata *metadata, | |
138 | void *client_data) { | |
139 | struct flac_state *const state = client_data; | |
140 | const FLAC__StreamMetadata_StreamInfo *const stream_info | |
141 | = &metadata->data.stream_info; | |
142 | ||
143 | if(metadata->type == FLAC__METADATA_TYPE_STREAMINFO) | |
144 | /* FLAC uses 0 to mean unknown and conveniently so do we */ | |
145 | state->duration = (stream_info->total_samples | |
146 | + stream_info->sample_rate - 1) | |
147 | / stream_info->sample_rate; | |
148 | } | |
149 | ||
150 | static void flac_error(const FLAC__FileDecoder attribute((unused)) *decoder, | |
151 | FLAC__StreamDecoderErrorStatus status, | |
152 | void *client_data) { | |
153 | const struct flac_state *const state = client_data; | |
154 | ||
155 | disorder_error(0, "error decoding %s: %s", state->path, | |
156 | FLAC__StreamDecoderErrorStatusString[status]); | |
157 | } | |
158 | ||
159 | static FLAC__StreamDecoderWriteStatus flac_write | |
160 | (const FLAC__FileDecoder attribute((unused)) *decoder, | |
161 | const FLAC__Frame attribute((unused)) *frame, | |
162 | const FLAC__int32 attribute((unused)) *const buffer_[], | |
163 | void attribute((unused)) *client_data) { | |
164 | const struct flac_state *const state = client_data; | |
165 | ||
166 | if(state->duration >= 0) | |
167 | return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT; | |
168 | else | |
169 | return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE; | |
170 | } | |
171 | ||
172 | static long tl_flac(const char *path) { | |
c57f1201 | 173 | struct flac_state state[1]; |
174 | ||
175 | state->duration = -1; /* error */ | |
176 | state->path = path; | |
762806f1 RK |
177 | #if HAVE_FLAC_FILE_DECODER_H |
178 | { | |
179 | FLAC__FileDecoder *fd = 0; | |
180 | FLAC__FileDecoderState fs; | |
181 | ||
182 | if(!(fd = FLAC__file_decoder_new())) { | |
183 | disorder_error(0, "FLAC__file_decoder_new failed"); | |
184 | goto fail; | |
185 | } | |
186 | if(!(FLAC__file_decoder_set_filename(fd, path))) { | |
187 | disorder_error(0, "FLAC__file_set_filename failed"); | |
188 | goto fail; | |
189 | } | |
190 | FLAC__file_decoder_set_metadata_callback(fd, flac_metadata); | |
191 | FLAC__file_decoder_set_error_callback(fd, flac_error); | |
192 | FLAC__file_decoder_set_write_callback(fd, flac_write); | |
193 | FLAC__file_decoder_set_client_data(fd, state); | |
194 | if((fs = FLAC__file_decoder_init(fd))) { | |
195 | disorder_error(0, "FLAC__file_decoder_init: %s", | |
196 | FLAC__FileDecoderStateString[fs]); | |
197 | goto fail; | |
198 | } | |
199 | FLAC__file_decoder_process_until_end_of_metadata(fd); | |
200 | fail: | |
201 | if(fd) | |
202 | FLAC__file_decoder_delete(fd); | |
9f110560 | 203 | } |
762806f1 RK |
204 | #else |
205 | { | |
206 | FLAC__StreamDecoder *sd = 0; | |
207 | FLAC__StreamDecoderInitStatus is; | |
208 | ||
209 | if(!(sd = FLAC__stream_decoder_new())) { | |
210 | disorder_error(0, "FLAC__stream_decoder_new failed"); | |
211 | goto fail; | |
212 | } | |
213 | if((is = FLAC__stream_decoder_init_file(sd, path, flac_write, flac_metadata, | |
214 | flac_error, state))) { | |
215 | disorder_error(0, "FLAC__stream_decoder_init_file %s: %s", | |
216 | path, FLAC__StreamDecoderInitStatusString[is]); | |
217 | goto fail; | |
218 | } | |
219 | FLAC__stream_decoder_process_until_end_of_metadata(sd); | |
c57f1201 | 220 | fail: |
762806f1 RK |
221 | if(sd) |
222 | FLAC__stream_decoder_delete(sd); | |
223 | } | |
224 | #endif | |
c57f1201 | 225 | return state->duration; |
226 | } | |
227 | ||
460b9539 | 228 | static const struct { |
229 | const char *ext; | |
230 | long (*fn)(const char *path); | |
231 | } file_formats[] = { | |
c57f1201 | 232 | { ".FLAC", tl_flac }, |
460b9539 | 233 | { ".MP3", tl_mp3 }, |
234 | { ".OGG", tl_ogg }, | |
235 | { ".WAV", tl_wav }, | |
c57f1201 | 236 | { ".flac", tl_flac }, |
460b9539 | 237 | { ".mp3", tl_mp3 }, |
238 | { ".ogg", tl_ogg }, | |
239 | { ".wav", tl_wav } | |
240 | }; | |
241 | #define N_FILE_FORMATS (int)(sizeof file_formats / sizeof *file_formats) | |
242 | ||
243 | long disorder_tracklength(const char attribute((unused)) *track, | |
244 | const char *path) { | |
245 | const char *ext = strrchr(path, '.'); | |
246 | int l, r, m = 0, c = 0; /* quieten compiler */ | |
247 | ||
248 | if(ext) { | |
249 | l = 0; | |
250 | r = N_FILE_FORMATS - 1; | |
251 | while(l <= r && (c = strcmp(ext, file_formats[m = (l + r) / 2].ext))) | |
252 | if(c < 0) | |
253 | r = m - 1; | |
254 | else | |
255 | l = m + 1; | |
256 | if(!c) | |
257 | return file_formats[m].fn(path); | |
258 | } | |
259 | return 0; | |
260 | } | |
261 | ||
262 | /* | |
263 | Local Variables: | |
264 | c-basic-offset:2 | |
265 | comment-column:40 | |
266 | fill-column:79 | |
267 | End: | |
268 | */ |