Discussion:
[PATCH] add input plugin priorities and better fallback
Johannes Weißl
2011-03-14 05:17:50 UTC
Permalink
cmus decides the input plugin for local files based on their filename
extension (which is fast and usually works). Some input plugins however
can't handle all their file types, and it would be very difficult to
implement that. An example is mp4.c, which can only decode AAC, but not
ALAC (Apple Lossless) files, which have also .m4a extension. FFmpeg
contains a decoder, but mp4.c can't call ffmpeg.c.

This patch implements a fallback system:
If the input plugin fails because it doesn't support an otherwise valid
file, it can return with a new error message (IP_ERROR_UNSUPPORTED_FILE_TYPE).
cmus will then try the next input plugin which supports the given
filename extension. The order is given by new ip_priorities.

The priorities also solve the problem when two input plugins have a
common subset of file extensions (e.g. mikmod<->modplug).

New file types supported:
* ALAC (Apple Lossless), .m4a
* non-PCM .wav files (a-law, μ-law, gsm, ...)
---
aac.c | 1 +
ffmpeg.c | 12 ++++-
flac.c | 1 +
input.c | 125 ++++++++++++++++++++++++++++++++++++++++---------------------
ip.h | 5 ++-
mad.c | 1 +
mikmod.c | 2 +-
modplug.c | 1 +
mp4.c | 6 ++-
mpc.c | 1 +
vorbis.c | 1 +
wav.c | 5 +-
wavpack.c | 1 +
13 files changed, 111 insertions(+), 51 deletions(-)

diff --git a/aac.c b/aac.c
index ec723b8..0ff0b7c 100644
--- a/aac.c
+++ b/aac.c
@@ -428,5 +428,6 @@ const struct input_plugin_ops ip_ops = {
.duration = aac_duration
};

+const int ip_priority = 50;
const char * const ip_extensions[] = { "aac", NULL };
const char * const ip_mime_types[] = { "audio/aac", "audio/aacp", NULL };
diff --git a/ffmpeg.c b/ffmpeg.c
index 28a2796..14196f6 100644
--- a/ffmpeg.c
+++ b/ffmpeg.c
@@ -187,7 +187,7 @@ static int ffmpeg_open(struct input_plugin_data *ip_data)
codec = avcodec_find_decoder(cc->codec_id);
if (!codec) {
d_print("codec not found: %d, %s\n", cc->codec_id, cc->codec_name);
- err = -IP_ERROR_FILE_FORMAT;
+ err = -IP_ERROR_UNSUPPORTED_FILE_TYPE;
break;
}

@@ -196,7 +196,7 @@ static int ffmpeg_open(struct input_plugin_data *ip_data)

if (avcodec_open(cc, codec) < 0) {
d_print("could not open codec: %d, %s\n", cc->codec_id, cc->codec_name);
- err = -IP_ERROR_FILE_FORMAT;
+ err = -IP_ERROR_UNSUPPORTED_FILE_TYPE;
break;
}

@@ -435,9 +435,15 @@ const struct input_plugin_ops ip_ops = {
.duration = ffmpeg_duration
};

+const int ip_priority = 30;
#ifdef USE_FALLBACK_IP
const char *const ip_extensions[] = { "any", NULL };
#else
-const char *const ip_extensions[] = { "ape", "wma", "mka", NULL };
+const char *const ip_extensions[] = { "ape", "wma", "mka",
+ /* also supported by other plugins */
+ "aac", "fla", "flac", "m4a", "m4b", "mp+", "mp2", "mp3", "mp4", "mpc",
+ "mpp", "ogg", "wav", "wv",
+ NULL
+};
#endif
const char *const ip_mime_types[] = { NULL };
diff --git a/flac.c b/flac.c
index 7bdf0ec..6ca55bb 100644
--- a/flac.c
+++ b/flac.c
@@ -522,5 +522,6 @@ const struct input_plugin_ops ip_ops = {
.duration = flac_duration
};

+const int ip_priority = 50;
const char * const ip_extensions[] = { "flac", "fla", NULL };
const char * const ip_mime_types[] = { NULL };
diff --git a/input.c b/input.c
index dcff162..69dbf2a 100644
--- a/input.c
+++ b/input.c
@@ -26,6 +26,7 @@
#include "utils.h"
#include "cmus.h"
#include "list.h"
+#include "mergesort.h"
#include "misc.h"
#include "debug.h"
#include "ui_curses.h"
@@ -75,6 +76,7 @@ struct ip {
char *name;
void *handle;

+ int priority;
const char * const *extensions;
const char * const *mime_types;
const struct input_plugin_ops *ops;
@@ -108,30 +110,22 @@ static const char *get_extension(const char *filename)
return NULL;
}

