disco-dl.c (8273B)
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 #include <id3.h> 11 12 #include "config.h" 13 14 #define DIR_SEP "/" 15 16 typedef struct 17 { 18 char *band; 19 char *album; 20 char *genre; 21 int year; 22 char *url; 23 char *tracklist; 24 char *dir; 25 } Album; 26 27 typedef struct 28 { 29 char *tracknum; 30 char *title; 31 char *path; 32 Album *album; 33 } Track; 34 35 static int make_dir(const char *name); 36 static char *make_message(const char *str, ...); 37 static Track *get_track(Album **album, char **track_name, int count); 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 set_tag(ID3Tag *tag, ID3_FrameID frameId, char *value); 42 static void id3_tag(Track *track); 43 static void dl_album(Album *album); 44 static void convert(Track *track); 45 static void fatal(const char *fmt, ...); 46 47 int 48 main(void) 49 { 50 int line_count = 0; 51 Album **albums; 52 53 albums = get_albums(&line_count); 54 55 for (int i = 0; i < line_count; i++) { 56 dl_album(albums[i]); 57 tag_album(albums[i]); 58 59 free(albums[i]->band); 60 free(albums[i]->album); 61 free(albums[i]->genre); 62 free(albums[i]->url); 63 free(albums[i]->tracklist); 64 free(albums[i]->dir); 65 free(albums[i]); 66 } 67 free(albums); 68 69 return EXIT_SUCCESS; 70 } 71 72 void 73 dl_album(Album *album) 74 { 75 char *genredir = make_message("%s%s%s", ROOT_DIR, DIR_SEP, album->genre); 76 char *banddir = make_message("%s%s%s", genredir, DIR_SEP, album->band); 77 char *albumdir = make_message("%s%s%s", banddir, DIR_SEP, album->album); 78 char *sys_command; 79 80 make_dir(ROOT_DIR); 81 make_dir(genredir); 82 make_dir(banddir); 83 int status = make_dir(albumdir); 84 if (status == 1) 85 fprintf(stdout, "Pathname already exists: %s\n", albumdir); 86 status = chdir(albumdir); 87 if (status != 0) 88 exit(status); 89 90 album->dir = albumdir; 91 92 sys_command = make_message( 93 "yt-dlp -x -f bestaudio -i -o \"%s/%(playlist_index)s - %(title)s.%(ext)s\" \"%s\"", 94 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 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 id3_tag(track); 119 } 120 free(track->path); 121 free(track->tracknum); 122 free(track); 123 } 124 } 125 126 free(token); 127 free(tracklist); 128 } 129 } 130 131 Track * 132 get_track(Album **album, char **track_name, int count) 133 { 134 char *filename; 135 char *tracknumber; 136 FTS *ftsp; 137 FTSENT *p, *chp; 138 int fts_options = FTS_COMFOLLOW | FTS_LOGICAL | FTS_NOCHDIR; 139 char *pp[] = { (*album)->dir, NULL }; 140 Track *track = NULL; 141 char *substr; 142 143 144 if ((ftsp = fts_open(pp, fts_options, NULL)) == NULL) 145 fatal("Error during fts_open %s\n", pp); 146 147 chp = fts_children(ftsp, FTS_NAMEONLY); 148 149 if (chp == NULL) 150 fatal("Error during fts_children %s\n", pp); 151 152 while ((p = fts_read(ftsp)) != NULL) { 153 switch (p->fts_info) { 154 case FTS_F: 155 filename = basename(p->fts_path); 156 substr = strstr(filename, " "); 157 if (substr == NULL) 158 break; 159 tracknumber = calloc(3, sizeof(char)); 160 memcpy(tracknumber, filename, substr - filename); 161 if (atoi(tracknumber) == count) { 162 track = (Track*) malloc(sizeof(Track)); 163 if (track == NULL) 164 fatal("Fatal: failed to allocate bytes for track.\n"); 165 track->path = (char*) malloc(strlen(p->fts_path) + 1); 166 if (track->path == NULL) 167 fatal("Fatal: failed to allocate bytes for track->path.\n"); 168 track->title = *track_name; 169 track->tracknum = tracknumber; 170 track->album = *album; 171 strcpy(track->path, p->fts_path); 172 goto end; 173 } 174 free(tracknumber); 175 176 break; 177 default: 178 break; 179 } 180 } 181 end: 182 183 fts_close(ftsp); 184 185 return track; 186 } 187 188 void 189 convert(Track *track){ 190 char *sys_command; 191 char *path; 192 char *base; 193 194 base = strdup(track->path); 195 base = dirname(base); 196 197 path = make_message("%s%s%s - %s.mp3", base, DIR_SEP, track->tracknum, track->title); 198 sys_command = make_message("ffmpeg -loglevel error -i \"%s\" \"%s\"", track->path, path); 199 system(sys_command); 200 free(sys_command); 201 202 remove(track->path); 203 free(track->path); 204 track->path = strdup(path); 205 206 free(path); 207 free(base); 208 } 209 210 Album ** 211 get_albums(int *line_count) 212 { 213 char *line_buf = NULL; 214 size_t line_buf_size = 0; 215 ssize_t line_size; 216 FILE *fp; 217 Album **albums; 218 219 if ((fp = fopen(FILENAME, "r")) == NULL) 220 fatal("Error opening file '%s'\n", FILENAME); 221 222 line_size = getline(&line_buf, &line_buf_size, fp); 223 224 if ((albums = (Album**) malloc(sizeof(Album))) == NULL) 225 fatal("Fatal: failed to allocate bytes for albums.\n"); 226 227 if (line_size <= 0) 228 fatal("File '%s' is empty\n", FILENAME); 229 230 while (line_size >= 0) { 231 albums = (Album**) realloc(albums, (sizeof(Album) * (*line_count + 1))); 232 if (albums == NULL) 233 fatal("Fatal: failed to reallocate bytes for albums.\n"); 234 235 albums[*line_count] = get_album(line_buf, line_size); 236 237 line_size = getline(&line_buf, &line_buf_size, fp); 238 (*line_count)++; 239 } 240 241 free(line_buf); 242 fclose(fp); 243 244 return albums; 245 } 246 247 Album * 248 get_album(char *line_buf, ssize_t line_size) 249 { 250 Album *album = (Album*) malloc(sizeof(Album)); 251 if (album == NULL) 252 fatal("Fatal: failed to allocate bytes for album.\n"); 253 254 album->band = (char*) malloc(line_size); 255 album->album = (char*) malloc(line_size); 256 album->genre = (char*) malloc(line_size); 257 album->url = (char*) malloc(line_size); 258 album->tracklist = (char*) malloc(line_size); 259 260 if (album->band == NULL 261 || album->album == NULL 262 || album->genre == NULL 263 || album->url == NULL 264 || album->tracklist == NULL) 265 fatal("Fatal: failed to allocate bytes for album.\n"); 266 267 sscanf(line_buf, 268 "%[^|]|%[^|]|%[^|]|%d|%[^|]|%[^0]", 269 album->band, 270 album->album, 271 album->genre, 272 &album->year, 273 album->url, 274 album->tracklist); 275 276 return album; 277 } 278 279 int 280 make_dir(const char * name) 281 { 282 int status = EXIT_SUCCESS; 283 errno = 0; 284 int ret = mkdir(name, S_IRWXU); 285 if (ret == -1) { 286 switch (errno) { 287 case EACCES: 288 fatal("The parent directory does not allow write: %s\n", name); 289 break; 290 case EEXIST: 291 status = 1; 292 break; 293 case ENAMETOOLONG: 294 fatal("Pathname is too long: %s\n", name); 295 break; 296 default: 297 fatal("mkdir error: %s\n", name); 298 break; 299 } 300 } 301 302 return status; 303 } 304 305 char * 306 concat(const char *s1, const char *s2) 307 { 308 char *result = malloc(strlen(s1) + strlen(s2) + 1); 309 if (result == NULL) 310 fatal("Fatal: failed to allocate bytes for concat.\n"); 311 312 strcpy(result, s1); 313 strcat(result, s2); 314 315 return result; 316 } 317 318 char * 319 make_message(const char *fmt, ...) 320 { 321 int n = 0; 322 size_t size = 0; 323 char *p = NULL; 324 va_list ap; 325 326 va_start(ap, fmt); 327 n = vsnprintf(p, size, fmt, ap); 328 va_end(ap); 329 330 if (n < 0) 331 return NULL; 332 333 size = (size_t) n + 1; 334 p = malloc(size); 335 if (p == NULL) 336 return NULL; 337 338 va_start(ap, fmt); 339 n = vsnprintf(p, size, fmt, ap); 340 va_end(ap); 341 342 if (n < 0) { 343 free(p); 344 return NULL; 345 } 346 347 return p; 348 } 349 350 void 351 id3_tag(Track *track) 352 { 353 ID3Tag *tag; 354 char yearstr[5]; 355 356 if ((tag = ID3Tag_New()) != NULL) { 357 snprintf(yearstr, 5, "%d", track->album->year); 358 359 (void) ID3Tag_Link(tag, track->path); 360 361 set_tag(tag, ID3FID_TITLE, track->title); 362 set_tag(tag, ID3FID_YEAR, yearstr); 363 set_tag(tag, ID3FID_LEADARTIST, track->album->band); 364 set_tag(tag, ID3FID_ALBUM, track->album->album); 365 set_tag(tag, ID3FID_CONTENTTYPE, track->album->genre); 366 set_tag(tag, ID3FID_TRACKNUM, track->tracknum); 367 368 ID3Tag_Update(tag); 369 } 370 } 371 372 void 373 set_tag(ID3Tag *tag, ID3_FrameID frameId, char *value) 374 { 375 ID3Frame *frame; 376 ID3Field *field; 377 378 if ((frame = ID3Tag_FindFrameWithID(tag, frameId)) != NULL) { 379 field = ID3Frame_GetField(frame, ID3FN_TEXT); 380 ID3Field_SetASCII(field, value); 381 } else { 382 frame = ID3Frame_New(); 383 ID3Frame_SetID(frame, frameId); 384 field = ID3Frame_GetField(frame, ID3FN_TEXT); 385 ID3Field_SetASCII(field, value); 386 ID3Tag_AddFrame(tag, frame); 387 } 388 } 389 390 void 391 fatal(const char *fmt, ...) 392 { 393 va_list ap; 394 395 va_start(ap, fmt); 396 vfprintf(stderr, fmt, ap); 397 va_end(ap); 398 399 exit(EXIT_FAILURE); 400 }