aac: Collect all interesting ID3 frames
[cmus.git] / ape.c
blob6cf1e152216edb09ff3607b1bde8b488b7a70d5a
1 /*
2 * Copyright 2006 Chun-Yu Shei <cshei AT cs.indiana.edu>
4 * Cleaned up by Timo Hirvonen <tihirvon@gmail.com>
5 */
7 #include "ip.h"
8 #include "ape.h"
9 #include "file.h"
10 #include "xmalloc.h"
12 #include <inttypes.h>
13 #include <errno.h>
14 #include <sys/types.h>
15 #include <sys/stat.h>
16 #include <unistd.h>
18 /* http://www.personal.uni-jena.de/~pfk/mpp/sv8/apetag.html */
20 #define PREAMBLE_SIZE (8)
21 static const char preamble[PREAMBLE_SIZE] = { 'A', 'P', 'E', 'T', 'A', 'G', 'E', 'X' };
23 /* NOTE: not sizeof(struct ape_header)! */
24 #define HEADER_SIZE (32)
26 static inline uint32_t get_le32(const char *buf)
28 const unsigned char *b = (const unsigned char *)buf;
30 return b[0] | (b[1] << 8) | (b[2] << 16) | (b[3] << 24);
33 /* returns position of APE header or -1 if not found */
34 static int find_ape_tag_slow(int fd)
36 char buf[4096];
37 int match = 0;
38 int pos = 0;
40 /* seek to start of file */
41 if (lseek(fd, pos, SEEK_SET) == -1)
42 return -1;
44 while (1) {
45 int i, got = read(fd, buf, sizeof(buf));
47 if (got == -1) {
48 if (errno == EAGAIN || errno == EINTR)
49 continue;
50 break;
52 if (got == 0)
53 break;
55 for (i = 0; i < got; i++) {
56 if (buf[i] != preamble[match]) {
57 match = 0;
58 continue;
61 match++;
62 if (match == PREAMBLE_SIZE)
63 return pos + i + 1 - PREAMBLE_SIZE;
65 pos += got;
67 return -1;
70 static int ape_parse_header(const char *buf, struct ape_header *h)
72 if (memcmp(buf, preamble, PREAMBLE_SIZE))
73 return 0;
75 h->version = get_le32(buf + 8);
76 h->size = get_le32(buf + 12);
77 h->count = get_le32(buf + 16);
78 h->flags = get_le32(buf + 20);
79 return 1;
82 static int read_header(int fd, struct ape_header *h)
84 char buf[HEADER_SIZE];
86 if (read_all(fd, buf, sizeof(buf)) != sizeof(buf))
87 return 0;
88 return ape_parse_header(buf, h);
91 /* sets fd right after the header and returns 1 if found,
92 * otherwise returns 0
94 static int find_ape_tag(int fd, struct ape_header *h, int slow)
96 int pos;
98 if (lseek(fd, -HEADER_SIZE, SEEK_END) == -1)
99 return 0;
100 if (read_header(fd, h))
101 return 1;
103 if (!slow)
104 return 0;
106 pos = find_ape_tag_slow(fd);
107 if (pos == -1)
108 return 0;
109 if (lseek(fd, pos, SEEK_SET) == -1)
110 return 0;
111 return read_header(fd, h);
115 * All keys are ASCII and length is 2..255
117 * UTF-8: Artist, Album, Title, Genre
118 * Integer: Track (N or N/M)
119 * Date: Year (release), "Record Date"
121 * UTF-8 strings are NOT zero terminated.
123 * Also support "discnumber" (vorbis) and "disc" (non-standard)
125 static int ape_parse_one(const char *buf, int size, char **keyp, char **valp)
127 int pos = 0;
129 while (size - pos > 8) {
130 uint32_t val_len, flags;
131 char *key, *val;
132 int max_key_len, key_len;
134 val_len = get_le32(buf + pos); pos += 4;
135 flags = get_le32(buf + pos); pos += 4;
137 max_key_len = size - pos - val_len - 1;
138 if (max_key_len < 0) {
139 /* corrupt */
140 break;
143 for (key_len = 0; key_len < max_key_len && buf[pos + key_len]; key_len++)
144 ; /* nothing */
145 if (buf[pos + key_len]) {
146 /* corrupt */
147 break;
150 if (!AF_IS_UTF8(flags)) {
151 /* ignore binary data */
152 pos += key_len + 1 + val_len;
153 continue;
156 key = xstrdup(buf + pos);
157 pos += key_len + 1;
159 /* should not be NUL-terminated */
160 val = xstrndup(buf + pos, val_len);
161 pos += val_len;
163 /* could be moved to comment.c but I don't think anyone else would use it */
164 if (!strcasecmp(key, "record date") || !strcasecmp(key, "year")) {
165 free(key);
166 key = xstrdup("date");
169 if (!strcasecmp(key, "date")) {
170 /* Date format
172 * 1999-08-11 12:34:56
173 * 1999-08-11 12:34
174 * 1999-08-11
175 * 1999-08
176 * 1999
177 * 1999-W34 (week 34, totally crazy)
179 * convert to year, pl.c supports only years anyways
181 * FIXME: which one is the most common tag (year or record date)?
183 if (strlen(val) > 4)
184 val[4] = 0;
187 *keyp = key;
188 *valp = val;
189 return pos;
191 return -1;
194 static off_t get_size(int fd)
196 struct stat statbuf;
198 if (fstat(fd, &statbuf) || !(statbuf.st_mode & S_IFREG))
199 return 0;
200 return statbuf.st_size;
203 /* return the number of comments, or -1 */
204 int ape_read_tags(struct apetag *ape, int fd, int slow)
206 struct ape_header *h = &ape->header;
207 int old_pos, rc = -1;
209 /* save position */
210 old_pos = lseek(fd, 0, SEEK_CUR);
212 if (!find_ape_tag(fd, h, slow))
213 goto fail;
215 if (AF_IS_FOOTER(h->flags)) {
216 /* seek back right after the header */
217 if (lseek(fd, get_size(fd) - h->size, SEEK_SET) == -1)
218 goto fail;
221 /* ignore insane tags */
222 if (h->size > 1024 * 1024)
223 goto fail;
225 ape->buf = xnew(char, h->size);
226 if (read_all(fd, ape->buf, h->size) != h->size)
227 goto fail;
229 rc = h->count;
231 fail:
232 lseek(fd, old_pos, SEEK_SET);
233 return rc;
236 /* returned key-name must be free'd */
237 char *ape_get_comment(struct apetag *ape, char **val)
239 struct ape_header *h = &ape->header;
240 char *key;
241 int rc;
243 if (ape->pos >= h->size)
244 return NULL;
246 rc = ape_parse_one(ape->buf + ape->pos, h->size - ape->pos, &key, val);
247 if (rc < 0)
248 return NULL;
249 ape->pos += rc;
251 return key;