···11-/***************************************************************************
22- * __________ __ ___.
33- * Open \______ \ ____ ____ | | _\_ |__ _______ ___
44- * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
55- * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
66- * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
77- * \/ \/ \/ \/ \/
88- * $Id$
99- *
1010- * Copyright (C) 2024 Roman Artiukhin
1111- *
1212- * This program is free software; you can redistribute it and/or
1313- * modify it under the terms of the GNU General Public License
1414- * as published by the Free Software Foundation; either version 2
1515- * of the License, or (at your option) any later version.
1616- *
1717- * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
1818- * KIND, either express or implied.
1919- *
2020- ****************************************************************************/
2121-#include <stdbool.h>
2222-2323-int id3_unsynchronize(char* tag, int len, bool *ff_found);
···3939long read_vorbis_tags(int fd, struct mp3entry *id3,
4040 long tag_remaining);
41414242+struct ogg_file
4343+{
4444+ int fd;
4545+ bool packet_ended;
4646+ long packet_remaining;
4747+};
4848+4949+#ifdef HAVE_ALBUMART
5050+int id3_unsynchronize(char* tag, int len, bool *ff_found);
5151+5252+size_t base64_decode(const char *in, size_t in_len, unsigned char *out);
5353+5454+bool parse_flac_album_art(unsigned char *buf, int bytes_read, enum mp3_aa_type *type, int *picframe_pos);
5555+5656+int get_ogg_format_and_move_to_comments(int fd, unsigned char *buf);
5757+bool ogg_file_init(struct ogg_file* file, int fd, int type, int remaining);
5858+ssize_t ogg_file_read(struct ogg_file* file, void* buffer, size_t buffer_size);
5959+#endif
6060+4261int string_option(const char *option, const char *const oplist[], bool ignore_case);
4362bool skip_id3v2(int fd, struct mp3entry *id3);
4463long read_string(int fd, char* buf, long buf_size, int eos, long size);
+68-58
lib/rbcodec/metadata/ogg.c
···3030#include "metadata_parsers.h"
3131#include "logf.h"
32323333-/* A simple parser to read vital metadata from an Ogg Vorbis file.
3434- * Can also handle parsing Ogg Speex files for metadata. Returns
3535- * false if metadata needed by the codec couldn't be read.
3636- */
3737-bool get_ogg_metadata(int fd, struct mp3entry* id3)
3333+//NOTE: buf size must be >= 92 bytes
3434+int get_ogg_format_and_move_to_comments(int fd, unsigned char *buf)
3835{
3939- /* An Ogg File is split into pages, each starting with the string
4040- * "OggS". Each page has a timestamp (in PCM samples) referred to as
4141- * the "granule position".
4242- *
4343- * An Ogg Vorbis has the following structure:
4444- * 1) Identification header (containing samplerate, numchannels, etc)
4545- * 2) Comment header - containing the Vorbis Comments
4646- * 3) Setup header - containing codec setup information
4747- * 4) Many audio packets...
4848- *
4949- * An Ogg Speex has the following structure:
5050- * 1) Identification header (containing samplerate, numchannels, etc)
5151- * Described in this page: (http://www.speex.org/manual2/node7.html)
5252- * 2) Comment header - containing the Vorbis Comments
5353- * 3) Many audio packets.
5454- */
5555-5656- /* Use the path name of the id3 structure as a temporary buffer. */
5757- unsigned char* buf = (unsigned char *)id3->path;
5858- long comment_size;
5959- long remaining = 0;
6060- long last_serial = 0;
6161- long serial, r;
6262- int segments, header_size;
6363- int i;
6464- bool eof = false;
6565-6636 /* 92 bytes is enough for both Vorbis and Speex headers */
6737 if ((lseek(fd, 0, SEEK_SET) < 0) || (read(fd, buf, 92) < 92))
6838 {
6969- return false;
3939+ return AFMT_UNKNOWN;
7040 }
71417242 /* All Ogg streams start with OggS */
7343 if (memcmp(buf, "OggS", 4) != 0)
7444 {
7575- return false;
4545+ return AFMT_UNKNOWN;
7646 }
77477848 /* Check for format magic and then get metadata */
7949 if (memcmp(&buf[29], "vorbis", 6) == 0)
8050 {
8181- id3->codectype = AFMT_OGG_VORBIS;
8282- id3->frequency = get_long_le(&buf[40]);
8383- id3->vbr = true;
8484-8551 /* Comments are in second Ogg page (byte 58 onwards for Vorbis) */
8652 if (lseek(fd, 58, SEEK_SET) < 0)
8753 {
8888- return false;
5454+ return AFMT_UNKNOWN;
8955 }
5656+ return AFMT_OGG_VORBIS;
9057 }
9158 else if (memcmp(&buf[28], "Speex ", 8) == 0)
9259 {
9393- id3->codectype = AFMT_SPEEX;
9494- id3->frequency = get_slong(&buf[64]);
9595- id3->vbr = get_long_le(&buf[88]);
9696-9797- header_size = get_long_le(&buf[60]);
6060+ uint32_t header_size = get_long_le(&buf[60]);
98619962 /* Comments are in second Ogg page (byte 108 onwards for Speex) */
10063 if (lseek(fd, 28 + header_size, SEEK_SET) < 0)
10164 {
102102- return false;
6565+ return AFMT_UNKNOWN;
10366 }
6767+6868+ return AFMT_SPEEX;
10469 }
10570 else if (memcmp(&buf[28], "OpusHead", 8) == 0)
10671 {
107107- id3->codectype = AFMT_OPUS;
108108- id3->frequency = 48000;
109109- id3->vbr = true;
110110-111111-// FIXME handle an actual channel mapping table
11272 /* Comments are in second Ogg page (byte 108 onwards for Speex) */
11373 if (lseek(fd, 47, SEEK_SET) < 0)
11474 {
115115- DEBUGF("Couldnotseektoogg");
116116- return false;
7575+ DEBUGF("Could not seek to ogg");
7676+ return AFMT_UNKNOWN;
11777 }
7878+ return AFMT_OPUS;
11879 }
119119- else
8080+ /* Unsupported format, try to print the marker, catches Ogg/FLAC at least */
8181+ DEBUGF("Unsupported format in Ogg stream: %16s\n", &buf[28]);
8282+ return AFMT_UNKNOWN;
8383+}
8484+8585+/* A simple parser to read vital metadata from an Ogg Vorbis file.
8686+ * Can also handle parsing Ogg Speex files for metadata. Returns
8787+ * false if metadata needed by the codec couldn't be read.
8888+ */
8989+bool get_ogg_metadata(int fd, struct mp3entry* id3)
9090+{
9191+ /* An Ogg File is split into pages, each starting with the string
9292+ * "OggS". Each page has a timestamp (in PCM samples) referred to as
9393+ * the "granule position".
9494+ *
9595+ * An Ogg Vorbis has the following structure:
9696+ * 1) Identification header (containing samplerate, numchannels, etc)
9797+ * 2) Comment header - containing the Vorbis Comments
9898+ * 3) Setup header - containing codec setup information
9999+ * 4) Many audio packets...
100100+ *
101101+ * An Ogg Speex has the following structure:
102102+ * 1) Identification header (containing samplerate, numchannels, etc)
103103+ * Described in this page: (http://www.speex.org/manual2/node7.html)
104104+ * 2) Comment header - containing the Vorbis Comments
105105+ * 3) Many audio packets.
106106+ */
107107+108108+ /* Use the path name of the id3 structure as a temporary buffer. */
109109+ unsigned char* buf = (unsigned char *)id3->path;
110110+ long comment_size;
111111+ long last_serial = 0;
112112+ long serial, r;
113113+ int segments;
114114+ int i;
115115+ bool eof = false;
116116+117117+ id3->codectype = get_ogg_format_and_move_to_comments(fd, buf);
118118+ switch (id3->codectype)
120119 {
121121- /* Unsupported format, try to print the marker, catches Ogg/FLAC at least */
122122- DEBUGF("Usupported format in Ogg stream: %16s\n", &buf[28]);
123123- return false;
120120+ case AFMT_OGG_VORBIS:
121121+ id3->frequency = get_long_le(&buf[40]);
122122+ id3->vbr = true;
123123+ break;
124124+ case AFMT_SPEEX:
125125+ id3->frequency = get_slong(&buf[64]);
126126+ id3->vbr = get_long_le(&buf[88]);
127127+ break;
128128+ case AFMT_OPUS:
129129+ id3->frequency = 48000;
130130+ id3->vbr = true;
131131+ // FIXME handle an actual channel mapping table
132132+ break;
133133+ default:
134134+ return false;
124135 }
125136126137 id3->filesize = filesize(fd);
···129140 * one from the last page (since we only support a single bitstream).
130141 */
131142 serial = get_long_le(&buf[14]);
143143+ long remaining = 0;
132144 comment_size = read_vorbis_tags(fd, id3, remaining);
133145134146 /* We now need to search for the last page in the file - identified by
···140152 {
141153 return false;
142154 }
143143-144144- remaining = 0;
145155146156 while (!eof)
147157 {
+99-23
lib/rbcodec/metadata/vorbis.c
···2626#include "platform.h"
2727#include "metadata.h"
2828#include "metadata_common.h"
2929-#include "metadata_parsers.h"
30293130/* Define LOGF_ENABLE to enable logf output in this file */
3231/*#define LOGF_ENABLE*/
3332#include "logf.h"
34333535-struct file
3636-{
3737- int fd;
3838- bool packet_ended;
3939- long packet_remaining;
4040-};
4141-4242-4334/* Read an Ogg page header. file->packet_remaining is set to the size of the
4435 * first packet on the page; file->packet_ended is set to true if the packet
4536 * ended on the current page. Returns true if the page header was
4637 * successfully read.
4738 */
4848-static bool file_read_page_header(struct file* file)
3939+static bool file_read_page_header(struct ogg_file* file)
4940{
5041 unsigned char buffer[64];
5142 ssize_t table_left;
···110101 * 0 if there is no more data to read (in the packet or the file), < 0 if a
111102 * read error occurred.
112103 */
113113-static ssize_t file_read(struct file* file, void* buffer, size_t buffer_size)
104104+ssize_t ogg_file_read(struct ogg_file* file, void* buffer, size_t buffer_size)
114105{
115106 ssize_t done = 0;
116107 ssize_t count = -1;
···167158168159/* Read an int32 from file. Returns false if a read error occurred.
169160 */
170170-static bool file_read_int32(struct file* file, int32_t* value)
161161+static bool file_read_int32(struct ogg_file* file, int32_t* value)
171162{
172163 char buf[sizeof(int32_t)];
173164174174- if (file_read(file, buf, sizeof(buf)) < (ssize_t) sizeof(buf))
165165+ if (ogg_file_read(file, buf, sizeof(buf)) < (ssize_t) sizeof(buf))
175166 {
176167 return false;
177168 }
···190181 * Unfortunately this is a slightly modified copy of read_string() in
191182 * metadata_common.c...
192183 */
193193-static long file_read_string(struct file* file, char* buffer,
184184+static long file_read_string(struct ogg_file* file, char* buffer,
194185 long buffer_size, int eos, long size)
195186{
196187 long read_bytes = 0;
···199190 {
200191 char c;
201192202202- if (file_read(file, &c, 1) != 1)
193193+ if (ogg_file_read(file, &c, 1) != 1)
203194 {
204195 read_bytes = -1;
205196 break;
···221212 else if (eos == -1)
222213 {
223214 /* No point in reading any more, skip remaining data */
224224- if (file_read(file, NULL, size) < 0)
215215+ if (ogg_file_read(file, NULL, size) < 0)
225216 {
226217 read_bytes = -1;
227218 }
···244235 * max amount to read if codec type is FLAC; it is ignored otherwise.
245236 * Returns true if the file was successfully initialized.
246237 */
247247-static bool file_init(struct file* file, int fd, int type, int remaining)
238238+bool ogg_file_init(struct ogg_file* file, int fd, int type, int remaining)
248239{
249240 memset(file, 0, sizeof(*file));
250241 file->fd = fd;
···262253 char buffer[7];
263254264255 /* Read packet header (type and id string) */
265265- if (file_read(file, buffer, sizeof(buffer)) < (ssize_t) sizeof(buffer))
256256+ if (ogg_file_read(file, buffer, sizeof(buffer)) < (ssize_t) sizeof(buffer))
266257 {
267258 return false;
268259 }
···280271 char buffer[8];
281272282273 /* Read comment header */
283283- if (file_read(file, buffer, sizeof(buffer)) < (ssize_t) sizeof(buffer))
274274+ if (ogg_file_read(file, buffer, sizeof(buffer)) < (ssize_t) sizeof(buffer))
284275 {
285276 return false;
286277 }
···300291 return true;
301292}
302293294294+#define B64_START_CHAR '+'
295295+/* maps char codes to BASE64 codes ('A': 0, 'B': 1,... '+': 62, '-': 63 */
296296+const char b64_codes[] =
297297+{ /* Starts from first valid base 64 char '+' with char code 43 (B64_START_CHAR)
298298+ * For valid base64 chars: index in 0..63; for invalid: -1, for =: -2 */
299299+ 62, -1, -1, -1, 63, /* 43-47 (+ /) */
300300+ 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -2, -1, -1, /* 48-63 (0-9 and =) */
301301+ -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, /* 64-79 (A-O) */
302302+ 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, /* 80-95 (P-Z) */
303303+ -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, /* 96-111 (a-o) */
304304+ 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51 /* 112-127 (p-z) */
305305+};
306306+307307+size_t base64_decode(const char *in, size_t in_len, unsigned char *out)
308308+{
309309+ size_t i = 0;
310310+ int val = 0;
311311+ size_t len = 0;
312312+313313+ while (i < in_len)
314314+ {
315315+ if (in[i] == '=') //is it padding?
316316+ {
317317+ switch (i & 3)
318318+ {
319319+ case 2:
320320+ out[len++] = (val >> 4) & 0xFF;
321321+ break;
322322+ case 3:
323323+ out[len++] = (val >> 10) & 0xFF;
324324+ out[len++] = (val >> 2) & 0xFF;
325325+ break;
326326+ }
327327+ break;
328328+ }
329329+330330+ val = (val << 6) | b64_codes[in[i] - B64_START_CHAR];
331331+332332+ if ((++i & 3) == 0)
333333+ {
334334+ out[len++] = (val >> 16) & 0xFF;
335335+ out[len++] = (val >> 8) & 0xFF;
336336+ out[len++] = val & 0xFF;
337337+ }
338338+ }
339339+ return len;
340340+}
341341+342342+size_t base64_encoded_size(size_t inlen)
343343+{
344344+ size_t ret = inlen;
345345+ if (inlen % 3 != 0)
346346+ ret += 3 - (inlen % 3);
347347+ ret /= 3;
348348+ ret *= 4;
349349+350350+ return ret;
351351+}
303352304353/* Read the items in a Vorbis comment packet. For Ogg files, the file must
305354 * be located on a page start, for other files, the beginning of the comment
···309358long read_vorbis_tags(int fd, struct mp3entry *id3,
310359 long tag_remaining)
311360{
312312- struct file file;
361361+ struct ogg_file file;
313362 char *buf = id3->id3v2buf;
314363 int32_t comment_count;
315364 int32_t len;
···317366 int buf_remaining = sizeof(id3->id3v2buf) + sizeof(id3->id3v1buf);
318367 int i;
319368320320- if (!file_init(&file, fd, id3->codectype, tag_remaining))
369369+ if (!ogg_file_init(&file, fd, id3->codectype, tag_remaining))
321370 {
322371 return 0;
323372 }
324373325374 /* Skip vendor string */
326375327327- if (!file_read_int32(&file, &len) || (file_read(&file, NULL, len) < 0))
376376+ if (!file_read_int32(&file, &len) || (ogg_file_read(&file, NULL, len) < 0))
328377 {
329378 return 0;
330379 }
···355404 }
356405357406 len -= read_len;
407407+#ifdef HAVE_ALBUMART
408408+ int before_block_pos = lseek(fd, 0, SEEK_CUR);
409409+#endif
358410 read_len = file_read_string(&file, id3->path, sizeof(id3->path), -1, len);
359411360412 if (read_len < 0)
···363415 }
364416365417 logf("Vorbis comment %d: %s=%s", i, name, id3->path);
418418+#ifdef HAVE_ALBUMART
419419+ if (!id3->has_embedded_albumart /* only use the first PICTURE */
420420+ && !strcasecmp(name, "METADATA_BLOCK_PICTURE"))
421421+ {
422422+ int after_block_pos = lseek(fd, 0, SEEK_CUR);
366423424424+ char* buf = id3->path;
425425+ size_t outlen = base64_decode(buf, MIN(read_len, (int32_t) sizeof(id3->path)), buf);
426426+427427+ int picframe_pos;
428428+ parse_flac_album_art(buf, outlen, &id3->albumart.type, &picframe_pos);
429429+ if(id3->albumart.type != AA_TYPE_UNKNOWN)
430430+ {
431431+ //NOTE: This is not exact location due to padding in base64 (up to 3 chars)!!
432432+ // But it's OK with our jpeg decoder if we add or miss few bytes in jpeg header
433433+ const int picframe_pos_b64 = base64_encoded_size(picframe_pos + 4);
434434+435435+ id3->has_embedded_albumart = true;
436436+ id3->albumart.type |= AA_FLAG_VORBIS_BASE64;
437437+ id3->albumart.pos = picframe_pos_b64 + before_block_pos;
438438+ id3->albumart.size = after_block_pos - id3->albumart.pos;
439439+ }
440440+ }
441441+ else
442442+#endif
367443 /* Is it an embedded cuesheet? */
368444 if (!strcasecmp(name, "CUESHEET"))
369445 {
···385461 /* Skip to the end of the block (needed by FLAC) */
386462 if (file.packet_remaining)
387463 {
388388- if (file_read(&file, NULL, file.packet_remaining) < 0)
464464+ if (ogg_file_read(&file, NULL, file.packet_remaining) < 0)
389465 {
390466 return 0;
391467 }