disco-dl.c (9484B)
1 #include <stdarg.h> 2 #include <unistd.h> 3 #include <errno.h> 4 #include <stdlib.h> 5 #include <sys/stat.h> 6 #include <stdio.h> 7 #include <string.h> 8 #include <libgen.h> 9 #include <fts.h> 10 11 #include "config.h" 12 13 #define DIR_SEP "/" 14 15 typedef struct 16 { 17 char *band; 18 char *album; 19 char *genre; 20 int year; 21 char *url; 22 char *tracklist; 23 char *dir; 24 } Album; 25 26 typedef struct 27 { 28 char *tracknum; 29 char *title; 30 char *path; 31 Album *album; 32 } Track; 33 34 static int make_dir(const char *name); 35 static char *concat(const char *s1, const char *s2); 36 static Track *get_track(Album **album, char **track_name, unsigned int count); 37 static char *make_message(const char *str, ...); 38 static Album **get_albums(int *line_count); 39 static Album *get_album(char *line_buf, ssize_t line_size); 40 static void tag_album(Album *album); 41 static void dl_album(Album *album); 42 static void id3_tag(Track *track); 43 static void tag(char *tag, char *value, FILE **f1); 44 static void merge_file(char *prefile1, char *file2); 45 static void convert(Track *track); 46 static void fatal(const char *fmt, ...); 47 48 int 49 main(void) 50 { 51 int line_count = 0; 52 Album **albums; 53 54 albums = get_albums(&line_count); 55 56 for (int i = 0; i < line_count; i++) { 57 dl_album(albums[i]); 58 tag_album(albums[i]); 59 60 free(albums[i]->band); 61 free(albums[i]->album); 62 free(albums[i]->genre); 63 free(albums[i]->url); 64 free(albums[i]->tracklist); 65 free(albums[i]->dir); 66 free(albums[i]); 67 } 68 free(albums); 69 70 return EXIT_SUCCESS; 71 } 72 73 void 74 dl_album(Album *album) 75 { 76 char *genredir = make_message("%s%s%s", ROOT_DIR, DIR_SEP, album->genre); 77 char *banddir = make_message("%s%s%s", genredir, DIR_SEP, album->band); 78 char *albumdir = make_message("%s%s%s", banddir, DIR_SEP, album->album); 79 char *sys_command; 80 81 make_dir(ROOT_DIR); 82 make_dir(genredir); 83 make_dir(banddir); 84 int status = make_dir(albumdir); 85 if (status == 1) 86 fprintf(stdout, "Pathname already exists: %s\n", albumdir); 87 status = chdir(albumdir); 88 if (status != 0) 89 exit(status); 90 91 album->dir = albumdir; 92 93 94 sys_command = make_message("yt-dlp -x -f bestaudio -i -o \"%s/%(playlist_index)s - %(title)s.%(ext)s\" \"%s\"", albumdir, album->url); 95 system(sys_command); 96 97 free(sys_command); 98 free(genredir); 99 free(banddir); 100 } 101 102 void 103 tag_album(Album *album) 104 { 105 char *token; 106 char *tracklist; 107 unsigned int count = 0; 108 Track *track; 109 110 if (album->tracklist && *album->tracklist != '\0') { 111 tracklist = album->tracklist; 112 while ((token = strsep(&tracklist, ";")) != NULL) { 113 track = get_track(&album, &token, ++count); 114 if (track != NULL) { 115 if (track->title != NULL) { 116 printf("%s - %s\n", track->tracknum, track->title); 117 convert(track); 118 /* TODO: fix tagging implementation and remove from convert() 119 id3_tag(track); 120 */ 121 } 122 free(track->path); 123 free(track->tracknum); 124 free(track); 125 } 126 } 127 128 free(token); 129 free(tracklist); 130 } 131 } 132 133 Track * 134 get_track(Album **album, char **track_name, unsigned int count) 135 { 136 char *filename; 137 char *tracknumber; 138 FTS *ftsp; 139 FTSENT *p, *chp; 140 int fts_options = FTS_COMFOLLOW | FTS_LOGICAL | FTS_NOCHDIR; 141 char *pp[] = { (*album)->dir, NULL }; 142 Track *track = NULL; 143 char *substr; 144 145 146 if ((ftsp = fts_open(pp, fts_options, NULL)) == NULL) 147 fatal("Error during fts_open %s\n", pp); 148 149 chp = fts_children(ftsp, FTS_NAMEONLY); 150 151 if (chp == NULL) 152 fatal("Error during fts_children %s\n", pp); 153 154 while ((p = fts_read(ftsp)) != NULL) { 155 switch (p->fts_info) { 156 case FTS_F: 157 filename = basename(p->fts_path); 158 substr = strstr(filename, " "); 159 if (substr == NULL) 160 break; 161 tracknumber = calloc(3, sizeof(char)); 162 memcpy(tracknumber, filename, substr - filename); 163 if (atoi(tracknumber) == count) { 164 track = (Track*) malloc(sizeof(Track)); 165 if (track == NULL) 166 fatal("Fatal: failed to allocate bytes for track.\n"); 167 track->path = (char*) malloc(strlen(p->fts_path) + 1); 168 if (track->path == NULL) 169 fatal("Fatal: failed to allocate bytes for track->path.\n"); 170 track->title = *track_name; 171 track->tracknum = tracknumber; 172 track->album = *album; 173 strcpy(track->path, p->fts_path); 174 goto end; 175 } 176 free(tracknumber); 177 178 break; 179 default: 180 break; 181 } 182 } 183 end: 184 185 fts_close(ftsp); 186 187 return track; 188 } 189 190 void 191 convert(Track *track){ 192 char *sys_command; 193 char *path; 194 char *base; 195 196 base = strdup(track->path); 197 base = dirname(base); 198 199 path = make_message("%s%s%s - %s.mp3", base, DIR_SEP, track->tracknum, track->title); 200 sys_command = make_message("ffmpeg -loglevel error -i \"%s\" \"%s\"", track->path, path); 201 system(sys_command); 202 free(sys_command); 203 204 remove(track->path); 205 free(track->path); 206 track->path = strdup(path); 207 208 sys_command = make_message("id3 -t '%s' -a '%s' -l '%s' -y '%d' -n '%s' -g '%s' '%s'", 209 track->title, 210 track->album->band, 211 track->album->album, 212 track->album->year, 213 track->tracknum, 214 track->album->genre, 215 track->path); 216 system(sys_command); 217 218 free(path); 219 free(base); 220 free(sys_command); 221 } 222 223 Album ** 224 get_albums(int *line_count) 225 { 226 char *line_buf = NULL; 227 size_t line_buf_size = 0; 228 ssize_t line_size; 229 FILE *fp = fopen(FILENAME, "r"); 230 Album **albums = NULL; 231 232 if (!fp) 233 fatal("Error opening file '%s'\n", FILENAME); 234 235 line_size = getline(&line_buf, &line_buf_size, fp); 236 albums = (Album**) malloc(sizeof(Album)); 237 238 if (albums == NULL) 239 fatal("Fatal: failed to allocate bytes for albums.\n"); 240 241 if (line_size <= 0) 242 fatal("File '%s' is empty\n", FILENAME); 243 244 while (line_size >= 0) { 245 albums = (Album**) realloc(albums, (sizeof(Album) * (*line_count + 1))); 246 if (albums == NULL) 247 fatal("Fatal: failed to reallocate bytes for albums.\n"); 248 249 albums[*line_count] = get_album(line_buf, line_size); 250 251 line_size = getline(&line_buf, &line_buf_size, fp); 252 (*line_count)++; 253 } 254 255 free(line_buf); 256 fclose(fp); 257 258 return albums; 259 } 260 261 Album * 262 get_album(char *line_buf, ssize_t line_size) 263 { 264 Album *album = (Album*) malloc(sizeof(Album)); 265 if (album == NULL) 266 fatal("Fatal: failed to allocate bytes for album.\n"); 267 268 album->band = (char*) malloc(line_size); 269 album->album = (char*) malloc(line_size); 270 album->genre = (char*) malloc(line_size); 271 album->url = (char*) malloc(line_size); 272 album->tracklist = (char*) malloc(line_size); 273 274 if (album->band == NULL | album->album == NULL | album->genre == NULL | album->url == NULL | album->tracklist == NULL) 275 fatal("Fatal: failed to allocate bytes for album.\n"); 276 277 sscanf(line_buf, 278 "%[^|]|%[^|]|%[^|]|%d|%[^|]|%[^0]", 279 album->band, 280 album->album, 281 album->genre, 282 &album->year, 283 album->url, 284 album->tracklist); 285 286 return album; 287 } 288 289 int 290 make_dir(const char * name) 291 { 292 int status = EXIT_SUCCESS; 293 errno = 0; 294 int ret = mkdir(name, S_IRWXU); 295 if (ret == -1) { 296 switch (errno) { 297 case EACCES: 298 fatal("The parent directory does not allow write: %s\n", name); 299 case EEXIST: 300 status = 1; 301 break; 302 case ENAMETOOLONG: 303 fatal("Pathname is too long: %s\n", name); 304 default: 305 fatal("mkdir error: %s\n", name); 306 } 307 } 308 309 return status; 310 } 311 312 char * 313 concat(const char *s1, const char *s2) 314 { 315 char *result = malloc(strlen(s1) + strlen(s2) + 1); 316 if (result == NULL) 317 fatal("Fatal: failed to allocate bytes for concat.\n"); 318 319 strcpy(result, s1); 320 strcat(result, s2); 321 322 return result; 323 } 324 325 char * 326 make_message(const char *fmt, ...) 327 { 328 int n = 0; 329 size_t size = 0; 330 char *p = NULL; 331 va_list ap; 332 333 va_start(ap, fmt); 334 n = vsnprintf(p, size, fmt, ap); 335 va_end(ap); 336 337 if (n < 0) 338 return NULL; 339 340 size = (size_t) n + 1; 341 p = malloc(size); 342 if (p == NULL) 343 return NULL; 344 345 va_start(ap, fmt); 346 n = vsnprintf(p, size, fmt, ap); 347 va_end(ap); 348 349 if (n < 0) { 350 free(p); 351 return NULL; 352 } 353 354 return p; 355 } 356 357 void 358 id3_tag(Track *track) 359 { 360 char *tagfile = "taginf.txt"; 361 char yearstr[5]; 362 unsigned char pad[7] = { 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x76 }; 363 364 int len = snprintf(yearstr, 5, "%d", track->album->year); 365 FILE* fp; 366 fp = fopen(tagfile, "wb"); 367 if (fp == NULL) 368 fatal("Failed to open file %s\n", tagfile); 369 370 fprintf(fp, "ID3"); 371 fwrite(pad, sizeof(pad), 1, fp); 372 373 tag("TRCK", track->tracknum, &fp); 374 tag("TIT2", track->title, &fp); 375 tag("TYER", yearstr, &fp); 376 tag("TPE1", track->album->band, &fp); 377 tag("TALB", track->album->album, &fp); 378 tag("TCON", track->album->genre, &fp); 379 380 fclose(fp); 381 merge_file(tagfile, track->path); 382 } 383 384 void tag 385 (char *tag, char *value, FILE **f1) 386 { 387 int size = 0; 388 unsigned char pad[3] = { 0x00, 0x00, 0x00 }; 389 390 fprintf(*f1, tag); 391 fwrite(pad, sizeof(pad), 1, *f1); 392 size = strlen(value) + 1; 393 fprintf(*f1, "%c", size); 394 fwrite(pad, sizeof(pad), 1, *f1); 395 fprintf(*f1, "%s", value); 396 } 397 398 void 399 merge_file(char *prefile, char *file) 400 { 401 FILE *f1, *f2, *f3; 402 char *tmp = "tmp.txt"; 403 int i = 0; 404 int ch; 405 f1 = fopen(prefile, "rb"); 406 f2 = fopen(file, "rb"); 407 f3 = fopen(tmp, "wb"); 408 409 if (f1 == NULL) 410 fatal("Failed to open file %s\n", prefile); 411 if (f2 == NULL) 412 fatal("Failed to open file %s\n", file); 413 if (f3 == NULL) 414 fatal("Failed to open file %s\n", tmp); 415 416 /* 417 while ((ch = fgetc(f2)) != EOF && ++i <= 34) 418 fputc(ch, f3); 419 420 while ((ch = fgetc(f1)) != EOF) 421 fputc(ch, f3); 422 423 i = 0; 424 while ((ch = fgetc(f2)) != EOF) { 425 if (++i > 34) 426 fputc(ch, f3); 427 } 428 */ 429 430 while ((ch = fgetc(f1)) != EOF) 431 fputc(ch, f3); 432 433 while ((ch = fgetc(f2)) != EOF) 434 fputc(ch, f3); 435 436 fclose(f1); 437 fclose(f2); 438 fclose(f3); 439 440 rename(tmp, file); 441 remove(prefile); 442 remove(tmp); 443 } 444 445 void 446 fatal(const char *fmt, ...) 447 { 448 va_list ap; 449 450 va_start(ap, fmt); 451 vfprintf(stderr, fmt, ap); 452 va_end(ap); 453 454 exit(EXIT_FAILURE); 455 } 456