1
/*
2
 * Copyright (C) 2008-2011 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
27
#include <stdio.h>
28
#include <fcntl.h>
29
30
31
struct cue_private {
32
	struct input_plugin *child;
33
34
	char *cue_filename;
35
	int track_n;
36
37
	double start_offset;
38
	double current_offset;
39
	double end_offset;
40
};
41
42
43
static int __parse_cue_url(const char *url, char **filename, int *track_n)
44
{
45
	const char *slash;
46
	long n;
47
48
	if (!is_cue_url(url))
49
		return 1;
50
51
	url += 6;
52
53
	slash = strrchr(url, '/');
54
	if (!slash)
55
		return 1;
56
57
	if (str_to_int(slash + 1, &n) != 0)
58
		return 1;
59
60
	*filename = xstrndup(url, slash - url);
61
	*track_n = n;
62
	return 0;
63
}
64
65
66
static double __to_seconds(long v)
67
{
68
	const int FRAMES_IN_SECOND = 75;
69
70
	return (double)v / FRAMES_IN_SECOND;
71
}
72
73
74
static char *__make_absolute_path(const char *abs_filename, const char *rel_filename)
75
{
76
	char *s;
77
	const char *slash;
78
	char buf[4096] = {0};
79
80
	slash = strrchr(abs_filename, '/');
81
	if (slash == NULL)
82
		return xstrdup(rel_filename);
83
84
	s = xstrndup(abs_filename, slash - abs_filename);
85
	snprintf(buf, sizeof buf, "%s/%s", s, rel_filename);
86
87
	free(s);
88
	return xstrdup(buf);
89
}
90
91
92
static int cue_open(struct input_plugin_data *ip_data)
93
{
94
	int rc;
95
	FILE *cue;
96
	Cd *cd;
97
	Track *t;
98
	char *child_filename;
99
	struct cue_private *priv;
100
101
	priv = xnew(struct cue_private, 1);
102
103
	rc = __parse_cue_url(ip_data->filename, &priv->cue_filename, &priv->track_n);
104
	if (rc) {
105
		rc = -IP_ERROR_INVALID_URI;
106
		goto url_parse_failed;
107
	}
108
109
	cue = fopen(priv->cue_filename, "r");
110
	if (cue == NULL) {
111
		rc = -IP_ERROR_ERRNO;
112
		goto cue_open_failed;
113
	}
114
115
	cd = cue_parse_file__no_stderr_garbage(cue);
116
	if (cd == NULL) {
117
		rc = -IP_ERROR_FILE_FORMAT;
118
		goto cue_parse_failed;
119
	}
120
121
	t = cd_get_track(cd, priv->track_n);
122
	if (t == NULL) {
123
		rc = -IP_ERROR_FILE_FORMAT;
124
		goto cue_read_failed;
125
	}
126
127
	child_filename = track_get_filename(t);
128
	if (child_filename == NULL) {
129
		rc = -IP_ERROR_FILE_FORMAT;
130
		goto cue_read_failed;
131
	}
132
	child_filename = __make_absolute_path(priv->cue_filename, child_filename);
133
134
	priv->child = ip_new(child_filename);
135
	free(child_filename);
136
137
	rc = ip_open(priv->child);
138
	if (rc)
139
		goto ip_open_failed;
140
141
	ip_setup(priv->child);
142
143
	priv->start_offset = __to_seconds(track_get_start(t));
144
	priv->current_offset = priv->start_offset;
145
146
	rc = ip_seek(priv->child, priv->start_offset);
147
	if (rc)
148
		goto ip_open_failed;
149
150
	if (track_get_length(t) != 0)
151
		priv->end_offset = priv->start_offset + __to_seconds(track_get_length(t));
152
	else
153
		priv->end_offset = ip_duration(priv->child);
154
155
	ip_data->fd = open(ip_get_filename(priv->child), O_RDONLY);
156
	if (ip_data->fd == -1)
157
		goto ip_open_failed;
158
159
	ip_data->private = priv;
160
	ip_data->sf = ip_get_sf(priv->child);
161
	ip_get_channel_map(priv->child, ip_data->channel_map);
162
163
	fclose(cue);
164
	cd_delete(cd);
165
	return 0;
166
167
ip_open_failed:
168
	ip_delete(priv->child);
169
170
cue_read_failed:
171
	cd_delete(cd);
172
173
cue_parse_failed:
174
	fclose(cue);
175
176
cue_open_failed:
177
	free(priv->cue_filename);
178
179
url_parse_failed:
180
	free(priv);
181
182
	return rc;
183
}
184
185
186
static int cue_close(struct input_plugin_data *ip_data)
187
{
188
	struct cue_private *priv = ip_data->private;
189
190
	close(ip_data->fd);
191
	ip_data->fd = -1;
192
193
	ip_delete(priv->child);
194
	free(priv->cue_filename);
195
196
	free(priv);
197
	ip_data->private = NULL;
198
199
	return 0;
200
}
201
202
203
static int cue_read(struct input_plugin_data *ip_data, char *buffer, int count)
204
{
205
	int rc;
206
	sample_format_t sf;
207
	double len;
208
	double rem_len;
209
	struct cue_private *priv = ip_data->private;
210
211
	if (priv->current_offset >= priv->end_offset)
212
		return 0;
213
214
	rc = ip_read(priv->child, buffer, count);
215
	if (rc <= 0)
216
		return rc;
217
218
	sf = ip_get_sf(priv->child);
219
	len = (double)rc / sf_get_second_size(sf);
220
221
	rem_len = priv->end_offset - priv->current_offset;
222
	priv->current_offset += len;
223
224
	if (priv->current_offset >= priv->end_offset)
225
		rc = (int)(rem_len * sf_get_rate(sf)) * sf_get_frame_size(sf);
226
227
	return rc;
228
}
229
230
231
static int cue_seek(struct input_plugin_data *ip_data, double offset)
232
{
233
	struct cue_private *priv = ip_data->private;
234
	double new_offset = priv->start_offset + offset;
235
236
	if (new_offset > priv->end_offset)
237
		new_offset = priv->end_offset;
238
239
	priv->current_offset = new_offset;
240
241
	return ip_seek(priv->child, new_offset);
242
}
243
244
245
static int cue_read_comments(struct input_plugin_data *ip_data, struct keyval **comments)
246
{
247
	int rc;
248
	FILE *cue;
249
	Cd *cd;
250
	Rem *cd_rem;
251
	Cdtext *cd_cdtext;
252
	Track *t;
253
	Rem *track_rem;
254
	Cdtext *track_cdtext;
255
	char *val;
256
	char buf[32] = {0};
257
	GROWING_KEYVALS(c);
258
	struct cue_private *priv = ip_data->private;
259
260
	cue = fopen(priv->cue_filename, "r");
261
	if (cue == NULL) {
262
		rc = -IP_ERROR_ERRNO;
263
		goto cue_open_failed;
264
	}
265
266
	cd = cue_parse_file__no_stderr_garbage(cue);
267
	if (cd == NULL) {
268
		rc = -IP_ERROR_FILE_FORMAT;
269
		goto cue_parse_failed;
270
	}
271
272
	t = cd_get_track(cd, priv->track_n);
273
	if (t == NULL) {
274
		rc = -IP_ERROR_FILE_FORMAT;
275
		goto get_track_failed;
276
	}
277
278
	snprintf(buf, sizeof buf, "%d", priv->track_n);
279
	comments_add(&c, "tracknumber", xstrdup(buf));
280
281
	cd_rem = cd_get_rem(cd);
282
	cd_cdtext = cd_get_cdtext(cd);
283
	track_rem = track_get_rem(t);
284
	track_cdtext = track_get_cdtext(t);
285
286
	val = cdtext_get(PTI_TITLE, track_cdtext);
287
	if (val != NULL)
288
		comments_add(&c, "title", xstrdup(val));
289
290
	val = cdtext_get(PTI_TITLE, cd_cdtext);
291
	if (val != NULL)
292
		comments_add(&c, "album", xstrdup(val));
293
294
	val = cdtext_get(PTI_PERFORMER, track_cdtext);
295
	if (val != NULL)
296
		comments_add(&c, "artist", xstrdup(val));
297
298
	val = cdtext_get(PTI_PERFORMER, cd_cdtext);
299
	if (val != NULL)
300
		comments_add(&c, "albumartist", xstrdup(val));
301
302
	val = rem_get(REM_DATE, track_rem);
303
	if (val != NULL) {
304
		comments_add(&c, "date", xstrdup(val));
305
	} else {
306
		val = rem_get(REM_DATE, cd_rem);
307
		if (val != NULL)
308
			comments_add(&c, "date", xstrdup(val));
309
	}
310
311
	/*
312
	 * TODO:
313
	 * - replaygain REMs
314
	 * - genre?
315
	 */
