mirror of
https://github.com/fmang/opustags.git
synced 2024-11-09 23:17:23 +01:00
Implement embedded picture decoding
This commit is contained in:
parent
9652f50316
commit
66fb3574a1
51
src/opus.cc
51
src/opus.cc
@ -110,3 +110,54 @@ ot::dynamic_ogg_packet ot::render_tags(const opus_tags& tags)
|
||||
|
||||
return op;
|
||||
}
|
||||
|
||||
/**
|
||||
* The METADATA_BLOCK_PICTURE binary data, after base64 decoding, is organized like this:
|
||||
*
|
||||
* - 4 bytes for the picture type,
|
||||
* - 4 + n bytes for the MIME type,
|
||||
* - 4 + n bytes for the description string,
|
||||
* - 16 bytes of picture attributes,
|
||||
* - 4 + n bytes for the picture data.
|
||||
*
|
||||
* Integers are all big endian.
|
||||
*/
|
||||
ot::picture::picture(std::string block)
|
||||
: storage(std::move(block))
|
||||
{
|
||||
auto bytes = reinterpret_cast<const uint8_t*>(storage.data());
|
||||
|
||||
size_t mime_offset = 4;
|
||||
if (storage.size() < mime_offset + 4)
|
||||
throw status { st::invalid_size, "missing MIME type in picture block" };
|
||||
uint32_t mime_size = be32toh(*reinterpret_cast<const uint32_t*>(bytes + mime_offset));
|
||||
|
||||
size_t desc_offset = mime_offset + 4 + mime_size;
|
||||
if (storage.size() < desc_offset + 4)
|
||||
throw status { st::invalid_size, "missing description in picture block" };
|
||||
uint32_t desc_size = be32toh(*reinterpret_cast<const uint32_t*>(bytes + desc_offset));
|
||||
|
||||
size_t pic_offset = desc_offset + 4 + desc_size + 16;
|
||||
if (storage.size() < pic_offset + 4)
|
||||
throw status { st::invalid_size, "missing picture data in picture block" };
|
||||
uint32_t pic_size = be32toh(*reinterpret_cast<const uint32_t*>(bytes + pic_offset));
|
||||
|
||||
if (storage.size() != pic_offset + 4 + pic_size)
|
||||
throw status { st::invalid_size, "invalid picture block size" };
|
||||
|
||||
mime_type = std::string_view(reinterpret_cast<const char*>(bytes + mime_offset + 4), mime_size);
|
||||
picture_data = std::string_view(reinterpret_cast<const char*>(bytes + pic_offset + 4), pic_size);
|
||||
}
|
||||
|
||||
std::optional<ot::picture> ot::extract_cover(const ot::opus_tags& tags)
|
||||
{
|
||||
static const std::string_view prefix = "METADATA_BLOCK_PICTURE="sv;
|
||||
auto is_cover = [](const std::string& tag) { return tag.starts_with(prefix); };
|
||||
auto cover_tag = std::find_if(tags.comments.begin(), tags.comments.end(), is_cover);
|
||||
if (cover_tag == tags.comments.end())
|
||||
return {}; // No cover art.
|
||||
|
||||
std::string_view cover_value = *cover_tag;
|
||||
cover_value.remove_prefix(prefix.size());
|
||||
return picture(decode_base64(cover_value));
|
||||
}
|
||||
|
@ -51,6 +51,8 @@
|
||||
#include <libkern/OSByteOrder.h>
|
||||
#define htole32(x) OSSwapHostToLittleInt32(x)
|
||||
#define le32toh(x) OSSwapLittleToHostInt32(x)
|
||||
#define htobe32(x) OSSwapHostToBigInt32(x)
|
||||
#define be32toh(x) OSSwapBigToHostInt32(x)
|
||||
#endif
|
||||
|
||||
using namespace std::literals;
|
||||
@ -89,6 +91,7 @@ enum class st {
|
||||
cut_comment_count,
|
||||
cut_comment_length,
|
||||
cut_comment_data,
|
||||
invalid_size,
|
||||
/* CLI */
|
||||
bad_arguments,
|
||||
};
|
||||
@ -398,6 +401,26 @@ opus_tags parse_tags(const ogg_packet& packet);
|
||||
*/
|
||||
dynamic_ogg_packet render_tags(const opus_tags& tags);
|
||||
|
||||
/**
|
||||
* Extracted data from the METADATA_BLOCK_PICTURE tag. See
|
||||
* <https://xiph.org/flac/format.html#metadata_block_picture> for the full specifications.
|
||||
*
|
||||
* It may contain all kinds of metadata but most are not used at all. For now, let’s assume all
|
||||
* pictures have picture type 3 (front cover), and empty metadata.
|
||||
*/
|
||||
struct picture {
|
||||
/** Extract the picture information from serialized binary data.*/
|
||||
picture(std::string block);
|
||||
std::string_view mime_type;
|
||||
std::string_view picture_data;
|
||||
/** To avoid needless copies of the picture data, move the original data block there. The
|
||||
* string_view attributes will refer to it. */
|
||||
std::string storage;
|
||||
};
|
||||
|
||||
/** Extract the first picture embedded in the tags, regardless of its type. */
|
||||
std::optional<picture> extract_cover(const opus_tags& tags);
|
||||
|
||||
/** \} */
|
||||
|
||||
/***********************************************************************************************//**
|
||||
|
33
t/opus.cc
33
t/opus.cc
@ -135,12 +135,43 @@ static void recode_padding()
|
||||
throw failure("the rendered packet is not what we expected");
|
||||
}
|
||||
|
||||
static void extract_cover()
|
||||
{
|
||||
std::string_view picture_data = ""sv
|
||||
"\x00\x00\x00\x03" // Picture type 3.
|
||||
"\x00\x00\x00\x09" "image/foo" // MIME type.
|
||||
"\x00\x00\x00\x00" "" // Description.
|
||||
"\x00\x00\x00\x00" // Width.
|
||||
"\x00\x00\x00\x00" // Height.
|
||||
"\x00\x00\x00\x00" // Color depth.
|
||||
"\x00\x00\x00\x00" // Palette size.
|
||||
"\x00\x00\x00\x0C" "Picture data";
|
||||
|
||||
ot::opus_tags tags;
|
||||
tags.comments.push_front("METADATA_BLOCK_PICTURE=" + ot::encode_base64(picture_data));
|
||||
std::optional<ot::picture> cover = ot::extract_cover(tags);
|
||||
if (!cover)
|
||||
throw failure("could not extract the cover");
|
||||
if (cover->mime_type != "image/foo")
|
||||
throw failure("bad extracted MIME type");
|
||||
if (cover->picture_data != "Picture data")
|
||||
throw failure("bad extracted picture data");
|
||||
|
||||
std::string_view truncated_data = picture_data.substr(0, picture_data.size() - 1);
|
||||
tags.comments.push_front("METADATA_BLOCK_PICTURE=" + ot::encode_base64(truncated_data));
|
||||
try {
|
||||
ot::extract_cover(tags);
|
||||
throw failure("accepted a bad picture block");
|
||||
} catch (const ot::status& rc) {}
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
std::cout << "1..4\n";
|
||||
std::cout << "1..5\n";
|
||||
run(parse_standard, "parse a standard OpusTags packet");
|
||||
run(parse_corrupted, "correctly reject invalid packets");
|
||||
run(recode_standard, "recode a standard OpusTags packet");
|
||||
run(recode_padding, "recode a OpusTags packet with padding");
|
||||
run(extract_cover, "extract the cover art");
|
||||
return 0;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user