mirror of
https://github.com/fmang/opustags.git
synced 2025-01-15 12:43:17 +01:00
Use std::u8string where appropriate
This commit is contained in:
parent
89dc000927
commit
1d13c258e4
@ -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;
|
||||
|
93
src/cli.cc
93
src/cli.cc
@ -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::string’s 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) { // Don’t 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);
|
||||
}
|
||||
|
||||
|
24
src/opus.cc
24
src/opus.cc
@ -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());
|
||||
}
|
||||
|
@ -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 file’s 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
|
||||
|
36
t/base64.cc
36
t/base64.cc
@ -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) {
|
||||
}
|
||||
|
34
t/cli.cc
34
t/cli.cc
@ -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");
|
||||
}
|
||||
|
16
t/opus.cc
16
t/opus.cc
@ -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()
|
||||
|
Loading…
x
Reference in New Issue
Block a user