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 /* returns position of APE header or -1 if not found */
27 static int find_ape_tag_slow(int fd
)
33 /* seek to start of file */
34 if (lseek(fd
, pos
, SEEK_SET
) == -1)
38 int i
, got
= read(fd
, buf
, sizeof(buf
));
41 if (errno
== EAGAIN
|| errno
== EINTR
)
48 for (i
= 0; i
< got
; i
++) {
49 if (buf
[i
] != preamble
[match
]) {
55 if (match
== PREAMBLE_SIZE
)
56 return pos
+ i
+ 1 - PREAMBLE_SIZE
;
63 static int ape_parse_header(const char *buf
, struct ape_header
*h
)
65 if (memcmp(buf
, preamble
, PREAMBLE_SIZE
))
68 h
->version
= read_le32(buf
+ 8);
69 h
->size
= read_le32(buf
+ 12);
70 h
->count
= read_le32(buf
+ 16);
71 h
->flags
= read_le32(buf
+ 20);
75 static int read_header(int fd
, struct ape_header
*h
)
77 char buf
[HEADER_SIZE
];
79 if (read_all(fd
, buf
, sizeof(buf
)) != sizeof(buf
))
81 return ape_parse_header(buf
, h
);
84 /* sets fd right after the header and returns 1 if found,
87 static int find_ape_tag(int fd
, struct ape_header
*h
, int slow
)
91 if (lseek(fd
, -HEADER_SIZE
, SEEK_END
) == -1)
93 if (read_header(fd
, h
))
99 pos
= find_ape_tag_slow(fd
);
102 if (lseek(fd
, pos
, SEEK_SET
) == -1)
104 return read_header(fd
, h
);
108 * All keys are ASCII and length is 2..255
110 * UTF-8: Artist, Album, Title, Genre
111 * Integer: Track (N or N/M)
112 * Date: Year (release), "Record Date"
114 * UTF-8 strings are NOT zero terminated.
116 * Also support "discnumber" (vorbis) and "disc" (non-standard)
118 static int ape_parse_one(const char *buf
, int size
, char **keyp
, char **valp
)
122 while (size
- pos
> 8) {
123 uint32_t val_len
, flags
;
125 int max_key_len
, key_len
;
127 val_len
= read_le32(buf
+ pos
); pos
+= 4;
128 flags
= read_le32(buf
+ pos
); pos
+= 4;
130 max_key_len
= size
- pos
- val_len
- 1;
131 if (max_key_len
< 0) {
136 for (key_len
= 0; key_len
< max_key_len
&& buf
[pos
+ key_len
]; key_len
++)
138 if (buf
[pos
+ key_len
]) {
143 if (!AF_IS_UTF8(flags
)) {
144 /* ignore binary data */
145 pos
+= key_len
+ 1 + val_len
;
149 key
= xstrdup(buf
+ pos
);
152 /* should not be NUL-terminated */
153 val
= xstrndup(buf
+ pos
, val_len
);
156 /* could be moved to comment.c but I don't think anyone else would use it */
157 if (!strcasecmp(key
, "record date") || !strcasecmp(key
, "year")) {
159 key
= xstrdup("date");
162 if (!strcasecmp(key
, "date")) {
165 * 1999-08-11 12:34:56
170 * 1999-W34 (week 34, totally crazy)
172 * convert to year, pl.c supports only years anyways
174 * FIXME: which one is the most common tag (year or record date)?
187 static off_t
get_size(int fd
)
191 if (fstat(fd
, &statbuf
) || !(statbuf
.st_mode
& S_IFREG
))
193 return statbuf
.st_size
;
196 /* return the number of comments, or -1 */
197 int ape_read_tags(struct apetag
*ape
, int fd
, int slow
)
199 struct ape_header
*h
= &ape
->header
;
200 int old_pos
, rc
= -1;
203 old_pos
= lseek(fd
, 0, SEEK_CUR
);
205 if (!find_ape_tag(fd
, h
, slow
))
208 if (AF_IS_FOOTER(h
->flags
)) {
209 /* seek back right after the header */
210 if (lseek(fd
, get_size(fd
) - h
->size
, SEEK_SET
) == -1)
214 /* ignore insane tags */
215 if (h
->size
> 1024 * 1024)
218 ape
->buf
= xnew(char, h
->size
);
219 if (read_all(fd
, ape
->buf
, h
->size
) != h
->size
)
225 lseek(fd
, old_pos
, SEEK_SET
);
229 /* returned key-name must be free'd */
230 char *ape_get_comment(struct apetag
*ape
, char **val
)
232 struct ape_header
*h
= &ape
->header
;
236 if (ape
->pos
>= h
->size
)
239 rc
= ape_parse_one(ape
->buf
+ ape
->pos
, h
->size
- ape
->pos
, &key
, val
);