316
317
	keyvals_terminate(&c);
318
	*comments = c.keyvals;
319
320
	cd_delete(cd);
321
	fclose(cue);
322
	return 0;
323
324
get_track_failed:
325
	cd_delete(cd);
326
327
cue_parse_failed:
328
	fclose(cue);
329
330
cue_open_failed:
331
	return rc;
332
}
333
334
335
static int cue_duration(struct input_plugin_data *ip_data)
336
{
337
	struct cue_private *priv = ip_data->private;
338
339
	return priv->end_offset - priv->start_offset;
340
}
341
342
343
static long cue_bitrate(struct input_plugin_data *ip_data)
344
{
345
	struct cue_private *priv = ip_data->private;
346
347
	return ip_bitrate(priv->child);
348
}
349
350
351
static long cue_current_bitrate(struct input_plugin_data *ip_data)
352
{
353
	struct cue_private *priv = ip_data->private;
354
355
	return ip_current_bitrate(priv->child);
356
}
357
358
359
static char *cue_codec(struct input_plugin_data *ip_data)
360
{
361
	struct cue_private *priv = ip_data->private;
362
363
	return ip_codec(priv->child);
364
}
365
366
367
static char *cue_codec_profile(struct input_plugin_data *ip_data)
368
{
369
	struct cue_private *priv = ip_data->private;
370
371
	return ip_codec_profile(priv->child);
372
}
373
374
375
static int cue_set_option(int key, const char *val)
376
{
377
	return -IP_ERROR_NOT_OPTION;
378
}
379
380
381
static int cue_get_option(int key, char **val)
382
{
383
	return -IP_ERROR_NOT_OPTION;
384
}
385
386
387
const struct input_plugin_ops ip_ops = {
388
	.open            = cue_open,
389
	.close           = cue_close,
390
	.read            = cue_read,
391
	.seek            = cue_seek,
392
	.read_comments   = cue_read_comments,
393
	.duration        = cue_duration,
394
	.bitrate         = cue_bitrate,
395
	.bitrate_current = cue_current_bitrate,
396
	.codec           = cue_codec,
397
	.codec_profile   = cue_codec_profile,
398
	.set_option      = cue_set_option,
399
	.get_option      = cue_get_option
400
};
401
402
const int ip_priority = 50;
403
const char * const ip_extensions[] = { NULL };
404
const char * const ip_mime_types[] = { "application/x-cue", NULL };
405
const char * const ip_options[] = { NULL };