-static const struct input_plugin_ops *get_ops_by_filename(const char *filename)
+static const struct input_plugin_ops *get_ops_by_extension(const char *ext, struct list_head **headp)
{
- struct ip *ip;
- const char *ext;
- struct ip *fallback_ip = NULL;
+ struct list_head *node = *headp;

- ext = get_extension(filename);
- if (ext == NULL)
- return NULL;
- list_for_each_entry(ip, &ip_head, node) {
+ for (node = node->next; node != &ip_head; node = node->next) {
+ struct ip *ip = list_entry(node, struct ip, node);
const char * const *exts = ip->extensions;
int i;

for (i = 0; exts[i]; i++) {
- if (strcasecmp("any", exts[i]) == 0)
- fallback_ip = ip;
- if (strcasecmp(ext, exts[i]) == 0){
+ if (strcasecmp(ext, exts[i]) == 0 || strcmp("any", exts[i]) == 0) {
+ *headp = node;
return ip->ops;
}
}
}
- if (fallback_ip)
- return fallback_ip->ops;
-
return NULL;
}

@@ -374,20 +368,78 @@ static int open_remote(struct input_plugin *ip)

rc = setup_remote(ip, hg.headers, hg.fd);
http_get_free(&hg);
+ if (rc == 0)
+ rc = ip->ops->open(&ip->data);
return rc;
}

+static void ip_init(struct input_plugin *ip, char *filename)
+{
+ memset(ip, 0, sizeof(*ip));
+ ip->http_code = -1;
+ ip->pcm_convert_scale = -1;
+ ip->duration = -1;
+ ip->data.fd = -1;
+ ip->data.filename = filename;
+ ip->data.remote = is_url(filename);
+}
+
+static void ip_reset(struct input_plugin *ip, int close_fd)
+{
+ int fd = ip->data.fd;
+ free(ip->data.metadata);
+ ip_init(ip, ip->data.filename);
+ if (fd != -1) {
+ if (close_fd)
+ close(fd);
+ else {
+ lseek(fd, 0, SEEK_SET);
+ ip->data.fd = fd;
+ }
+ }
+}
+
static int open_file(struct input_plugin *ip)
{
- ip->ops = get_ops_by_filename(ip->data.filename);
- if (ip->ops == NULL)
+ const struct input_plugin_ops *ops;
+ struct list_head *head = &ip_head;
+ const char *ext;
+ int rc = 0;
+
+ ext = get_extension(ip->data.filename);
+ if (!ext)
+ return -IP_ERROR_UNRECOGNIZED_FILE_TYPE;
+
+ ops = get_ops_by_extension(ext, &head);
+ if (!ops)
return -IP_ERROR_UNRECOGNIZED_FILE_TYPE;
+
ip->data.fd = open(ip->data.filename, O_RDONLY);
- if (ip->data.fd == -1) {
- ip->ops = NULL;
+ if (ip->data.fd == -1)
return -IP_ERROR_ERRNO;
+
+ while (1) {
+ ip->ops = ops;
+ rc = ip->ops->open(&ip->data);
+ if (rc != -IP_ERROR_UNSUPPORTED_FILE_TYPE)
+ break;
+
+ ops = get_ops_by_extension(ext, &head);
+ if (!ops)
+ break;
+
+ ip_reset(ip, 0);
+ d_print("fallback: try next plugin for `%s'\n", ip->data.filename);
}
- return 0;
+
+ return rc;
+}
+
+static int sort_ip(const struct list_head *a_, const struct list_head *b_)
+{
+ const struct ip *a = list_entry(a_, struct ip, node);
+ const struct ip *b = list_entry(b_, struct ip, node);
+ return b->priority - a->priority;
}

void ip_load_plugins(void)
@@ -405,6 +457,7 @@ void ip_load_plugins(void)
struct ip *ip;
void *so;
char *ext;
+ const int *priority_ptr;

if (d->d_name[0] == '.')
continue;
@@ -424,35 +477,27 @@ void ip_load_plugins(void)

ip = xnew(struct ip, 1);

+ priority_ptr = dlsym(so, "ip_priority");
ip->extensions = dlsym(so, "ip_extensions");
ip->mime_types = dlsym(so, "ip_mime_types");
ip->ops = dlsym(so, "ip_ops");
- if (!ip->extensions || !ip->mime_types || !ip->ops) {
+ if (!priority_ptr || !ip->extensions || !ip->mime_types || !ip->ops) {
error_msg("%s: missing symbol", filename);
free(ip);
dlclose(so);
continue;
}
+ ip->priority = *priority_ptr;

ip->name = xstrndup(d->d_name, ext - d->d_name);
ip->handle = so;

list_add_tail(&ip->node, &ip_head);
}
+ list_mergesort(&ip_head, sort_ip);
closedir(dir);
}

-static void ip_init(struct input_plugin *ip, char *filename)
-{
- memset(ip, 0, sizeof(*ip));
- ip->http_code = -1;
- ip->pcm_convert_scale = -1;
- ip->duration = -1;
- ip->data.fd = -1;
- ip->data.filename = filename;
- ip->data.remote = is_url(filename);
-}
-
struct input_plugin *ip_new(const char *filename)
{
struct input_plugin *ip = xnew(struct input_plugin, 1);
@@ -475,7 +520,7 @@ int ip_open(struct input_plugin *ip)

BUG_ON(ip->open);

- /* set fd and ops */
+ /* set fd and ops, call ops->open */
if (ip->data.remote) {
rc = open_remote(ip);
} else {
@@ -485,17 +530,7 @@ int ip_open(struct input_plugin *ip)
if (rc) {
d_print("opening `%s' failed: %d %s\n", ip->data.filename, rc,
rc == -1 ? strerror(errno) : "");
- return rc;
- }
-
- rc = ip->ops->open(&ip->data);
- if (rc) {
- d_print("opening file `%s' failed: %d %s\n", ip->data.filename, rc,
- rc == -1 ? strerror(errno) : "");
- if (ip->data.fd != -1)
- close(ip->data.fd);
- free(ip->data.metadata);
- ip_init(ip, ip->data.filename);
+ ip_reset(ip, 1);
return rc;
}
ip->open = 1;
@@ -711,6 +746,10 @@ char *ip_get_error_msg(struct input_plugin *ip, int rc, const char *arg)
snprintf(buffer, sizeof(buffer),
"%s: unrecognized filename extension", arg);
break;
+ case IP_ERROR_UNSUPPORTED_FILE_TYPE:
+ snprintf(buffer, sizeof(buffer),
+ "%s: unsupported file format", arg);
+ break;
case IP_ERROR_FUNCTION_NOT_SUPPORTED:
snprintf(buffer, sizeof(buffer),
"%s: function not supported", arg);
diff --git a/ip.h b/ip.h
index f44cf29..0919b26 100644
--- a/ip.h
+++ b/ip.h
@@ -33,8 +33,10 @@ enum {
IP_ERROR_SUCCESS,
/* system error (error code in errno) */
IP_ERROR_ERRNO,
- /* file type not supported */
+ /* file type not recognized */
IP_ERROR_UNRECOGNIZED_FILE_TYPE,
+ /* file type recognized, but not supported */
+ IP_ERROR_UNSUPPORTED_FILE_TYPE,
/* function not supported (usually seek) */
IP_ERROR_FUNCTION_NOT_SUPPORTED,
/* input plugin detected corrupted file */
@@ -86,6 +88,7 @@ struct input_plugin_ops {

/* symbols exported by plugin */
extern const struct input_plugin_ops ip_ops;
+extern const int ip_priority;
extern const char * const ip_extensions[];
extern const char * const ip_mime_types[];

diff --git a/mad.c b/mad.c
index 0ae0199..6b3b9c1 100644
--- a/mad.c
+++ b/mad.c
@@ -190,6 +190,7 @@ const struct input_plugin_ops ip_ops = {
.duration = mad_duration
};

+const int ip_priority = 55;
const char * const ip_extensions[] = { "mp3", "mp2", NULL };
const char * const ip_mime_types[] = {
"audio/mpeg", "audio/x-mp3", "audio/x-mpeg", NULL
diff --git a/mikmod.c b/mikmod.c
index 86acaf2..32cd727 100644
--- a/mikmod.c
+++ b/mikmod.c
@@ -151,10 +151,10 @@ const struct input_plugin_ops ip_ops = {
.duration = mik_duration
};

+const int ip_priority = 40;
const char * const ip_extensions[] = {
"mod", "s3m", "xm", "it", "669", "amf", "dsm",
"far", "med", "mtm", "stm", "ult",
NULL
};
-
const char * const ip_mime_types[] = { NULL };
diff --git a/modplug.c b/modplug.c
index b4a5d05..61364e4 100644
--- a/modplug.c
+++ b/modplug.c
@@ -159,6 +159,7 @@ const struct input_plugin_ops ip_ops = {
.duration = mod_duration
};

+const int ip_priority = 50;
const char * const ip_extensions[] = {
"mod", "s3m", "xm", "it", "669", "amf", "ams", "dbm", "dmf", "dsm",
"far", "mdl", "med", "mtm", "okt", "ptm", "stm", "ult", "umx", "mt2",
diff --git a/mp4.c b/mp4.c
index 04814ad..e75d907 100644
--- a/mp4.c
+++ b/mp4.c
@@ -98,6 +98,7 @@ static int mp4_open(struct input_plugin_data *ip_data)
NeAACDecConfigurationPtr neaac_cfg;
unsigned char *buf;
unsigned int buf_size;
+ int rc = -IP_ERROR_FILE_FORMAT;


/* http://sourceforge.net/forum/message.php?msg_id=3578887 */
@@ -127,6 +128,8 @@ static int mp4_open(struct input_plugin_data *ip_data)
priv->mp4.track = mp4_get_track(priv->mp4.handle);
if (priv->mp4.track == MP4_INVALID_TRACK_ID) {
d_print("MP4FindTrackId failed\n");
+ if (MP4GetNumberOfTracks(priv->mp4.handle, MP4_AUDIO_TRACK_TYPE, 0) > 0)
+ rc = -IP_ERROR_UNSUPPORTED_FILE_TYPE;
goto out;
}

@@ -167,7 +170,7 @@ out:
if (priv->decoder)
NeAACDecClose(priv->decoder);
free(priv);
- return -IP_ERROR_FILE_FORMAT;
+ return rc;
}

static int mp4_close(struct input_plugin_data *ip_data)
@@ -468,5 +471,6 @@ const struct input_plugin_ops ip_ops = {
.duration = mp4_duration
};

+const int ip_priority = 50;
const char * const ip_extensions[] = { "mp4", "m4a", "m4b", NULL };
const char * const ip_mime_types[] = { /*"audio/mp4", "audio/mp4a-latm",*/ NULL };
diff --git a/mpc.c b/mpc.c
index 0eec85c..50d7e5d 100644
--- a/mpc.c
+++ b/mpc.c
@@ -365,5 +365,6 @@ const struct input_plugin_ops ip_ops = {
.duration = mpc_duration
};

+const int ip_priority = 50;
const char *const ip_extensions[] = { "mpc", "mpp", "mp+", NULL };
const char *const ip_mime_types[] = { "audio/x-musepack", NULL };
diff --git a/vorbis.c b/vorbis.c
index 8932986..584d636 100644
--- a/vorbis.c
+++ b/vorbis.c
@@ -288,5 +288,6 @@ const struct input_plugin_ops ip_ops = {
.duration = vorbis_duration
};

+const int ip_priority = 50;
const char * const ip_extensions[] = { "ogg", NULL };
const char * const ip_mime_types[] = { "application/ogg", "audio/x-ogg", NULL };
diff --git a/wav.c b/wav.c
index 9a7ce0c..a2f6ef1 100644
--- a/wav.c
+++ b/wav.c
@@ -177,8 +177,8 @@ static int wav_open(struct input_plugin_data *ip_data)
free(fmt);

if (format_tag != WAVE_FORMAT_PCM) {
- d_print("invalid format tag %u, should be 1\n", format_tag);
- rc = -IP_ERROR_FILE_FORMAT;
+ d_print("unsupported format tag %u, should be 1\n", format_tag);
+ rc = -IP_ERROR_UNSUPPORTED_FILE_TYPE;
goto error_exit;
}
if ((bits != 8 && bits != 16 && bits != 24 && bits != 32) || channels < 1 || channels > 2) {
@@ -381,5 +381,6 @@ const struct input_plugin_ops ip_ops = {
.duration = wav_duration
};

+const int ip_priority = 50;
const char * const ip_extensions[] = { "wav", NULL };
const char * const ip_mime_types[] = { NULL };
diff --git a/wavpack.c b/wavpack.c
index b1fb8c7..d9d666f 100644
--- a/wavpack.c
+++ b/wavpack.c
@@ -318,5 +318,6 @@ const struct input_plugin_ops ip_ops = {
.duration = wavpack_duration
};

+const int ip_priority = 50;
const char * const ip_extensions[] = { "wv", NULL };
const char * const ip_mime_types[] = { "audio/x-wavpack", NULL };
--
1.7.4.1
Johannes Weißl
2011-03-17 00:18:07 UTC
Permalink
---
input.c | 2 +-
1 files changed, 1 insertions(+), 1 deletions(-)

diff --git a/input.c b/input.c
index 69dbf2a..a5183ab 100644
--- a/input.c
+++ b/input.c
@@ -824,7 +824,7 @@ void ip_dump_plugins(void)

printf("Input Plugins: %s\n", plugin_dir);
list_for_each_entry(ip, &ip_head, node) {
- printf(" %s:\n File Types:", ip->name);
+ printf(" %s:\n Priority: %d\n File Types:", ip->name, ip->priority);
for (i = 0; ip->extensions[i]; i++)
printf(" %s", ip->extensions[i]);
printf("\n MIME Types:");
--
1.7.4.1
Johannes Weißl
2011-03-17 20:27:20 UTC
Permalink
* True Audio (TTA): .tta
* Shorten (SHN): .shn
* AC3: .ac3
* AIFF: .aif, .aifc, .aiff
* Sun Au: .au
---
ffmpeg.c | 3 ++-
1 files changed, 2 insertions(+), 1 deletions(-)

diff --git a/ffmpeg.c b/ffmpeg.c
index a41357c..d5951ec 100644
--- a/ffmpeg.c
+++ b/ffmpeg.c
@@ -438,7 +438,8 @@ const int ip_priority = 30;
#ifdef USE_FALLBACK_IP
const char *const ip_extensions[] = { "any", NULL };
#else
-const char *const ip_extensions[] = { "ape", "wma", "mka",
+const char *const ip_extensions[] = {
+ "ac3", "aif", "aifc", "aiff", "ape", "au", "mka", "shn", "tta", "wma",
/* also supported by other plugins */
"aac", "fla", "flac", "m4a", "m4b", "mp+", "mp2", "mp3", "mp4", "mpc",
"mpp", "ogg", "wav", "wv",
--
1.7.4.1
Johannes Weißl
2011-03-17 20:32:19 UTC
Permalink
Attention: this information can't be (reliably) used as quality measure,
because different codecs / bitrate profiles (cbr, vbr) / encoder
software / encoder settings have great influence on the quality.

For lossless (compressed) formats like flac or wavpack, the bitrate can
be used as measure for the compression level, not audio quality (for
same source wav files).

For some formats (especially pure aac and mp3), the bitrate information
can be wrong, because it is either guessed or taken from the header.

New sort key: bitrate
New format option: %{bitrate}

Bump cache version to 3.
---
aac.c | 22 +++++++++++++++++++++-
cache.c | 6 +++++-
cmus.c | 1 +
ffmpeg.c | 10 +++++++++-
flac.c | 18 +++++++++++++++---
input.c | 12 ++++++++++++
input.h | 1 +
ip.h | 1 +
mad.c | 10 +++++++++-
mikmod.c | 8 +++++++-
modplug.c | 8 +++++++-
mp4.c | 10 +++++++++-
mpc.c | 13 ++++++++++++-
nomad.c | 47 ++++++++++++++++++++++++++++++++++++++++-------
nomad.h | 1 +
options.c | 1 +
track_info.c | 3 +++
track_info.h | 2 ++
ui_curses.c | 3 +++
vorbis.c | 12 +++++++++++-
wav.c | 9 ++++++++-
wavpack.c | 12 +++++++++++-
22 files changed, 189 insertions(+), 21 deletions(-)

diff --git a/aac.c b/aac.c
index 0ff0b7c..ce74ad4 100644
--- a/aac.c
+++ b/aac.c
@@ -42,6 +42,7 @@ struct aac_private {

unsigned char channels;
unsigned long sample_rate;
+ long bitrate;

char *overflow_buf;
int overflow_buf_len;
@@ -193,6 +194,7 @@ static int aac_open(struct input_plugin_data *ip_data)
/* init private struct */
priv = xnew0(struct aac_private, 1);
priv->decoder = NeAACDecOpen();
+ priv->bitrate = -1;
ip_data->private = priv;

/* set decoder config */
@@ -391,6 +393,14 @@ static int aac_duration(struct input_plugin_data *ip_data)
if (file_size == -1)
return -IP_ERROR_FUNCTION_NOT_SUPPORTED;

+ /* Seek to the middle of the file. There is almost always silence at
+ * the beginning, which gives wrong results. */
+ if (lseek(ip_data->fd, file_size/2, SEEK_SET) == -1)
+ return -IP_ERROR_FUNCTION_NOT_SUPPORTED;
+
+ priv->rbuf_pos = 0;
+ priv->rbuf_len = 0;
+
/* guess track length by decoding the first 10 frames */
while (frames < 10) {
if (buffer_fill_frame(ip_data) <= 0)
@@ -416,16 +426,26 @@ static int aac_duration(struct input_plugin_data *ip_data)
samples /= priv->channels;
bytes /= frames;

+ /* 8 * file_size / duration */
+ priv->bitrate = (8 * bytes * priv->sample_rate) / samples;
+
return ((file_size / bytes) * samples) / priv->sample_rate;
}

+static long aac_bitrate(struct input_plugin_data *ip_data)
+{
+ struct aac_private *priv = ip_data->private;
+ return priv->bitrate != -1 ? priv->bitrate : -IP_ERROR_FUNCTION_NOT_SUPPORTED;
+}
+
const struct input_plugin_ops ip_ops = {
.open = aac_open,
.close = aac_close,
.read = aac_read,
.seek = aac_seek,
.read_comments = aac_read_comments,
- .duration = aac_duration
+ .duration = aac_duration,
+ .bitrate = aac_bitrate
};

const int ip_priority = 50;
diff --git a/cache.c b/cache.c
index 46752f5..804681f 100644
--- a/cache.c
+++ b/cache.c
@@ -29,6 +29,7 @@ struct cache_entry {
// NOTE: size does not include padding bytes
unsigned int size;
int duration;
+ long bitrate;
time_t mtime;

// filename and N * (key, val)
@@ -100,6 +101,7 @@ static struct track_info *cache_entry_to_ti(struct cache_entry *e)
int pos, i, count;

ti->duration = e->duration;
+ ti->bitrate = e->bitrate;
ti->mtime = e->mtime;

// count strings (filename + key/val pairs)
@@ -237,7 +239,7 @@ int cache_init(void)
cache_header[4] = flags & 0xff;

/* assumed version */
- cache_header[3] = 0x02;
+ cache_header[3] = 0x03;

cache_filename = xstrjoin(cmus_config_dir, "/cache");
return read_cache();
@@ -289,6 +291,7 @@ static void write_ti(int fd, struct gbuf *buf, struct track_info *ti, unsigned i
count = 0;
e.size = sizeof(e);
e.duration = ti->duration;
+ e.bitrate = ti->bitrate;
e.mtime = ti->mtime;
len[count] = strlen(ti->filename) + 1;
e.size += len[count++];
@@ -367,6 +370,7 @@ static struct track_info *ip_get_ti(const char *filename)
ti = track_info_new(filename);
track_info_set_comments(ti, comments);
ti->duration = ip_duration(ip);
+ ti->bitrate = ip_bitrate(ip);
ti->mtime = ip_is_remote(ip) ? -1 : file_get_mtime(filename);
}
ip_delete(ip);
diff --git a/cmus.c b/cmus.c
index c925761..a131983 100644
--- a/cmus.c
+++ b/cmus.c
@@ -159,6 +159,7 @@ static int save_ext_playlist_cb(void *data, struct track_info *ti)

gbuf_addf(&buf, "file %s\n", escape(ti->filename));
gbuf_addf(&buf, "duration %d\n", ti->duration);
+ gbuf_addf(&buf, "bitrate %ld\n", ti->bitrate);
for (i = 0; ti->comments[i].key; i++)
gbuf_addf(&buf, "tag %s %s\n",
ti->comments[i].key,
diff --git a/ffmpeg.c b/ffmpeg.c
index d5951ec..2e637d0 100644
--- a/ffmpeg.c
+++ b/ffmpeg.c
@@ -425,13 +425,21 @@ static int ffmpeg_duration(struct input_plugin_data *ip_data)
return priv->input_context->duration / AV_TIME_BASE;
}

+static long ffmpeg_bitrate(struct input_plugin_data *ip_data)
+{
+ struct ffmpeg_private *priv = ip_data->private;
+ long bitrate = priv->input_context->bit_rate;
+ return bitrate ? bitrate : -IP_ERROR_FUNCTION_NOT_SUPPORTED;
+}
+
const struct input_plugin_ops ip_ops = {
.open = ffmpeg_open,
.close = ffmpeg_close,
.read = ffmpeg_read,
.seek = ffmpeg_seek,
.read_comments = ffmpeg_read_comments,
- .duration = ffmpeg_duration
+ .duration = ffmpeg_duration,
+ .bitrate = ffmpeg_bitrate
};

const int ip_priority = 30;
diff --git a/flac.c b/flac.c
index 6ca55bb..a25a6a2 100644
--- a/flac.c
+++ b/flac.c
@@ -56,7 +56,7 @@ struct flac_private {
unsigned int buf_rpos;

struct keyval *comments;
- int duration;
+ double duration;

unsigned int ignore_next_write : 1;
};
@@ -300,7 +300,7 @@ static void metadata_cb(const Dec *dec, const FLAC__StreamMetadata *metadata, vo
sf_signed(1) |
sf_channels(si->channels);
if (!ip_data->remote && si->total_samples)
- priv->duration = si->total_samples / si->sample_rate;
+ priv->duration = (double) si->total_samples / si->sample_rate;
}
break;
case FLAC__METADATA_TYPE_VORBIS_COMMENT:
@@ -513,13 +513,25 @@ static int flac_duration(struct input_plugin_data *ip_data)
return priv->duration;
}

+static long flac_bitrate(struct input_plugin_data *ip_data)
+{
+ struct flac_private *priv = ip_data->private;
+ off_t file_size = lseek(ip_data->fd, 0, SEEK_END);
+ if (file_size == -1)
+ return -IP_ERROR_FUNCTION_NOT_SUPPORTED;
+ if (priv->duration < 1)
+ return -1;
+ return file_size * 8 / priv->duration;
+}
+
const struct input_plugin_ops ip_ops = {
.open = flac_open,
.close = flac_close,
.read = flac_read,
.seek = flac_seek,
.read_comments = flac_read_comments,
- .duration = flac_duration
+ .duration = flac_duration,
+ .bitrate = flac_bitrate
};

const int ip_priority = 50;
diff --git a/input.c b/input.c
index a5183ab..9341a31 100644
--- a/input.c
+++ b/input.c
@@ -56,6 +56,8 @@ struct input_plugin {

/* cached duration, -1 = unset */
int duration;
+ /* cached bitrate, -1 = unset */
+ long bitrate;

/*
* pcm is converted to 16-bit signed little-endian stereo
@@ -379,6 +381,7 @@ static void ip_init(struct input_plugin *ip, char *filename)
ip->http_code = -1;
ip->pcm_convert_scale = -1;
ip->duration = -1;
+ ip->bitrate = -1;
ip->data.fd = -1;
ip->data.filename = filename;
ip->data.remote = is_url(filename);
@@ -697,6 +700,15 @@ int ip_duration(struct input_plugin *ip)
return ip->duration;
}

+int ip_bitrate(struct input_plugin *ip)
+{
+ if (ip->bitrate == -1)
+ ip->bitrate = ip->ops->bitrate(&ip->data);
+ if (ip->bitrate < 0)
+ return -1;
+ return ip->bitrate;
+}
+
sample_format_t ip_get_sf(struct input_plugin *ip)
{
BUG_ON(!ip->open);
diff --git a/input.h b/input.h
index e391f44..8128fe6 100644
--- a/input.h
+++ b/input.h
@@ -51,6 +51,7 @@ int ip_seek(struct input_plugin *ip, double offset);
int ip_read_comments(struct input_plugin *ip, struct keyval **comments);

int ip_duration(struct input_plugin *ip);
+int ip_bitrate(struct input_plugin *ip);

sample_format_t ip_get_sf(struct input_plugin *ip);
const char *ip_get_filename(struct input_plugin *ip);
diff --git a/ip.h b/ip.h
index 0919b26..18f0872 100644
--- a/ip.h
+++ b/ip.h
@@ -84,6 +84,7 @@ struct input_plugin_ops {
int (*read_comments)(struct input_plugin_data *ip_data,
struct keyval **comments);
int (*duration)(struct input_plugin_data *ip_data);
+ long (*bitrate)(struct input_plugin_data *ip_data);
};

/* symbols exported by plugin */
diff --git a/mad.c b/mad.c
index 6b3b9c1..33dc3d1 100644
--- a/mad.c
+++ b/mad.c
@@ -181,13 +181,21 @@ static int mad_duration(struct input_plugin_data *ip_data)
return nomad_time_total(nomad);
}

+static long mad_bitrate(struct input_plugin_data *ip_data)
+{
+ struct nomad *nomad = ip_data->private;
+ long bitrate = nomad_bitrate(nomad);
+ return bitrate != -1 ? bitrate : -IP_ERROR_FUNCTION_NOT_SUPPORTED;
+}
+
const struct input_plugin_ops ip_ops = {
.open = mad_open,
.close = mad_close,
.read = mad_read,
.seek = mad_seek,
.read_comments = mad_read_comments,
- .duration = mad_duration
+ .duration = mad_duration,
+ .bitrate = mad_bitrate
};

const int ip_priority = 55;
diff --git a/mikmod.c b/mikmod.c
index 32cd727..9d43510 100644
--- a/mikmod.c
+++ b/mikmod.c
@@ -142,13 +142,19 @@ static int mik_duration(struct input_plugin_data *ip_data)
return -IP_ERROR_FUNCTION_NOT_SUPPORTED;
}

+static long mik_bitrate(struct input_plugin_data *ip_data)
+{
+ return -IP_ERROR_FUNCTION_NOT_SUPPORTED;
+}
+
const struct input_plugin_ops ip_ops = {
.open = mik_open,
.close = mik_close,
.read = mik_read,
.seek = mik_seek,
.read_comments = mik_read_comments,
- .duration = mik_duration
+ .duration = mik_duration,
+ .bitrate = mik_bitrate
};

const int ip_priority = 40;
diff --git a/modplug.c b/modplug.c
index 61364e4..550ac0e 100644
--- a/modplug.c
+++ b/modplug.c
@@ -150,13 +150,19 @@ static int mod_duration(struct input_plugin_data *ip_data)
return (ModPlug_GetLength(priv->file) + 500) / 1000;
}

+static long mod_bitrate(struct input_plugin_data *ip_data)
+{
+ return -IP_ERROR_FUNCTION_NOT_SUPPORTED;
+}
+
const struct input_plugin_ops ip_ops = {
.open = mod_open,
.close = mod_close,
.read = mod_read,
.seek = mod_seek,
.read_comments = mod_read_comments,
- .duration = mod_duration
+ .duration = mod_duration,
+ .bitrate = mod_bitrate
};

const int ip_priority = 50;
diff --git a/mp4.c b/mp4.c
index e75d907..ca07941 100644
--- a/mp4.c
+++ b/mp4.c
@@ -462,13 +462,21 @@ static int mp4_duration(struct input_plugin_data *ip_data)
return duration / scale;
}

+static long mp4_bitrate(struct input_plugin_data *ip_data)
+{
+ struct mp4_private *priv = ip_data->private;
+ long bitrate = MP4GetTrackBitRate(priv->mp4.handle, priv->mp4.track);
+ return bitrate ? bitrate : -IP_ERROR_FUNCTION_NOT_SUPPORTED;
+}
+
const struct input_plugin_ops ip_ops = {
.open = mp4_open,
.close = mp4_close,
.read = mp4_read,
.seek = mp4_seek,
.read_comments = mp4_read_comments,
- .duration = mp4_duration
+ .duration = mp4_duration,
+ .bitrate = mp4_bitrate
};

const int ip_priority = 50;
diff --git a/mpc.c b/mpc.c
index 50d7e5d..dcdcb98 100644
--- a/mpc.c
+++ b/mpc.c
@@ -356,13 +356,24 @@ static int mpc_duration(struct input_plugin_data *ip_data)
#endif
}

+static long mpc_bitrate(struct input_plugin_data *ip_data)
+{
+ struct mpc_private *priv = ip_data->private;
+ if (priv->info.average_bitrate)
+ return (long) (priv->info.average_bitrate + 0.5);
+ if (priv->info.bitrate)
+ return priv->info.bitrate;
+ return -IP_ERROR_FUNCTION_NOT_SUPPORTED;
+}
+
const struct input_plugin_ops ip_ops = {
.open = mpc_open,
.close = mpc_close,
.read = mpc_read,
.seek = mpc_seek,
.read_comments = mpc_read_comments,
- .duration = mpc_duration
+ .duration = mpc_duration,
+ .bitrate = mpc_bitrate
};

const int ip_priority = 50;
diff --git a/nomad.c b/nomad.c
index bc578b6..03842d7 100644
--- a/nomad.c
+++ b/nomad.c
@@ -67,6 +67,7 @@ struct nomad {
int end_drop_frames;

struct {
+ unsigned int is_info : 1;
unsigned int flags;
unsigned int nr_frames;
unsigned int bytes;
@@ -232,9 +233,16 @@ static int xing_parse(struct nomad *nomad)
if (bitlen < 64)
return -1;
xing_id = mad_bit_read(&ptr, 32);
- if (xing_id != (('X' << 24) | ('i' << 16) | ('n' << 8) | 'g') &&
- xing_id != (('I' << 24) | ('n' << 16) | ('f' << 8) | 'o'))
+ switch (xing_id) {
+ case (('X' << 24) | ('i' << 16) | ('n' << 8) | 'g'):
+ nomad->xing.is_info = 0;
+ break;
+ case (('I' << 24) | ('n' << 16) | ('f' << 8) | 'o'):
+ nomad->xing.is_info = 1;
+ break;
+ default:
return -1;
+ }
nomad->xing.flags = mad_bit_read(&ptr, 32);
bitlen -= 64;
if (nomad->xing.flags & XING_FRAMES) {
@@ -388,10 +396,8 @@ static void build_seek_index(struct nomad *nomad)
nomad->seek_idx.size++;
}

-static void calc_fast(struct nomad *nomad)
+static void calc_frames_fast(struct nomad *nomad)
{
- nomad->info.avg_bitrate = -1;
- nomad->info.vbr = -1;
if (nomad->has_xing && (nomad->xing.flags & XING_FRAMES) && nomad->xing.nr_frames) {
nomad->info.nr_frames = nomad->xing.nr_frames;
mad_timer_multiply(&nomad->timer, nomad->info.nr_frames);
@@ -402,11 +408,30 @@ static void calc_fast(struct nomad *nomad)
}
}

+static void calc_bitrate_fast(struct nomad *nomad)
+{
+ if (nomad->has_xing && (nomad->xing.flags & XING_BYTES) && !nomad->xing.is_info) {
+ nomad->info.avg_bitrate = (nomad->xing.bytes * 8.0) / nomad->info.duration;
+ nomad->info.vbr = 1;
+#if defined(DEBUG_XING)
+ d_print("seems to be VBR, bitrate from Xing Header: %d\n", nomad->info.avg_bitrate);
+#endif
+ } else {
+ nomad->info.avg_bitrate = nomad->frame.header.bitrate;
+ nomad->info.vbr = 0;
+#if defined(DEBUG_XING)
+ if (nomad->xing.is_info)
+ d_print("Xing header == Info, ");
+ d_print("taking bitrate from first frame: %d\n", nomad->info.avg_bitrate);
+#endif
+ }
+}
+
/*
* fields
* nomad->info.avg_bitrate and
* nomad->info.vbr
- * are filled only if fast = 0
+ * are only estimated if fast = 1
*/
static int scan(struct nomad *nomad)
{
@@ -452,7 +477,7 @@ static int scan(struct nomad *nomad)
xing_parse(nomad);

if (nomad->fast) {
- calc_fast(nomad);
+ calc_frames_fast(nomad);
break;
}
} else {
@@ -468,6 +493,9 @@ static int scan(struct nomad *nomad)
nomad->info.duration = timer_to_seconds(nomad->timer);
if (!nomad->fast)
nomad->info.avg_bitrate = bitrate_sum / nomad->info.nr_frames;
+ else {
+ calc_bitrate_fast(nomad);
+ }
nomad->cur_frame = 0;
nomad->cbs.lseek(nomad->datasource, 0, SEEK_SET);
nomad->input_offset = 0;
@@ -860,3 +888,8 @@ double nomad_time_total(struct nomad *nomad)
{
return nomad->info.duration;
}
+
+int nomad_bitrate(struct nomad *nomad)
+{
+ return nomad->info.avg_bitrate;
+}
diff --git a/nomad.h b/nomad.h
index de1227b..5710ad4 100644
--- a/nomad.h
+++ b/nomad.h
@@ -79,5 +79,6 @@ int nomad_time_seek(struct nomad *nomad, double pos);

double nomad_time_tell(struct nomad *nomad);
double nomad_time_total(struct nomad *nomad);
+int nomad_bitrate(struct nomad *nomad);

#endif
diff --git a/options.c b/options.c
index f519142..1c3a2b2 100644
--- a/options.c
+++ b/options.c
@@ -205,6 +205,7 @@ static const struct {
{ "rg_track_peak", SORT_RG_TRACK_PEAK },
{ "rg_album_gain", SORT_RG_ALBUM_GAIN },
{ "rg_album_peak", SORT_RG_ALBUM_PEAK },
+ { "bitrate", SORT_BITRATE },
{ NULL, SORT_INVALID }
};

diff --git a/track_info.c b/track_info.c
index 17ba9d4..5f785fa 100644
--- a/track_info.c
+++ b/track_info.c
@@ -193,6 +193,9 @@ int track_info_cmp(const struct track_info *a, const struct track_info *b, const
case SORT_RG_ALBUM_PEAK:
res = doublecmp0(getentry(a, key, double), getentry(b, key, double));
break;
+ case SORT_BITRATE:
+ res = getentry(a, key, long) - getentry(b, key, long);
+ break;
default:
av = getentry(a, key, const char *);
bv = getentry(b, key, const char *);
diff --git a/track_info.h b/track_info.h
index 814434f..864f206 100644
--- a/track_info.h
+++ b/track_info.h
@@ -31,6 +31,7 @@ struct track_info {

time_t mtime;
int duration;
+ long bitrate;
int ref;
char *filename;

@@ -76,6 +77,7 @@ typedef size_t sort_key_t;
#define SORT_ALBUMARTIST offsetof(struct track_info, collkey_albumartist)
#define SORT_FILENAME offsetof(struct track_info, filename)
#define SORT_FILEMTIME offsetof(struct track_info, mtime)
+#define SORT_BITRATE offsetof(struct track_info, bitrate)
#define SORT_INVALID ((sort_key_t) (-1))

#define TI_MATCH_ARTIST (1 << 0)
diff --git a/ui_curses.c b/ui_curses.c
index 90cb13a..b91ec60 100644
--- a/ui_curses.c
+++ b/ui_curses.c
@@ -216,6 +216,7 @@ enum {
TF_GENRE,
TF_COMMENT,
TF_DURATION,
+ TF_BITRATE,
TF_PATHFILE,
TF_FILE,
TF_RG_TRACK_GAIN,
@@ -236,6 +237,7 @@ static struct format_option track_fopts[NR_TFS + 1] = {
DEF_FO_STR('g', "genre", 0),
DEF_FO_STR('c', "comment", 0),
DEF_FO_TIME('d', "duration", 0),
+ DEF_FO_INT('\0', "bitrate", 0),
DEF_FO_STR('f', "path", 0),
DEF_FO_STR('F', "filename", 0),
DEF_FO_DOUBLE('\0', "rg_track_gain", 0),
@@ -533,6 +535,7 @@ static void fill_track_fopts_track_info(struct track_info *info)
fopt_set_double(&track_fopts[TF_RG_TRACK_PEAK], info->rg_track_peak, isnan(info->rg_track_peak));
fopt_set_double(&track_fopts[TF_RG_ALBUM_GAIN], info->rg_album_gain, isnan(info->rg_album_gain));
fopt_set_double(&track_fopts[TF_RG_ALBUM_PEAK], info->rg_album_peak, isnan(info->rg_album_peak));
+ fopt_set_int(&track_fopts[TF_BITRATE], (int) (info->bitrate / 1000. + 0.5), info->bitrate == -1);
fopt_set_str(&track_fopts[TF_PATHFILE], filename);
if (is_url(info->filename)) {
fopt_set_str(&track_fopts[TF_FILE], filename);
diff --git a/vorbis.c b/vorbis.c
index 584d636..ac9217a 100644
--- a/vorbis.c
+++ b/vorbis.c
@@ -279,13 +279,23 @@ static int vorbis_duration(struct input_plugin_data *ip_data)
return duration;
}

+static long vorbis_bitrate(struct input_plugin_data *ip_data)
+{
+ struct vorbis_private *priv = ip_data->private;
+ long bitrate = ov_bitrate(&priv->vf, -1);
+ if (bitrate == OV_EINVAL || bitrate == OV_FALSE)
+ return -IP_ERROR_FUNCTION_NOT_SUPPORTED;
+ return bitrate;
+}
+
const struct input_plugin_ops ip_ops = {
.open = vorbis_open,
.close = vorbis_close,
.read = vorbis_read,
.seek = vorbis_seek,
.read_comments = vorbis_read_comments,
- .duration = vorbis_duration
+ .duration = vorbis_duration,
+ .bitrate = vorbis_bitrate
};

const int ip_priority = 50;
diff --git a/wav.c b/wav.c
index a2f6ef1..c75cfa8 100644
--- a/wav.c
+++ b/wav.c
@@ -372,13 +372,20 @@ static int wav_duration(struct input_plugin_data *ip_data)
return duration;
}

+static long wav_bitrate(struct input_plugin_data *ip_data)
+{
+ sample_format_t sf = ip_data->sf;
+ return sf_get_bits(sf) * sf_get_rate(sf) * sf_get_channels(sf);
+}
+
const struct input_plugin_ops ip_ops = {
.open = wav_open,
.close = wav_close,
.read = wav_read,
.seek = wav_seek,
.read_comments = wav_read_comments,
- .duration = wav_duration
+ .duration = wav_duration,
+ .bitrate = wav_bitrate
};

const int ip_priority = 50;
diff --git a/wavpack.c b/wavpack.c
index d9d666f..9794cef 100644
--- a/wavpack.c
+++ b/wavpack.c
@@ -309,13 +309,23 @@ static int wavpack_duration(struct input_plugin_data *ip_data)
return duration;
}

+static long wavpack_bitrate(struct input_plugin_data *ip_data)
+{
+ struct wavpack_private *priv = ip_data->private;
+ double bitrate = WavpackGetAverageBitrate(priv->wpc, 1);
+ if (!bitrate)
+ return -IP_ERROR_FUNCTION_NOT_SUPPORTED;
+ return (long) (bitrate + 0.5);
+}
+
const struct input_plugin_ops ip_ops = {
.open = wavpack_open,
.close = wavpack_close,
.read = wavpack_read,
.seek = wavpack_seek,
.read_comments = wavpack_read_comments,
- .duration = wavpack_duration
+ .duration = wavpack_duration,
+ .bitrate = wavpack_bitrate
};

const int ip_priority = 50;
--
1.7.4.1
Johannes Weißl
2011-03-17 20:32:20 UTC
Permalink
This is pretty interesting for
* container formats (.mka, .ogg, .m4a)
* formats with different versions, but same extension (.mpc, .wmv)
* uncompressed formats (.wav, .aiff)

The codec names match the ffmpeg codec names.

New sort key: codec
New format option: %{codec}

Bump cache version to 4.
---
aac.c | 8 +++++++-
cache.c | 12 +++++++++---
cmus.c | 1 +
ffmpeg.c | 11 ++++++++++-
flac.c | 8 +++++++-
input.c | 10 ++++++++++
input.h | 1 +
ip.h | 1 +
mad.c | 17 ++++++++++++++++-
mikmod.c | 8 +++++++-
modplug.c | 8 +++++++-
mp4.c | 8 +++++++-
mpc.c | 15 ++++++++++++++-
nomad.c | 5 +++++
nomad.h | 1 +
options.c | 1 +
track_info.c | 2 ++
track_info.h | 2 ++
ui_curses.c | 3 +++
vorbis.c | 8 +++++++-
wav.c | 15 ++++++++++++++-
wavpack.c | 8 +++++++-
22 files changed, 139 insertions(+), 14 deletions(-)

diff --git a/aac.c b/aac.c
index ce74ad4..ef991c9 100644
--- a/aac.c
+++ b/aac.c
@@ -438,6 +438,11 @@ static long aac_bitrate(struct input_plugin_data *ip_data)
return priv->bitrate != -1 ? priv->bitrate : -IP_ERROR_FUNCTION_NOT_SUPPORTED;
}

+static char *aac_codec(struct input_plugin_data *ip_data)
+{
+ return xstrdup("aac");
+}
+
const struct input_plugin_ops ip_ops = {
.open = aac_open,
.close = aac_close,
@@ -445,7 +450,8 @@ const struct input_plugin_ops ip_ops = {
.seek = aac_seek,
.read_comments = aac_read_comments,
.duration = aac_duration,
- .bitrate = aac_bitrate
+ .bitrate = aac_bitrate,
+ .codec = aac_codec
};

const int ip_priority = 50;
diff --git a/cache.c b/cache.c
index 804681f..13ea3e7 100644
--- a/cache.c
+++ b/cache.c
@@ -32,7 +32,7 @@ struct cache_entry {
long bitrate;
time_t mtime;

- // filename and N * (key, val)
+ // filename, codec and N * (key, val)
char strings[];
};

@@ -110,10 +110,12 @@ static struct track_info *cache_entry_to_ti(struct cache_entry *e)
if (!strings[i])
count++;
}
- count = (count - 1) / 2;
+ count = (count - 2) / 2;

// NOTE: filename already copied by track_info_new()
pos = strlen(strings) + 1;
+ ti->codec = xstrdup(strings + pos);
+ pos = strlen(strings + pos) + 1;
kv = xnew(struct keyval, count + 1);
for (i = 0; i < count; i++) {
int size;
@@ -239,7 +241,7 @@ int cache_init(void)
cache_header[4] = flags & 0xff;

/* assumed version */
- cache_header[3] = 0x03;
+ cache_header[3] = 0x04;

cache_filename = xstrjoin(cmus_config_dir, "/cache");
return read_cache();
@@ -295,6 +297,8 @@ static void write_ti(int fd, struct gbuf *buf, struct track_info *ti, unsigned i
e.mtime = ti->mtime;
len[count] = strlen(ti->filename) + 1;
e.size += len[count++];
+ len[count] = strlen(ti->codec) + 1;
+ e.size += len[count++];
for (i = 0; kv[i].key; i++) {
len[count] = strlen(kv[i].key) + 1;
e.size += len[count++];
@@ -311,6 +315,7 @@ static void write_ti(int fd, struct gbuf *buf, struct track_info *ti, unsigned i
gbuf_set(buf, 0, pad);
gbuf_add_bytes(buf, &e, sizeof(e));
gbuf_add_bytes(buf, ti->filename, len[count++]);
+ gbuf_add_bytes(buf, ti->codec, len[count++]);
for (i = 0; kv[i].key; i++) {
gbuf_add_bytes(buf, kv[i].key, len[count++]);
gbuf_add_bytes(buf, kv[i].val, len[count++]);
@@ -371,6 +376,7 @@ static struct track_info *ip_get_ti(const char *filename)
track_info_set_comments(ti, comments);
ti->duration = ip_duration(ip);
ti->bitrate = ip_bitrate(ip);
+ ti->codec = ip_codec(ip);
ti->mtime = ip_is_remote(ip) ? -1 : file_get_mtime(filename);
}
ip_delete(ip);
diff --git a/cmus.c b/cmus.c
index a131983..6d09165 100644
--- a/cmus.c
+++ b/cmus.c
@@ -159,6 +159,7 @@ static int save_ext_playlist_cb(void *data, struct track_info *ti)

gbuf_addf(&buf, "file %s\n", escape(ti->filename));
gbuf_addf(&buf, "duration %d\n", ti->duration);
+ gbuf_addf(&buf, "codec %s\n", ti->codec);
gbuf_addf(&buf, "bitrate %ld\n", ti->bitrate);
for (i = 0; ti->comments[i].key; i++)
gbuf_addf(&buf, "tag %s %s\n",
diff --git a/ffmpeg.c b/ffmpeg.c
index 2e637d0..6de0a14 100644
--- a/ffmpeg.c
+++ b/ffmpeg.c
@@ -55,6 +55,7 @@ struct ffmpeg_output {
struct ffmpeg_private {
AVCodecContext *codec_context;
AVFormatContext *input_context;
+ AVCodec *codec;
int stream_index;

struct ffmpeg_input *input;
@@ -216,6 +217,7 @@ static int ffmpeg_open(struct input_plugin_data *ip_data)
priv = xnew(struct ffmpeg_private, 1);
priv->codec_context = cc;
priv->input_context = ic;
+ priv->codec = codec;
priv->stream_index = stream_index;
priv->input = ffmpeg_input_create();
if (priv->input == NULL) {
@@ -432,6 +434,12 @@ static long ffmpeg_bitrate(struct input_plugin_data *ip_data)
return bitrate ? bitrate : -IP_ERROR_FUNCTION_NOT_SUPPORTED;
}

+static char *ffmpeg_codec(struct input_plugin_data *ip_data)
+{
+ struct ffmpeg_private *priv = ip_data->private;
+ return xstrdup(priv->codec->name);
+}
+
const struct input_plugin_ops ip_ops = {
.open = ffmpeg_open,
.close = ffmpeg_close,
@@ -439,7 +447,8 @@ const struct input_plugin_ops ip_ops = {
.seek = ffmpeg_seek,
.read_comments = ffmpeg_read_comments,
.duration = ffmpeg_duration,
- .bitrate = ffmpeg_bitrate
+ .bitrate = ffmpeg_bitrate,
+ .codec = ffmpeg_codec
};

const int ip_priority = 30;
diff --git a/flac.c b/flac.c
index a25a6a2..e590052 100644
--- a/flac.c
+++ b/flac.c
@@ -524,6 +524,11 @@ static long flac_bitrate(struct input_plugin_data *ip_data)
return file_size * 8 / priv->duration;
}

+static char *flac_codec(struct input_plugin_data *ip_data)
+{
+ return xstrdup("flac");
+}
+
const struct input_plugin_ops ip_ops = {
.open = flac_open,
.close = flac_close,
@@ -531,7 +536,8 @@ const struct input_plugin_ops ip_ops = {
.seek = flac_seek,
.read_comments = flac_read_comments,
.duration = flac_duration,
- .bitrate = flac_bitrate
+ .bitrate = flac_bitrate,
+ .codec = flac_codec
};

const int ip_priority = 50;
diff --git a/input.c b/input.c
index 9341a31..f1f4739 100644
--- a/input.c
+++ b/input.c
@@ -58,6 +58,8 @@ struct input_plugin {
int duration;
/* cached bitrate, -1 = unset */
long bitrate;
+ /* cached codec, NULL = unset */
+ char *codec;

/*
* pcm is converted to 16-bit signed little-endian stereo
@@ -382,6 +384,7 @@ static void ip_init(struct input_plugin *ip, char *filename)
ip->pcm_convert_scale = -1;
ip->duration = -1;
ip->bitrate = -1;
+ ip->codec = NULL;
ip->data.fd = -1;
ip->data.filename = filename;
ip->data.remote = is_url(filename);
@@ -709,6 +712,13 @@ int ip_bitrate(struct input_plugin *ip)
return ip->bitrate;
}

+char *ip_codec(struct input_plugin *ip)
+{
+ if (!ip->codec)
+ ip->codec = ip->ops->codec(&ip->data);
+ return ip->codec;
+}
+
sample_format_t ip_get_sf(struct input_plugin *ip)
{
BUG_ON(!ip->open);
diff --git a/input.h b/input.h
index 8128fe6..1f08f84 100644
--- a/input.h
+++ b/input.h
@@ -52,6 +52,7 @@ int ip_read_comments(struct input_plugin *ip, struct keyval **comments);

int ip_duration(struct input_plugin *ip);
int ip_bitrate(struct input_plugin *ip);
+char *ip_codec(struct input_plugin *ip);

sample_format_t ip_get_sf(struct input_plugin *ip);
const char *ip_get_filename(struct input_plugin *ip);
diff --git a/ip.h b/ip.h
index 18f0872..acaa8b9 100644
--- a/ip.h
+++ b/ip.h
@@ -85,6 +85,7 @@ struct input_plugin_ops {
struct keyval **comments);
int (*duration)(struct input_plugin_data *ip_data);
long (*bitrate)(struct input_plugin_data *ip_data);
+ char *(*codec)(struct input_plugin_data *ip_data);
};

/* symbols exported by plugin */
diff --git a/mad.c b/mad.c
index 33dc3d1..cea61f9 100644
--- a/mad.c
+++ b/mad.c
@@ -188,6 +188,20 @@ static long mad_bitrate(struct input_plugin_data *ip_data)
return bitrate != -1 ? bitrate : -IP_ERROR_FUNCTION_NOT_SUPPORTED;
}

+static char *mad_codec(struct input_plugin_data *ip_data)
+{
+ struct nomad *nomad = ip_data->private;
+ switch (nomad_layer(nomad)) {
+ case 3:
+ return xstrdup("mp3");
+ case 2:
+ return xstrdup("mp2");
+ case 1:
+ return xstrdup("mp1");
+ }
+ return NULL;
+}
+
const struct input_plugin_ops ip_ops = {
.open = mad_open,
.close = mad_close,
@@ -195,7 +209,8 @@ const struct input_plugin_ops ip_ops = {
.seek = mad_seek,
.read_comments = mad_read_comments,
.duration = mad_duration,
- .bitrate = mad_bitrate
+ .bitrate = mad_bitrate,
+ .codec = mad_codec
};

const int ip_priority = 55;
diff --git a/mikmod.c b/mikmod.c
index 9d43510..4e956f5 100644
--- a/mikmod.c
+++ b/mikmod.c
@@ -147,6 +147,11 @@ static long mik_bitrate(struct input_plugin_data *ip_data)
return -IP_ERROR_FUNCTION_NOT_SUPPORTED;
}

+static char *mik_codec(struct input_plugin_data *ip_data)
+{
+ return NULL;
+}
+
const struct input_plugin_ops ip_ops = {
.open = mik_open,
.close = mik_close,
@@ -154,7 +159,8 @@ const struct input_plugin_ops ip_ops = {
.seek = mik_seek,
.read_comments = mik_read_comments,
.duration = mik_duration,
- .bitrate = mik_bitrate
+ .bitrate = mik_bitrate,
+ .codec = mik_codec
};

const int ip_priority = 40;
diff --git a/modplug.c b/modplug.c
index 550ac0e..f7266da 100644
--- a/modplug.c
+++ b/modplug.c
@@ -155,6 +155,11 @@ static long mod_bitrate(struct input_plugin_data *ip_data)
return -IP_ERROR_FUNCTION_NOT_SUPPORTED;
}

+static char *mod_codec(struct input_plugin_data *ip_data)
+{
+ return NULL;
+}
+
const struct input_plugin_ops ip_ops = {
.open = mod_open,
.close = mod_close,
@@ -162,7 +167,8 @@ const struct input_plugin_ops ip_ops = {
.seek = mod_seek,
.read_comments = mod_read_comments,
.duration = mod_duration,
- .bitrate = mod_bitrate
+ .bitrate = mod_bitrate,
+ .codec = mod_codec
};

const int ip_priority = 50;
diff --git a/mp4.c b/mp4.c
index ca07941..5149358 100644
--- a/mp4.c
+++ b/mp4.c
@@ -469,6 +469,11 @@ static long mp4_bitrate(struct input_plugin_data *ip_data)
return bitrate ? bitrate : -IP_ERROR_FUNCTION_NOT_SUPPORTED;
}

+static char *mp4_codec(struct input_plugin_data *ip_data)
+{
+ return xstrdup("aac");
+}
+
const struct input_plugin_ops ip_ops = {
.open = mp4_open,
.close = mp4_close,
@@ -476,7 +481,8 @@ const struct input_plugin_ops ip_ops = {
.seek = mp4_seek,
.read_comments = mp4_read_comments,
.duration = mp4_duration,
- .bitrate = mp4_bitrate
+ .bitrate = mp4_bitrate,
+ .codec = mp4_codec
};

const int ip_priority = 50;
diff --git a/mpc.c b/mpc.c
index dcdcb98..05990e4 100644
--- a/mpc.c
+++ b/mpc.c
@@ -366,6 +366,18 @@ static long mpc_bitrate(struct input_plugin_data *ip_data)
return -IP_ERROR_FUNCTION_NOT_SUPPORTED;
}

+static char *mpc_codec(struct input_plugin_data *ip_data)
+{
+ struct mpc_private *priv = ip_data->private;
+ switch (priv->info.stream_version) {
+ case 7:
+ return xstrdup("mpc7");
+ case 8:
+ return xstrdup("mpc8");
+ }
+ return NULL;
+}
+
const struct input_plugin_ops ip_ops = {
.open = mpc_open,
.close = mpc_close,
@@ -373,7 +385,8 @@ const struct input_plugin_ops ip_ops = {
.seek = mpc_seek,
.read_comments = mpc_read_comments,
.duration = mpc_duration,
- .bitrate = mpc_bitrate
+ .bitrate = mpc_bitrate,
+ .codec = mpc_codec
};

const int ip_priority = 50;
diff --git a/nomad.c b/nomad.c
index 03842d7..c776bec 100644
--- a/nomad.c
+++ b/nomad.c
@@ -893,3 +893,8 @@ int nomad_bitrate(struct nomad *nomad)
{
return nomad->info.avg_bitrate;
}
+
+int nomad_layer(struct nomad *nomad)
+{
+ return nomad->info.layer;
+}
diff --git a/nomad.h b/nomad.h
index 5710ad4..605a812 100644
--- a/nomad.h
+++ b/nomad.h
@@ -80,5 +80,6 @@ int nomad_time_seek(struct nomad *nomad, double pos);
double nomad_time_tell(struct nomad *nomad);
double nomad_time_total(struct nomad *nomad);
int nomad_bitrate(struct nomad *nomad);
+int nomad_layer(struct nomad *nomad);

#endif
diff --git a/options.c b/options.c
index 1c3a2b2..c93c677 100644
--- a/options.c
+++ b/options.c
@@ -206,6 +206,7 @@ static const struct {
{ "rg_album_gain", SORT_RG_ALBUM_GAIN },
{ "rg_album_peak", SORT_RG_ALBUM_PEAK },
{ "bitrate", SORT_BITRATE },
+ { "codec", SORT_CODEC },
{ NULL, SORT_INVALID }
};

diff --git a/track_info.c b/track_info.c
index 5f785fa..3eb4a74 100644
--- a/track_info.c
+++ b/track_info.c
@@ -33,6 +33,7 @@ static void track_info_free(struct track_info *ti)
{
keyvals_free(ti->comments);
free(ti->filename);
+ free(ti->codec);
free(ti->collkey_artist);
free(ti->collkey_album);
free(ti->collkey_title);
@@ -49,6 +50,7 @@ struct track_info *track_info_new(const char *filename)
ti->filename = xstrdup(filename);
ti->ref = 1;
ti->comments = NULL;
+ ti->codec = NULL;
return ti;
}

diff --git a/track_info.h b/track_info.h
index 864f206..15a3db7 100644
--- a/track_info.h
+++ b/track_info.h
@@ -32,6 +32,7 @@ struct track_info {
time_t mtime;
int duration;
long bitrate;
+ char *codec;
int ref;
char *filename;

@@ -78,6 +79,7 @@ typedef size_t sort_key_t;
#define SORT_FILENAME offsetof(struct track_info, filename)
#define SORT_FILEMTIME offsetof(struct track_info, mtime)
#define SORT_BITRATE offsetof(struct track_info, bitrate)
+#define SORT_CODEC offsetof(struct track_info, codec)
#define SORT_INVALID ((sort_key_t) (-1))

#define TI_MATCH_ARTIST (1 << 0)
diff --git a/ui_curses.c b/ui_curses.c
index b91ec60..217ed41 100644
--- a/ui_curses.c
+++ b/ui_curses.c
@@ -217,6 +217,7 @@ enum {
TF_COMMENT,
TF_DURATION,
TF_BITRATE,
+ TF_CODEC,
TF_PATHFILE,
TF_FILE,
TF_RG_TRACK_GAIN,
@@ -238,6 +239,7 @@ static struct format_option track_fopts[NR_TFS + 1] = {
DEF_FO_STR('c', "comment", 0),
DEF_FO_TIME('d', "duration", 0),
DEF_FO_INT('\0', "bitrate", 0),
+ DEF_FO_STR('\0', "codec", 0),
DEF_FO_STR('f', "path", 0),
DEF_FO_STR('F', "filename", 0),
DEF_FO_DOUBLE('\0', "rg_track_gain", 0),
@@ -536,6 +538,7 @@ static void fill_track_fopts_track_info(struct track_info *info)
fopt_set_double(&track_fopts[TF_RG_ALBUM_GAIN], info->rg_album_gain, isnan(info->rg_album_gain));
fopt_set_double(&track_fopts[TF_RG_ALBUM_PEAK], info->rg_album_peak, isnan(info->rg_album_peak));
fopt_set_int(&track_fopts[TF_BITRATE], (int) (info->bitrate / 1000. + 0.5), info->bitrate == -1);
+ fopt_set_str(&track_fopts[TF_CODEC], info->codec);
fopt_set_str(&track_fopts[TF_PATHFILE], filename);
if (is_url(info->filename)) {
fopt_set_str(&track_fopts[TF_FILE], filename);
diff --git a/vorbis.c b/vorbis.c
index ac9217a..277acfe 100644
--- a/vorbis.c
+++ b/vorbis.c
@@ -288,6 +288,11 @@ static long vorbis_bitrate(struct input_plugin_data *ip_data)
return bitrate;
}

+static char *vorbis_codec(struct input_plugin_data *ip_data)
+{
+ return xstrdup("vorbis");
+}
+
const struct input_plugin_ops ip_ops = {
.open = vorbis_open,
.close = vorbis_close,
@@ -295,7 +300,8 @@ const struct input_plugin_ops ip_ops = {
.seek = vorbis_seek,
.read_comments = vorbis_read_comments,
.duration = vorbis_duration,
- .bitrate = vorbis_bitrate
+ .bitrate = vorbis_bitrate,
+ .codec = vorbis_codec
};

const int ip_priority = 50;
diff --git a/wav.c b/wav.c
index c75cfa8..b20c9c6 100644
--- a/wav.c
+++ b/wav.c
@@ -23,6 +23,7 @@
#include "debug.h"
#include "utils.h"

+#include <stdio.h>
#include <string.h>
#include <errno.h>

@@ -378,6 +379,17 @@ static long wav_bitrate(struct input_plugin_data *ip_data)
return sf_get_bits(sf) * sf_get_rate(sf) * sf_get_channels(sf);
}

+static char *wav_codec(struct input_plugin_data *ip_data)
+{
+ char buf[16];
+ snprintf(buf, 16, "pcm_%c%u%s",
+ sf_get_signed(ip_data->sf) ? 's' : 'u',
+ sf_get_bits(ip_data->sf),
+ sf_get_bigendian(ip_data->sf) ? "be" : "le");
+
+ return xstrdup(buf);
+}
+
const struct input_plugin_ops ip_ops = {
.open = wav_open,
.close = wav_close,
@@ -385,7 +397,8 @@ const struct input_plugin_ops ip_ops = {
.seek = wav_seek,
.read_comments = wav_read_comments,
.duration = wav_duration,
- .bitrate = wav_bitrate
+ .bitrate = wav_bitrate,
+ .codec = wav_codec
};

const int ip_priority = 50;
diff --git a/wavpack.c b/wavpack.c
index 9794cef..2408cc6 100644
--- a/wavpack.c
+++ b/wavpack.c
@@ -318,6 +318,11 @@ static long wavpack_bitrate(struct input_plugin_data *ip_data)
return (long) (bitrate + 0.5);
}

+static char *wavpack_codec(struct input_plugin_data *ip_data)
+{
+ return xstrdup("wavpack");
+}
+
const struct input_plugin_ops ip_ops = {
.open = wavpack_open,
.close = wavpack_close,
@@ -325,7 +330,8 @@ const struct input_plugin_ops ip_ops = {
.seek = wavpack_seek,
.read_comments = wavpack_read_comments,
.duration = wavpack_duration,
- .bitrate = wavpack_bitrate
+ .bitrate = wavpack_bitrate,
+ .codec = wavpack_codec
};

const int ip_priority = 50;
--
1.7.4.1
Johannes Weißl
2011-03-17 21:45:23 UTC
Permalink
---
cache.c | 2 +-
1 files changed, 1 insertions(+), 1 deletions(-)

diff --git a/cache.c b/cache.c
index 13ea3e7..e9ab5f8 100644
--- a/cache.c
+++ b/cache.c
@@ -85,7 +85,7 @@ static int valid_cache_entry(const struct cache_entry *e, unsigned int avail)
if (!e->strings[i])
count++;
}
- if (count % 2 == 0)
+ if (count % 2 == 1)
return 0;
if (e->strings[str_size - 1])
return 0;
--
1.7.4.1
Johannes Weißl
2011-03-18 02:07:34 UTC
Permalink
---
cache.c | 4 ++--
1 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/cache.c b/cache.c
index e9ab5f8..6b9e6b8 100644
--- a/cache.c
+++ b/cache.c
@@ -104,7 +104,7 @@ static struct track_info *cache_entry_to_ti(struct cache_entry *e)
ti->bitrate = e->bitrate;
ti->mtime = e->mtime;

- // count strings (filename + key/val pairs)
+ // count strings (filename + codec + key/val pairs)
count = 0;
for (i = 0; i < str_size; i++) {
if (!strings[i])
@@ -115,7 +115,7 @@ static struct track_info *cache_entry_to_ti(struct cache_entry *e)
// NOTE: filename already copied by track_info_new()
pos = strlen(strings) + 1;
ti->codec = xstrdup(strings + pos);
- pos = strlen(strings + pos) + 1;
+ pos += strlen(strings + pos) + 1;
kv = xnew(struct keyval, count + 1);
for (i = 0; i < count; i++) {
int size;
--
1.7.4.1
Johannes Weißl
2011-03-24 16:36:07 UTC
Permalink
---
cache.c | 6 +++---
1 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/cache.c b/cache.c
index 6b9e6b8..1408e48 100644
--- a/cache.c
+++ b/cache.c
@@ -114,7 +114,7 @@ static struct track_info *cache_entry_to_ti(struct cache_entry *e)

// NOTE: filename already copied by track_info_new()
pos = strlen(strings) + 1;
- ti->codec = xstrdup(strings + pos);
+ ti->codec = strings[pos] ? xstrdup(strings + pos) : NULL;
pos += strlen(strings + pos) + 1;
kv = xnew(struct keyval, count + 1);
for (i = 0; i < count; i++) {
@@ -297,7 +297,7 @@ static void write_ti(int fd, struct gbuf *buf, struct track_info *ti, unsigned i
e.mtime = ti->mtime;
len[count] = strlen(ti->filename) + 1;
e.size += len[count++];
- len[count] = strlen(ti->codec) + 1;
+ len[count] = (ti->codec ? strlen(ti->codec) : 0) + 1;
e.size += len[count++];
for (i = 0; kv[i].key; i++) {
len[count] = strlen(kv[i].key) + 1;
@@ -315,7 +315,7 @@ static void write_ti(int fd, struct gbuf *buf, struct track_info *ti, unsigned i
gbuf_set(buf, 0, pad);
gbuf_add_bytes(buf, &e, sizeof(e));
gbuf_add_bytes(buf, ti->filename, len[count++]);
- gbuf_add_bytes(buf, ti->codec, len[count++]);
+ gbuf_add_bytes(buf, ti->codec ? ti->codec : "", len[count++]);
for (i = 0; kv[i].key; i++) {
gbuf_add_bytes(buf, kv[i].key, len[count++]);
gbuf_add_bytes(buf, kv[i].val, len[count++]);
--
1.7.4.1
Johannes Weißl
2011-03-24 16:36:08 UTC
Permalink
---
input.c | 2 ++
1 files changed, 2 insertions(+), 0 deletions(-)

diff --git a/input.c b/input.c
index f1f4739..c791ae8 100644
--- a/input.c
+++ b/input.c
@@ -714,6 +714,8 @@ int ip_bitrate(struct input_plugin *ip)

char *ip_codec(struct input_plugin *ip)
{
+ if (ip->data.remote)
+ return NULL;
if (!ip->codec)
ip->codec = ip->ops->codec(&ip->data);
return ip->codec;
--
1.7.4.1
gt
2011-03-18 01:44:50 UTC
Permalink
Post by Johannes Weißl
Attention: this information can't be (reliably) used as quality measure,
because different codecs / bitrate profiles (cbr, vbr) / encoder
software / encoder settings have great influence on the quality.
For lossless (compressed) formats like flac or wavpack, the bitrate can
be used as measure for the compression level, not audio quality (for
same source wav files).
For some formats (especially pure aac and mp3), the bitrate information
can be wrong, because it is either guessed or taken from the header.
New sort key: bitrate
New format option: %{bitrate}
Thanks a lot for this, this was one of the few features missing from
cmus :)
Johannes Weißl
2011-03-18 02:06:44 UTC
Permalink
Post by gt
Post by Johannes Weißl
Attention: this information can't be (reliably) used as quality measure,
because different codecs / bitrate profiles (cbr, vbr) / encoder
software / encoder settings have great influence on the quality.
For lossless (compressed) formats like flac or wavpack, the bitrate can
be used as measure for the compression level, not audio quality (for
same source wav files).
For some formats (especially pure aac and mp3), the bitrate information
can be wrong, because it is either guessed or taken from the header.
New sort key: bitrate
New format option: %{bitrate}
Thanks a lot for this, this was one of the few features missing from
cmus :)
Thanks :-). I pushed a branch "next" with all my patches to:
git clone -b next git://gitorious.org/~jmuc/cmus/jw-cmus.git
so it is easier to test!


Johannes
Johannes Weißl
2011-03-24 16:36:45 UTC
Permalink
---
input.c | 2 ++
1 files changed, 2 insertions(+), 0 deletions(-)

diff --git a/input.c b/input.c
index c791ae8..f8dc709 100644
--- a/input.c
+++ b/input.c
@@ -705,6 +705,8 @@ int ip_duration(struct input_plugin *ip)

int ip_bitrate(struct input_plugin *ip)
{
+ if (ip->data.remote)
+ return -1;
if (ip->bitrate == -1)
ip->bitrate = ip->ops->bitrate(&ip->data);
if (ip->bitrate < 0)
--
1.7.4.1
Johannes Weißl
2011-03-24 16:38:09 UTC
Permalink
---
input.c | 4 ++--
1 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/input.c b/input.c
index f8dc709..9b438b6 100644
--- a/input.c
+++ b/input.c
@@ -372,8 +372,6 @@ static int open_remote(struct input_plugin *ip)

rc = setup_remote(ip, hg.headers, hg.fd);
http_get_free(&hg);
- if (rc == 0)
- rc = ip->ops->open(&ip->data);
return rc;
}

@@ -529,6 +527,8 @@ int ip_open(struct input_plugin *ip)
/* set fd and ops, call ops->open */
if (ip->data.remote) {
rc = open_remote(ip);
+ if (rc == 0)
+ rc = ip->ops->open(&ip->data);
} else {
rc = open_file(ip);
}
--
1.7.4.1
Loading...