disco-dl

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

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 }