From 0847bcc110eaa8f2c8a70c0b85f98788969e1e14 Mon Sep 17 00:00:00 2001 From: Roman Artiukhin Date: Tue, 14 Jan 2025 09:53:06 +0200 Subject: [PATCH] metadata: opus, vorbis, speex: support embedded jpeg album art It includes .opus, .ogg, .oga, .spx files Change-Id: I3d0ee9806b05911fc8c3ce5cb761de87d4166141 --- apps/recorder/jpeg_load.c | 75 +++++++++++-- lib/rbcodec/metadata/embedded_metadata.h | 23 ---- lib/rbcodec/metadata/flac.c | 78 ++++++++------ lib/rbcodec/metadata/id3tags.c | 1 - lib/rbcodec/metadata/metadata.h | 1 + lib/rbcodec/metadata/metadata_common.h | 19 ++++ lib/rbcodec/metadata/ogg.c | 130 ++++++++++++----------- lib/rbcodec/metadata/vorbis.c | 122 +++++++++++++++++---- 8 files changed, 300 insertions(+), 149 deletions(-) delete mode 100644 lib/rbcodec/metadata/embedded_metadata.h diff --git a/apps/recorder/jpeg_load.c b/apps/recorder/jpeg_load.c index 1f459b08ec..52d426fdec 100644 --- a/apps/recorder/jpeg_load.c +++ b/apps/recorder/jpeg_load.c @@ -26,7 +26,7 @@ * KIND, either express or implied. * ****************************************************************************/ -#include "embedded_metadata.h" +#include "metadata_common.h" #include "plugin.h" #include "debug.h" #include "jpeg_load.h" @@ -81,6 +81,7 @@ struct jpeg int buf_index; int (*read_buf)(struct jpeg* p_jpeg, size_t count); + bool (*skip_bytes_seek)(struct jpeg* p_jpeg); void* custom_param; #endif unsigned long len; @@ -881,22 +882,50 @@ static int read_buf(struct jpeg* p_jpeg, size_t count) return read(p_jpeg->fd, p_jpeg->buf, count); } +INLINE void fill_buf(struct jpeg* p_jpeg) +{ + p_jpeg->buf_left = p_jpeg->read_buf(p_jpeg, MIN(JPEG_READ_BUF_SIZE, p_jpeg->len)); + p_jpeg->buf_index = 0; + if (p_jpeg->buf_left > 0) + p_jpeg->len -= p_jpeg->buf_left; +} + #ifdef HAVE_ALBUMART static int read_buf_id3_unsync(struct jpeg* p_jpeg, size_t count) { count = read(p_jpeg->fd, p_jpeg->buf, count); return id3_unsynchronize(p_jpeg->buf, count, (bool*) &p_jpeg->custom_param); } -#endif -INLINE void fill_buf(struct jpeg* p_jpeg) +static int read_buf_vorbis_base64(struct jpeg* p_jpeg, size_t count) { - p_jpeg->buf_left = p_jpeg->read_buf(p_jpeg, MIN(JPEG_READ_BUF_SIZE, p_jpeg->len)); - p_jpeg->buf_index = 0; - if (p_jpeg->buf_left > 0) - p_jpeg->len -= p_jpeg->buf_left; + struct ogg_file* ogg = p_jpeg->custom_param; + unsigned char* buf = p_jpeg->buf; + count = ogg_file_read(ogg, buf, count); + if (count == (size_t) -1) + return 0; + + return base64_decode(buf, count, buf); } +/* when pjpeg->read_buf involves additional data processing (like base64 decoding) + * we can't use lseek and have to call pjpeg->read_buf for proper seek */ +static bool skip_bytes_read_buf(struct jpeg* p_jpeg) +{ + do + { + int count = -p_jpeg->buf_left; + fill_buf(p_jpeg); + if (p_jpeg->buf_left < 0) + return false; + p_jpeg->buf_left -= count; + p_jpeg->buf_index += count; + } while (p_jpeg->buf_left < 0); + return true; +} + +#endif /* HAVE_ALBUMART */ + static unsigned char *jpeg_getc(struct jpeg* p_jpeg) { if (UNLIKELY(p_jpeg->buf_left < 1)) @@ -907,7 +936,7 @@ static unsigned char *jpeg_getc(struct jpeg* p_jpeg) return (p_jpeg->buf_index++) + p_jpeg->buf; } -INLINE bool skip_bytes_seek(struct jpeg* p_jpeg) +static bool skip_bytes_seek(struct jpeg* p_jpeg) { if (UNLIKELY(lseek(p_jpeg->fd, -p_jpeg->buf_left, SEEK_CUR) < 0)) return false; @@ -919,7 +948,7 @@ static bool skip_bytes(struct jpeg* p_jpeg, int count) { p_jpeg->buf_left -= count; p_jpeg->buf_index += count; - return p_jpeg->buf_left >= 0 || skip_bytes_seek(p_jpeg); + return p_jpeg->buf_left >= 0 || p_jpeg->skip_bytes_seek(p_jpeg); } static void jpeg_putc(struct jpeg* p_jpeg) @@ -2055,6 +2084,7 @@ int clip_jpeg_fd(int fd, int flags, p_jpeg->len = filesize(p_jpeg->fd); p_jpeg->read_buf = read_buf; + p_jpeg->skip_bytes_seek = skip_bytes_seek; #ifdef HAVE_ALBUMART if (flags & AA_FLAG_ID3_UNSYNC) @@ -2062,9 +2092,34 @@ int clip_jpeg_fd(int fd, int flags, p_jpeg->read_buf = read_buf_id3_unsync; p_jpeg->custom_param = false; } + else if (flags & AA_FLAG_VORBIS_BASE64) + { + struct ogg_file* ogg = alloca(sizeof(*ogg)); + off_t pic_pos = lseek(fd, 0, SEEK_CUR); + + // we need 92 bytes for format probing, reuse some available space + unsigned char* buf_format = (unsigned char*) p_jpeg->quanttable; + int type = get_ogg_format_and_move_to_comments(fd, buf_format); + + ogg_file_init(ogg, fd, type, 0); + bool packet_found; + do + { + int seek_from_cur_pos = pic_pos - lseek(fd, 0, SEEK_CUR); + packet_found = seek_from_cur_pos <= ogg->packet_remaining; + if (ogg_file_read(ogg, NULL, packet_found ? seek_from_cur_pos : ogg->packet_remaining) < 0) + return -1; + } + while (!packet_found); + + p_jpeg->read_buf = read_buf_vorbis_base64; + p_jpeg->skip_bytes_seek = skip_bytes_read_buf; + p_jpeg->custom_param = ogg; + } #else (void)flags; -#endif +#endif /* HAVE_ALBUMART */ + #endif status = process_markers(p_jpeg); #ifndef JPEG_FROM_MEM diff --git a/lib/rbcodec/metadata/embedded_metadata.h b/lib/rbcodec/metadata/embedded_metadata.h deleted file mode 100644 index 4f2416548a..0000000000 --- a/lib/rbcodec/metadata/embedded_metadata.h +++ /dev/null @@ -1,23 +0,0 @@ -/*************************************************************************** - * __________ __ ___. - * Open \______ \ ____ ____ | | _\_ |__ _______ ___ - * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / - * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < - * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ - * \/ \/ \/ \/ \/ - * $Id$ - * - * Copyright (C) 2024 Roman Artiukhin - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY - * KIND, either express or implied. - * - ****************************************************************************/ -#include - -int id3_unsynchronize(char* tag, int len, bool *ff_found); diff --git a/lib/rbcodec/metadata/flac.c b/lib/rbcodec/metadata/flac.c index 99c0efeca9..dda9fc61c6 100644 --- a/lib/rbcodec/metadata/flac.c +++ b/lib/rbcodec/metadata/flac.c @@ -30,6 +30,49 @@ #include "metadata_parsers.h" #include "logf.h" +#ifdef HAVE_ALBUMART + +bool parse_flac_album_art(unsigned char *buf, int bytes_read, enum mp3_aa_type *type, int *picframe_pos) +{ + *picframe_pos = 4; /* skip picture type */ + int mime_length, description_length; + + if (bytes_read <= *picframe_pos + 4) /* get_long_be expects 4 chars */ + { + logf("flac picture length invalid!"); + return false; + } + + mime_length = get_long_be(&buf[(*picframe_pos)]); + + char *mime = buf + *picframe_pos + 4; + *picframe_pos += 4 + mime_length; + + if (bytes_read < *picframe_pos) + { + logf("flac picture length invalid!"); + return false; + } + + *type = AA_TYPE_UNKNOWN; + if (memcmp(mime, "image/", 6) == 0) + { + mime += 6; + if (strcmp(mime, "jpeg") == 0 || strcmp(mime, "jpg") == 0){ + *type = AA_TYPE_JPG; + }else if (strcmp(mime, "png") == 0) + *type = AA_TYPE_PNG; + } + + description_length = get_long_be(&buf[(*picframe_pos)]); + + /* 16 = skip picture width,height,color-depth,color-used */ + *picframe_pos += 4 + description_length + 16; + return true; +} + +#endif /* HAVE_ALBUMART */ + bool get_flac_metadata(int fd, struct mp3entry* id3) { /* A simple parser to read vital metadata from a FLAC file - length, @@ -119,45 +162,16 @@ bool get_flac_metadata(int fd, struct mp3entry* id3) if(!id3->has_embedded_albumart) /* only use the first PICTURE */ { unsigned int buf_size = MIN(sizeof(id3->path), i); - int picframe_pos = 4; /* skip picture type */ - int mime_length, description_length; id3->albumart.pos = lseek(fd, 0, SEEK_CUR); int bytes_read = read(fd, buf, buf_size); buf[buf_size-1] = '\0'; i -= bytes_read; - if (bytes_read <= picframe_pos + 4) /* get_long_be expects 4 chars */ - { - logf("flac picture length invalid!"); + + int picframe_pos; + if (!parse_flac_album_art(buf, bytes_read, &id3->albumart.type, &picframe_pos)) return false; - } - - mime_length = get_long_be(&buf[picframe_pos]); - - char *mime = buf + picframe_pos + 4; - picframe_pos += 4 + mime_length; - - if (bytes_read < picframe_pos) - { - logf("flac picture length invalid!"); - return false; - } - - id3->albumart.type = AA_TYPE_UNKNOWN; - if (memcmp(mime, "image/", 6) == 0) - { - mime += 6; - if (strcmp(mime, "jpeg") == 0 || strcmp(mime, "jpg") == 0){ - id3->albumart.type = AA_TYPE_JPG; - }else if (strcmp(mime, "png") == 0) - id3->albumart.type = AA_TYPE_PNG; - } - - description_length = get_long_be(&buf[picframe_pos]); - - /* 16 = skip picture width,height,color-depth,color-used */ - picframe_pos += 4 + description_length + 16; /* if we support the format and image length is in the buffer */ if(id3->albumart.type != AA_TYPE_UNKNOWN diff --git a/lib/rbcodec/metadata/id3tags.c b/lib/rbcodec/metadata/id3tags.c index c7e63b0921..bbcf728dc7 100644 --- a/lib/rbcodec/metadata/id3tags.c +++ b/lib/rbcodec/metadata/id3tags.c @@ -45,7 +45,6 @@ #include "mp3data.h" #include "metadata_common.h" #include "metadata_parsers.h" -#include "embedded_metadata.h" #include "misc.h" static unsigned long unsync(unsigned long b0, diff --git a/lib/rbcodec/metadata/metadata.h b/lib/rbcodec/metadata/metadata.h index de06b63efc..96b9d298c4 100644 --- a/lib/rbcodec/metadata/metadata.h +++ b/lib/rbcodec/metadata/metadata.h @@ -207,6 +207,7 @@ enum mp3_aa_type { AA_TYPE_JPG, AA_FLAG_ID3_UNSYNC = 1 << (AA_FLAGS_SHIFT + 0), + AA_FLAG_VORBIS_BASE64 = 1 << (AA_FLAGS_SHIFT + 1), }; struct mp3_albumart { diff --git a/lib/rbcodec/metadata/metadata_common.h b/lib/rbcodec/metadata/metadata_common.h index 0f6fcb279c..9d2260f76a 100644 --- a/lib/rbcodec/metadata/metadata_common.h +++ b/lib/rbcodec/metadata/metadata_common.h @@ -39,6 +39,25 @@ bool read_ape_tags(int fd, struct mp3entry* id3); long read_vorbis_tags(int fd, struct mp3entry *id3, long tag_remaining); +struct ogg_file +{ + int fd; + bool packet_ended; + long packet_remaining; +}; + +#ifdef HAVE_ALBUMART +int id3_unsynchronize(char* tag, int len, bool *ff_found); + +size_t base64_decode(const char *in, size_t in_len, unsigned char *out); + +bool parse_flac_album_art(unsigned char *buf, int bytes_read, enum mp3_aa_type *type, int *picframe_pos); + +int get_ogg_format_and_move_to_comments(int fd, unsigned char *buf); +bool ogg_file_init(struct ogg_file* file, int fd, int type, int remaining); +ssize_t ogg_file_read(struct ogg_file* file, void* buffer, size_t buffer_size); +#endif + int string_option(const char *option, const char *const oplist[], bool ignore_case); bool skip_id3v2(int fd, struct mp3entry *id3); long read_string(int fd, char* buf, long buf_size, int eos, long size); diff --git a/lib/rbcodec/metadata/ogg.c b/lib/rbcodec/metadata/ogg.c index 5d302442ed..6db8132ab9 100644 --- a/lib/rbcodec/metadata/ogg.c +++ b/lib/rbcodec/metadata/ogg.c @@ -30,6 +30,58 @@ #include "metadata_parsers.h" #include "logf.h" +//NOTE: buf size must be >= 92 bytes +int get_ogg_format_and_move_to_comments(int fd, unsigned char *buf) +{ + /* 92 bytes is enough for both Vorbis and Speex headers */ + if ((lseek(fd, 0, SEEK_SET) < 0) || (read(fd, buf, 92) < 92)) + { + return AFMT_UNKNOWN; + } + + /* All Ogg streams start with OggS */ + if (memcmp(buf, "OggS", 4) != 0) + { + return AFMT_UNKNOWN; + } + + /* Check for format magic and then get metadata */ + if (memcmp(&buf[29], "vorbis", 6) == 0) + { + /* Comments are in second Ogg page (byte 58 onwards for Vorbis) */ + if (lseek(fd, 58, SEEK_SET) < 0) + { + return AFMT_UNKNOWN; + } + return AFMT_OGG_VORBIS; + } + else if (memcmp(&buf[28], "Speex ", 8) == 0) + { + uint32_t header_size = get_long_le(&buf[60]); + + /* Comments are in second Ogg page (byte 108 onwards for Speex) */ + if (lseek(fd, 28 + header_size, SEEK_SET) < 0) + { + return AFMT_UNKNOWN; + } + + return AFMT_SPEEX; + } + else if (memcmp(&buf[28], "OpusHead", 8) == 0) + { + /* Comments are in second Ogg page (byte 108 onwards for Speex) */ + if (lseek(fd, 47, SEEK_SET) < 0) + { + DEBUGF("Could not seek to ogg"); + return AFMT_UNKNOWN; + } + return AFMT_OPUS; + } + /* Unsupported format, try to print the marker, catches Ogg/FLAC at least */ + DEBUGF("Unsupported format in Ogg stream: %16s\n", &buf[28]); + return AFMT_UNKNOWN; +} + /* A simple parser to read vital metadata from an Ogg Vorbis file. * Can also handle parsing Ogg Speex files for metadata. Returns * false if metadata needed by the codec couldn't be read. @@ -56,71 +108,30 @@ bool get_ogg_metadata(int fd, struct mp3entry* id3) /* Use the path name of the id3 structure as a temporary buffer. */ unsigned char* buf = (unsigned char *)id3->path; long comment_size; - long remaining = 0; long last_serial = 0; long serial, r; - int segments, header_size; + int segments; int i; bool eof = false; - /* 92 bytes is enough for both Vorbis and Speex headers */ - if ((lseek(fd, 0, SEEK_SET) < 0) || (read(fd, buf, 92) < 92)) + id3->codectype = get_ogg_format_and_move_to_comments(fd, buf); + switch (id3->codectype) { - return false; - } - - /* All Ogg streams start with OggS */ - if (memcmp(buf, "OggS", 4) != 0) - { - return false; - } - - /* Check for format magic and then get metadata */ - if (memcmp(&buf[29], "vorbis", 6) == 0) - { - id3->codectype = AFMT_OGG_VORBIS; - id3->frequency = get_long_le(&buf[40]); - id3->vbr = true; - - /* Comments are in second Ogg page (byte 58 onwards for Vorbis) */ - if (lseek(fd, 58, SEEK_SET) < 0) - { + case AFMT_OGG_VORBIS: + id3->frequency = get_long_le(&buf[40]); + id3->vbr = true; + break; + case AFMT_SPEEX: + id3->frequency = get_slong(&buf[64]); + id3->vbr = get_long_le(&buf[88]); + break; + case AFMT_OPUS: + id3->frequency = 48000; + id3->vbr = true; + // FIXME handle an actual channel mapping table + break; + default: return false; - } - } - else if (memcmp(&buf[28], "Speex ", 8) == 0) - { - id3->codectype = AFMT_SPEEX; - id3->frequency = get_slong(&buf[64]); - id3->vbr = get_long_le(&buf[88]); - - header_size = get_long_le(&buf[60]); - - /* Comments are in second Ogg page (byte 108 onwards for Speex) */ - if (lseek(fd, 28 + header_size, SEEK_SET) < 0) - { - return false; - } - } - else if (memcmp(&buf[28], "OpusHead", 8) == 0) - { - id3->codectype = AFMT_OPUS; - id3->frequency = 48000; - id3->vbr = true; - -// FIXME handle an actual channel mapping table - /* Comments are in second Ogg page (byte 108 onwards for Speex) */ - if (lseek(fd, 47, SEEK_SET) < 0) - { - DEBUGF("Couldnotseektoogg"); - return false; - } - } - else - { - /* Unsupported format, try to print the marker, catches Ogg/FLAC at least */ - DEBUGF("Usupported format in Ogg stream: %16s\n", &buf[28]); - return false; } id3->filesize = filesize(fd); @@ -129,6 +140,7 @@ bool get_ogg_metadata(int fd, struct mp3entry* id3) * one from the last page (since we only support a single bitstream). */ serial = get_long_le(&buf[14]); + long remaining = 0; comment_size = read_vorbis_tags(fd, id3, remaining); /* We now need to search for the last page in the file - identified by @@ -141,8 +153,6 @@ bool get_ogg_metadata(int fd, struct mp3entry* id3) return false; } - remaining = 0; - while (!eof) { r = read(fd, &buf[remaining], MAX_PATH - remaining); diff --git a/lib/rbcodec/metadata/vorbis.c b/lib/rbcodec/metadata/vorbis.c index 6c1485e07b..db2215b594 100644 --- a/lib/rbcodec/metadata/vorbis.c +++ b/lib/rbcodec/metadata/vorbis.c @@ -26,26 +26,17 @@ #include "platform.h" #include "metadata.h" #include "metadata_common.h" -#include "metadata_parsers.h" /* Define LOGF_ENABLE to enable logf output in this file */ /*#define LOGF_ENABLE*/ #include "logf.h" -struct file -{ - int fd; - bool packet_ended; - long packet_remaining; -}; - - /* Read an Ogg page header. file->packet_remaining is set to the size of the * first packet on the page; file->packet_ended is set to true if the packet * ended on the current page. Returns true if the page header was * successfully read. */ -static bool file_read_page_header(struct file* file) +static bool file_read_page_header(struct ogg_file* file) { unsigned char buffer[64]; ssize_t table_left; @@ -110,7 +101,7 @@ static bool file_read_page_header(struct file* file) * 0 if there is no more data to read (in the packet or the file), < 0 if a * read error occurred. */ -static ssize_t file_read(struct file* file, void* buffer, size_t buffer_size) +ssize_t ogg_file_read(struct ogg_file* file, void* buffer, size_t buffer_size) { ssize_t done = 0; ssize_t count = -1; @@ -167,11 +158,11 @@ static ssize_t file_read(struct file* file, void* buffer, size_t buffer_size) /* Read an int32 from file. Returns false if a read error occurred. */ -static bool file_read_int32(struct file* file, int32_t* value) +static bool file_read_int32(struct ogg_file* file, int32_t* value) { char buf[sizeof(int32_t)]; - if (file_read(file, buf, sizeof(buf)) < (ssize_t) sizeof(buf)) + if (ogg_file_read(file, buf, sizeof(buf)) < (ssize_t) sizeof(buf)) { return false; } @@ -190,7 +181,7 @@ static bool file_read_int32(struct file* file, int32_t* value) * Unfortunately this is a slightly modified copy of read_string() in * metadata_common.c... */ -static long file_read_string(struct file* file, char* buffer, +static long file_read_string(struct ogg_file* file, char* buffer, long buffer_size, int eos, long size) { long read_bytes = 0; @@ -199,7 +190,7 @@ static long file_read_string(struct file* file, char* buffer, { char c; - if (file_read(file, &c, 1) != 1) + if (ogg_file_read(file, &c, 1) != 1) { read_bytes = -1; break; @@ -221,7 +212,7 @@ static long file_read_string(struct file* file, char* buffer, else if (eos == -1) { /* No point in reading any more, skip remaining data */ - if (file_read(file, NULL, size) < 0) + if (ogg_file_read(file, NULL, size) < 0) { read_bytes = -1; } @@ -244,7 +235,7 @@ static long file_read_string(struct file* file, char* buffer, * max amount to read if codec type is FLAC; it is ignored otherwise. * Returns true if the file was successfully initialized. */ -static bool file_init(struct file* file, int fd, int type, int remaining) +bool ogg_file_init(struct ogg_file* file, int fd, int type, int remaining) { memset(file, 0, sizeof(*file)); file->fd = fd; @@ -262,7 +253,7 @@ static bool file_init(struct file* file, int fd, int type, int remaining) char buffer[7]; /* Read packet header (type and id string) */ - if (file_read(file, buffer, sizeof(buffer)) < (ssize_t) sizeof(buffer)) + if (ogg_file_read(file, buffer, sizeof(buffer)) < (ssize_t) sizeof(buffer)) { return false; } @@ -280,7 +271,7 @@ static bool file_init(struct file* file, int fd, int type, int remaining) char buffer[8]; /* Read comment header */ - if (file_read(file, buffer, sizeof(buffer)) < (ssize_t) sizeof(buffer)) + if (ogg_file_read(file, buffer, sizeof(buffer)) < (ssize_t) sizeof(buffer)) { return false; } @@ -300,6 +291,64 @@ static bool file_init(struct file* file, int fd, int type, int remaining) return true; } +#define B64_START_CHAR '+' +/* maps char codes to BASE64 codes ('A': 0, 'B': 1,... '+': 62, '-': 63 */ +const char b64_codes[] = +{ /* Starts from first valid base 64 char '+' with char code 43 (B64_START_CHAR) + * For valid base64 chars: index in 0..63; for invalid: -1, for =: -2 */ + 62, -1, -1, -1, 63, /* 43-47 (+ /) */ + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -2, -1, -1, /* 48-63 (0-9 and =) */ + -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, /* 64-79 (A-O) */ + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, /* 80-95 (P-Z) */ + -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, /* 96-111 (a-o) */ + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51 /* 112-127 (p-z) */ +}; + +size_t base64_decode(const char *in, size_t in_len, unsigned char *out) +{ + size_t i = 0; + int val = 0; + size_t len = 0; + + while (i < in_len) + { + if (in[i] == '=') //is it padding? + { + switch (i & 3) + { + case 2: + out[len++] = (val >> 4) & 0xFF; + break; + case 3: + out[len++] = (val >> 10) & 0xFF; + out[len++] = (val >> 2) & 0xFF; + break; + } + break; + } + + val = (val << 6) | b64_codes[in[i] - B64_START_CHAR]; + + if ((++i & 3) == 0) + { + out[len++] = (val >> 16) & 0xFF; + out[len++] = (val >> 8) & 0xFF; + out[len++] = val & 0xFF; + } + } + return len; +} + +size_t base64_encoded_size(size_t inlen) +{ + size_t ret = inlen; + if (inlen % 3 != 0) + ret += 3 - (inlen % 3); + ret /= 3; + ret *= 4; + + return ret; +} /* Read the items in a Vorbis comment packet. For Ogg files, the file must * be located on a page start, for other files, the beginning of the comment @@ -309,7 +358,7 @@ static bool file_init(struct file* file, int fd, int type, int remaining) long read_vorbis_tags(int fd, struct mp3entry *id3, long tag_remaining) { - struct file file; + struct ogg_file file; char *buf = id3->id3v2buf; int32_t comment_count; int32_t len; @@ -317,14 +366,14 @@ long read_vorbis_tags(int fd, struct mp3entry *id3, int buf_remaining = sizeof(id3->id3v2buf) + sizeof(id3->id3v1buf); int i; - if (!file_init(&file, fd, id3->codectype, tag_remaining)) + if (!ogg_file_init(&file, fd, id3->codectype, tag_remaining)) { return 0; } /* Skip vendor string */ - if (!file_read_int32(&file, &len) || (file_read(&file, NULL, len) < 0)) + if (!file_read_int32(&file, &len) || (ogg_file_read(&file, NULL, len) < 0)) { return 0; } @@ -355,6 +404,9 @@ long read_vorbis_tags(int fd, struct mp3entry *id3, } len -= read_len; +#ifdef HAVE_ALBUMART + int before_block_pos = lseek(fd, 0, SEEK_CUR); +#endif read_len = file_read_string(&file, id3->path, sizeof(id3->path), -1, len); if (read_len < 0) @@ -363,7 +415,31 @@ long read_vorbis_tags(int fd, struct mp3entry *id3, } logf("Vorbis comment %d: %s=%s", i, name, id3->path); +#ifdef HAVE_ALBUMART + if (!id3->has_embedded_albumart /* only use the first PICTURE */ + && !strcasecmp(name, "METADATA_BLOCK_PICTURE")) + { + int after_block_pos = lseek(fd, 0, SEEK_CUR); + char* buf = id3->path; + size_t outlen = base64_decode(buf, MIN(read_len, (int32_t) sizeof(id3->path)), buf); + + int picframe_pos; + parse_flac_album_art(buf, outlen, &id3->albumart.type, &picframe_pos); + if(id3->albumart.type != AA_TYPE_UNKNOWN) + { + //NOTE: This is not exact location due to padding in base64 (up to 3 chars)!! + // But it's OK with our jpeg decoder if we add or miss few bytes in jpeg header + const int picframe_pos_b64 = base64_encoded_size(picframe_pos + 4); + + id3->has_embedded_albumart = true; + id3->albumart.type |= AA_FLAG_VORBIS_BASE64; + id3->albumart.pos = picframe_pos_b64 + before_block_pos; + id3->albumart.size = after_block_pos - id3->albumart.pos; + } + } + else +#endif /* Is it an embedded cuesheet? */ if (!strcasecmp(name, "CUESHEET")) { @@ -385,7 +461,7 @@ long read_vorbis_tags(int fd, struct mp3entry *id3, /* Skip to the end of the block (needed by FLAC) */ if (file.packet_remaining) { - if (file_read(&file, NULL, file.packet_remaining) < 0) + if (ogg_file_read(&file, NULL, file.packet_remaining) < 0) { return 0; }