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