2 * Copyright 2006 Chun-Yu Shei <cshei AT cs.indiana.edu>
4 * Cleaned up by Timo Hirvonen <tihirvon@gmail.com>
14 #include <sys/types.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
)
40 /* seek to start of file */
41 if (lseek(fd
, pos
, SEEK_SET
) == -1)
45 int i
, got
= read(fd
, buf
, sizeof(buf
));
48 if (errno
== EAGAIN
|| errno
== EINTR
)
55 for (i
= 0; i
< got
; i
++) {
56 if (buf
[i
] != preamble
[match
]) {
62 if (match
== PREAMBLE_SIZE
)
63 return pos
+ i
+ 1 - PREAMBLE_SIZE
;
70 static int ape_parse_header(const char *buf
, struct ape_header
*h
)
72 if (memcmp(buf
, preamble
, PREAMBLE_SIZE
))
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);
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
))
88 return ape_parse_header(buf
, h
);
91 /* sets fd right after the header and returns 1 if found,
94 static int find_ape_tag(int fd
, struct ape_header
*h
, int slow
)
98 if (lseek(fd
, -HEADER_SIZE
, SEEK_END
) == -1)
100 if (read_header(fd
, h
))
106 pos
= find_ape_tag_slow(fd
);
109 if (lseek(fd
, pos
, SEEK_SET
) == -1)
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
)
129 while (size
- pos
> 8) {
130 uint32_t val_len
, flags
;
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) {
143 for (key_len
= 0; key_len
< max_key_len
&& buf
[pos
+ key_len
]; key_len
++)
145 if (buf
[pos
+ key_len
]) {
150 if (!AF_IS_UTF8(flags
)) {
151 /* ignore binary data */
152 pos
+= key_len
+ 1 + val_len
;
156 key
= xstrdup(buf
+ pos
);
159 /* should not be NUL-terminated */
160 val
= xstrndup(buf
+ 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")) {
166 key
= xstrdup("date");
169 if (!strcasecmp(key
, "date")) {
172 * 1999-08-11 12:34:56
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)?
194 static off_t
get_size(int fd
)
198 if (fstat(fd
, &statbuf
) || !(statbuf
.st_mode
& S_IFREG
))
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;
210 old_pos
= lseek(fd
, 0, SEEK_CUR
);
212 if (!find_ape_tag(fd
, h
, slow
))
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)
221 /* ignore insane tags */
222 if (h
->size
> 1024 * 1024)
225 ape
->buf
= xnew(char, h
->size
);
226 if (read_all(fd
, ape
->buf
, h
->size
) != h
->size
)
232 lseek(fd
, old_pos
, SEEK_SET
);
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
;
243 if (ape
->pos
>= h
->size
)
246 rc
= ape_parse_one(ape
->buf
+ ape
->pos
, h
->size
- ape
->pos
, &key
, val
);