1 ////////////////////////////////////////////////////////////////////////////////
2 static int opt_tagload_koi8
= 1;
3 static int opt_tagload_dostat
= 0;
6 static char *str_transliterate (const char *str
) {
9 char *outs
, *ibuf
, *obuf
, *res
;
11 if (str
== NULL
) return strdup("");
12 if (str
== NULL
|| !str
[0]) { outs
= strdup(str
); goto done
; }
13 for (const unsigned char *u
= (const unsigned char *)str
; *u
; ++u
) if (*u
>= 128) { asis
= 0; break; }
14 if (asis
) { outs
= strdup(str
); goto done
; }
15 //cd = iconv_open("ascii//translit//ignore", "utf-8");
16 cd
= iconv_open("cp866//translit//ignore", "utf-8");
17 if (cd
== (iconv_t
)-1) return NULL
;
18 outs
= calloc(1, strlen(str
)*6+4);
27 il
= iconv(cd
, &ibuf
, &il
, &obuf
, &ol
);
29 if (il
== (size_t)-1) {
33 res
= calloc(ool
-ol
+1, 1);
34 if (ool
-ol
> 0) memcpy(res
, outs
, ool
-ol
);
36 outs
= translitstr(res
);
39 for (char *s
= outs
; *s
; ++s
) {
41 if (!isalnum(*s
)) *s
= '_';
43 while (*outs
&& *outs
== '_') memmove(outs
, outs
+1, strlen(outs
));
46 for (char *s
= outs
+1; *s
; ++s
) {
48 if (obuf
[-1] != '_') *obuf
++ = *s
;
53 while (obuf
> outs
&& obuf
[-1] == '_') --obuf
;
60 ////////////////////////////////////////////////////////////////////////////////
61 static char *str_tokoi (const char *str
) {
62 if (opt_tagload_koi8
) {
65 char *outs
, *ibuf
, *obuf
, *res
;
67 if (str
== NULL
) return strdup("");
68 if (str
== NULL
|| !str
[0]) { res
= strdup(str
); goto done
; }
69 for (const unsigned char *u
= (const unsigned char *)str
; *u
; ++u
) if (*u
>= 128) { asis
= 0; break; }
70 if (asis
) { res
= strdup(str
); goto done
; }
71 cd
= iconv_open("koi8-u//translit//ignore", "utf-8");
72 if (cd
== (iconv_t
)-1) return NULL
;
73 outs
= calloc(1, strlen(str
)*6+4);
82 il
= iconv(cd
, &ibuf
, &il
, &obuf
, &ol
);
84 if (il
== (size_t)-1) {
88 res
= calloc(ool
-ol
+1, 1);
89 if (ool
-ol
> 0) memcpy(res
, outs
, ool
-ol
);
94 if (str
== NULL
) return strdup("");
99 ////////////////////////////////////////////////////////////////////////////////
105 char *artist
; // can't be NULL
106 char *album
; // can't be NULL
107 char *title
; // can't be NULL
108 char *genre
; // can be NULL
109 uint16_t year
; // can be 0
110 uint8_t track
; // can be 0
114 ////////////////////////////////////////////////////////////////////////////////
115 static inline char *dupstrn (const void *ptr
, int size
) {
116 char *res
= calloc(1, size
+1);
117 if (size
> 0) memcpy(res
, ptr
, size
);
123 static inline void trimstr (char *s
) {
125 for (ns
= s
; *ns
&& isspace(*ns
); ++ns
) ;
126 if (ns
!= s
) memmove(s
, ns
, strlen(ns
)+1);
128 while (ns
>= s
&& isspace(*ns
)) --ns
;
130 for (; *s
; ++s
) if (*s
== '/') *s
= '_';
134 #define GETFIELDS(tagname,fldname) do { \
135 if (strcmp(nm, tagname) == 0) { \
136 if (fi->fldname != NULL) free(fi->fldname); \
137 fi->fldname = dupstrn(p, sz); \
138 if (fi->fldname != NULL && strcmp(tagname, "filename") != 0) trimstr(fi->fldname); \
139 if (!fi->fldname[0]) { free(fi->fldname); fi->fldname = NULL; } \
145 #define GETFIELDN(tagname,fldname,nsz) do { \
146 if (strcmp(nm, tagname) == 0) { \
147 if (sz != nsz) goto error; \
148 memcpy(&fi->fldname, p, nsz); \
154 // and skip to next one
155 static int tagfile_item_read (FILE *fl
, tagfile_item_t
*fi
) {
157 static char buf
[65536], nm
[65536];
159 memset(fi
, 0, sizeof(*fi
));
160 if (fread(&size
, 2, 1, fl
) != 1 || size
< 1) return -1;
161 if (fread(buf
, size
, 1, fl
) != 1) return -1;
168 if (sz
> size
) goto error
;
176 if (size
< 2) goto error
;
180 if (sz
> size
) goto error
;
183 GETFIELDS("filename", filename
);
184 GETFIELDS("artist", artist
);
185 GETFIELDS("album", album
);
186 GETFIELDS("title", title
);
187 GETFIELDS("genre", genre
);
188 GETFIELDN("inode", inode
, 8);
189 GETFIELDN("size", size
, 8);
190 GETFIELDN("mtime", mtime
, 8);
191 GETFIELDN("year", year
, 2);
192 GETFIELDN("track", track
, 1);
196 if (fi
->filename
== NULL
) goto error
;
198 if (fi
->title
== NULL
) fi
->title
= strdup("unknown title");
199 if (fi
->album
== NULL
) fi
->album
= strdup("unknown album");
200 if (fi
->artist
== NULL
) fi
->artist
= strdup("unknown artist");
201 if (fi
->genre
== NULL
) fi
->genre
= strdup("unknown genre");
205 if (fi
->genre
!= NULL
) free(fi
->genre
);
206 if (fi
->title
!= NULL
) free(fi
->title
);
207 if (fi
->album
!= NULL
) free(fi
->album
);
208 if (fi
->artist
!= NULL
) free(fi
->artist
);
209 if (fi
->filename
!= NULL
) free(fi
->filename
);
210 memset(fi
, 0, sizeof(*fi
));
215 static void tagfile_item_clear (tagfile_item_t
*fi
) {
217 if (fi
->genre
!= NULL
) free(fi
->genre
);
218 if (fi
->title
!= NULL
) free(fi
->title
);
219 if (fi
->album
!= NULL
) free(fi
->album
);
220 if (fi
->artist
!= NULL
) free(fi
->artist
);
221 if (fi
->filename
!= NULL
) free(fi
->filename
);
222 memset(fi
, 0, sizeof(*fi
));
227 ////////////////////////////////////////////////////////////////////////////////
228 typedef struct file_info_t file_info_t
;
233 // file ids for this tag
243 char *s
; // lowercased, must be free()d
248 // 'transliterated' tags
249 static tagvalue_t
*tagv_hash_t
= NULL
;
250 static tagvalue_t
*tagv_artist_hash_t
= NULL
;
251 static tagvalue_t
*tagv_album_hash_t
= NULL
;
252 static tagvalue_t
*tagv_title_hash_t
= NULL
;
253 static tagvalue_t
*tagv_genre_hash_t
= NULL
;
255 static tagvalue_t
*tagv_hash_o
= NULL
;
256 static tagvalue_t
*tagv_artist_hash_o
= NULL
;
257 static tagvalue_t
*tagv_album_hash_o
= NULL
;
258 static tagvalue_t
*tagv_title_hash_o
= NULL
;
259 static tagvalue_t
*tagv_genre_hash_o
= NULL
;
260 // year is the same for both
261 static tagvalue_t
*tagv_year_hash
= NULL
;
263 static tagalias_t
*tagv_norm_hash
= NULL
; // 'normalized aliases' hash
266 ////////////////////////////////////////////////////////////////////////////////
267 // note that two years here is just for convience, they are the same
269 char *fullname
; // full name
270 char *shortname
; // should be unique
272 tagvalue_t
*year_o
; // can be NULL
273 tagvalue_t
*artist_o
; // can't be NULL
274 tagvalue_t
*album_o
; // can't be NULL
275 tagvalue_t
*title_o
; // can't be NULL
276 tagvalue_t
*genre_o
; // can' be NULL
278 tagvalue_t
*year_t
; // can be NULL
279 tagvalue_t
*artist_t
; // can't be NULL
280 tagvalue_t
*album_t
; // can't be NULL
281 tagvalue_t
*title_t
; // can't be NULL
282 tagvalue_t
*genre_t
; // can' be NULL
290 char *shortname
; // points to fi->shortname
295 ////////////////////////////////////////////////////////////////////////////////
296 static int file_info_count
= 0;
297 static file_info_t
*file_name_hash
= NULL
;
298 static file_sname_t
*file_sname_hash
= NULL
;
301 ////////////////////////////////////////////////////////////////////////////////
302 static void tagvalue_add_id (tagvalue_t
*tv
, file_info_t
*fi
) {
303 //if (bsearch(fi, tv->ids, tv->idcount, sizeof(tv->ids[0]), cmp_ptr) != NULL) return;
304 for (int f
= 0; f
< tv
->idcount
; ++f
) if (tv
->ids
[f
] == fi
) return;
305 if (tv
->idcount
+1 > tv
->idallocated
) {
306 int newsz
= ((tv
->idcount
+1)|0x1f)+1;
307 file_info_t
**n
= realloc(tv
->ids
, sizeof(tv
->ids
[0])*newsz
);
309 fprintf(stderr
, "FATAL: out of memory in tagvalue_add_id!\n");
312 tv
->idallocated
= newsz
;
315 tv
->ids
[tv
->idcount
++] = fi
;
316 //qsort(tv->ids, tv->idcount, sizeof(tv->ids[0]), cmp_ptr);
320 static inline void normalize_tag_str (char *d
, const char *s
) {
322 unsigned char ch
= (unsigned char)(*s
);
323 if (isspace(*s
)) continue;
324 if (ch
== 127) continue;
325 if (opt_tagload_koi8
) {
328 if (*d
== '£') *d
= 'Å';
329 if (*d
== 'Ý') *d
= 'Û';
330 if (*d
== 'Ó') *d
= 'c'; // russian to latin
332 } else if (isdigit(*d
) || (*d
>= 'a' && *d
<= 'z')) {
339 } else if (isdigit(*d
) || (*d
>= 'a' && *d
<= 'z')) {
348 static void tagfile_item_regtag (file_info_t
*fi
, tagvalue_t
**tv_o
, tagvalue_t
**tv_t
, const char *value
) {
349 tagvalue_t
*tv
= NULL
;
351 int freess
= 1, frees1
= 1;
353 ss
= str_tokoi(value
);
354 s1
= str_transliterate(value
);
355 if (ss
== NULL
|| s1
== NULL
) {
356 fprintf(stderr
, "FATAL: tagfile_item_regtag iconv error!\n");
359 s2
= alloca(strlen(ss
)+strlen(s1
)+2);
361 *tv_o
= *tv_t
= NULL
;
363 HASH_FIND_STR(tagv_hash_o
, ss
, tv
);
367 normalize_tag_str(s2
, ss
);
368 HASH_FIND_STR(tagv_norm_hash
, s2
, ta
);
371 tv
= calloc(1, sizeof(*tv
));
373 HASH_ADD_KEYPTR(hh
, tagv_hash_o
, tv
->s
, strlen(tv
->s
), tv
);
375 ta
= calloc(1, sizeof(*ta
));
378 HASH_ADD_KEYPTR(hh
, tagv_norm_hash
, ta
->s
, strlen(ta
->s
), ta
);
383 tagvalue_add_id(tv
, fi
);
386 // 'transliterated' tag
388 HASH_FIND_STR(tagv_hash_t
, s1
, tv
);
392 tv
= calloc(1, sizeof(*tv
));
394 HASH_ADD_KEYPTR(hh
, tagv_hash_t
, tv
->s
, strlen(tv
->s
), tv
);
396 tagvalue_add_id(tv
, fi
);
400 if (frees1
) free(s1
);
401 if (freess
) free(ss
);
405 static void tagfile_item_regtag_ex (file_info_t
*fi
, tagvalue_t
*tv_x
, tagvalue_t
**tagh
) {
408 HASH_FIND_STR(*tagh
, tv_x
->s
, tv
);
411 tv
= calloc(1, sizeof(*tv
));
413 HASH_ADD_KEYPTR(hh
, *tagh
, tv
->s
, strlen(tv
->s
), tv
);
415 tagvalue_add_id(tv
, fi
);
420 static void tagfile_item_register (const tagfile_item_t
*tfi
) {
421 file_info_t
*fi
, *of
;
424 HASH_FIND_STR(file_name_hash
, tfi
->filename
, of
);
426 dlogf("duplicate file: [%s]\n", tfi
->filename
);
427 return; // nothing to do, this file already registered
430 fi
= calloc(1, sizeof(*fi
));
431 fi
->fullname
= strdup(tfi
->filename
);
433 HASH_ADD_KEYPTR(hh
, file_name_hash
, fi
->fullname
, strlen(fi
->fullname
), fi
);
435 tagfile_item_regtag(fi
, &fi
->artist_o
, &fi
->artist_t
, tfi
->artist
);
436 tagfile_item_regtag(fi
, &fi
->album_o
, &fi
->album_t
, tfi
->album
);
437 tagfile_item_regtag(fi
, &fi
->title_o
, &fi
->title_t
, tfi
->title
);
438 tagfile_item_regtag(fi
, &fi
->genre_o
, &fi
->genre_t
, tfi
->genre
);
440 tagfile_item_regtag_ex(fi
, fi
->artist_o
, &tagv_artist_hash_o
);
441 tagfile_item_regtag_ex(fi
, fi
->album_o
, &tagv_album_hash_o
);
442 tagfile_item_regtag_ex(fi
, fi
->title_o
, &tagv_title_hash_o
);
443 tagfile_item_regtag_ex(fi
, fi
->genre_o
, &tagv_genre_hash_o
);
445 tagfile_item_regtag_ex(fi
, fi
->artist_t
, &tagv_artist_hash_t
);
446 tagfile_item_regtag_ex(fi
, fi
->album_t
, &tagv_album_hash_t
);
447 tagfile_item_regtag_ex(fi
, fi
->title_t
, &tagv_title_hash_t
);
448 tagfile_item_regtag_ex(fi
, fi
->genre_t
, &tagv_genre_hash_t
);
450 fi
->year_o
= fi
->year_t
= NULL
;
451 if (tfi
->year
!= 0) {
452 sprintf(buf
, "%u", tfi
->year
);
453 tagfile_item_regtag(fi
, &fi
->year_o
, &fi
->year_t
, buf
);
454 tagfile_item_regtag_ex(fi
, fi
->year_o
, &tagv_year_hash
);
459 char *t
= strrchr(fi
->fullname
, '/')+1;
460 HASH_FIND_STR(file_sname_hash
, t
, of
);
463 //dlogf("000: [%s]", t);
464 char *newn
= alloca(strlen(t
)+32);
465 char *ext
= strrchr(t
, '.');
467 for (int n
= 1; n
< 999999; ++n
) {
468 sprintf(newn
, "%s_%d", t
, n
);
469 HASH_FIND_STR(file_sname_hash
, newn
, of
);
470 if (of
== NULL
) break;
475 for (int n
= 1; n
< 999999; ++n
) {
476 sprintf(newn
, "%s_%d.%s", t
, n
, ext
+1);
477 HASH_FIND_STR(file_sname_hash
, newn
, of
);
478 if (of
== NULL
) break;
482 fi
->shortname
= strdup(newn
);
483 //dlogf("DUPNAME: [%s] -> [%s]\n", t, newn);
485 fi
->shortname
= strdup(t
);
488 of
= calloc(1, sizeof(*of
));
490 of
->shortname
= fi
->shortname
;
491 //dlogf("002: [%s]", of->shortname);
492 HASH_ADD_KEYPTR(hh
, file_sname_hash
, of
->shortname
, strlen(of
->shortname
), of
);
498 ////////////////////////////////////////////////////////////////////////////////
499 static int load_tagfile (const char *fname
) {
501 int tyh_sort (const tagvalue_t *t0, const tagvalue_t *t1) {
502 return strcmp(t0->s, t1->s);
505 int sname_sort (const file_sname_t *t0, const file_sname_t *t1) {
506 return strcmp(t0->shortname, t1->shortname);
509 int name_sort (const file_info_t *t0, const file_info_t *t1) {
510 return strcmp(t0->fullname, t1->fullname);
513 tagalias_t
*ta
, *tatmp
;
517 fl
= fopen(fname
, "r");
518 if (fl
== NULL
) return -1; // no tags
519 if (fread(sign
, 4, 1, fl
) != 1) goto error
;
520 if (memcmp(sign
, "TFI0", 4) != 0) goto error
;
521 if (fread(&fc
, 4, 1, fl
) != 1) goto error
;
522 printf("loading %u files...\n", (unsigned int)fc
);
526 if (tagfile_item_read(fl
, &tfi
) != 0) goto error
;
527 if (opt_tagload_dostat
) {
528 if (stat(tfi
.filename
, &st
) != 0) goto fileerror
;
529 if (!S_ISREG(st
.st_mode
)) goto fileerror
;
531 //if (st.st_ino != tfi.inode || st.st_mtime != tfi.mtime || st.st_size != tfi.size) goto fileerror; // file was changed, drop and rescan
532 tagfile_item_register(&tfi
);
534 tagfile_item_clear(&tfi
);
535 //if ((fc&0xff) == 0) { fprintf(stdout, "\r%u left (years: %d)\x1b[K", fc, HASH_COUNT(tagv_year_hash)); fflush(stdout); }
538 file_info_count
= HASH_COUNT(file_name_hash
);
539 printf("%d files loaded, %d tags registered, %d normal tags registered.\n", file_info_count
, HASH_COUNT(tagv_hash_o
), HASH_COUNT(tagv_hash_t
));
542 HASH_SORT(tagv_year_hash, tyh_sort);
543 HASH_SORT(file_name_hash, name_sort);
544 HASH_SORT(file_sname_hash, sname_sort);
547 for (const tagvalue_t *tv = tagv_year_hash; tv != NULL; tv = tv->hh.next) dlogf("year: [%s] (%d)\n", tv->s, tv->idcount);
549 // clear 'normalized' hash, we don't need it anymore
550 HASH_ITER(hh
, tagv_norm_hash
, ta
, tatmp
) {
551 HASH_DEL(tagv_norm_hash
, ta
);
563 ////////////////////////////////////////////////////////////////////////////////
564 #define ARRAYLEN(arr) (sizeof((arr))/sizeof((arr)[0]))
566 static const char *specdirs
[] = {
580 ////////////////////////////////////////////////////////////////////////////////
581 static inline tagvalue_t
**special_hash (const char *name
) {
582 if (strcmp(name
, "artist") == 0) return &tagv_artist_hash_t
;
583 if (strcmp(name
, "album") == 0) return &tagv_album_hash_t
;
584 if (strcmp(name
, "title") == 0) return &tagv_title_hash_t
;
585 if (strcmp(name
, "genre") == 0) return &tagv_genre_hash_t
;
586 if (strcmp(name
, "year") == 0) return &tagv_year_hash
;
587 if (strcmp(name
, "oartist") == 0) return &tagv_artist_hash_o
;
588 if (strcmp(name
, "oalbum") == 0) return &tagv_album_hash_o
;
589 if (strcmp(name
, "otitle") == 0) return &tagv_title_hash_o
;
590 if (strcmp(name
, "ogenre") == 0) return &tagv_genre_hash_o
;
591 if (strcmp(name
, "oyear") == 0) return &tagv_year_hash
;
596 // offset in file_info_t
597 static inline int special_ofs (const char *name
) {
598 if (strcmp(name
, "artist") == 0) return __builtin_offsetof(file_info_t
, artist_t
);
599 if (strcmp(name
, "album") == 0) return __builtin_offsetof(file_info_t
, album_t
);
600 if (strcmp(name
, "title") == 0) return __builtin_offsetof(file_info_t
, title_t
);
601 if (strcmp(name
, "genre") == 0) return __builtin_offsetof(file_info_t
, genre_t
);
602 if (strcmp(name
, "year") == 0) return __builtin_offsetof(file_info_t
, year_t
);
603 if (strcmp(name
, "oartist") == 0) return __builtin_offsetof(file_info_t
, artist_o
);
604 if (strcmp(name
, "oalbum") == 0) return __builtin_offsetof(file_info_t
, album_o
);
605 if (strcmp(name
, "otitle") == 0) return __builtin_offsetof(file_info_t
, title_o
);
606 if (strcmp(name
, "ogenre") == 0) return __builtin_offsetof(file_info_t
, genre_o
);
607 if (strcmp(name
, "oyear") == 0) return __builtin_offsetof(file_info_t
, year_o
);