1 /*
2 * Copyright 2008-2013 Various Authors
3 * Copyright 2004-2005 Timo Hirvonen
4 *
5 * This program is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU General Public License as
7 * published by the Free Software Foundation; either version 2 of the
8 * License, or (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful, but
11 * WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, see <http://www.gnu.org/licenses/>.
17 */
18
19 #include "track_info.h"
20 #include "comment.h"
21 #include "uchar.h"
22 #include "u_collate.h"
23 #include "misc.h"
24 #include "xmalloc.h"
25 #include "utils.h"
26 #include "debug.h"
27 #include "path.h"
28 #include "ui_curses.h"
29
30 #include <string.h>
31 #include <stdatomic.h>
32 #include <math.h>
33
34 struct track_info_priv {
35 struct track_info ti;
36 _Atomic uint32_t ref_count;
37 };
38
track_info_to_priv(struct track_info * ti)39 static struct track_info_priv *track_info_to_priv(struct track_info *ti)
40 {
41 return container_of(ti, struct track_info_priv, ti);
42 }
43
track_info_new(const char * filename)44 struct track_info *track_info_new(const char *filename)
45 {
46 static _Atomic uint64_t cur_uid = ATOMIC_VAR_INIT(1);
47 uint64_t uid = atomic_fetch_add_explicit(&cur_uid, 1, memory_order_relaxed);
48 BUG_ON(uid == 0);
49
50 struct track_info_priv *priv = xnew(struct track_info_priv, 1);
51 atomic_init(&priv->ref_count, 1);
52
53 struct track_info *ti = &priv->ti;
54 ti->uid = uid;
55 ti->filename = xstrdup(filename);
56 ti->play_count = 0;
57 ti->comments = NULL;
58 ti->bpm = -1;
59 ti->codec = NULL;
60 ti->codec_profile = NULL;
61
62 return ti;
63 }
64
track_info_set_comments(struct track_info * ti,struct keyval * comments)65 void track_info_set_comments(struct track_info *ti, struct keyval *comments) {
66 long int r128_track_gain;
67 long int r128_album_gain;
68
69 ti->comments = comments;
70 ti->artist = keyvals_get_val(comments, "artist");
71 ti->album = keyvals_get_val(comments, "album");
72 ti->title = keyvals_get_val(comments, "title");
73 ti->tracknumber = comments_get_int(comments, "tracknumber");
74 ti->discnumber = comments_get_int(comments, "discnumber");
75 ti->date = comments_get_date(comments, "date");
76 ti->originaldate = comments_get_date(comments, "originaldate");
77 ti->genre = keyvals_get_val(comments, "genre");
78 ti->comment = keyvals_get_val(comments, "comment");
79 ti->albumartist = comments_get_albumartist(comments);
80 ti->artistsort = comments_get_artistsort(comments);
81 ti->albumsort = keyvals_get_val(comments, "albumsort");
82 ti->is_va_compilation = track_is_va_compilation(comments);
83 ti->media = keyvals_get_val(comments, "media");
84
85 int bpm = comments_get_int(comments, "bpm");
86 if (ti->bpm == 0 || ti->bpm == -1) {
87 ti->bpm = bpm;
88 }
89
90 if (ti->artist == NULL && ti->albumartist != NULL) {
91 /* best guess */
92 ti->artist = ti->albumartist;
93 }
94
95 if (track_info_has_tag(ti) && ti->title == NULL) {
96 /* best guess */
97 ti->title = path_basename(ti->filename);
98 }
99
100 ti->rg_track_gain = comments_get_double(comments, "replaygain_track_gain");
101 ti->rg_track_peak = comments_get_double(comments, "replaygain_track_peak");
102 ti->rg_album_gain = comments_get_double(comments, "replaygain_album_gain");
103 ti->rg_album_peak = comments_get_double(comments, "replaygain_album_peak");
104
105 if (comments_get_signed_int(comments, "r128_track_gain", &r128_track_gain) != -1) {
106 ti->rg_track_gain = (r128_track_gain / 256.0) + 5;
107 }
108
109 if (comments_get_signed_int(comments, "r128_album_gain", &r128_album_gain) != -1) {
110 ti->rg_album_gain = (r128_album_gain / 256.0) + 5;
111 }
112
113 ti->collkey_artist = u_strcasecoll_key0(ti->artist);
114 ti->collkey_album = u_strcasecoll_key0(ti->album);
115 ti->collkey_title = u_strcasecoll_key0(ti->title);
116 ti->collkey_genre = u_strcasecoll_key0(ti->genre);
117 ti->collkey_comment = u_strcasecoll_key0(ti->comment);
118 ti->collkey_albumartist = u_strcasecoll_key0(ti->albumartist);
119 }
120
track_info_ref(struct track_info * ti)121 void track_info_ref(struct track_info *ti)
122 {
123 struct track_info_priv *priv = track_info_to_priv(ti);
124 atomic_fetch_add_explicit(&priv->ref_count, 1, memory_order_relaxed);
125 }
126
track_info_unref(struct track_info * ti)127 void track_info_unref(struct track_info *ti)
128 {
129 struct track_info_priv *priv = track_info_to_priv(ti);
130 uint32_t prev = atomic_fetch_sub_explicit(&priv->ref_count, 1,
131 memory_order_acq_rel);
132 if (prev == 1) {
133 keyvals_free(ti->comments);
134 free(ti->filename);
135 free(ti->codec);
136 free(ti->codec_profile);
137 free(ti->collkey_artist);
138 free(ti->collkey_album);
139 free(ti->collkey_title);
140 free(ti->collkey_genre);
141 free(ti->collkey_comment);
142 free(ti->collkey_albumartist);
143 free(priv);
144 }
145 }
146
track_info_unique_ref(struct track_info * ti)147 bool track_info_unique_ref(struct track_info *ti)
148 {
149 struct track_info_priv *priv = track_info_to_priv(ti);
150 return atomic_load_explicit(&priv->ref_count, memory_order_relaxed) == 1;
151 }
152
track_info_has_tag(const struct track_info * ti)153 int track_info_has_tag(const struct track_info *ti)
154 {
155 return ti->artist || ti->album || ti->title;
156 }
157
match_word(const struct track_info * ti,const char * word,unsigned int flags)158 static inline int match_word(const struct track_info *ti, const char *word, unsigned int flags)
159 {
160 return ((flags & TI_MATCH_ARTIST) && ti->artist && u_strcasestr_base(ti->artist, word)) ||
161 ((flags & TI_MATCH_ALBUM) && ti->album && u_strcasestr_base(ti->album, word)) ||
162 ((flags & TI_MATCH_TITLE) && ti->title && u_strcasestr_base(ti->title, word)) ||
163 ((flags & TI_MATCH_ALBUMARTIST) && ti->albumartist && u_strcasestr_base(ti->albumartist, word));
164 }
165
flags_set(const struct track_info * ti,unsigned int flags)166 static inline int flags_set(const struct track_info *ti, unsigned int flags)
167 {
168 return ((flags & TI_MATCH_ARTIST) && ti->artist) ||
169 ((flags & TI_MATCH_ALBUM) && ti->album) ||
170 ((flags & TI_MATCH_TITLE) && ti->title) ||
171 ((flags & TI_MATCH_ALBUMARTIST) && ti->albumartist);
172 }
173
track_info_matches_full(const struct track_info * ti,const char * text,unsigned int flags,unsigned int exclude_flags,int match_all_words)174 int track_info_matches_full(const struct track_info *ti, const char *text,
175 unsigned int flags, unsigned int exclude_flags, int match_all_words)
176 {
177 char **words;
178 int i, matched = 0;
179
180 words = get_words(text);
181 for (i = 0; words[i]; i++) {
182 const char *word = words[i];
183
184 matched = 0;
185 if (flags_set(ti, flags) && match_word(ti, word, flags)) {
186 matched = 1;
187 } else {
188 /* compare with url or filename without path */
189 const char *filename = ti->filename;
190
191 if (!is_url(filename))
192 filename = path_basename(filename);
193
194 if (u_strcasestr_filename(filename, word))
195 matched = 1;
196 }
197
198 if (flags_set(ti, exclude_flags) && match_word(ti, word, exclude_flags))
199 matched = 0;
200
201 if (match_all_words ? !matched : matched)
202 break;
203
204 }
205 free_str_array(words);
206 return matched;
207 }
208
track_info_matches(const struct track_info * ti,const char * text,unsigned int flags)209 int track_info_matches(const struct track_info *ti, const char *text, unsigned int flags)
210 {
211 return track_info_matches_full(ti, text, flags, 0, 1);
212 }
213
doublecmp0(double a,double b)214 static int doublecmp0(double a, double b)
215 {
216 double x;
217 /* fast check for NaN */
218 int r = (b != b) - (a != a);
219 if (r)
220 return r;
221 x = a - b;
222 return (x > 0) - (x < 0);
223 }
224
225 /* this function gets called *a lot*, it must be very fast */
track_info_cmp(const struct track_info * a,const struct track_info * b,const sort_key_t * keys)226 int track_info_cmp(const struct track_info *a, const struct track_info *b, const sort_key_t *keys)
227 {
228 int i, rev = 0, res = 0;
229
230 for (i = 0; keys[i] != SORT_INVALID; i++) {
231 sort_key_t key = keys[i];
232 const char *av, *bv;
233
234 rev = 0;
235 if (key >= REV_SORT__START) {
236 rev = 1;
237 key -= REV_SORT__START;
238 }
239
240 switch (key) {
241 case SORT_TRACKNUMBER:
242 case SORT_DISCNUMBER:
243 case SORT_DATE:
244 case SORT_ORIGINALDATE:
245 case SORT_PLAY_COUNT:
246 case SORT_BPM:
247 res = getentry(a, key, int) - getentry(b, key, int);
248 break;
249 case SORT_FILEMTIME:
250 res = a->mtime - b->mtime;
251 break;
252 case SORT_FILENAME:
253 /* NOTE: filenames are not necessarily UTF-8 */
254 res = strcoll(a->filename, b->filename);
255 break;
256 case SORT_RG_TRACK_GAIN:
257 case SORT_RG_TRACK_PEAK:
258 case SORT_RG_ALBUM_GAIN:
259 case SORT_RG_ALBUM_PEAK:
260 res = doublecmp0(getentry(a, key, double), getentry(b, key, double));
261 break;
262 case SORT_BITRATE:
263 res = getentry(a, key, long) - getentry(b, key, long);
264 break;
265 default:
266 av = getentry(a, key, const char *);
267 bv = getentry(b, key, const char *);
268 res = strcmp0(av, bv);
269 break;
270 }
271
272 if (res)
273 break;
274 }
275 return rev ? -res : res;
276 }
277
278 static const struct {
279 const char *str;
280 sort_key_t key;
281 } sort_key_map[] = {
282 { "artist", SORT_ARTIST },
283 { "album", SORT_ALBUM },
284 { "title", SORT_TITLE },
285 { "play_count", SORT_PLAY_COUNT },
286 { "tracknumber", SORT_TRACKNUMBER },
287 { "discnumber", SORT_DISCNUMBER },
288 { "date", SORT_DATE },
289 { "originaldate", SORT_ORIGINALDATE },
290 { "genre", SORT_GENRE },
291 { "comment", SORT_COMMENT },
292 { "albumartist", SORT_ALBUMARTIST },
293 { "filename", SORT_FILENAME },
294 { "filemtime", SORT_FILEMTIME },
295 { "rg_track_gain", SORT_RG_TRACK_GAIN },
296 { "rg_track_peak", SORT_RG_TRACK_PEAK },
297 { "rg_album_gain", SORT_RG_ALBUM_GAIN },
298 { "rg_album_peak", SORT_RG_ALBUM_PEAK },
299 { "bitrate", SORT_BITRATE },
300 { "codec", SORT_CODEC },
301 { "codec_profile", SORT_CODEC_PROFILE },
302 { "media", SORT_MEDIA },
303 { "bpm", SORT_BPM },
304 { "-artist", REV_SORT_ARTIST },
305 { "-album", REV_SORT_ALBUM },
306 { "-title", REV_SORT_TITLE },
307 { "-play_count", REV_SORT_PLAY_COUNT },
308 { "-tracknumber", REV_SORT_TRACKNUMBER },
309 { "-discnumber", REV_SORT_DISCNUMBER },
310 { "-date", REV_SORT_DATE },
311 { "-originaldate", REV_SORT_ORIGINALDATE },
312 { "-genre", REV_SORT_GENRE },
313 { "-comment", REV_SORT_COMMENT },
314 { "-albumartist", REV_SORT_ALBUMARTIST },
315 { "-filename", REV_SORT_FILENAME },
316 { "-filemtime", REV_SORT_FILEMTIME },
317 { "-rg_track_gain", REV_SORT_RG_TRACK_GAIN },
318 { "-rg_track_peak", REV_SORT_RG_TRACK_PEAK },
319 { "-rg_album_gain", REV_SORT_RG_ALBUM_GAIN },
320 { "-rg_album_peak", REV_SORT_RG_ALBUM_PEAK },
321 { "-bitrate", REV_SORT_BITRATE },
322 { "-codec", REV_SORT_CODEC },
323 { "-codec_profile", REV_SORT_CODEC_PROFILE },
324 { "-media", REV_SORT_MEDIA },
325 { "-bpm", REV_SORT_BPM },
326 { NULL, SORT_INVALID }
327 };
328
parse_sort_keys(const char * value)329 sort_key_t *parse_sort_keys(const char *value)
330 {
331 sort_key_t *keys;
332 const char *s, *e;
333 int size = 4;
334 int pos = 0;
335
336 keys = xnew(sort_key_t, size);
337
338 s = value;
339 while (1) {
340 char buf[32];
341 int i, len;
342
343 while (*s == ' ')
344 s++;
345
346 e = s;
347 while (*e && *e != ' ')
348 e++;
349
350 len = e - s;
351 if (len == 0)
352 break;
353 if (len > 31)
354 len = 31;
355
356 memcpy(buf, s, len);
357 buf[len] = 0;
358 s = e;
359
360 for (i = 0; ; i++) {
361 if (sort_key_map[i].str == NULL) {
362 error_msg("invalid sort key '%s'", buf);
363 free(keys);
364 return NULL;
365 }
366
367 if (strcmp(buf, sort_key_map[i].str) == 0)
368 break;
369 }
370 if (pos == size - 1) {
371 size *= 2;
372 keys = xrenew(sort_key_t, keys, size);
373 }
374 keys[pos++] = sort_key_map[i].key;
375 }
376 keys[pos] = SORT_INVALID;
377 return keys;
378 }
379
sort_key_to_str(sort_key_t key)380 const char *sort_key_to_str(sort_key_t key)
381 {
382 int i;
383 for (i = 0; sort_key_map[i].str; i++) {
384 if (sort_key_map[i].key == key)
385 return sort_key_map[i].str;
386 }
387 return NULL;
388 }
389
sort_keys_to_str(const sort_key_t * keys,char * buf,size_t bufsize)390 void sort_keys_to_str(const sort_key_t *keys, char *buf, size_t bufsize)
391 {
392 int i, pos = 0;
393
394 for (i = 0; keys[i] != SORT_INVALID; i++) {
395 const char *key = sort_key_to_str(keys[i]);
396 int len = strlen(key);
397
398 if ((int)bufsize - pos - len - 2 < 0)
399 break;
400
401 memcpy(buf + pos, key, len);
402 pos += len;
403 buf[pos++] = ' ';
404 }
405 if (pos > 0)
406 pos--;
407 buf[pos] = 0;
408 }
409