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