1 /*
2  * Copyright 2011-2013 Various Authors
3  * Copyright 2011 Johannes Weißl
4  *
5  * Based on cdda.c from XMMS2.
6  *
7  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License as
9  * published by the Free Software Foundation; either version 2 of the
10  * License, or (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful, but
13  * WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, see <http://www.gnu.org/licenses/>.
19  */
20 
21 #include "../ip.h"
22 #include "../file.h"
23 #include "../xmalloc.h"
24 #include "../debug.h"
25 #include "../utils.h"
26 #include "../options.h"
27 #include "../comment.h"
28 #include "../discid.h"
29 
30 #include <cdio/cdio.h>
31 #include <cdio/logging.h>
32 #if LIBCDIO_VERSION_NUM >= 90
33 #include <cdio/paranoia/cdda.h>
34 #else
35 #include <cdio/cdda.h>
36 #endif
37 
38 #include <stdio.h>
39 #include <stdlib.h>
40 #include <string.h>
41 #include <sys/types.h>
42 #include <sys/stat.h>
43 #include <fcntl.h>
44 
45 #undef HAVE_CDDB
46 
47 #ifdef HAVE_CONFIG
48 #include "../config/cdio.h"
49 #endif
50 
51 #ifdef HAVE_CDDB
52 #include "../http.h"
53 #include "../xstrjoin.h"
54 #include <cddb/cddb.h>
55 #endif
56 
57 #ifdef HAVE_CDDB
58 static char *cddb_url = NULL;
59 #endif
60 
61 static struct {
62 	CdIo_t *cdio;
63 	cdrom_drive_t *drive;
64 	const char *disc_id;
65 	const char *device;
66 } cached;
67 
68 struct cdda_private {
69 	CdIo_t *cdio;
70 	cdrom_drive_t *drive;
71 	char *disc_id;
72 	char *device;
73 	track_t track;
74 	lsn_t first_lsn;
75 	lsn_t last_lsn;
76 	lsn_t current_lsn;
77 	int first_read;
78 
79 	char read_buf[CDIO_CD_FRAMESIZE_RAW];
80 	unsigned long buf_used;
81 };
82 
libcdio_log(cdio_log_level_t level,const char * message)83 static void libcdio_log(cdio_log_level_t level, const char *message)
84 {
85 	const char *level_names[] = { "DEBUG", "INFO", "WARN", "ERROR", "ASSERT" };
86 	int len = strlen(message);
87 	if (len > 0 && message[len-1] == '\n')
88 		len--;
89 	if (len > 0) {
90 		level = clamp(level, 1, N_ELEMENTS(level_names));
91 		d_print("%s: %.*s\n", level_names[level-1], len, message);
92 	}
93 }
94 
libcdio_open(struct input_plugin_data * ip_data)95 static int libcdio_open(struct input_plugin_data *ip_data)
96 {
97 	struct cdda_private *priv, priv_init = {
98 		.first_read = 1,
99 		.buf_used = CDIO_CD_FRAMESIZE_RAW
100 	};
101 	CdIo_t *cdio = NULL;
102 	cdrom_drive_t *drive = NULL;
103 	const char *device = cdda_device;
104 	lsn_t first_lsn;
105 	int track = -1;
106 	char *disc_id = NULL;
107 	char *msg = NULL;
108 	int rc = 0, save = 0;
109 
110 	if (!parse_cdda_url(ip_data->filename, &disc_id, &track, NULL)) {
111 		rc = -IP_ERROR_INVALID_URI;
112 		goto end;
113 	}
114 
115 	if (track == -1) {
116 		d_print("invalid or missing track number, aborting!\n");
117 		rc = -IP_ERROR_INVALID_URI;
118 		goto end;
119 	}
120 
121 	/* In case of cue/toc/nrg, take filename (= disc_id) as device.
122 	 * A real disc_id is base64 encoded and never contains a slash */
123 	if (strchr(disc_id, '/'))
124 		device = disc_id;
125 
126 	ip_data->fd = open(device, O_RDONLY);
127 	if (ip_data->fd == -1) {
128 		save = errno;
129 		d_print("could not open device %s\n", device);
130 		rc = -IP_ERROR_ERRNO;
131 		goto end;
132 	}
133 
134 	if (cached.cdio && strcmp(disc_id, cached.disc_id) == 0 && strcmp(device, cached.device) == 0) {
135 		cdio = cached.cdio;
136 		drive = cached.drive;
137 	} else {
138 		cdio_log_set_handler(libcdio_log);
139 		cdio = cdio_open(device, DRIVER_UNKNOWN);
140 		if (!cdio) {
141 			d_print("failed to open device %s\n", device);
142 			rc = -IP_ERROR_NO_DISC;
143 			goto end;
144 		}
145 		cdio_set_speed(cdio, 1);
146 
147 		drive = cdio_cddap_identify_cdio(cdio, CDDA_MESSAGE_LOGIT, &msg);
148 		if (!drive) {
149 			d_print("failed to identify drive, aborting!\n");
150 			rc = -IP_ERROR_NO_DISC;
151 			goto end;
152 		}
153 		d_print("%s", msg);
154 		cdio_cddap_verbose_set(drive, CDDA_MESSAGE_LOGIT, CDDA_MESSAGE_LOGIT);
155 		drive->b_swap_bytes = 1;
156 
157 		if (cdio_cddap_open(drive)) {
158 			d_print("unable to open disc, aborting!\n");
159 			rc = -IP_ERROR_NO_DISC;
160 			goto end;
161 		}
162 	}
163 
164 	first_lsn = cdio_cddap_track_firstsector(drive, track);
165 	if (first_lsn == -1) {
166 		d_print("no such track: %d, aborting!\n", track);
167 		rc = -IP_ERROR_INVALID_URI;
168 		goto end;
169 	}
170 
171 	priv = xnew(struct cdda_private, 1);
172 	*priv = priv_init;
173 	priv->cdio = cdio;
174 	priv->drive = drive;
175 	priv->disc_id = xstrdup(disc_id);
176 	priv->device = xstrdup(device);
177 	priv->track = track;
178 	priv->first_lsn = first_lsn;
179 	priv->last_lsn = cdio_cddap_track_lastsector(drive, priv->track);
180 	priv->current_lsn = first_lsn;
181 
182 	cached.cdio = priv->cdio;
183 	cached.drive = priv->drive;
184 	cached.disc_id = priv->disc_id;
185 	cached.device = priv->device;
186 
187 	ip_data->private = priv;
188 	ip_data->sf = sf_bits(16) | sf_rate(44100) | sf_channels(2) | sf_signed(1);
189 	ip_data->sf |= sf_host_endian();
190 
191 end:
192 	free(disc_id);
193 
194 	if (rc < 0) {
195 		if (ip_data->fd != -1)
196 			close(ip_data->fd);
197 		ip_data->fd = -1;
198 	}
199 
200 	if (rc == -IP_ERROR_ERRNO)
201 		errno = save;
202 	return rc;
203 }
204 
libcdio_close(struct input_plugin_data * ip_data)205 static int libcdio_close(struct input_plugin_data *ip_data)
206 {
207 	struct cdda_private *priv = ip_data->private;
208 
209 	if (ip_data->fd != -1)
210 		close(ip_data->fd);
211 	ip_data->fd = -1;
212 
213 	if (strcmp(priv->disc_id, cached.disc_id) != 0 || strcmp(priv->device, cached.device) != 0) {
214 		cdio_cddap_close_no_free_cdio(priv->drive);
215 		cdio_destroy(priv->cdio);
216 		free(priv->disc_id);
217 		free(priv->device);
218 	}
219 
220 	free(priv);
221 	ip_data->private = NULL;
222 	return 0;
223 }
224 
libcdio_read(struct input_plugin_data * ip_data,char * buffer,int count)225 static int libcdio_read(struct input_plugin_data *ip_data, char *buffer, int count)
226 {
227 	struct cdda_private *priv = ip_data->private;
228 	int rc = 0;
229 
230 	if (priv->first_read || cdio_get_media_changed(priv->cdio)) {
231 		char *disc_id;
232 		priv->first_read = 0;
233 		if (!get_disc_id(priv->device, &disc_id, NULL))
234 			return -IP_ERROR_NO_DISC;
235 		if (strcmp(disc_id, priv->disc_id) != 0) {
236 			free(disc_id);
237 			return -IP_ERROR_WRONG_DISC;
238 		}
239 		free(disc_id);
240 	}
241 
242 	if (priv->current_lsn >= priv->last_lsn)
243 		return 0;
244 
245 	if (priv->buf_used == CDIO_CD_FRAMESIZE_RAW) {
246 		cdio_cddap_read(priv->drive, priv->read_buf, priv->current_lsn, 1);
247 		priv->current_lsn++;
248 		priv->buf_used = 0;
249 	}
250 
251 	if (count >= CDIO_CD_FRAMESIZE_RAW) {
252 		rc = CDIO_CD_FRAMESIZE_RAW - priv->buf_used;
253 		memcpy(buffer, priv->read_buf + priv->buf_used, rc);
254 	} else {
255 		unsigned long buf_left = CDIO_CD_FRAMESIZE_RAW - priv->buf_used;
256 
257 		if (buf_left < count) {
258 			memcpy(buffer, priv->read_buf + priv->buf_used, buf_left);
259 			rc = buf_left;
260 		} else {
261 			memcpy(buffer, priv->read_buf + priv->buf_used, count);
262 			rc = count;
263 		}
264 	}
265 	priv->buf_used += rc;
266 
267 	return rc;
268 }
269 
libcdio_seek(struct input_plugin_data * ip_data,double offset)270 static int libcdio_seek(struct input_plugin_data *ip_data, double offset)
271 {
272 	struct cdda_private *priv = ip_data->private;
273 	lsn_t new_lsn;
274 	int64_t samples = offset * 44100;
275 
276 	/* Magic number 42... really should think of a better way to do this but
277 	 * it seemed that the lsn is off by about 42 everytime...
278 	 */
279 	new_lsn = samples / 441.0 * CDIO_CD_FRAMES_PER_SEC / 100 + 42;
280 
281 	if ((priv->first_lsn + new_lsn) > priv->last_lsn) {
282 		d_print("trying to seek past the end of track.\n");
283 		return -1;
284 	}
285 
286 	priv->current_lsn = priv->first_lsn + new_lsn;
287 
288 	return 0;
289 }
290 
291 #ifdef HAVE_CDDB
parse_cddb_url(const char * url,struct http_uri * http_uri,int * use_http)292 static int parse_cddb_url(const char *url, struct http_uri *http_uri, int *use_http)
293 {
294 	char *full_url;
295 	int rc;
296 
297 	if (is_http_url(url)) {
298 		*use_http = 1;
299 		full_url = xstrdup(url);
300 	} else {
301 		*use_http = 0;
302 		full_url = xstrjoin("http://", url);
303 	}
304 
305 	rc = http_parse_uri(full_url, http_uri);
306 	free(full_url);
307 	return rc == 0;
308 }
309 
setup_cddb_conn(cddb_conn_t * cddb_conn)310 static void setup_cddb_conn(cddb_conn_t *cddb_conn)
311 {
312 	struct http_uri http_uri, http_proxy_uri;
313 	const char *proxy;
314 	int use_http;
315 
316 	parse_cddb_url(cddb_url, &http_uri, &use_http);
317 
318 	proxy = getenv("http_proxy");
319 	if (proxy && http_parse_uri(proxy, &http_proxy_uri) == 0) {
320 		cddb_http_proxy_enable(cddb_conn);
321 		cddb_set_http_proxy_server_name(cddb_conn, http_proxy_uri.host);
322 		cddb_set_http_proxy_server_port(cddb_conn, http_proxy_uri.port);
323 		if (http_proxy_uri.user)
324 			cddb_set_http_proxy_username(cddb_conn, http_proxy_uri.user);
325 		if (http_proxy_uri.pass)
326 			cddb_set_http_proxy_password(cddb_conn, http_proxy_uri.pass);
327 		http_free_uri(&http_proxy_uri);
328 	} else
329 		cddb_http_proxy_disable(cddb_conn);
330 
331 	if (use_http)
332 		cddb_http_enable(cddb_conn);
333 	else
334 		cddb_http_disable(cddb_conn);
335 
336 	cddb_set_server_name(cddb_conn, http_uri.host);
337 	cddb_set_email_address(cddb_conn, "me@home");
338 	cddb_set_server_port(cddb_conn, http_uri.port);
339 	if (strcmp(http_uri.path, "/") != 0)
340 		cddb_set_http_path_query(cddb_conn, http_uri.path);
341 #ifdef DEBUG_CDDB
342 	cddb_cache_disable(cddb_conn);
343 #endif
344 
345 	http_free_uri(&http_uri);
346 }
347 #endif
348 
349 
350 #define add_comment(c, x)	do { if (x) comments_add_const(c, #x, x); } while (0)
351 
libcdio_read_comments(struct input_plugin_data * ip_data,struct keyval ** comments)352 static int libcdio_read_comments(struct input_plugin_data *ip_data, struct keyval **comments)
353 {
354 	struct cdda_private *priv = ip_data->private;
355 	GROWING_KEYVALS(c);
356 	const char *artist = NULL, *albumartist = NULL, *album = NULL,
357 		*title = NULL, *genre = NULL, *comment = NULL;
358 	const cdtext_t *cdt;
359 #ifdef HAVE_CDDB
360 	bool track_comments_found = false;
361 	cddb_conn_t *cddb_conn = NULL;
362 	cddb_disc_t *cddb_disc = NULL;
363 #endif
364 	char buf[64];
365 
366 #if LIBCDIO_VERSION_NUM >= 90
367 	cdt = cdio_get_cdtext(priv->cdio);
368 	if (cdt) {
369 		artist = cdtext_get(cdt, CDTEXT_FIELD_PERFORMER, priv->track);
370 		title = cdtext_get(cdt, CDTEXT_FIELD_TITLE, priv->track);
371 		genre = cdtext_get(cdt, CDTEXT_FIELD_GENRE, priv->track);
372 		comment = cdtext_get(cdt, CDTEXT_FIELD_MESSAGE, priv->track);
373 
374 #ifdef HAVE_CDDB
375 		if (title)
376 			track_comments_found = true;
377 #endif
378 
379 		album = cdtext_get(cdt, CDTEXT_FIELD_TITLE, 0);
380 		albumartist = cdtext_get(cdt, CDTEXT_FIELD_PERFORMER, 0);
381 		if (!artist)
382 			artist = albumartist;
383 		if (!genre)
384 			genre = cdtext_get(cdt, CDTEXT_FIELD_GENRE, 0);
385 		if (!comment)
386 			comment = cdtext_get(cdt, CDTEXT_FIELD_MESSAGE, 0);
387 	}
388 #else
389 	cdt = cdio_get_cdtext(priv->cdio, priv->track);
390 	if (cdt) {
391 		char * const *field = cdt->field;
392 		artist = field[CDTEXT_PERFORMER];
393 		title = field[CDTEXT_TITLE];
394 		genre = field[CDTEXT_GENRE];
395 		comment = field[CDTEXT_MESSAGE];
396 #ifdef HAVE_CDDB
397 		track_comments_found = true;
398 #endif
399 	}
400 	cdt = cdio_get_cdtext(priv->cdio, 0);
401 	if (cdt) {
402 		char * const *field = cdt->field;
403 		album = field[CDTEXT_TITLE];
404 		albumartist = field[CDTEXT_PERFORMER];
405 		if (!artist)
406 			artist = field[CDTEXT_PERFORMER];
407 		if (!genre)
408 			genre = field[CDTEXT_GENRE];
409 		if (!comment)
410 			comment = field[CDTEXT_MESSAGE];
411 	}
412 #endif
413 
414 #ifdef HAVE_CDDB
415 	if (!track_comments_found && cddb_url && cddb_url[0]) {
416 		cddb_track_t *cddb_track;
417 		track_t i_tracks = cdio_get_num_tracks(priv->cdio);
418 		track_t i_first_track = cdio_get_first_track_num(priv->cdio);
419 		unsigned int year;
420 		int i;
421 
422 		cddb_conn = cddb_new();
423 		if (!cddb_conn)
424 			malloc_fail();
425 
426 		setup_cddb_conn(cddb_conn);
427 
428 		cddb_disc = cddb_disc_new();
429 		if (!cddb_disc)
430 			malloc_fail();
431 		for (i = 0; i < i_tracks; i++) {
432 			cddb_track = cddb_track_new();
433 			if (!cddb_track)
434 				malloc_fail();
435 			cddb_track_set_frame_offset(cddb_track,
436 					cdio_get_track_lba(priv->cdio, i+i_first_track));
437 			cddb_disc_add_track(cddb_disc, cddb_track);
438 		}
439 
440 		cddb_disc_set_length(cddb_disc, cdio_get_track_lba(priv->cdio,
441 					CDIO_CDROM_LEADOUT_TRACK) / CDIO_CD_FRAMES_PER_SEC);
442 		if (cddb_query(cddb_conn, cddb_disc) == 1 && cddb_read(cddb_conn, cddb_disc)) {
443 			albumartist = cddb_disc_get_artist(cddb_disc);
444 			album = cddb_disc_get_title(cddb_disc);
445 			genre = cddb_disc_get_genre(cddb_disc);
446 			year = cddb_disc_get_year(cddb_disc);
447 			if (year) {
448 				sprintf(buf, "%u", year);
449 				comments_add_const(&c, "date", buf);
450 			}
451 			cddb_track = cddb_disc_get_track(cddb_disc, priv->track - 1);
452 			artist = cddb_track_get_artist(cddb_track);
453 			if (!artist)
454 				artist = albumartist;
455 			title = cddb_track_get_title(cddb_track);
456 		}
457 	}
458 #endif
459 
460 	add_comment(&c, artist);
461 	add_comment(&c, albumartist);
462 	add_comment(&c, album);
463 	add_comment(&c, title);
464 	add_comment(&c, genre);
465 	add_comment(&c, comment);
466 
467 	sprintf(buf, "%02d", priv->track);
468 	comments_add_const(&c, "tracknumber", buf);
469 
470 #ifdef HAVE_CDDB
471 	if (cddb_disc)
472 		cddb_disc_destroy(cddb_disc);
473 	if (cddb_conn)
474 		cddb_destroy(cddb_conn);
475 #endif
476 
477 	keyvals_terminate(&c);
478 	*comments = c.keyvals;
479 	return 0;
480 }
481 
libcdio_duration(struct input_plugin_data * ip_data)482 static int libcdio_duration(struct input_plugin_data *ip_data)
483 {
484 	struct cdda_private *priv = ip_data->private;
485 
486 	return (priv->last_lsn - priv->first_lsn) / CDIO_CD_FRAMES_PER_SEC;
487 }
488 
libcdio_bitrate(struct input_plugin_data * ip_data)489 static long libcdio_bitrate(struct input_plugin_data *ip_data)
490 {
491 	return 44100 * 16 * 2;
492 }
493 
libcdio_codec(struct input_plugin_data * ip_data)494 static char *libcdio_codec(struct input_plugin_data *ip_data)
495 {
496 	return xstrdup("cdda");
497 }
498 
libcdio_codec_profile(struct input_plugin_data * ip_data)499 static char *libcdio_codec_profile(struct input_plugin_data *ip_data)
500 {
501 	struct cdda_private *priv = ip_data->private;
502 	discmode_t cd_discmode = cdio_get_discmode(priv->cdio);
503 
504 	return xstrdup(discmode2str[cd_discmode]);
505 }
506 
507 #ifdef HAVE_CDDB
libcdio_set_cddb_url(const char * val)508 static int libcdio_set_cddb_url(const char *val)
509 {
510 	struct http_uri http_uri;
511 	int use_http;
512 	if (!parse_cddb_url(val, &http_uri, &use_http))
513 		return -IP_ERROR_INVALID_URI;
514 	http_free_uri(&http_uri);
515 	free(cddb_url);
516 	cddb_url = xstrdup(val);
517 	return 0;
518 }
519 
libcdio_get_cddb_url(char ** val)520 static int libcdio_get_cddb_url(char **val)
521 {
522 	if (!cddb_url)
523 		cddb_url = xstrdup("freedb.freedb.org:8880");
524 	*val = xstrdup(cddb_url);
525 	return 0;
526 }
527 #endif
528 
529 const struct input_plugin_ops ip_ops = {
530 	.open = libcdio_open,
531 	.close = libcdio_close,
532 	.read = libcdio_read,
533 	.seek = libcdio_seek,
534 	.read_comments = libcdio_read_comments,
535 	.duration = libcdio_duration,
536 	.bitrate = libcdio_bitrate,
537 	.codec = libcdio_codec,
538 	.codec_profile = libcdio_codec_profile,
539 };
540 
541 const struct input_plugin_opt ip_options[] = {
542 #ifdef HAVE_CDDB
543 	{ "cddb_url", libcdio_set_cddb_url, libcdio_get_cddb_url },
544 #endif
545 	{ NULL },
546 };
547 
548 const int ip_priority = 50;
549 const char * const ip_extensions[] = { NULL };
550 const char * const ip_mime_types[] = { "x-content/audio-cdda", NULL };
551 const unsigned ip_abi_version = IP_ABI_VERSION;
552