cmus2tsv

convert cmus' cache format to TSV
git clone https://hhvn.uk/cmus2tsv
git clone git://hhvn.uk/cmus2tsv
Log | Files | Refs

cmus2tsv.c (5142B)


      1 /*
      2  * cmus2tsv.c
      3  *
      4  * Copyright (c) 2022 hhvn <dev@hhvn.uk>
      5  *
      6  * Permission to use, copy, modify, and distribute this software for any
      7  * purpose with or without fee is hereby granted, provided that the above
      8  * copyright notice and this permission notice appear in all copies.
      9  *
     10  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
     11  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
     12  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
     13  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
     14  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
     15  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
     16  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
     17  *
     18  */
     19 
     20 #include <stdio.h>
     21 #include <fcntl.h>
     22 #include <errno.h>
     23 #include <unistd.h>
     24 #include <stdlib.h>
     25 #include <libgen.h>
     26 #include <stdint.h>
     27 #include <string.h>
     28 #include <assert.h>
     29 #include <sys/stat.h>
     30 #include <sys/mman.h>
     31 
     32 #define CACHE_PATH	".config/cmus/cache"
     33 
     34 #define ALIGN(size) (((size) + sizeof(long) - 1) & ~(sizeof(long) - 1))
     35 
     36 #define CACHE_VERSION   0x0d
     37 
     38 #define CACHE_64_BIT	0x01
     39 #define CACHE_BE	0x02
     40 
     41 #define ENTRY_USED_SIZE		28
     42 #define ENTRY_RESERVED_SIZE	52
     43 #define ENTRY_TOTAL_SIZE	(ENTRY_RESERVED_SIZE + ENTRY_USED_SIZE)
     44 
     45 char cache_header[8] = "CTC\0\0\0\0\0";
     46 struct entry {
     47 	uint32_t size;
     48 
     49 	int32_t play_count;
     50 	int64_t mtime;
     51 	int32_t duration;
     52 	int32_t bitrate;
     53 	int32_t bpm;
     54 
     55 	uint8_t _reserved[ENTRY_RESERVED_SIZE];
     56 
     57 	char strings[];
     58 };
     59 
     60 /* all (significant) metadata stored in track_info */
     61 enum {
     62 	META_TITLE,
     63 	META_ARTIST,
     64 	META_GENRE,
     65 	META_ALBUM,
     66 	META_ALBUMARTIST,
     67 	META_TRACK,
     68 	META_COMMENT,
     69 	META_LAST
     70 };
     71 
     72 void
     73 display_header(void) {
     74 	printf("Filename\tTitle\tArtist\tGenre\tAlbum\tAlbumArtist\tTrack\tDuration\tDuration(s)\tPlay count\tComment\n");
     75 }
     76 
     77 void
     78 display(struct entry *e) {
     79 	int size, pos, i, count;
     80 	char *metakey, *metaval;
     81 	char *meta[META_LAST];
     82 
     83 	for (size = e->size - sizeof(*e), count = i = 0; i < size; i++) {
     84 		if (!e->strings[i])
     85 			count++;
     86 	}
     87 	count = (count - 3) / 2;
     88 
     89 	printf("%s\t", e->strings); /* filename */
     90 	pos = strlen(e->strings) + 1;
     91 	pos += strlen(e->strings + pos) + 1;
     92 	pos += strlen(e->strings + pos) + 1;
     93 
     94 	for (i = 0; i < META_LAST; i++)
     95 		meta[i] = "";
     96 
     97 	for (i = 0; i < count; i++) {
     98 		size = strlen(e->strings + pos) + 1;
     99 		metakey = e->strings + pos;
    100 		pos += size;
    101 
    102 		size = strlen(e->strings + pos) + 1;
    103 		metaval = e->strings + pos;
    104 		pos += size;
    105 
    106 		if (strcmp(metakey, "title") == 0)
    107 			meta[META_TITLE] = metaval;
    108 		else if (strcmp(metakey, "artist") == 0)
    109 			meta[META_ARTIST] = metaval;
    110 		else if (strcmp(metakey, "album") == 0)
    111 			meta[META_ALBUM] = metaval;
    112 		else if (strcmp(metakey, "tracknumber") == 0)
    113 			meta[META_TRACK] = metaval;
    114 		else if (strcmp(metakey, "genre") == 0)
    115 			meta[META_GENRE] = metaval;
    116 		else if (strcmp(metakey, "albumartist") == 0)
    117 			meta[META_ALBUMARTIST] = metaval;
    118 		else if (strcmp(metakey, "comment") == 0)
    119 			meta[META_COMMENT] = metaval;
    120 	}
    121 
    122 	if (!*meta[META_ALBUMARTIST])
    123 		meta[META_ALBUMARTIST] = meta[META_ARTIST];
    124 
    125 	printf("%s\t%s\t%s\t%s\t%s\t%s\t", meta[META_TITLE],
    126 			meta[META_ARTIST], meta[META_GENRE],
    127 			meta[META_ALBUM], meta[META_ALBUMARTIST],
    128 			meta[META_TRACK]);
    129 
    130 	if (e->duration < 60)
    131 		printf("00:%02d\t", e->duration);
    132 	else
    133 		printf("%02d:%02d\t", ((int)e->duration / 60), e->duration - ((int)e->duration / 60) * 60);
    134 
    135 	printf("%ld\t%d\t", e->duration, e->play_count);
    136 	printf("%s\n", meta[META_COMMENT]);
    137 }
    138 
    139 void
    140 read_cache(char *file) {
    141 	struct entry *e;
    142 	unsigned int flags = 0;
    143 	unsigned int size, offset = 0;
    144 	struct stat st;
    145 	char *buf;
    146 	int fd;
    147 
    148 #ifdef BIG_ENDIAN
    149 	flags |= CACHE_BE;
    150 #endif
    151 
    152 	if (sizeof(long) == 8)
    153 		flags |= CACHE_64_BIT;
    154 
    155 	cache_header[7] = flags & 0xff; flags >>= 8;
    156 	cache_header[6] = flags & 0xff; flags >>= 8;
    157 	cache_header[5] = flags & 0xff; flags >>= 8;
    158 	cache_header[4] = flags & 0xff;
    159 
    160 	cache_header[3] = CACHE_VERSION;
    161 
    162 	if ((fd = open(file, O_RDONLY)) < 0) {
    163 		fprintf(stderr, "%s: %s\n", strerror(errno), file);
    164 		exit(EXIT_FAILURE);
    165 	}
    166 	fstat(fd, &st);
    167 	if (st.st_size < sizeof(cache_header))
    168 		goto close;
    169 	size = st.st_size;
    170 
    171 	buf = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0);
    172 	if (buf == MAP_FAILED)
    173 		goto close;
    174 
    175 	if (memcmp(buf, cache_header, sizeof(cache_header)))
    176 		goto corrupt;
    177 
    178 	offset = sizeof(cache_header);
    179 	while (offset < size) {
    180 		e = (void *)buf + offset;
    181 		display(e);
    182 		offset += ALIGN(e->size);
    183 	}
    184 
    185 	munmap(buf, size);
    186 	close(fd);
    187 	return;
    188 
    189 corrupt:
    190 	fprintf(stderr, "Corrupt: %s\n", file);
    191 	munmap(buf, size);
    192 close:
    193 	close(fd);
    194 }
    195 
    196 int
    197 main(int argc, char *argv[]) {
    198 	char *prog;
    199 	char *home, *file;
    200 	size_t len;
    201 
    202 	prog = basename(argv[0]);
    203 
    204 	if (argc < 2) {
    205 		home = getenv("HOME");
    206 		assert(home);
    207 		len = strlen(home) + strlen(CACHE_PATH) + 2;
    208 		file = malloc(len);
    209 		assert(file);
    210 		snprintf(file, len, "%s/%s", home, CACHE_PATH);
    211 	} else if (argc == 2) {
    212 		file = argv[1];
    213 	} else {
    214 		fprintf(stderr, "usage: %s [cachefile]\n", prog);
    215 		return 2;
    216 	}
    217 
    218 	display_header();
    219 	read_cache(file);
    220 
    221 	if (file != argv[1])
    222 		free(file);
    223 }