Add option --vendor

This commit is contained in:
Frédéric Mangano 2023-05-04 10:58:44 +09:00
parent 330fe5e9f2
commit dcb128f179
7 changed files with 63 additions and 47 deletions

View File

@ -64,6 +64,7 @@ Documentation
-e, --edit edit tags interactively in VISUAL/EDITOR
--output-cover FILE extract and save the cover art, if any
--set-cover FILE sets the cover art
--vendor print the vendor string
--raw disable encoding conversion
See the man page, `opustags.1`, for extensive documentation.

View File

@ -121,6 +121,10 @@ Specify \fB-\fP to read the picture from standard input.
In theory, an Opus file may contain multiple pictures with different roles, though in practice only
the front cover really matters. opustags can currently only handle one front cover and nothing else.
.TP
.B \-\-vendor
Print the vendor string from the OpusTags packet and do nothing else. Standard tags operations are
not supported when specifying this flag.
.TP
.B \-\-raw
OpusTags metadata should always be encoded in UTF-8, as per RFC 7845. However, some files may be
corrupted or possibly even contain intentional binary data. In that case, --raw lets you edit that

View File

@ -38,6 +38,7 @@ Options:
-e, --edit edit tags interactively in VISUAL/EDITOR
--output-cover FILE extract and save the cover art, if any
--set-cover FILE sets the cover art
--vendor print the vendor string
--raw disable encoding conversion
See the man page for extensive documentation.
@ -56,6 +57,7 @@ static struct option getopt_options[] = {
{"edit", no_argument, 0, 'e'},
{"output-cover", required_argument, 0, 'c'},
{"set-cover", required_argument, 0, 'C'},
{"vendor", no_argument, 0, 'v'},
{"raw", no_argument, 0, 'r'},
{NULL, 0, 0, 0}
};
@ -123,6 +125,9 @@ ot::options ot::parse_options(int argc, char** argv, FILE* comments_input)
throw status {st::bad_arguments, "Cannot specify --set-cover more than once."};
set_cover = optarg;
break;
case 'v':
opt.print_vendor = true;
break;
case 'r':
opt.raw = true;
break;
@ -172,6 +177,8 @@ ot::options ot::parse_options(int argc, char** argv, FILE* comments_input)
}
}
bool read_only = !opt.in_place && !opt.path_out.has_value();
if (opt.in_place && opt.path_out)
throw status {st::bad_arguments, "Cannot combine --in-place and --output."};
@ -184,7 +191,7 @@ ot::options ot::parse_options(int argc, char** argv, FILE* comments_input)
if (opt.edit_interactively && (stdin_as_input || opt.path_out == "-" || opt.cover_out == "-"))
throw status {st::bad_arguments, "Cannot edit interactively when standard input or standard output are already used."};
if (opt.edit_interactively && !opt.path_out.has_value() && !opt.in_place)
if (opt.edit_interactively && read_only)
throw status {st::bad_arguments, "Cannot edit interactively when no output is specified."};
if (opt.edit_interactively && (opt.delete_all || !opt.to_add.empty() || !opt.to_delete.empty()))
@ -196,6 +203,9 @@ ot::options ot::parse_options(int argc, char** argv, FILE* comments_input)
if (opt.cover_out && opt.paths_in.size() > 1)
throw status {st::bad_arguments, "Cannot use --output-cover with multiple input files."};
if (opt.print_vendor && !read_only)
throw status {st::bad_arguments, "--vendor is only supported in read-only mode."};
if (set_cover) {
byte_string picture_data = ot::slurp_binary_file(set_cover->c_str());
opt.to_delete.push_back(u8"METADATA_BLOCK_PICTURE"s);
@ -231,6 +241,26 @@ static std::u8string format_value(const std::u8string& source)
return formatted;
}
/**
* Convert the comment from UTF-8 to the system encoding if relevant, and print it with a trailing
* line feed.
*/
static void puts_utf8(std::u8string_view str, FILE* output, bool raw)
{
if (raw) {
fwrite(str.data(), 1, str.size(), output);
} else {
try {
std::string local = ot::decode_utf8(str);
fwrite(local.data(), 1, local.size(), output);
} catch (ot::status& rc) {
rc.message += " See --raw.";
throw;
}
}
putc('\n', output);
}
/**
* Print comments in a human readable format that can also be read back in by #read_comment.
*
@ -249,21 +279,8 @@ void ot::print_comments(const std::list<std::u8string>& comments, FILE* output,
}
}
}
std::u8string utf8_comment = format_value(source_comment);
// Convert the comment from UTF-8 to the system encoding if relevant.
if (raw) {
fwrite(utf8_comment.data(), 1, utf8_comment.size(), output);
} else {
try {
std::string local = decode_utf8(utf8_comment);
fwrite(local.data(), 1, local.size(), output);
} catch (ot::status& rc) {
rc.message += " See --raw.";
throw;
}
}
putc('\n', output);
puts_utf8(utf8_comment, output, raw);
}
if (has_control)
fputs("warning: Some tags contain control characters.\n", stderr);
@ -498,8 +515,12 @@ static void process(ot::ogg_reader& reader, ot::ogg_writer* writer, const ot::op
writer->write_header_packet(serialno, pageno, packet);
pageno_offset = writer->next_page_no - 1 - reader.absolute_page_no;
} else {
if (opt.cover_out != "-")
ot::print_comments(tags.comments, stdout, opt.raw);
if (opt.cover_out != "-") {
if (opt.print_vendor)
puts_utf8(tags.vendor, stdout, opt.raw);
else
ot::print_comments(tags.comments, stdout, opt.raw);
}
break;
}
} else if (writer) {

View File

@ -3,7 +3,7 @@
* \ingroup opus
*
* The way Opus is encapsulated into an Ogg stream, and the content of the packets we're dealing
* with here is defined by [RFC 7584](https://tools.ietf.org/html/rfc7845.html).
* with here is defined by [RFC 7845](https://tools.ietf.org/html/rfc7845.html).
*
* Section 3 "Packet Organization" is critical for us:
*

View File

@ -515,6 +515,13 @@ struct options {
* Option: --output-cover
*/
std::optional<std::string> cover_out;
/**
* Print the vendor string at the beginning of the OpusTags packet instead of printing the
* tags. Only applicable in read-only mode.
*
* Option: --vendor
*/
bool print_vendor = false;
/**
* Disable encoding conversions. OpusTags are specified to always be encoded as UTF-8, but
* if for some reason a specific file contains binary tags that someone would like to

View File

@ -185,6 +185,8 @@ void check_bad_arguments()
"Cannot specify standard output for both --output and --output-cover.", "-o and --output-cover conflict");
error_case({"opustags", "-i", "x", "y", "--output-cover", "z"},
"Cannot use --output-cover with multiple input files.", "--output-cover with multiple input");
error_case({"opustags", "-i", "--vendor", "x"},
"--vendor is only supported in read-only mode.", "--vendor when editing");
error_case({"opustags", "-d", "\xFF", "x"},
"Could not encode argument into UTF-8:",
"-d with binary data");

View File

@ -4,7 +4,8 @@ use strict;
use warnings;
use utf8;
use Test::More tests => 59;
use Test::More tests => 60;
use Test::Deep qw(cmp_deeply re);
use Digest::MD5;
use File::Basename;
@ -53,34 +54,9 @@ $help->[0] =~ /^([^\n]*+)/;
my $version = $1;
like($version, qr/^opustags version (\d+\.\d+\.\d+)/, 'get the version string');
my $expected_help = <<"EOF";
$version
Usage: opustags --help
opustags [OPTIONS] FILE
opustags OPTIONS -i FILE...
opustags OPTIONS FILE -o FILE
Options:
-h, --help print this help
-o, --output FILE specify the output file
-i, --in-place overwrite the input files
-y, --overwrite overwrite the output file if it already exists
-a, --add FIELD=VALUE add a comment
-d, --delete FIELD[=VALUE] delete previously existing comments
-D, --delete-all delete all the previously existing comments
-s, --set FIELD=VALUE replace a comment
-S, --set-all import comments from standard input
-e, --edit edit tags interactively in VISUAL/EDITOR
--output-cover FILE extract and save the cover art, if any
--set-cover FILE sets the cover art
--raw disable encoding conversion
See the man page for extensive documentation.
EOF
is_deeply(opustags('--help'), [$expected_help, '', 0], '--help displays the help message');
is_deeply(opustags('-h'), [$expected_help, '', 0], '-h displays the help message too');
my $expected_help = qr{opustags version .*\n\nUsage: opustags --help\n};
cmp_deeply(opustags('--help'), [re($expected_help), '', 0], '--help displays the help message');
cmp_deeply(opustags('-h'), [re($expected_help), '', 0], '-h displays the help message too');
is_deeply(opustags('--derp'), ['', <<"EOF", 512], 'unrecognized option shows an error');
error: Unrecognized option '--derp'.
@ -342,3 +318,8 @@ unlink('out.png');
is_deeply(opustags(qw(-D --set-cover - gobble.opus), { in => "GIF8 x" }), [<<'END_OUT', '', 0], 'read the cover from stdin');
METADATA_BLOCK_PICTURE=AAAAAwAAAAlpbWFnZS9naWYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAZHSUY4IHg=
END_OUT
####################################################################################################
# Vendor string
is_deeply(opustags(qw(--vendor gobble.opus)), ["Lavf58.12.100\n", '', 0], 'print the vendor string');