/* * Copyright (C) 2008-2013 Various Authors * Copyright (C) 2011 Gregory Petrosyan * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "../ip.h" #include "../debug.h" #include "../input.h" #include "../utils.h" #include "../comment.h" #include "../xmalloc.h" #include "../cue_utils.h" #include "../cue.h" #include #include #include struct cue_private { struct input_plugin *child; char *cue_filename; int track_n; double start_offset; double current_offset; double end_offset; }; static int _parse_cue_url(const char *url, char **filename, int *track_n) { const char *slash; long n; if (!is_cue_url(url)) return 1; url += 6; slash = strrchr(url, '/'); if (!slash) return 1; if (str_to_int(slash + 1, &n) != 0) return 1; *filename = xstrndup(url, slash - url); *track_n = n; return 0; } static char *_make_absolute_path(const char *abs_filename, const char *rel_filename) { char *s; const char *slash; char buf[4096] = {0}; slash = strrchr(abs_filename, '/'); if (slash == NULL) return xstrdup(rel_filename); s = xstrndup(abs_filename, slash - abs_filename); snprintf(buf, sizeof buf, "%s/%s", s, rel_filename); free(s); return xstrdup(buf); } static int cue_open(struct input_plugin_data *ip_data) { int rc; char *child_filename; struct cue_sheet *cd; struct cue_track *t; struct cue_private *priv; priv = xnew(struct cue_private, 1); rc = _parse_cue_url(ip_data->filename, &priv->cue_filename, &priv->track_n); if (rc) { rc = -IP_ERROR_INVALID_URI; goto url_parse_failed; } cd = cue_from_file(priv->cue_filename); if (cd == NULL) { rc = -IP_ERROR_FILE_FORMAT; goto cue_parse_failed; } t = cue_get_track(cd, priv->track_n); if (!t) { rc = -IP_ERROR_FILE_FORMAT; goto cue_read_failed; } child_filename = _make_absolute_path(priv->cue_filename, cd->file); priv->child = ip_new(child_filename); free(child_filename); rc = ip_open(priv->child); if (rc) goto ip_open_failed; ip_setup(priv->child); priv->start_offset = t->offset; priv->current_offset = t->offset; rc = ip_seek(priv->child, priv->start_offset); if (rc) goto ip_open_failed; if (t->length >= 0) priv->end_offset = priv->start_offset + t->length; else priv->end_offset = ip_duration(priv->child); ip_data->fd = open(ip_get_filename(priv->child), O_RDONLY); if (ip_data->fd == -1) goto ip_open_failed; ip_data->private = priv; ip_data->sf = ip_get_sf(priv->child); ip_get_channel_map(priv->child, ip_data->channel_map); cue_free(cd); return 0; ip_open_failed: ip_delete(priv->child); cue_read_failed: cue_free(cd); cue_parse_failed: free(priv->cue_filename); url_parse_failed: free(priv); return rc; } static int cue_close(struct input_plugin_data *ip_data) { struct cue_private *priv = ip_data->private; close(ip_data->fd); ip_data->fd = -1; ip_delete(priv->child); free(priv->cue_filename); free(priv); ip_data->private = NULL; return 0; } static int cue_read(struct input_plugin_data *ip_data, char *buffer, int count) { int rc; sample_format_t sf; double len; double rem_len; struct cue_private *priv = ip_data->private; if (priv->current_offset >= priv->end_offset) return 0; rc = ip_read(priv->child, buffer, count); if (rc <= 0) return rc; sf = ip_get_sf(priv->child); len = (double)rc / sf_get_second_size(sf); rem_len = priv->end_offset - priv->current_offset; priv->current_offset += len; if (priv->current_offset >= priv->end_offset) rc = lround(rem_len * sf_get_rate(sf)) * sf_get_frame_size(sf); return rc; } static int cue_seek(struct input_plugin_data *ip_data, double offset) { struct cue_private *priv = ip_data->private; double new_offset = priv->start_offset + offset; if (new_offset > priv->end_offset) new_offset = priv->end_offset; priv->current_offset = new_offset; return ip_seek(priv->child, new_offset); } static int cue_read_comments(struct input_plugin_data *ip_data, struct keyval **comments) { struct cue_private *priv = ip_data->private; struct cue_sheet *cd = cue_from_file(priv->cue_filename); struct cue_track *t; int rc; char buf[32] = { 0 }; GROWING_KEYVALS(c); if (cd == NULL) { rc = -IP_ERROR_FILE_FORMAT; goto cue_parse_failed; } t = cue_get_track(cd, priv->track_n); if (!t) { rc = -IP_ERROR_FILE_FORMAT; goto get_track_failed; } snprintf(buf, sizeof buf, "%d", priv->track_n); comments_add_const(&c, "tracknumber", buf); if (t->meta.title) comments_add_const(&c, "title", t->meta.title); if (cd->meta.title) comments_add_const(&c, "album", cd->meta.title); if (t->meta.performer) comments_add_const(&c, "artist", t->meta.performer); if (cd->meta.performer) comments_add_const(&c, "albumartist", cd->meta.performer); if (t->meta.date) comments_add_const(&c, "date", t->meta.date); else if (cd->meta.date) comments_add_const(&c, "date", cd->meta.date); if (cd->meta.compilation) comments_add_const(&c, "compilation", cd->meta.compilation); if (cd->meta.discnumber) comments_add_const(&c, "discnumber", cd->meta.discnumber); /* * TODO: * - replaygain REMs * - genre? */ keyvals_terminate(&c); *comments = c.keyvals; cue_free(cd); return 0; get_track_failed: cue_free(cd); cue_parse_failed: return rc; } static int cue_duration(struct input_plugin_data *ip_data) { struct cue_private *priv = ip_data->private; return priv->end_offset - priv->start_offset; } static long cue_bitrate(struct input_plugin_data *ip_data) { struct cue_private *priv = ip_data->private; return ip_bitrate(priv->child); } static long cue_current_bitrate(struct input_plugin_data *ip_data) { struct cue_private *priv = ip_data->private; return ip_current_bitrate(priv->child); } static char *cue_codec(struct input_plugin_data *ip_data) { struct cue_private *priv = ip_data->private; return ip_codec(priv->child); } static char *cue_codec_profile(struct input_plugin_data *ip_data) { struct cue_private *priv = ip_data->private; return ip_codec_profile(priv->child); } const struct input_plugin_ops ip_ops = { .open = cue_open, .close = cue_close, .read = cue_read, .seek = cue_seek, .read_comments = cue_read_comments, .duration = cue_duration, .bitrate = cue_bitrate, .bitrate_current = cue_current_bitrate, .codec = cue_codec, .codec_profile = cue_codec_profile, }; const int ip_priority = 50; const char * const ip_extensions[] = { NULL }; const char * const ip_mime_types[] = { "application/x-cue", NULL }; const struct input_plugin_opt ip_options[] = { { NULL } }; const unsigned ip_abi_version = IP_ABI_VERSION;