Use std::u8string where appropriate

This commit is contained in:
Frédéric Mangano 2023-03-03 12:07:31 +09:00
parent 89dc000927
commit 1d13c258e4
7 changed files with 123 additions and 122 deletions

View File

@ -13,10 +13,10 @@
#include <cstring>
static const char base64_table[65] =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
static const char8_t base64_table[65] =
u8"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
std::string ot::encode_base64(ot::byte_string_view src)
std::u8string ot::encode_base64(ot::byte_string_view src)
{
size_t len = src.size();
size_t num_blocks = (len + 2) / 3; // Count of 3-byte blocks, rounded up.
@ -24,12 +24,12 @@ std::string ot::encode_base64(ot::byte_string_view src)
if (olen < len)
throw std::overflow_error("failed to encode excessively long base64 block");
std::string out;
std::u8string out;
out.resize(olen);
const uint8_t* in = src.data();
const uint8_t* end = in + len;
char* pos = out.data();
char8_t* pos = out.data();
while (end - in >= 3) {
*pos++ = base64_table[in[0] >> 2];
*pos++ = base64_table[((in[0] & 0x03) << 4) | (in[1] >> 4)];
@ -53,10 +53,10 @@ std::string ot::encode_base64(ot::byte_string_view src)
return out;
}
ot::byte_string ot::decode_base64(std::string_view src)
ot::byte_string ot::decode_base64(std::u8string_view src)
{
// Remove the padding and rely on the string length instead.
while (src.back() == '=')
while (src.back() == u8'=')
src.remove_suffix(1);
size_t olen = src.size() / 4 * 3; // Whole blocks;

View File

@ -65,6 +65,8 @@ ot::options ot::parse_options(int argc, char** argv, FILE* comments_input)
options opt;
const char* equal;
ot::status rc;
std::list<std::string> local_to_add; // opt.to_add before UTF-8 conversion.
std::list<std::string> local_to_delete; // opt.to_delete before UTF-8 conversion.
bool set_all = false;
std::optional<std::string> set_cover;
opt = {};
@ -90,7 +92,7 @@ ot::options ot::parse_options(int argc, char** argv, FILE* comments_input)
opt.overwrite = true;
break;
case 'd':
opt.to_delete.emplace_back(optarg);
local_to_delete.emplace_back(optarg);
break;
case 'a':
case 's':
@ -98,8 +100,8 @@ ot::options ot::parse_options(int argc, char** argv, FILE* comments_input)
if (equal == nullptr)
throw status {st::bad_arguments, "Comment does not contain an equal sign: "s + optarg + "."};
if (c == 's')
opt.to_delete.emplace_back(optarg, equal - optarg);
opt.to_add.emplace_back(optarg);
local_to_delete.emplace_back(optarg, equal - optarg);
local_to_add.emplace_back(optarg);
break;
case 'S':
opt.delete_all = true;
@ -151,14 +153,22 @@ ot::options ot::parse_options(int argc, char** argv, FILE* comments_input)
throw status { st::bad_arguments, "Cannot use standard input more than once." };
// Convert arguments to UTF-8.
if (!opt.raw) {
for (std::list<std::string>* args : { &opt.to_add, &opt.to_delete }) {
try {
for (std::string& arg : *args)
arg = to_utf8(arg);
} catch (const ot::status& rc) {
throw status {st::bad_arguments, "Could not encode argument into UTF-8: " + rc.message};
}
if (opt.raw) {
// Cast the user data without any encoding conversion.
auto cast_to_utf8 = [](std::string_view in)
{ return std::u8string(reinterpret_cast<const char8_t*>(in.data()), in.size()); };
std::transform(local_to_add.begin(), local_to_add.end(),
std::back_inserter(opt.to_add), cast_to_utf8);
std::transform(local_to_delete.begin(), local_to_delete.end(),
std::back_inserter(opt.to_delete), cast_to_utf8);
} else {
try {
std::transform(local_to_add.begin(), local_to_add.end(),
std::back_inserter(opt.to_add), encode_utf8);
std::transform(local_to_delete.begin(), local_to_delete.end(),
std::back_inserter(opt.to_delete), encode_utf8);
} catch (const ot::status& rc) {
throw status {st::bad_arguments, "Could not encode argument into UTF-8: " + rc.message};
}
}
@ -188,13 +198,13 @@ ot::options ot::parse_options(int argc, char** argv, FILE* comments_input)
if (set_cover) {
byte_string picture_data = ot::slurp_binary_file(set_cover->c_str());
opt.to_delete.push_back("METADATA_BLOCK_PICTURE");
opt.to_delete.push_back(u8"METADATA_BLOCK_PICTURE"s);
opt.to_add.push_back(ot::make_cover(picture_data));
}
if (set_all) {
// Read comments from stdin and prepend them to opt.to_add.
std::list<std::string> comments = read_comments(comments_input, opt.raw);
std::list<std::u8string> comments = read_comments(comments_input, opt.raw);
opt.to_add.splice(opt.to_add.begin(), std::move(comments));
}
return opt;
@ -202,21 +212,21 @@ ot::options ot::parse_options(int argc, char** argv, FILE* comments_input)
/** Format a UTF-8 string by adding tabulations (\t) after line feeds (\n) to mark continuation for
* multiline values. */
static std::string format_value(const std::string& source)
static std::u8string format_value(const std::u8string& source)
{
auto newline_count = std::count(source.begin(), source.end(), '\n');
auto newline_count = std::count(source.begin(), source.end(), u8'\n');
// General case: the value fits on a single line. Use std::strings copy constructor for the
// most efficient copy we could hope for.
if (newline_count == 0)
return source;
std::string formatted;
std::u8string formatted;
formatted.reserve(source.size() + newline_count);
for (auto c : source) {
formatted.push_back(c);
if (c == '\n')
formatted.push_back('\t');
formatted.push_back(u8'\t');
}
return formatted;
}
@ -227,11 +237,10 @@ static std::string format_value(const std::string& source)
* To disambiguate between a newline embedded in a comment and a newline representing the start of
* the next tag, continuation lines always have a single TAB (^I) character added to the beginning.
*/
void ot::print_comments(const std::list<std::string>& comments, FILE* output, bool raw)
void ot::print_comments(const std::list<std::u8string>& comments, FILE* output, bool raw)
{
std::string local;
bool has_control = false;
for (const std::string& source_comment : comments) {
for (const std::u8string& source_comment : comments) {
if (!has_control) { // Dont bother analyzing comments if the flag is already up.
for (unsigned char c : source_comment) {
if (c < 0x20 && c != '\n') {
@ -241,46 +250,43 @@ void ot::print_comments(const std::list<std::string>& comments, FILE* output, bo
}
}
std::string utf8_comment = format_value(source_comment);
const std::string* comment;
std::u8string utf8_comment = format_value(source_comment);
// Convert the comment from UTF-8 to the system encoding if relevant.
if (raw) {
comment = &utf8_comment;
fwrite(utf8_comment.data(), 1, utf8_comment.size(), output);
} else {
try {
local = from_utf8(utf8_comment);
comment = &local;
std::string local = decode_utf8(utf8_comment);
fwrite(local.data(), 1, local.size(), output);
} catch (ot::status& rc) {
rc.message += " See --raw.";
throw;
}
}
fwrite(comment->data(), 1, comment->size(), output);
putc('\n', output);
}
if (has_control)
fputs("warning: Some tags contain control characters.\n", stderr);
}
std::list<std::string> ot::read_comments(FILE* input, bool raw)
std::list<std::u8string> ot::read_comments(FILE* input, bool raw)
{
std::list<std::string> comments;
std::list<std::u8string> comments;
comments.clear();
char* source_line = nullptr;
size_t buflen = 0;
ssize_t nread;
std::string* previous_comment = nullptr;
std::u8string* previous_comment = nullptr;
while ((nread = getline(&source_line, &buflen, input)) != -1) {
if (nread > 0 && source_line[nread - 1] == '\n')
--nread; // Chomp.
std::string line;
std::u8string line;
if (raw) {
line = std::string(source_line, nread);
line = std::u8string(reinterpret_cast<char8_t*>(source_line), nread);
} else {
try {
line = to_utf8(std::string_view(source_line, nread));
line = encode_utf8(std::string_view(source_line, nread));
} catch (const ot::status& rc) {
free(source_line);
throw ot::status {ot::st::badly_encoded, "UTF-8 conversion error: " + rc.message};
@ -290,10 +296,10 @@ std::list<std::string> ot::read_comments(FILE* input, bool raw)
if (line.empty()) {
// Ignore empty lines.
previous_comment = nullptr;
} else if (line[0] == '#') {
} else if (line[0] == u8'#') {
// Ignore comments.
previous_comment = nullptr;
} else if (line[0] == '\t') {
} else if (line[0] == u8'\t') {
// Continuation line: append the current line to the previous tag.
if (previous_comment == nullptr) {
ot::status rc = {ot::st::error, "Unexpected continuation line: " + std::string(source_line, nread)};
@ -303,7 +309,7 @@ std::list<std::string> ot::read_comments(FILE* input, bool raw)
line[0] = '\n';
previous_comment->append(line);
}
} else if (line.find('=') == std::string::npos) {
} else if (line.find(u8'=') == decltype(line)::npos) {
ot::status rc = {ot::st::error, "Malformed tag: " + std::string(source_line, nread)};
free(source_line);
throw rc;
@ -315,19 +321,20 @@ std::list<std::string> ot::read_comments(FILE* input, bool raw)
return comments;
}
void ot::delete_comments(std::list<std::string>& comments, const std::string& selector)
void ot::delete_comments(std::list<std::u8string>& comments, const std::u8string& selector)
{
auto name = selector.data();
auto equal = selector.find('=');
auto value = (equal == std::string::npos ? nullptr : name + equal + 1);
auto equal = selector.find(u8'=');
auto value = (equal == std::u8string::npos ? nullptr : name + equal + 1);
auto name_len = value ? equal : selector.size();
auto value_len = value ? selector.size() - equal - 1 : 0;
auto it = comments.begin(), end = comments.end();
while (it != end) {
auto current = it++;
/** \todo Avoid using strncasecmp because it assumes the system locale is UTF-8. */
bool name_match = current->size() > name_len + 1 &&
(*current)[name_len] == '=' &&
strncasecmp(current->data(), name, name_len) == 0;
strncasecmp((const char*) current->data(), (const char*) name, name_len) == 0;
if (!name_match)
continue;
bool value_match = value == nullptr ||
@ -343,11 +350,11 @@ static void edit_tags(ot::opus_tags& tags, const ot::options& opt)
{
if (opt.delete_all) {
tags.comments.clear();
} else for (const std::string& name : opt.to_delete) {
ot::delete_comments(tags.comments, name.c_str());
} else for (const std::u8string& name : opt.to_delete) {
ot::delete_comments(tags.comments, name);
}
for (const std::string& comment : opt.to_add)
for (const std::u8string& comment : opt.to_add)
tags.comments.emplace_back(comment);
}

View File

@ -30,14 +30,14 @@ ot::opus_tags ot::parse_tags(const ogg_packet& packet)
if (packet.bytes < 0)
throw status {st::int_overflow, "Overflowing comment header length"};
size_t size = static_cast<size_t>(packet.bytes);
const char* data = reinterpret_cast<char*>(packet.packet);
const uint8_t* data = reinterpret_cast<uint8_t*>(packet.packet);
size_t pos = 0;
opus_tags my_tags;
// Magic number
if (8 > size)
throw status {st::cut_magic_number, "Comment header too short for the magic number"};
if (memcmp(data, "OpusTags", 8) != 0)
if (memcmp(data, u8"OpusTags", 8) != 0)
throw status {st::bad_magic_number, "Comment header did not start with OpusTags"};
// Vendor
@ -48,7 +48,7 @@ ot::opus_tags ot::parse_tags(const ogg_packet& packet)
size_t vendor_length = le32toh(*((uint32_t*) (data + pos)));
if (pos + 4 + vendor_length > size)
throw status {st::cut_vendor_data, "Vendor string did not fit the comment header"};
my_tags.vendor = std::string(data + pos + 4, vendor_length);
my_tags.vendor = std::u8string(reinterpret_cast<const char8_t*>(&data[pos + 4]), vendor_length);
pos += 4 + my_tags.vendor.size();
// Comment count
@ -66,13 +66,13 @@ ot::opus_tags ot::parse_tags(const ogg_packet& packet)
if (pos + 4 + comment_length > size)
throw status {st::cut_comment_data,
"Comment string did not fit the comment header"};
const char *comment_value = data + pos + 4;
auto comment_value = reinterpret_cast<const char8_t*>(&data[pos + 4]);
my_tags.comments.emplace_back(comment_value, comment_length);
pos += 4 + comment_length;
}
// Extra data
my_tags.extra_data = std::string(data + pos, size - pos);
my_tags.extra_data = byte_string(data + pos, size - pos);
return my_tags;
}
@ -80,7 +80,7 @@ ot::opus_tags ot::parse_tags(const ogg_packet& packet)
ot::dynamic_ogg_packet ot::render_tags(const opus_tags& tags)
{
size_t size = 8 + 4 + tags.vendor.size() + 4;
for (const std::string& comment : tags.comments)
for (const std::u8string& comment : tags.comments)
size += 4 + comment.size();
size += tags.extra_data.size();
@ -100,7 +100,7 @@ ot::dynamic_ogg_packet ot::render_tags(const opus_tags& tags)
n = htole32(tags.comments.size());
memcpy(data, &n, 4);
data += 4;
for (const std::string& comment : tags.comments) {
for (const std::u8string& comment : tags.comments) {
n = htole32(comment.size());
memcpy(data, &n, 4);
memcpy(data+4, comment.data(), comment.size());
@ -166,8 +166,8 @@ ot::byte_string ot::picture::serialize() const
*/
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); };
static const std::u8string_view prefix = u8"METADATA_BLOCK_PICTURE="sv;
auto is_cover = [](const std::u8string& 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.
@ -177,7 +177,7 @@ std::optional<ot::picture> ot::extract_cover(const ot::opus_tags& tags)
fputs("warning: Found multiple covers; only the first will be extracted."
" Please report your use case if you need a finer selection.\n", stderr);
std::string_view cover_value = *cover_tag;
std::u8string_view cover_value = *cover_tag;
cover_value.remove_prefix(prefix.size());
return picture(decode_base64(cover_value));
}
@ -202,10 +202,10 @@ static ot::byte_string_view detect_mime_type(ot::byte_string_view data)
return "application/octet-stream"_bsv;
}
std::string ot::make_cover(ot::byte_string_view picture_data)
std::u8string ot::make_cover(ot::byte_string_view picture_data)
{
picture pic;
pic.mime_type = detect_mime_type(picture_data);
pic.picture_data = picture_data;
return "METADATA_BLOCK_PICTURE=" + encode_base64(pic.serialize());
return u8"METADATA_BLOCK_PICTURE=" + encode_base64(pic.serialize());
}

View File

@ -186,8 +186,8 @@ void run_editor(std::string_view editor, std::string_view path);
*/
timespec get_file_timestamp(const char* path);
std::string encode_base64(byte_string_view src);
byte_string decode_base64(std::string_view src);
std::u8string encode_base64(byte_string_view src);
byte_string decode_base64(std::u8string_view src);
/** \} */
@ -361,7 +361,7 @@ struct opus_tags {
* OpusTags packets begin with a vendor string, meant to identify the implementation of the
* encoder. It is expected to be an arbitrary UTF-8 string.
*/
std::string vendor;
std::u8string vendor;
/**
* Comments are strings in the NAME=Value format. A comment may also be called a field, or a
* tag.
@ -370,7 +370,7 @@ struct opus_tags {
* can be any valid UTF-8 string. The specification is not too clear for Opus, but let's
* assume it's the same.
*/
std::list<std::string> comments;
std::list<std::u8string> comments;
/**
* According to RFC 7845:
* > Immediately following the user comment list, the comment header MAY contain
@ -382,7 +382,7 @@ struct opus_tags {
* In the future, we could add options to manipulate this data: view it, edit it, truncate
* it if it's marked as padding, truncate it unconditionally.
*/
std::string extra_data;
byte_string extra_data;
};
/**
@ -428,7 +428,7 @@ std::optional<picture> extract_cover(const opus_tags& tags);
* Return a METADATA_BLOCK_PICTURE tag defining the front cover art to the given picture data (JPEG,
* PNG). The MIME type is deduced from the magic number.
*/
std::string make_cover(byte_string_view picture_data);
std::u8string make_cover(byte_string_view picture_data);
/** \} */
@ -493,11 +493,9 @@ struct options {
* #to_add takes precedence over #to_delete, so if the same comment appears in both lists,
* the one in #to_delete applies only to the previously existing tags.
*
* The strings are stored in UTF-8.
*
* Option: --delete, --set
*/
std::list<std::string> to_delete;
std::list<std::u8string> to_delete;
/**
* Delete all the existing comments.
*
@ -508,11 +506,9 @@ struct options {
* List of comments to add, in the current system encoding. For exemple `TITLE=a b c`. They
* must be valid.
*
* The strings are stored in UTF-8.
*
* Options: --add, --set, --set-all
*/
std::list<std::string> to_add;
std::list<std::u8string> to_add;
/**
* If set, the input files cover art is exported to the specified file. - for stdout. Does
* not overwrite the file if it already exists unless -y is specified. Does nothing if the
@ -544,21 +540,19 @@ options parse_options(int argc, char** argv, FILE* comments);
*
* The output generated is meant to be parseable by #ot::read_comments.
*/
void print_comments(const std::list<std::string>& comments, FILE* output, bool raw);
void print_comments(const std::list<std::u8string>& comments, FILE* output, bool raw);
/**
* Parse the comments outputted by #ot::print_comments. Unless raw is true, the comments are
* converted from the system encoding to UTF-8, and returned as UTF-8.
*/
std::list<std::string> read_comments(FILE* input, bool raw);
std::list<std::u8string> read_comments(FILE* input, bool raw);
/**
* Remove all comments matching the specified selector, which may either be a field name or a
* NAME=VALUE pair. The field name is case-insensitive.
*
* The strings are all UTF-8.
*/
void delete_comments(std::list<std::string>& comments, const std::string& selector);
void delete_comments(std::list<std::u8string>& comments, const std::u8string& selector);
/**
* Main entry point to the opustags program, and pretty much the same as calling opustags from the

View File

@ -3,35 +3,35 @@
static void check_encode_base64()
{
is(ot::encode_base64(""_bsv), "", "empty");
is(ot::encode_base64("a"_bsv), "YQ==", "1 character");
is(ot::encode_base64("aa"_bsv), "YWE=", "2 characters");
is(ot::encode_base64("aaa"_bsv), "YWFh", "3 characters");
is(ot::encode_base64("aaaa"_bsv), "YWFhYQ==", "4 characters");
is(ot::encode_base64("\xFF\xFF\xFE"_bsv), "///+", "RFC alphabet");
is(ot::encode_base64("\0x"_bsv), "AHg=", "embedded null bytes");
opaque_is(ot::encode_base64(""_bsv), u8"", "empty");
opaque_is(ot::encode_base64("a"_bsv), u8"YQ==", "1 character");
opaque_is(ot::encode_base64("aa"_bsv), u8"YWE=", "2 characters");
opaque_is(ot::encode_base64("aaa"_bsv), u8"YWFh", "3 characters");
opaque_is(ot::encode_base64("aaaa"_bsv), u8"YWFhYQ==", "4 characters");
opaque_is(ot::encode_base64("\xFF\xFF\xFE"_bsv), u8"///+", "RFC alphabet");
opaque_is(ot::encode_base64("\0x"_bsv), u8"AHg=", "embedded null bytes");
}
static void check_decode_base64()
{
opaque_is(ot::decode_base64(""), ""_bsv, "empty");
opaque_is(ot::decode_base64("YQ=="), "a"_bsv, "1 character");
opaque_is(ot::decode_base64("YWE="), "aa"_bsv, "2 characters");
opaque_is(ot::decode_base64("YQ"), "a"_bsv, "padless 1 character");
opaque_is(ot::decode_base64("YWE"), "aa"_bsv, "padless 2 characters");
opaque_is(ot::decode_base64("YWFh"), "aaa"_bsv, "3 characters");
opaque_is(ot::decode_base64("YWFhYQ=="), "aaaa"_bsv, "4 characters");
opaque_is(ot::decode_base64("///+"), "\xFF\xFF\xFE"_bsv, "RFC alphabet");
opaque_is(ot::decode_base64("AHg="), "\0x"_bsv, "embedded null bytes");
opaque_is(ot::decode_base64(u8""), ""_bsv, "empty");
opaque_is(ot::decode_base64(u8"YQ=="), "a"_bsv, "1 character");
opaque_is(ot::decode_base64(u8"YWE="), "aa"_bsv, "2 characters");
opaque_is(ot::decode_base64(u8"YQ"), "a"_bsv, "padless 1 character");
opaque_is(ot::decode_base64(u8"YWE"), "aa"_bsv, "padless 2 characters");
opaque_is(ot::decode_base64(u8"YWFh"), "aaa"_bsv, "3 characters");
opaque_is(ot::decode_base64(u8"YWFhYQ=="), "aaaa"_bsv, "4 characters");
opaque_is(ot::decode_base64(u8"///+"), "\xFF\xFF\xFE"_bsv, "RFC alphabet");
opaque_is(ot::decode_base64(u8"AHg="), "\0x"_bsv, "embedded null bytes");
try {
ot::decode_base64("Y===");
ot::decode_base64(u8"Y===");
throw failure("accepted a bad block size");
} catch (const ot::status& e) {
}
try {
ot::decode_base64("\xFF bad message!");
ot::decode_base64(u8"\xFF bad message!");
throw failure("accepted an invalid character");
} catch (const ot::status& e) {
}

View File

@ -3,7 +3,7 @@
#include <string.h>
static ot::status read_comments(FILE* input, std::list<std::string>& comments, bool raw)
static ot::status read_comments(FILE* input, std::list<std::u8string>& comments, bool raw)
{
try {
comments = ot::read_comments(input, raw);
@ -15,7 +15,7 @@ static ot::status read_comments(FILE* input, std::list<std::string>& comments, b
void check_read_comments()
{
std::list<std::string> comments;
std::list<std::u8string> comments;
ot::status rc;
{
std::string txt = "TITLE=a b c\n\nARTIST=X\nArtist=Y\n"s;
@ -23,7 +23,7 @@ void check_read_comments()
rc = read_comments(input.get(), comments, false);
if (rc != ot::st::ok)
throw failure("could not read comments");
auto&& expected = {"TITLE=a b c", "ARTIST=X", "Artist=Y"};
auto&& expected = {u8"TITLE=a b c", u8"ARTIST=X", u8"Artist=Y"};
if (!std::equal(comments.begin(), comments.end(), expected.begin(), expected.end()))
throw failure("parsed user comments did not match expectations");
}
@ -40,7 +40,7 @@ void check_read_comments()
rc = read_comments(input.get(), comments, true);
if (rc != ot::st::ok)
throw failure("could not read comments");
if (comments.front() != "RAW=\xFF\xFF")
if (comments.front() != (char8_t*) "RAW=\xFF\xFF")
throw failure("parsed user comments did not match expectations");
}
{
@ -49,7 +49,7 @@ void check_read_comments()
rc = read_comments(input.get(), comments, true);
if (rc != ot::st::ok)
throw failure("could not read comments");
if (comments.front() != "MULTILINE=First\nSecond")
if (comments.front() != u8"MULTILINE=First\nSecond")
throw failure("parsed user comments did not match expectations");
}
{
@ -109,14 +109,14 @@ void check_good_arguments()
opt = parse({"opustags", "x", "--output", "y", "-D", "-s", "X=Y Z", "-d", "a=b"});
if (opt.paths_in.size() != 1 || opt.paths_in.front() != "x" || !opt.path_out ||
opt.path_out != "y" || !opt.delete_all || opt.overwrite || opt.to_delete.size() != 2 ||
opt.to_delete.front() != "X" || *std::next(opt.to_delete.begin()) != "a=b" ||
opt.to_add != std::list<std::string>{"X=Y Z"})
opt.to_delete.front() != u8"X" || *std::next(opt.to_delete.begin()) != u8"a=b" ||
opt.to_add != std::list<std::u8string>{ u8"X=Y Z" })
throw failure("unexpected option parsing result for case #1");
opt = parse({"opustags", "-S", "x", "-S", "-a", "x=y z", "-i"});
if (opt.paths_in.size() != 1 || opt.paths_in.front() != "x" || opt.path_out ||
!opt.overwrite || opt.to_delete.size() != 0 ||
opt.to_add != std::list<std::string>{"N=1", "x=y z"})
opt.to_add != std::list<std::u8string>{ u8"N=1", u8"x=y z" })
throw failure("unexpected option parsing result for case #2");
opt = parse({"opustags", "-i", "x", "y", "z"});
@ -130,7 +130,7 @@ void check_good_arguments()
throw failure("unexpected option parsing result for case #4");
opt = parse({"opustags", "-a", "X=\xFF", "--raw", "x"});
if (!opt.raw || opt.to_add.front() != "X=\xFF")
if (!opt.raw || opt.to_add.front() != u8"X=\xFF")
throw failure("--raw did not disable transcoding");
}
@ -198,23 +198,23 @@ void check_bad_arguments()
static void check_delete_comments()
{
using C = std::list<std::string>;
C original = {"TITLE=X", "Title=Y", "Title=Z", "ARTIST=A", "artIst=B"};
using C = std::list<std::u8string>;
C original = {u8"TITLE=X", u8"Title=Y", u8"Title=Z", u8"ARTIST=A", u8"artIst=B"};
C edited = original;
ot::delete_comments(edited, "derp");
ot::delete_comments(edited, u8"derp");
if (!std::equal(edited.begin(), edited.end(), original.begin(), original.end()))
throw failure("should not have deleted anything");
ot::delete_comments(edited, "Title");
C expected = {"ARTIST=A", "artIst=B"};
ot::delete_comments(edited, u8"Title");
C expected = {u8"ARTIST=A", u8"artIst=B"};
if (!std::equal(edited.begin(), edited.end(), expected.begin(), expected.end()))
throw failure("did not delete all titles correctly");
edited = original;
ot::delete_comments(edited, "titlE=Y");
ot::delete_comments(edited, "Title=z");
expected = {"TITLE=X", "Title=Z", "ARTIST=A", "artIst=B"};
ot::delete_comments(edited, u8"titlE=Y");
ot::delete_comments(edited, u8"Title=z");
expected = {u8"TITLE=X", u8"Title=Z", u8"ARTIST=A", u8"artIst=B"};
if (!std::equal(edited.begin(), edited.end(), expected.begin(), expected.end()))
throw failure("did not delete a specific title correctly");
}

View File

@ -16,15 +16,15 @@ static void parse_standard()
op.bytes = sizeof(standard_OpusTags) - 1;
op.packet = (unsigned char*) standard_OpusTags;
ot::opus_tags tags = ot::parse_tags(op);
if (tags.vendor != "opustags test packet")
if (tags.vendor != u8"opustags test packet")
throw failure("bad vendor string");
if (tags.comments.size() != 2)
throw failure("bad number of comments");
auto it = tags.comments.begin();
if (*it != "TITLE=Foo")
if (*it != u8"TITLE=Foo")
throw failure("bad title");
++it;
if (*it != "ARTIST=Bar")
if (*it != u8"ARTIST=Bar")
throw failure("bad artist");
if (tags.extra_data.size() != 0)
throw failure("found mysterious padding data");
@ -123,7 +123,7 @@ static void recode_padding()
op.packet = (unsigned char*) padded_OpusTags.data();
ot::opus_tags tags = ot::parse_tags(op);
if (tags.extra_data != "\0hello"s)
if (tags.extra_data != "\0hello"_bsv)
throw failure("corrupted extra data");
// recode the packet and ensure it's exactly the same
auto packet = ot::render_tags(tags);
@ -148,7 +148,7 @@ static void extract_cover()
"\x00\x00\x00\x0C" "Picture data";
ot::opus_tags tags;
tags.comments = { "METADATA_BLOCK_PICTURE=" + ot::encode_base64(picture_data) };
tags.comments = { u8"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");
@ -158,7 +158,7 @@ static void extract_cover()
throw failure("bad extracted picture data");
ot::byte_string_view truncated_data = picture_data.substr(0, picture_data.size() - 1);
tags.comments = { "METADATA_BLOCK_PICTURE=" + ot::encode_base64(truncated_data) };
tags.comments = { u8"METADATA_BLOCK_PICTURE=" + ot::encode_base64(truncated_data) };
try {
ot::extract_cover(tags);
throw failure("accepted a bad picture block");
@ -177,8 +177,8 @@ static void make_cover()
"\x00\x00\x00\x00" // Palette size.
"\x00\x00\x00\x11" "\x89PNG Picture data";
std::string expected = "METADATA_BLOCK_PICTURE=" + ot::encode_base64(picture_block);
is(ot::make_cover("\x89PNG Picture data"_bsv), expected, "build the picture tag");
std::u8string expected = u8"METADATA_BLOCK_PICTURE=" + ot::encode_base64(picture_block);
opaque_is(ot::make_cover("\x89PNG Picture data"_bsv), expected, "build the picture tag");
}
int main()