disco-dl

Discography/album downloader
git clone git://git.wimdupont.com/disco-dl.git
Log | Files | Refs | README | LICENSE

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 }