fusedrv: fixed some comments, added some funcions; purely cosmetic change (except...
[k8muffin.git] / src / tagscan.c
blob2f858db8c8dcd114bc9154049225039cf5555b8c
1 #ifndef _BSD_SOURCE
2 # define _BSD_SOURCE
3 #endif
4 #include <ctype.h>
5 #include <dirent.h>
6 #include <iconv.h>
7 #include <limits.h>
8 #include <stdint.h>
9 #include <stdio.h>
10 #include <stdlib.h>
11 #include <string.h>
12 #include <unistd.h>
14 #include <sys/stat.h>
15 #include <sys/types.h>
17 #include <tag_c.h>
19 #include "libdbg/dbglog.h"
20 #include "uthash.h"
23 ////////////////////////////////////////////////////////////////////////////////
24 typedef struct {
25 //char *name;
26 ino_t inode;
27 UT_hash_handle hh;
28 } dirlist_t;
31 static dirlist_t *direxcept = NULL;
34 ////////////////////////////////////////////////////////////////////////////////
35 typedef struct {
36 char *filename;
37 uint64_t inode;
38 uint64_t size;
39 uint64_t mtime;
40 uint16_t year; // can be 0
41 uint8_t track; // can be 0
42 char *artist; // can't be NULL
43 char *album; // can't be NULL
44 char *title; // can't be NULL
45 char *genre; // can be NULL
46 } tagfile_info_t;
49 ////////////////////////////////////////////////////////////////////////////////
50 static inline char *dupstrn (const void *ptr, int size) {
51 char *res = malloc(size+1);
52 if (size > 0) memcpy(res, ptr, size);
53 res[size] = 0;
54 return res;
58 #define GETFIELDS(tagname,fldname) do { \
59 if (strcmp(nm, tagname) == 0) { \
60 if (fi->fldname != NULL) free(fi->fldname); \
61 fi->fldname = dupstrn(p, sz); \
62 goto goon; \
63 } \
64 } while (0)
67 #define GETFIELDN(tagname,fldname,nsz) do { \
68 if (strcmp(nm, tagname) == 0) { \
69 if (sz != nsz) goto error; \
70 memcpy(&fi->fldname, p, nsz); \
71 goto goon; \
72 } \
73 } while (0)
76 // and skip to next one
77 static int finfo_read (FILE *fl, tagfile_info_t *fi) {
78 uint16_t size;
79 static char buf[65536], nm[65536];
80 char *p = buf;
81 memset(fi, 0, sizeof(*fi));
82 if (fread(&size, 2, 1, fl) != 1 || size < 1) return -1;
83 if (fread(buf, size, 1, fl) != 1) return -1;
84 while (size >= 2) {
85 uint16_t sz;
86 // name
87 memcpy(&sz, p, 2);
88 p += 2;
89 size -= 2;
90 if (sz > size) goto error;
91 size -= sz;
92 if (sz > 0) {
93 memcpy(nm, p, sz);
94 p += sz;
96 nm[sz] = 0;
97 // value
98 if (size < 2) goto error;
99 memcpy(&sz, p, 2);
100 p += 2;
101 size -= 2;
102 if (sz > size) goto error;
103 size -= sz;
105 GETFIELDS("filename", filename);
106 GETFIELDS("artist", artist);
107 GETFIELDS("album", album);
108 GETFIELDS("title", title);
109 GETFIELDS("genre", genre);
110 GETFIELDN("inode", inode, 8);
111 GETFIELDN("size", size, 8);
112 GETFIELDN("mtime", mtime, 8);
113 GETFIELDN("year", year, 2);
114 GETFIELDN("track", track, 1);
115 goon:
116 p += sz;
118 if (fi->filename == NULL) goto error;
119 if (size != 0) goto error;
120 return 0;
121 error:
122 if (fi->genre != NULL) free(fi->genre);
123 if (fi->title != NULL) free(fi->title);
124 if (fi->album != NULL) free(fi->album);
125 if (fi->artist != NULL) free(fi->artist);
126 if (fi->filename != NULL) free(fi->filename);
127 memset(fi, 0, sizeof(*fi));
128 return -1;
132 static void finfo_clear (tagfile_info_t *fi) {
133 if (fi != NULL) {
134 if (fi->genre != NULL) free(fi->genre);
135 if (fi->title != NULL) free(fi->title);
136 if (fi->album != NULL) free(fi->album);
137 if (fi->artist != NULL) free(fi->artist);
138 if (fi->filename != NULL) free(fi->filename);
139 memset(fi, 0, sizeof(*fi));
144 ////////////////////////////////////////////////////////////////////////////////
145 static tagfile_info_t *filelist = NULL;
146 static int fcount = 0;
147 static int fallocated = 0;
150 static void flist_clear (void) {
151 if (filelist != NULL) {
152 for (int f = fcount-1; f >= 0; --f) finfo_clear(&filelist[f]);
153 free(filelist);
154 fcount = fallocated = 0;
159 static tagfile_info_t *add_fname (const char *name, int doadd, int *isnew) {
160 int imin = 0;
161 if (name == NULL || !name[0]) abort();
162 if (fcount > 0) {
163 int imax = fcount-1, cmp;
164 // continually narrow search until just one element remains
165 while (imin < imax) {
166 int imid = (imin+imax)/2; // we will never overflow here; no, really!
167 //assert(imid < imax);
168 // note: 0 <= imin < imax implies imid will always be less than imax
169 // reduce the search
170 if (strcmp(filelist[imid].filename, name) < 0) imin = imid+1; else imax = imid;
172 // At exit of while:
173 // if A[] is empty, then imax < imin
174 // otherwise imax == imin
175 //if (imax != imin || imin < 0 || imin >= fcount) abort(); // the thing that should not be!
176 // deferred test for equality
177 if ((cmp = strcmp(filelist[imin].filename, name)) == 0) {
178 if (isnew != NULL) *isnew = 0;
179 return &filelist[imin]; // got it!
181 imin += (cmp < 0 ? 1 : 0); // newpos
183 if (isnew != NULL) *isnew = 1;
184 if (doadd) {
185 // add new
186 if (fcount+1 > fallocated) {
187 int newsz = ((fallocated+1)|0xfff)+1;
188 tagfile_info_t *nfl = realloc(filelist, sizeof(filelist[0])*newsz);
189 if (nfl == NULL) { fprintf(stderr, "FATAL: out of memory!\n"); abort(); }
190 filelist = nfl;
191 fallocated = newsz;
193 //fprintf(stderr, " fcount=%d; insertto=%d\n", fcount, imin);
194 // move
195 for (int f = fcount; f > imin; --f) filelist[f] = filelist[f-1];
196 ++fcount;
197 memset(&filelist[imin], 0, sizeof(filelist[imin]));
198 return &filelist[imin];
200 return NULL; // not found
204 ////////////////////////////////////////////////////////////////////////////////
205 static int load_tagfile (const char *fname) {
206 FILE *fl;
207 char sign[4];
208 uint32_t fc;
209 fl = fopen(fname, "r");
210 if (fl == NULL) return 0; // no tags yet
211 if (fread(sign, 4, 1, fl) != 1) goto error;
212 if (memcmp(sign, "TFI0", 4) != 0) goto error;
213 if (fread(&fc, 4, 1, fl) != 1) goto error;
214 printf("loading %u files...\n", (unsigned int)fc);
215 while (fc-- > 0) {
216 tagfile_info_t fi, *newfi;
217 struct stat st;
218 int isnew;
219 if (finfo_read(fl, &fi) != 0) goto error;
220 if (stat(fi.filename, &st) != 0) { finfo_clear(&fi); continue; }
221 if (!S_ISREG(st.st_mode)) { finfo_clear(&fi); continue; }
222 if (st.st_ino != fi.inode || st.st_mtime != fi.mtime || st.st_size != fi.size) { finfo_clear(&fi); continue; } // file was changed, drop and rescan
223 newfi = add_fname(fi.filename, 1, &isnew);
224 if (!isnew) finfo_clear(newfi);
225 *newfi = fi;
227 fclose(fl);
228 printf("%d files loaded\n", fcount);
229 return 0;
230 error:
231 // invalid tag file
232 fclose(fl);
233 return -1;
237 ////////////////////////////////////////////////////////////////////////////////
238 static int write_str (FILE *fo, const char *str) {
239 uint16_t ln;
240 int len;
241 if (str == NULL) abort();
242 len = strlen(str);
243 if (len > 4095) len = 4095;
244 ln = len;
245 if (fwrite(&ln, 2, 1, fo) != 1) return -1;
246 if (ln > 0 && fwrite(str, ln, 1, fo) != 1) return -1;
247 return 0;
251 static int write_strtag (FILE *fo, const char *name, const char *value) {
252 if (value != NULL) {
253 if (write_str(fo, name) < 0) return -1;
254 if (write_str(fo, value) < 0) return -1;
256 return 0;
260 static int write_numtag (FILE *fo, const char *name, const void *value, size_t sz) {
261 const uint8_t *v = (const uint8_t *)value;
262 int allzero = 1;
263 for (size_t f = 0; f < sz; ++f) if (v[f] != 0) { allzero = 0; break; }
264 if (!allzero) {
265 uint16_t wsz = sz;
266 if (write_str(fo, name) < 0) return -1;
267 if (fwrite(&wsz, 2, 1, fo) != 1) return -1;
268 if (fwrite(value, sz, 1, fo) != 1) return -1;
270 return 0;
274 static int write_tagfile (const char *filename) {
275 FILE *fo;
276 uint32_t fc = fcount;
277 fo = fopen(filename, "w");
278 if (fo == NULL) return -1;
279 if (fwrite("TFI0", 4, 1, fo) != 1) goto error;
280 if (fwrite(&fc, 4, 1, fo) != 1) goto error;
281 for (int f = 0; f < fcount; ++f) {
282 const tagfile_info_t *fi = &filelist[f];
283 long pos = ftell(fo), epos;
284 uint16_t size = 0;
285 if (fwrite(&size, 2, 1, fo) != 1) goto error; // will be fixed later
286 if (write_strtag(fo, "filename", fi->filename) < 0) goto error;
287 if (write_strtag(fo, "artist", fi->artist) < 0) goto error;
288 if (write_strtag(fo, "album", fi->album) < 0) goto error;
289 if (write_strtag(fo, "title", fi->title) < 0) goto error;
290 if (write_strtag(fo, "genre", fi->genre) < 0) goto error;
291 if (write_numtag(fo, "year", &fi->year, 2) < 0) goto error;
292 if (write_numtag(fo, "track", &fi->track, 1) < 0) goto error;
293 if (write_numtag(fo, "inode", &fi->inode, 8) < 0) goto error;
294 if (write_numtag(fo, "size", &fi->size, 8) < 0) goto error;
295 if (write_numtag(fo, "mtime", &fi->mtime, 8) < 0) goto error;
296 epos = ftell(fo);
297 if (epos-(pos+2) > 65535) goto error;
298 size = epos-(pos+2);
299 if (fseek(fo, pos, SEEK_SET) < 0) goto error;
300 if (fwrite(&size, 2, 1, fo) != 1) goto error;
301 if (fseek(fo, epos, SEEK_SET) < 0) goto error;
303 fclose(fo);
304 return 0;
305 error:
306 fclose(fo);
307 return -1;
311 ////////////////////////////////////////////////////////////////////////////////
312 static char *w2u (const char *str, int justcheck) {
313 iconv_t cd;
314 size_t il, ol, ool;
315 char *outs, *ibuf, *obuf, *res;
316 int asis = 1;
317 if (str == NULL) return (justcheck ? "" : strdup(""));
318 if (str == NULL || !str[0]) return (justcheck ? (char *)str : strdup(str));
319 for (const unsigned char *u = (const unsigned char *)str; *u; ++u) if (*u >= 128) { asis = 0; break; }
320 if (asis) return (justcheck ? (char *)str : strdup(str));
321 cd = iconv_open("utf-8", "cp1251");
322 if (cd == (iconv_t)-1) return NULL;
323 outs = malloc(strlen(str)*6+4);
324 if (outs == NULL) {
325 iconv_close(cd);
326 return NULL;
328 ibuf = (char *)str;
329 obuf = outs;
330 il = strlen(str);
331 ool = ol = il*4;
332 il = iconv(cd, &ibuf, &il, &obuf, &ol);
333 iconv_close(cd);
334 if (il == (size_t)-1) {
335 free(outs);
336 return NULL;
338 res = calloc(ool-ol+1, 1);
339 if (ool-ol > 0) memcpy(res, outs, ool-ol);
340 free(outs);
341 if (justcheck) free(res);
342 return res;
346 ////////////////////////////////////////////////////////////////////////////////
347 static char *str_convert (const char *str, const char *def, const char *fname, int toutf) {
348 const char *ostr = str;
349 char *ss, *s;
350 int prevwasspace = 0;
351 if (str == NULL || !str[0]) {
352 if (def == NULL) return NULL; // skip this tag
353 ostr = str = def;
355 if (toutf) {
356 char *t = w2u(str, 0);
357 if (t == NULL) {
358 dlogf("FUCK! %s: [%s]\n", fname, str);
359 abort();
361 //printf("u: [%s] -> [%s]\n", str, t);
362 ostr = str = t;
364 s = ss = alloca(strlen(str)+1);
365 while (*str && isspace(*str)) ++str;
366 while (*str) {
367 if (!isspace(*str)) {
368 *ss++ = *str++;
369 prevwasspace = 0;
370 } else {
371 if (!prevwasspace) {
372 *ss++ = ' ';
373 prevwasspace = 1;
375 ++str;
378 *ss = 0;
379 while (*s && isspace(s[strlen(s)-1])) s[strlen(s)-1] = 0;
380 if (strcmp(s, ostr) != 0) dlogf("%s: [%s] -> [%s]\n", fname, ostr, s);
381 if (toutf) free((void *)ostr);
382 if (!s[0]) return (def != NULL ? strdup(def) : NULL);
383 return strdup(s);
387 ////////////////////////////////////////////////////////////////////////////////
388 static const unsigned char utf8Length[256] = {
389 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, //0x00-0x0f
390 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, //0x10-0x1f
391 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, //0x20-0x2f
392 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, //0x30-0x3f
393 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, //0x40-0x4f
394 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, //0x50-0x5f
395 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, //0x60-0x6f
396 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, //0x70-0x7f
397 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, //0x80-0x8f
398 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, //0x90-0x9f
399 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, //0xa0-0xaf
400 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, //0xb0-0xbf
401 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, //0xc0-0xcf c0-c1: overlong encoding: start of a 2-byte sequence, but code point <= 127
402 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, //0xd0-0xdf
403 3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3, //0xe0-0xef
404 4,4,4,4,4,8,8,8,8,8,8,8,8,8,8,8 //0xf0-0xff
408 static inline int is_valid_utf8 (const void *buf) {
409 const unsigned char *data = (const unsigned char *)buf;
410 while (*data) {
411 unsigned char len = utf8Length[*data];
412 uint32_t uc;
413 switch (len) {
414 case 0: // ascii
415 data += 1;
416 continue;
417 case 8: case 9: // invalid
418 return 0;
420 // utf-8
421 uc = (*data++)&(0x7c>>len);
422 while (--len) {
423 if (!data[0]) return 0;
424 if (utf8Length[*data] != 9) return 0;
425 uc = (uc<<6)|((*data++)&0x3f);
427 if (uc > 0x10ffff) return 0;
428 if ((uc >= 0xd800 && uc <= 0xdfff) || // utf16/utf32 surrogates
429 (uc >= 0xfdd0 && uc <= 0xfdef) || // just for fun
430 (uc >= 0xfffe && uc <= 0xffff)) return 0; // bad unicode
432 return 1;
436 static inline int notutf (const char *str) {
437 int havehi = 0;
438 for (const unsigned char *s = (const unsigned char *)str; *s; ++s) if (*s >= 128) { havehi = 1; break; }
439 if (havehi) {
440 //printf("[%s]: %d\n", str, is_valid_utf8(str));
441 return (is_valid_utf8(str) == 0);
443 return 0;
447 ////////////////////////////////////////////////////////////////////////////////
448 static int fill_tags (const char *filename, tagfile_info_t *fi) {
449 int res = 0;
450 TagLib_Tag *tag;
451 //const TagLib_AudioProperties *properties;
452 TagLib_File *file;
453 const char *ext = strrchr(filename, '.');
454 int dotest;
455 struct stat st;
456 if (ext == NULL || (strcasecmp(ext, ".mp3") == 0)) {
457 taglib_set_strings_unicode(0);
458 dotest = 1;
459 } else {
460 taglib_set_strings_unicode(1);
461 dotest = 0;
463 file = taglib_file_new(filename);
464 if (file == NULL) return 0;
465 if (stat(filename, &st) != 0) {
466 taglib_tag_free_strings();
467 taglib_file_free(file);
468 return 0;
470 tag = taglib_file_tag(file);
471 if (tag != NULL) {
472 int toutf;
474 if (!dotest) goto doutf8;
475 if (notutf(taglib_tag_artist(tag)) ||
476 notutf(taglib_tag_album(tag)) ||
477 notutf(taglib_tag_title(tag)) ||
478 notutf(taglib_tag_genre(tag))) {
479 if (w2u(taglib_tag_artist(tag), 1) == NULL) goto doutf8;
480 if (w2u(taglib_tag_album(tag), 1) == NULL) goto doutf8;
481 if (w2u(taglib_tag_title(tag), 1) == NULL) goto doutf8;
482 if (w2u(taglib_tag_genre(tag), 1) == NULL) goto doutf8;
483 toutf = 1;
484 } else {
485 doutf8:
486 taglib_tag_free_strings();
487 taglib_file_free(file);
488 taglib_set_strings_unicode(1);
489 file = taglib_file_new(filename);
490 if (file == NULL) return 0;
491 tag = taglib_file_tag(file);
492 if (tag == NULL) {
493 taglib_tag_free_strings();
494 taglib_file_free(file);
495 return 0;
497 toutf = 0;
500 memset(fi, 0, sizeof(*fi));
501 //TODO: check for memory errors!
502 fi->filename = strdup(filename);
503 fi->inode = st.st_ino;
504 fi->size = st.st_size;
505 fi->mtime = st.st_mtime;
506 fi->artist = str_convert(taglib_tag_artist(tag), NULL, filename, toutf);
507 fi->album = str_convert(taglib_tag_album(tag), NULL, filename, toutf);
508 fi->title = str_convert(taglib_tag_title(tag), NULL, filename, toutf);
509 fi->genre = str_convert(taglib_tag_genre(tag), NULL, filename, toutf);
510 fi->year = taglib_tag_year(tag);
511 // sanitize year
512 if (fi->year > 0) {
513 if (fi->year < 30) fi->year += 2000;
514 else if (fi->year < 100) fi->year += 1900;
515 else if (fi->year < 1930) fi->year = 0;
518 fi->track = (taglib_tag_track(tag) < 256 ? taglib_tag_track(tag) : 0);
519 // fuck comments
520 res = 1;
524 properties = taglib_file_audioproperties(file);
525 if (properties != NULL) {
526 int seconds = taglib_audioproperties_length(properties)%60;
527 int minutes = (taglib_audioproperties_length(properties)-seconds)/60;
528 printf("-- AUDIO --\n");
529 printf("bitrate : %d\n", taglib_audioproperties_bitrate(properties));
530 printf("sample rate: %d\n", taglib_audioproperties_samplerate(properties));
531 printf("channels : %d\n", taglib_audioproperties_channels(properties));
532 printf("length : %d:%02i\n", minutes, seconds);
536 taglib_tag_free_strings();
537 taglib_file_free(file);
538 return res;
542 ////////////////////////////////////////////////////////////////////////////////
543 // 0: normal file; 1: dir; <0: error
545 static inline int getftype (const char *fname) {
546 struct stat st;
547 if (stat(fname, &st) == 0) {
548 if (S_ISDIR(st.st_mode)) return 1;
549 if (S_ISREG(st.st_mode)) return 0;
551 return -1;
556 static void process_dir (const char *path) {
557 struct stat st;
558 if (stat(path, &st) == 0 && S_ISDIR(st.st_mode)) {
559 DIR *dp = opendir(path);
560 if (dp != NULL) {
561 struct dirent *de;
562 char *rd, *fn;
564 dirlist_t *exc;
565 static char ppbuf[PATH_MAX+1];
566 char *pp = realpath(path, ppbuf);
567 if (pp == NULL || pp[0] != '/') {
568 fprintf(stderr, "FATAL: realpath(\"%s\") fucked! [%s]\n", path, pp);
569 abort();
571 //rd = malloc(strlen(pp)+4);
572 rd = alloca(strlen(pp)+4);
573 strcpy(rd, pp);
574 if (rd[strlen(rd)-1] != '/') strcat(rd, "/");
575 HASH_FIND(hh, direxcept, &st.st_ino, sizeof(st.st_ino), exc);
576 if (exc != NULL) {
577 printf("***SKIP: [%s]\n", rd);
578 closedir(dp);
579 return;
581 // add this dir to 'exception list', so we will not loop
582 exc = malloc(sizeof(*exc));
583 exc->inode = st.st_ino;
584 HASH_ADD_KEYPTR(hh, direxcept, &exc->inode, sizeof(exc->inode), exc);
586 //fn = malloc(strlen(rd)+8192);
587 fn = alloca(strlen(rd)+257);
589 printf("scanning: [%s] (%d)\n", rd, fcount);
590 while ((de = readdir(dp)) != NULL) {
591 struct stat st;
592 if (de->d_name[0] == '.' && (!de->d_name[1] || (de->d_name[1] == '.' && !de->d_name[2]))) continue;
593 if (!de->d_name[0]) continue; // just in case
594 sprintf(fn, "%s%s", rd, de->d_name);
595 if (stat(fn, &st) != 0) continue;
596 if (S_ISDIR(st.st_mode)) {
597 //strcat(fn, "/");
598 //fprintf(stderr, "[%s]\n", fn);
599 process_dir(fn);
600 } else if (S_ISREG(st.st_mode)) {
601 tagfile_info_t *fi = add_fname(fn, 0, NULL);
602 if (fi == NULL) {
603 // new file
604 tagfile_info_t i;
605 int res = fill_tags(fn, &i);
606 if (res < 0) { fprintf(stderr, "FATAL: writing error!\n"); abort(); }
607 if (res > 0) {
608 fi = add_fname(fn, 1, NULL);
609 *fi = i;
614 //free(fn);
615 //free(rd);
617 closedir(dp);
622 ////////////////////////////////////////////////////////////////////////////////
623 int main (int argc, char *argv[]) {
624 dbglog_set_screenout(0);
625 dbglog_set_fileout(0);
627 for (int f = 2; f < argc; ++f) {
628 if (strcmp(argv[f], "--except") == 0) {
629 struct stat st;
630 if (f+1 >= argc) {
631 fprintf(stderr, "FATAL: except what?\n");
632 return -1;
634 if (stat(argv[f+1], &st) == 0 && S_ISDIR(st.st_mode)) {
635 dirlist_t *t;
636 t = malloc(sizeof(*t));
637 t->inode = st.st_ino;
638 HASH_ADD_KEYPTR(hh, direxcept, &t->inode, sizeof(t->inode), t);
640 for (int c = f+2; c < argc; ++c) argv[c-2] = argv[c];
641 argc -= 2;
642 --f;
646 if (argc < 2) {
647 fprintf(stderr, "WTF?!\n");
648 return -1;
651 if (load_tagfile(argv[1]) != 0) { fprintf(stderr, "FATAL: can't open tagfile!\n"); return -1; }
653 if (argc >= 3) {
654 int oldfc;
655 //taglib_set_strings_unicode(1); // use UTF8
656 taglib_set_strings_unicode(0); // use UTF8
657 //taglib_id3v2_set_default_text_encoding(TagLib_ID3v2_UTF8); // used only for writing
658 oldfc = fcount;
659 for (int f = 2; f < argc; ++f) process_dir(argv[f]);
660 write_tagfile(argv[1]);
661 printf("%d new files processed\n", fcount-oldfc);
665 for (int f = 0; f < fcount; ++f) {
666 const tagfile_info_t *tfi = &filelist[f];
667 if (tfi->artist != NULL && strcasecmp(tfi->artist, "lustre") == 0) {
668 printf("lustre: [%s]\n", tfi->filename);
673 flist_clear();
674 return 0;