1 /*
2 * Copyright (C) 2008-2013 Various Authors
3 * Copyright (C) 2011 Gregory Petrosyan
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,
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 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19 #include "../ip.h"
20 #include "../debug.h"
21 #include "../input.h"
22 #include "../utils.h"
23 #include "../comment.h"
24 #include "../xmalloc.h"
25 #include "../cue_utils.h"
26 #include "../cue.h"
27
28 #include <stdio.h>
29 #include <fcntl.h>
30 #include <math.h>
31
32 struct cue_private {
33 struct input_plugin *child;
34
35 char *cue_filename;
36 int track_n;
37
38 double start_offset;
39 double current_offset;
40 double end_offset;
41 };
42
43
_parse_cue_url(const char * url,char ** filename,int * track_n)44 static int _parse_cue_url(const char *url, char **filename, int *track_n)
45 {
46 const char *slash;
47 long n;
48
49 if (!is_cue_url(url))
50 return 1;
51
52 url += 6;
53
54 slash = strrchr(url, '/');
55 if (!slash)
56 return 1;
57
58 if (str_to_int(slash + 1, &n) != 0)
59 return 1;
60
61 *filename = xstrndup(url, slash - url);
62 *track_n = n;
63 return 0;
64 }
65
66
_make_absolute_path(const char * abs_filename,const char * rel_filename)67 static char *_make_absolute_path(const char *abs_filename, const char *rel_filename)
68 {
69 char *s;
70 const char *slash;
71 char buf[4096] = {0};
72
73 slash = strrchr(abs_filename, '/');
74 if (slash == NULL)
75 return xstrdup(rel_filename);
76
77 s = xstrndup(abs_filename, slash - abs_filename);
78 snprintf(buf, sizeof buf, "%s/%s", s, rel_filename);
79
80 free(s);
81 return xstrdup(buf);
82 }
83
84
cue_open(struct input_plugin_data * ip_data)85 static int cue_open(struct input_plugin_data *ip_data)
86 {
87 int rc;
88 char *child_filename;
89 struct cue_sheet *cd;
90 struct cue_track *t;
91 struct cue_private *priv;
92
93 priv = xnew(struct cue_private, 1);
94
95 rc = _parse_cue_url(ip_data->filename, &priv->cue_filename, &priv->track_n);
96 if (rc) {
97 rc = -IP_ERROR_INVALID_URI;
98 goto url_parse_failed;
99 }
100
101 cd = cue_from_file(priv->cue_filename);
102 if (cd == NULL) {
103 rc = -IP_ERROR_FILE_FORMAT;
104 goto cue_parse_failed;
105 }
106
107 t = cue_get_track(cd, priv->track_n);
108 if (!t) {
109 rc = -IP_ERROR_FILE_FORMAT;
110 goto cue_read_failed;
111 }
112
113 child_filename = _make_absolute_path(priv->cue_filename, cd->file);
114 priv->child = ip_new(child_filename);
115 free(child_filename);
116
117 rc = ip_open(priv->child);
118 if (rc)
119 goto ip_open_failed;
120
121 ip_setup(priv->child);
122
123 priv->start_offset = t->offset;
124 priv->current_offset = t->offset;
125
126 rc = ip_seek(priv->child, priv->start_offset);
127 if (rc)
128 goto ip_open_failed;
129
130 if (t->length >= 0)
131 priv->end_offset = priv->start_offset + t->length;
132 else
133 priv->end_offset = ip_duration(priv->child);
134
135 ip_data->fd = open(ip_get_filename(priv->child), O_RDONLY);
136 if (ip_data->fd == -1)
137 goto ip_open_failed;
138
139 ip_data->private = priv;
140 ip_data->sf = ip_get_sf(priv->child);
141 ip_get_channel_map(priv->child, ip_data->channel_map);
142
143 cue_free(cd);
144 return 0;
145
146 ip_open_failed:
147 ip_delete(priv->child);
148
149 cue_read_failed:
150 cue_free(cd);
151
152 cue_parse_failed:
153 free(priv->cue_filename);
154
155 url_parse_failed:
156 free(priv);
157
158 return rc;
159 }
160
161
cue_close(struct input_plugin_data * ip_data)162 static int cue_close(struct input_plugin_data *ip_data)
163 {
164 struct cue_private *priv = ip_data->private;
165
166 close(ip_data->fd);
167 ip_data->fd = -1;
168
169 ip_delete(priv->child);
170 free(priv->cue_filename);
171
172 free(priv);
173 ip_data->private = NULL;
174
175 return 0;
176 }
177
178
cue_read(struct input_plugin_data * ip_data,char * buffer,int count)179 static int cue_read(struct input_plugin_data *ip_data, char *buffer, int count)
180 {
181 int rc;
182 sample_format_t sf;
183 double len;
184 double rem_len;
185 struct cue_private *priv = ip_data->private;
186
187 if (priv->current_offset >= priv->end_offset)
188 return 0;
189
190 rc = ip_read(priv->child, buffer, count);
191 if (rc <= 0)
192 return rc;
193
194 sf = ip_get_sf(priv->child);
195 len = (double)rc / sf_get_second_size(sf);
196
197 rem_len = priv->end_offset - priv->current_offset;
198 priv->current_offset += len;
199
200 if (priv->current_offset >= priv->end_offset)
201 rc = lround(rem_len * sf_get_rate(sf)) * sf_get_frame_size(sf);
202
203 return rc;
204 }
205
206
cue_seek(struct input_plugin_data * ip_data,double offset)207 static int cue_seek(struct input_plugin_data *ip_data, double offset)
208 {
209 struct cue_private *priv = ip_data->private;
210 double new_offset = priv->start_offset + offset;
211
212 if (new_offset > priv->end_offset)
213 new_offset = priv->end_offset;
214
215 priv->current_offset = new_offset;
216
217 return ip_seek(priv->child, new_offset);
218 }
219
220
cue_read_comments(struct input_plugin_data * ip_data,struct keyval ** comments)221 static int cue_read_comments(struct input_plugin_data *ip_data, struct keyval **comments)
222 {
223 struct cue_private *priv = ip_data->private;
224 struct cue_sheet *cd = cue_from_file(priv->cue_filename);
225 struct cue_track *t;
226 int rc;
227 char buf[32] = { 0 };
228 GROWING_KEYVALS(c);
229
230 if (cd == NULL) {
231 rc = -IP_ERROR_FILE_FORMAT;
232 goto cue_parse_failed;
233 }
234
235 t = cue_get_track(cd, priv->track_n);
236 if (!t) {
237 rc = -IP_ERROR_FILE_FORMAT;
238 goto get_track_failed;
239 }
240
241 snprintf(buf, sizeof buf, "%d", priv->track_n);
242 comments_add_const(&c, "tracknumber", buf);
243
244 if (t->meta.title)
245 comments_add_const(&c, "title", t->meta.title);
246 if (cd->meta.title)
247 comments_add_const(&c, "album", cd->meta.title);
248 if (t->meta.performer)
249 comments_add_const(&c, "artist", t->meta.performer);
250 if (cd->meta.performer)
251 comments_add_const(&c, "albumartist", cd->meta.performer);
252 if (t->meta.date)
253 comments_add_const(&c, "date", t->meta.date);
254 else if (cd->meta.date)
255 comments_add_const(&c, "date", cd->meta.date);
256 if (cd->meta.compilation)
257 comments_add_const(&c, "compilation", cd->meta.compilation);
258 if (cd->meta.discnumber)
259 comments_add_const(&c, "discnumber", cd->meta.discnumber);
260
261 /*
262 * TODO:
263 * - replaygain REMs
264 * - genre?
265 */
266
267 keyvals_terminate(&c);
268 *comments = c.keyvals;
269
270 cue_free(cd);
271 return 0;
272
273 get_track_failed:
274 cue_free(cd);
275
276 cue_parse_failed:
277 return rc;
278 }
279
280
cue_duration(struct input_plugin_data * ip_data)281 static int cue_duration(struct input_plugin_data *ip_data)
282 {
283 struct cue_private *priv = ip_data->private;
284
285 return priv->end_offset - priv->start_offset;
286 }
287
288
cue_bitrate(struct input_plugin_data * ip_data)289 static long cue_bitrate(struct input_plugin_data *ip_data)
290 {
291 struct cue_private *priv = ip_data->private;
292
293 return ip_bitrate(priv->child);
294 }
295
296
cue_current_bitrate(struct input_plugin_data * ip_data)297 static long cue_current_bitrate(struct input_plugin_data *ip_data)
298 {
299 struct cue_private *priv = ip_data->private;
300
301 return ip_current_bitrate(priv->child);
302 }
303
304
cue_codec(struct input_plugin_data * ip_data)305 static char *cue_codec(struct input_plugin_data *ip_data)
306 {
307 struct cue_private *priv = ip_data->private;
308
309 return ip_codec(priv->child);
310 }
311
312
cue_codec_profile(struct input_plugin_data * ip_data)313 static char *cue_codec_profile(struct input_plugin_data *ip_data)
314 {
315 struct cue_private *priv = ip_data->private;
316
317 return ip_codec_profile(priv->child);
318 }
319
320
321 const struct input_plugin_ops ip_ops = {
322 .open = cue_open,
323 .close = cue_close,
324 .read = cue_read,
325 .seek = cue_seek,
326 .read_comments = cue_read_comments,
327 .duration = cue_duration,
328 .bitrate = cue_bitrate,
329 .bitrate_current = cue_current_bitrate,
330 .codec = cue_codec,
331 .codec_profile = cue_codec_profile,
332 };
333
334 const int ip_priority = 50;
335 const char * const ip_extensions[] = { NULL };
336 const char * const ip_mime_types[] = { "application/x-cue", NULL };
337 const struct input_plugin_opt ip_options[] = { { NULL } };
338 const unsigned ip_abi_version = IP_ABI_VERSION;
339