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