Commit | Line | Data |
---|---|---|
460b9539 | 1 | /* |
2 | * This file is part of DisOrder. | |
c57f1201 | 3 | * Copyright (C) 2004, 2005, 2007 Richard Kettlewell |
460b9539 | 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> | |
762806f1 RK |
35 | /* libFLAC has had an API change and stupidly taken away the old API */ |
36 | #if HAVE_FLAC_FILE_DECODER_H | |
37 | # include <FLAC/file_decoder.h> | |
38 | #else | |
39 | # include <FLAC/stream_decoder.h> | |
40 | #define FLAC__FileDecoder FLAC__StreamDecoder | |
41 | #define FLAC__FileDecoderState FLAC__StreamDecoderState | |
42 | #endif | |
43 | ||
460b9539 | 44 | |
45 | #include <disorder.h> | |
46 | ||
47 | #include "madshim.h" | |
ce6c36be | 48 | #include "wav.h" |
460b9539 | 49 | |
50 | static void *mmap_file(const char *path, size_t *lengthp) { | |
51 | int fd; | |
52 | void *base; | |
53 | struct stat sb; | |
54 | ||
55 | if((fd = open(path, O_RDONLY)) < 0) { | |
56 | disorder_error(errno, "error opening %s", path); | |
57 | return 0; | |
58 | } | |
59 | if(fstat(fd, &sb) < 0) { | |
60 | disorder_error(errno, "error calling stat on %s", path); | |
61 | goto error; | |
62 | } | |
63 | if(sb.st_size == 0) /* can't map 0-length files */ | |
64 | goto error; | |
65 | if((base = mmap(0, sb.st_size, PROT_READ, | |
66 | MAP_SHARED, fd, 0)) == (void *)-1) { | |
67 | disorder_error(errno, "error calling mmap on %s", path); | |
68 | goto error; | |
69 | } | |
70 | *lengthp = sb.st_size; | |
71 | close(fd); | |
72 | return base; | |
73 | error: | |
74 | close(fd); | |
75 | return 0; | |
76 | } | |
77 | ||
78 | static long tl_mp3(const char *path) { | |
79 | size_t length; | |
80 | void *base; | |
81 | buffer b; | |
82 | ||
83 | if(!(base = mmap_file(path, &length))) return -1; | |
84 | b.duration = mad_timer_zero; | |
85 | scan_mp3(base, length, &b); | |
86 | munmap(base, length); | |
87 | return b.duration.seconds + !!b.duration.fraction; | |
88 | } | |
89 | ||
90 | static long tl_ogg(const char *path) { | |
91 | OggVorbis_File vf; | |
92 | FILE *fp = 0; | |
93 | double length; | |
94 | ||
95 | if(!path) goto error; | |
96 | if(!(fp = fopen(path, "rb"))) goto error; | |
97 | if(ov_open(fp, &vf, 0, 0)) goto error; | |
98 | fp = 0; | |
99 | length = ov_time_total(&vf, -1); | |
100 | ov_clear(&vf); | |
101 | return ceil(length); | |
102 | error: | |
103 | if(fp) fclose(fp); | |
104 | return -1; | |
105 | } | |
106 | ||
107 | static long tl_wav(const char *path) { | |
ce6c36be | 108 | struct wavfile f[1]; |
109 | int err, sample_frame_size; | |
110 | long duration; | |
460b9539 | 111 | |
ce6c36be | 112 | if((err = wav_init(f, path))) { |
113 | disorder_error(err, "error opening %s", path); | |
114 | return -1; | |
460b9539 | 115 | } |
ce6c36be | 116 | sample_frame_size = (f->bits + 7) / 8 * f->channels; |
117 | if(sample_frame_size) { | |
118 | const long long n_samples = f->datasize / sample_frame_size; | |
119 | duration = (n_samples + f->rate - 1) / f->rate; | |
120 | } else | |
121 | duration = -1; | |
122 | wav_destroy(f); | |
460b9539 | 123 | return duration; |
124 | } | |
125 | ||
c57f1201 | 126 | /* libFLAC's "simplified" interface is rather heavyweight... */ |
127 | ||
128 | struct flac_state { | |
129 | long duration; | |
130 | const char *path; | |
131 | }; | |
132 | ||
133 | static void flac_metadata(const FLAC__FileDecoder attribute((unused)) *decoder, | |
134 | const FLAC__StreamMetadata *metadata, | |
135 | void *client_data) { | |
136 | struct flac_state *const state = client_data; | |
137 | const FLAC__StreamMetadata_StreamInfo *const stream_info | |
138 | = &metadata->data.stream_info; | |
139 | ||
140 | if(metadata->type == FLAC__METADATA_TYPE_STREAMINFO) | |
141 | /* FLAC uses 0 to mean unknown and conveniently so do we */ | |
142 | state->duration = (stream_info->total_samples | |
143 | + stream_info->sample_rate - 1) | |
144 | / stream_info->sample_rate; | |
145 | } | |
146 | ||
147 | static void flac_error(const FLAC__FileDecoder attribute((unused)) *decoder, | |
148 | FLAC__StreamDecoderErrorStatus status, | |
149 | void *client_data) { | |
150 | const struct flac_state *const state = client_data; | |
151 | ||
152 | disorder_error(0, "error decoding %s: %s", state->path, | |
153 | FLAC__StreamDecoderErrorStatusString[status]); | |
154 | } | |
155 | ||
156 | static FLAC__StreamDecoderWriteStatus flac_write | |
157 | (const FLAC__FileDecoder attribute((unused)) *decoder, | |
158 | const FLAC__Frame attribute((unused)) *frame, | |
159 | const FLAC__int32 attribute((unused)) *const buffer_[], | |
160 | void attribute((unused)) *client_data) { | |
161 | const struct flac_state *const state = client_data; | |
162 | ||
163 | if(state->duration >= 0) | |
164 | return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT; | |
165 | else | |
166 | return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE; | |
167 | } | |
168 | ||
169 | static long tl_flac(const char *path) { | |
c57f1201 | 170 | struct flac_state state[1]; |
171 | ||
172 | state->duration = -1; /* error */ | |
173 | state->path = path; | |
762806f1 RK |
174 | #if HAVE_FLAC_FILE_DECODER_H |
175 | { | |
176 | FLAC__FileDecoder *fd = 0; | |
177 | FLAC__FileDecoderState fs; | |
178 | ||
179 | if(!(fd = FLAC__file_decoder_new())) { | |
180 | disorder_error(0, "FLAC__file_decoder_new failed"); | |
181 | goto fail; | |
182 | } | |
183 | if(!(FLAC__file_decoder_set_filename(fd, path))) { | |
184 | disorder_error(0, "FLAC__file_set_filename failed"); | |
185 | goto fail; | |
186 | } | |
187 | FLAC__file_decoder_set_metadata_callback(fd, flac_metadata); | |
188 | FLAC__file_decoder_set_error_callback(fd, flac_error); | |
189 | FLAC__file_decoder_set_write_callback(fd, flac_write); | |
190 | FLAC__file_decoder_set_client_data(fd, state); | |
191 | if((fs = FLAC__file_decoder_init(fd))) { | |
192 | disorder_error(0, "FLAC__file_decoder_init: %s", | |
193 | FLAC__FileDecoderStateString[fs]); | |
194 | goto fail; | |
195 | } | |
196 | FLAC__file_decoder_process_until_end_of_metadata(fd); | |
197 | fail: | |
198 | if(fd) | |
199 | FLAC__file_decoder_delete(fd); | |
9f110560 | 200 | } |
762806f1 RK |
201 | #else |
202 | { | |
203 | FLAC__StreamDecoder *sd = 0; | |
204 | FLAC__StreamDecoderInitStatus is; | |
205 | ||
206 | if(!(sd = FLAC__stream_decoder_new())) { | |
207 | disorder_error(0, "FLAC__stream_decoder_new failed"); | |
208 | goto fail; | |
209 | } | |
210 | if((is = FLAC__stream_decoder_init_file(sd, path, flac_write, flac_metadata, | |
211 | flac_error, state))) { | |
212 | disorder_error(0, "FLAC__stream_decoder_init_file %s: %s", | |
213 | path, FLAC__StreamDecoderInitStatusString[is]); | |
214 | goto fail; | |
215 | } | |
216 | FLAC__stream_decoder_process_until_end_of_metadata(sd); | |
c57f1201 | 217 | fail: |
762806f1 RK |
218 | if(sd) |
219 | FLAC__stream_decoder_delete(sd); | |
220 | } | |
221 | #endif | |
c57f1201 | 222 | return state->duration; |
223 | } | |
224 | ||
460b9539 | 225 | static const struct { |
226 | const char *ext; | |
227 | long (*fn)(const char *path); | |
228 | } file_formats[] = { | |
c57f1201 | 229 | { ".FLAC", tl_flac }, |
460b9539 | 230 | { ".MP3", tl_mp3 }, |
231 | { ".OGG", tl_ogg }, | |
232 | { ".WAV", tl_wav }, | |
c57f1201 | 233 | { ".flac", tl_flac }, |
460b9539 | 234 | { ".mp3", tl_mp3 }, |
235 | { ".ogg", tl_ogg }, | |
236 | { ".wav", tl_wav } | |
237 | }; | |
238 | #define N_FILE_FORMATS (int)(sizeof file_formats / sizeof *file_formats) | |
239 | ||
240 | long disorder_tracklength(const char attribute((unused)) *track, | |
241 | const char *path) { | |
242 | const char *ext = strrchr(path, '.'); | |
243 | int l, r, m = 0, c = 0; /* quieten compiler */ | |
244 | ||
245 | if(ext) { | |
246 | l = 0; | |
247 | r = N_FILE_FORMATS - 1; | |
248 | while(l <= r && (c = strcmp(ext, file_formats[m = (l + r) / 2].ext))) | |
249 | if(c < 0) | |
250 | r = m - 1; | |
251 | else | |
252 | l = m + 1; | |
253 | if(!c) | |
254 | return file_formats[m].fn(path); | |
255 | } | |
256 | return 0; | |
257 | } | |
258 | ||
259 | /* | |
260 | Local Variables: | |
261 | c-basic-offset:2 | |
262 | comment-column:40 | |
263 | fill-column:79 | |
264 | End: | |
265 | */ |