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 }