Skip to content

Commit

Permalink
Add option --vendor
Browse files Browse the repository at this point in the history
  • Loading branch information
fmang committed May 4, 2023
1 parent 330fe5e commit dcb128f
Show file tree
Hide file tree
Showing 7 changed files with 63 additions and 47 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
4 changes: 4 additions & 0 deletions opustags.1
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
55 changes: 38 additions & 17 deletions src/cli.cc
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ Usage: opustags --help
-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.
Expand All @@ -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}
};
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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."};

Expand All @@ -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()))
Expand All @@ -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);
Expand Down Expand Up @@ -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.
*
Expand All @@ -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);
Expand Down Expand Up @@ -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) {
Expand Down
2 changes: 1 addition & 1 deletion src/opus.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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:
*
Expand Down
7 changes: 7 additions & 0 deletions src/opustags.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions t/cli.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down
39 changes: 10 additions & 29 deletions t/opustags.t
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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'.
Expand Down Expand Up @@ -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');

0 comments on commit dcb128f

Please sign in to comment.