push
This commit is contained in:
346
ip/cue.c
Normal file
346
ip/cue.c
Normal file
@@ -0,0 +1,346 @@
|
||||
/*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#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 <stdio.h>
|
||||
#include <fcntl.h>
|
||||
#include <math.h>
|
||||
|
||||
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, t->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 = -1;
|
||||
|
||||
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;
|
||||
struct cue_private *priv = ip_data->private;
|
||||
|
||||
if (priv->end_offset >= 0.0 && priv->current_offset >= priv->end_offset)
|
||||
return 0;
|
||||
|
||||
rc = ip_read(priv->child, buffer, count);
|
||||
if (rc <= 0)
|
||||
return rc;
|
||||
|
||||
if (priv->end_offset >= 0.0) {
|
||||
sample_format_t sf = ip_get_sf(priv->child);
|
||||
double len = (double)rc / sf_get_second_size(sf);
|
||||
|
||||
double 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 (priv->end_offset >= 0.0 && 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);
|
||||
if (t->meta.genre)
|
||||
comments_add_const(&c, "genre", t->meta.genre);
|
||||
else if (cd->meta.genre)
|
||||
comments_add_const(&c, "genre", cd->meta.genre);
|
||||
|
||||
if (cd->meta.rg_gain)
|
||||
comments_add_const(&c, "replaygain_album_gain", cd->meta.rg_gain);
|
||||
if (cd->meta.rg_peak)
|
||||
comments_add_const(&c, "replaygain_album_peak", cd->meta.rg_peak);
|
||||
if (t->meta.rg_gain)
|
||||
comments_add_const(&c, "replaygain_track_gain", t->meta.rg_gain);
|
||||
if (t->meta.rg_peak)
|
||||
comments_add_const(&c, "replaygain_track_peak", t->meta.rg_peak);
|
||||
|
||||
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;
|
||||
if (priv->end_offset < 0.0)
|
||||
return ip_duration(priv->child) - priv->start_offset;
|
||||
else
|
||||
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[] = { "cue", 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;
|
||||
Reference in New Issue
Block a user