push
This commit is contained in:
428
track_info.c
Normal file
428
track_info.c
Normal file
@@ -0,0 +1,428 @@
|
||||
/*
|
||||
* Copyright 2008-2013 Various Authors
|
||||
* Copyright 2004-2005 Timo Hirvonen
|
||||
*
|
||||
* 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 "track_info.h"
|
||||
#include "comment.h"
|
||||
#include "uchar.h"
|
||||
#include "u_collate.h"
|
||||
#include "misc.h"
|
||||
#include "xmalloc.h"
|
||||
#include "utils.h"
|
||||
#include "debug.h"
|
||||
#include "path.h"
|
||||
#include "ui_curses.h"
|
||||
|
||||
#include <string.h>
|
||||
#include <stdatomic.h>
|
||||
#include <math.h>
|
||||
|
||||
struct track_info_priv {
|
||||
struct track_info ti;
|
||||
_Atomic uint32_t ref_count;
|
||||
};
|
||||
|
||||
static struct track_info_priv *track_info_to_priv(struct track_info *ti)
|
||||
{
|
||||
return container_of(ti, struct track_info_priv, ti);
|
||||
}
|
||||
|
||||
struct track_info *track_info_new(const char *filename)
|
||||
{
|
||||
static _Atomic uint64_t cur_uid = ATOMIC_VAR_INIT(1);
|
||||
uint64_t uid = atomic_fetch_add_explicit(&cur_uid, 1, memory_order_relaxed);
|
||||
BUG_ON(uid == 0);
|
||||
|
||||
struct track_info_priv *priv = xnew(struct track_info_priv, 1);
|
||||
atomic_init(&priv->ref_count, 1);
|
||||
|
||||
struct track_info *ti = &priv->ti;
|
||||
ti->uid = uid;
|
||||
ti->filename = xstrdup(filename);
|
||||
ti->play_count = 0;
|
||||
ti->comments = NULL;
|
||||
ti->bpm = -1;
|
||||
ti->codec = NULL;
|
||||
ti->codec_profile = NULL;
|
||||
ti->output_gain = 0;
|
||||
|
||||
return ti;
|
||||
}
|
||||
|
||||
void track_info_set_comments(struct track_info *ti, struct keyval *comments) {
|
||||
long int r128_track_gain;
|
||||
long int r128_album_gain;
|
||||
long int output_gain;
|
||||
|
||||
ti->comments = comments;
|
||||
ti->artist = keyvals_get_val(comments, "artist");
|
||||
ti->album = keyvals_get_val(comments, "album");
|
||||
ti->title = keyvals_get_val(comments, "title");
|
||||
ti->tracknumber = comments_get_int(comments, "tracknumber");
|
||||
ti->discnumber = comments_get_int(comments, "discnumber");
|
||||
ti->totaldiscs = comments_get_int(comments, "totaldiscs");
|
||||
ti->date = comments_get_date(comments, "date");
|
||||
ti->originaldate = comments_get_date(comments, "originaldate");
|
||||
ti->genre = keyvals_get_val(comments, "genre");
|
||||
ti->comment = keyvals_get_val(comments, "comment");
|
||||
ti->albumartist = comments_get_albumartist(comments);
|
||||
ti->artistsort = comments_get_artistsort(comments);
|
||||
ti->albumsort = keyvals_get_val(comments, "albumsort");
|
||||
ti->is_va_compilation = track_is_va_compilation(comments);
|
||||
ti->media = keyvals_get_val(comments, "media");
|
||||
|
||||
const char *bitrate_str = keyvals_get_val(comments, "bitrate");
|
||||
if (bitrate_str) {
|
||||
ti->bitrate = strtol(bitrate_str, NULL, 10) * 1000;
|
||||
}
|
||||
|
||||
int bpm = comments_get_int(comments, "bpm");
|
||||
if (ti->bpm == 0 || ti->bpm == -1) {
|
||||
ti->bpm = bpm;
|
||||
}
|
||||
|
||||
if (ti->artist == NULL && ti->albumartist != NULL) {
|
||||
/* best guess */
|
||||
ti->artist = ti->albumartist;
|
||||
}
|
||||
|
||||
if (track_info_has_tag(ti) && ti->title == NULL) {
|
||||
/* best guess */
|
||||
ti->title = path_basename(ti->filename);
|
||||
}
|
||||
|
||||
ti->rg_track_gain = comments_get_double(comments, "replaygain_track_gain");
|
||||
ti->rg_track_peak = comments_get_double(comments, "replaygain_track_peak");
|
||||
ti->rg_album_gain = comments_get_double(comments, "replaygain_album_gain");
|
||||
ti->rg_album_peak = comments_get_double(comments, "replaygain_album_peak");
|
||||
|
||||
if (comments_get_signed_int(comments, "r128_track_gain", &r128_track_gain) != -1) {
|
||||
double rg = (r128_track_gain / 256.0) + 5;
|
||||
ti->rg_track_gain = round(rg * 100) / 100.0;
|
||||
}
|
||||
|
||||
if (comments_get_signed_int(comments, "r128_album_gain", &r128_album_gain) != -1) {
|
||||
double rg = (r128_album_gain / 256.0) + 5;
|
||||
ti->rg_album_gain = round(rg * 100) / 100.0;
|
||||
}
|
||||
|
||||
if (comments_get_signed_int(comments, "output_gain", &output_gain) != -1) {
|
||||
ti->output_gain = (output_gain / 256.0);
|
||||
}
|
||||
|
||||
ti->collkey_artist = u_strcasecoll_key0(ti->artist);
|
||||
ti->collkey_album = u_strcasecoll_key0(ti->album);
|
||||
ti->collkey_title = u_strcasecoll_key0(ti->title);
|
||||
ti->collkey_genre = u_strcasecoll_key0(ti->genre);
|
||||
ti->collkey_comment = u_strcasecoll_key0(ti->comment);
|
||||
ti->collkey_albumartist = u_strcasecoll_key0(ti->albumartist);
|
||||
}
|
||||
|
||||
void track_info_ref(struct track_info *ti)
|
||||
{
|
||||
struct track_info_priv *priv = track_info_to_priv(ti);
|
||||
atomic_fetch_add_explicit(&priv->ref_count, 1, memory_order_relaxed);
|
||||
}
|
||||
|
||||
void track_info_unref(struct track_info *ti)
|
||||
{
|
||||
struct track_info_priv *priv = track_info_to_priv(ti);
|
||||
uint32_t prev = atomic_fetch_sub_explicit(&priv->ref_count, 1,
|
||||
memory_order_acq_rel);
|
||||
if (prev == 1) {
|
||||
keyvals_free(ti->comments);
|
||||
free(ti->filename);
|
||||
free(ti->codec);
|
||||
free(ti->codec_profile);
|
||||
free(ti->collkey_artist);
|
||||
free(ti->collkey_album);
|
||||
free(ti->collkey_title);
|
||||
free(ti->collkey_genre);
|
||||
free(ti->collkey_comment);
|
||||
free(ti->collkey_albumartist);
|
||||
free(priv);
|
||||
}
|
||||
}
|
||||
|
||||
bool track_info_unique_ref(struct track_info *ti)
|
||||
{
|
||||
struct track_info_priv *priv = track_info_to_priv(ti);
|
||||
return atomic_load_explicit(&priv->ref_count, memory_order_relaxed) == 1;
|
||||
}
|
||||
|
||||
int track_info_has_tag(const struct track_info *ti)
|
||||
{
|
||||
return ti->artist || ti->album || ti->title;
|
||||
}
|
||||
|
||||
static inline int match_word(const struct track_info *ti, const char *word, unsigned int flags)
|
||||
{
|
||||
return ((flags & TI_MATCH_ARTIST) && ti->artist && u_strcasestr_base(ti->artist, word)) ||
|
||||
((flags & TI_MATCH_ALBUM) && ti->album && u_strcasestr_base(ti->album, word)) ||
|
||||
((flags & TI_MATCH_TITLE) && ti->title && u_strcasestr_base(ti->title, word)) ||
|
||||
((flags & TI_MATCH_ALBUMARTIST) && ti->albumartist && u_strcasestr_base(ti->albumartist, word));
|
||||
}
|
||||
|
||||
static inline int flags_set(const struct track_info *ti, unsigned int flags)
|
||||
{
|
||||
return ((flags & TI_MATCH_ARTIST) && ti->artist) ||
|
||||
((flags & TI_MATCH_ALBUM) && ti->album) ||
|
||||
((flags & TI_MATCH_TITLE) && ti->title) ||
|
||||
((flags & TI_MATCH_ALBUMARTIST) && ti->albumartist);
|
||||
}
|
||||
|
||||
int track_info_matches_full(const struct track_info *ti, const char *text,
|
||||
unsigned int flags, unsigned int exclude_flags, int match_all_words)
|
||||
{
|
||||
char **words;
|
||||
int i, matched = 0;
|
||||
|
||||
words = get_words(text);
|
||||
for (i = 0; words[i]; i++) {
|
||||
const char *word = words[i];
|
||||
|
||||
matched = 0;
|
||||
if (flags_set(ti, flags)) {
|
||||
matched = match_word(ti, word, flags);
|
||||
} else {
|
||||
/* compare with url or filename without path */
|
||||
const char *filename = ti->filename;
|
||||
|
||||
if (!is_url(filename))
|
||||
filename = path_basename(filename);
|
||||
|
||||
if (u_strcasestr_filename(filename, word))
|
||||
matched = 1;
|
||||
}
|
||||
|
||||
if (match_word(ti, word, exclude_flags))
|
||||
matched = 0;
|
||||
|
||||
if (match_all_words ? !matched : matched)
|
||||
break;
|
||||
|
||||
}
|
||||
free_str_array(words);
|
||||
return matched;
|
||||
}
|
||||
|
||||
int track_info_matches(const struct track_info *ti, const char *text, unsigned int flags)
|
||||
{
|
||||
return track_info_matches_full(ti, text, flags, 0, 1);
|
||||
}
|
||||
|
||||
static int doublecmp0(double a, double b)
|
||||
{
|
||||
double x;
|
||||
/* fast check for NaN */
|
||||
int r = (b != b) - (a != a);
|
||||
if (r)
|
||||
return r;
|
||||
x = a - b;
|
||||
return (x > 0) - (x < 0);
|
||||
}
|
||||
|
||||
/* this function gets called *a lot*, it must be very fast */
|
||||
int track_info_cmp(const struct track_info *a, const struct track_info *b, const sort_key_t *keys)
|
||||
{
|
||||
int i, rev = 0, res = 0;
|
||||
|
||||
for (i = 0; keys[i] != SORT_INVALID; i++) {
|
||||
sort_key_t key = keys[i];
|
||||
const char *av, *bv;
|
||||
|
||||
rev = 0;
|
||||
if (key >= REV_SORT__START) {
|
||||
rev = 1;
|
||||
key -= REV_SORT__START;
|
||||
}
|
||||
|
||||
switch (key) {
|
||||
case SORT_TRACKNUMBER:
|
||||
case SORT_DISCNUMBER:
|
||||
case SORT_TOTALDISCS:
|
||||
case SORT_DATE:
|
||||
case SORT_ORIGINALDATE:
|
||||
case SORT_PLAY_COUNT:
|
||||
case SORT_BPM:
|
||||
case SORT_DURATION:
|
||||
res = getentry(a, key, int) - getentry(b, key, int);
|
||||
break;
|
||||
case SORT_FILEMTIME:
|
||||
res = a->mtime - b->mtime;
|
||||
break;
|
||||
case SORT_FILENAME:
|
||||
/* NOTE: filenames are not necessarily UTF-8 */
|
||||
res = strcoll(a->filename, b->filename);
|
||||
break;
|
||||
case SORT_RG_TRACK_GAIN:
|
||||
case SORT_RG_TRACK_PEAK:
|
||||
case SORT_RG_ALBUM_GAIN:
|
||||
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 *);
|
||||
res = strcmp0(av, bv);
|
||||
break;
|
||||
}
|
||||
|
||||
if (res)
|
||||
break;
|
||||
}
|
||||
return rev ? -res : res;
|
||||
}
|
||||
|
||||
static const struct {
|
||||
const char *str;
|
||||
sort_key_t key;
|
||||
} sort_key_map[] = {
|
||||
{ "artist", SORT_ARTIST },
|
||||
{ "album", SORT_ALBUM },
|
||||
{ "title", SORT_TITLE },
|
||||
{ "play_count", SORT_PLAY_COUNT },
|
||||
{ "duration", SORT_DURATION },
|
||||
{ "tracknumber", SORT_TRACKNUMBER },
|
||||
{ "discnumber", SORT_DISCNUMBER },
|
||||
{ "totaldiscs", SORT_TOTALDISCS },
|
||||
{ "date", SORT_DATE },
|
||||
{ "originaldate", SORT_ORIGINALDATE },
|
||||
{ "genre", SORT_GENRE },
|
||||
{ "comment", SORT_COMMENT },
|
||||
{ "albumartist", SORT_ALBUMARTIST },
|
||||
{ "filename", SORT_FILENAME },
|
||||
{ "filemtime", SORT_FILEMTIME },
|
||||
{ "rg_track_gain", SORT_RG_TRACK_GAIN },
|
||||
{ "rg_track_peak", SORT_RG_TRACK_PEAK },
|
||||
{ "rg_album_gain", SORT_RG_ALBUM_GAIN },
|
||||
{ "rg_album_peak", SORT_RG_ALBUM_PEAK },
|
||||
{ "bitrate", SORT_BITRATE },
|
||||
{ "codec", SORT_CODEC },
|
||||
{ "codec_profile", SORT_CODEC_PROFILE },
|
||||
{ "media", SORT_MEDIA },
|
||||
{ "bpm", SORT_BPM },
|
||||
{ "-artist", REV_SORT_ARTIST },
|
||||
{ "-album", REV_SORT_ALBUM },
|
||||
{ "-title", REV_SORT_TITLE },
|
||||
{ "-play_count", REV_SORT_PLAY_COUNT },
|
||||
{ "-duration", REV_SORT_DURATION },
|
||||
{ "-tracknumber", REV_SORT_TRACKNUMBER },
|
||||
{ "-discnumber", REV_SORT_DISCNUMBER },
|
||||
{ "-totaldiscs", REV_SORT_TOTALDISCS },
|
||||
{ "-date", REV_SORT_DATE },
|
||||
{ "-originaldate", REV_SORT_ORIGINALDATE },
|
||||
{ "-genre", REV_SORT_GENRE },
|
||||
{ "-comment", REV_SORT_COMMENT },
|
||||
{ "-albumartist", REV_SORT_ALBUMARTIST },
|
||||
{ "-filename", REV_SORT_FILENAME },
|
||||
{ "-filemtime", REV_SORT_FILEMTIME },
|
||||
{ "-rg_track_gain", REV_SORT_RG_TRACK_GAIN },
|
||||
{ "-rg_track_peak", REV_SORT_RG_TRACK_PEAK },
|
||||
{ "-rg_album_gain", REV_SORT_RG_ALBUM_GAIN },
|
||||
{ "-rg_album_peak", REV_SORT_RG_ALBUM_PEAK },
|
||||
{ "-bitrate", REV_SORT_BITRATE },
|
||||
{ "-codec", REV_SORT_CODEC },
|
||||
{ "-codec_profile", REV_SORT_CODEC_PROFILE },
|
||||
{ "-media", REV_SORT_MEDIA },
|
||||
{ "-bpm", REV_SORT_BPM },
|
||||
{ NULL, SORT_INVALID }
|
||||
};
|
||||
|
||||
sort_key_t *parse_sort_keys(const char *value)
|
||||
{
|
||||
sort_key_t *keys;
|
||||
const char *s, *e;
|
||||
int size = 4;
|
||||
int pos = 0;
|
||||
|
||||
keys = xnew(sort_key_t, size);
|
||||
|
||||
s = value;
|
||||
while (1) {
|
||||
char buf[32];
|
||||
int i, len;
|
||||
|
||||
while (*s == ' ')
|
||||
s++;
|
||||
|
||||
e = s;
|
||||
while (*e && *e != ' ')
|
||||
e++;
|
||||
|
||||
len = e - s;
|
||||
if (len == 0)
|
||||
break;
|
||||
if (len > 31)
|
||||
len = 31;
|
||||
|
||||
memcpy(buf, s, len);
|
||||
buf[len] = 0;
|
||||
s = e;
|
||||
|
||||
for (i = 0; ; i++) {
|
||||
if (sort_key_map[i].str == NULL) {
|
||||
error_msg("invalid sort key '%s'", buf);
|
||||
free(keys);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (strcmp(buf, sort_key_map[i].str) == 0)
|
||||
break;
|
||||
}
|
||||
if (pos == size - 1) {
|
||||
size *= 2;
|
||||
keys = xrenew(sort_key_t, keys, size);
|
||||
}
|
||||
keys[pos++] = sort_key_map[i].key;
|
||||
}
|
||||
keys[pos] = SORT_INVALID;
|
||||
return keys;
|
||||
}
|
||||
|
||||
const char *sort_key_to_str(sort_key_t key)
|
||||
{
|
||||
int i;
|
||||
for (i = 0; sort_key_map[i].str; i++) {
|
||||
if (sort_key_map[i].key == key)
|
||||
return sort_key_map[i].str;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void sort_keys_to_str(const sort_key_t *keys, char *buf, size_t bufsize)
|
||||
{
|
||||
int i, pos = 0;
|
||||
|
||||
for (i = 0; keys[i] != SORT_INVALID; i++) {
|
||||
const char *key = sort_key_to_str(keys[i]);
|
||||
int len = strlen(key);
|
||||
|
||||
if ((int)bufsize - pos - len - 2 < 0)
|
||||
break;
|
||||
|
||||
memcpy(buf + pos, key, len);
|
||||
pos += len;
|
||||
buf[pos++] = ' ';
|
||||
}
|
||||
if (pos > 0)
|
||||
pos--;
|
||||
buf[pos] = 0;
|
||||
}
|
||||
Reference in New Issue
Block a user