mirror of
https://github.com/fmang/opustags.git
synced 2025-01-15 12:43:17 +01:00
Support reading the cover art from a stream
This commit is contained in:
parent
46cc78bfff
commit
befae72d2a
@ -118,6 +118,7 @@ You can specify \fB-\fP for standard output, in which case the regular output wi
|
||||
.TP
|
||||
.B \-\-set-cover \fIFILE\fP
|
||||
Replace or set the cover art to the specified picture.
|
||||
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
|
||||
|
28
src/cli.cc
28
src/cli.cc
@ -136,17 +136,20 @@ ot::options ot::parse_options(int argc, char** argv, FILE* comments_input)
|
||||
return opt;
|
||||
|
||||
// All non-option arguments are input files.
|
||||
bool stdin_as_input = false;
|
||||
size_t stdin_uses = 0;
|
||||
for (int i = optind; i < argc; i++) {
|
||||
stdin_as_input = stdin_as_input || strcmp(argv[i], "-") == 0;
|
||||
if (strcmp(argv[i], "-") == 0)
|
||||
++stdin_uses;
|
||||
opt.paths_in.emplace_back(argv[i]);
|
||||
}
|
||||
bool stdin_as_input = stdin_uses > 0;
|
||||
|
||||
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_add.push_back(ot::make_cover(picture_data));
|
||||
}
|
||||
if (set_cover == "-")
|
||||
++stdin_uses;
|
||||
if (set_all)
|
||||
++stdin_uses;
|
||||
if (stdin_uses > 1)
|
||||
throw status { st::bad_arguments, "Cannot use standard input more than once." };
|
||||
|
||||
// Convert arguments to UTF-8.
|
||||
if (!opt.raw) {
|
||||
@ -169,10 +172,7 @@ ot::options ot::parse_options(int argc, char** argv, FILE* comments_input)
|
||||
if ((!opt.in_place || opt.edit_interactively) && opt.paths_in.size() != 1)
|
||||
throw status {st::bad_arguments, "Exactly one input file must be specified."};
|
||||
|
||||
if (set_all && stdin_as_input)
|
||||
throw status {st::bad_arguments, "Cannot use standard input as input file when --set-all is specified."};
|
||||
|
||||
if (opt.edit_interactively && (stdin_as_input || opt.path_out == "-"))
|
||||
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)
|
||||
@ -187,6 +187,12 @@ 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 (set_cover) {
|
||||
byte_string picture_data = ot::slurp_binary_file(set_cover->c_str());
|
||||
opt.to_delete.push_back("METADATA_BLOCK_PICTURE");
|
||||
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);
|
||||
|
@ -98,26 +98,47 @@ void ot::partial_file::abort()
|
||||
remove(temporary_name.c_str());
|
||||
}
|
||||
|
||||
/** \todo Support non-seekable files like streams. */
|
||||
/**
|
||||
* Determine the file size, in bytes, of the given file. Return -1 on for streams.
|
||||
*/
|
||||
static long get_file_size(FILE* f)
|
||||
{
|
||||
if (fseek(f, 0L, SEEK_END) != 0) {
|
||||
clearerr(f); // Recover.
|
||||
return -1;
|
||||
}
|
||||
long file_size = ftell(f);
|
||||
rewind(f);
|
||||
return file_size;
|
||||
}
|
||||
|
||||
ot::byte_string ot::slurp_binary_file(const char* filename)
|
||||
{
|
||||
std::ifstream file(filename, std::ios::binary | std::ios::ate);
|
||||
if (file.fail())
|
||||
file f = strcmp(filename, "-") == 0 ? freopen(nullptr, "rb", stdin)
|
||||
: fopen(filename, "rb");
|
||||
if (f == nullptr)
|
||||
throw status { st::standard_error,
|
||||
"Could not open '"s + filename + "': " + strerror(errno) + "." };
|
||||
|
||||
auto file_size = file.tellg();
|
||||
if (file_size == decltype(file)::pos_type(-1))
|
||||
throw status { st::standard_error,
|
||||
"Could not determine the size of '"s + filename + "': " + strerror(errno) + "." };
|
||||
|
||||
byte_string content;
|
||||
content.resize(file_size);
|
||||
file.seekg(0);
|
||||
file.read(reinterpret_cast<char*>(content.data()), file_size);
|
||||
if (file.fail())
|
||||
throw status { st::standard_error,
|
||||
"Could not read '"s + filename + "': " + strerror(errno) + "." };
|
||||
long file_size = get_file_size(f.get());
|
||||
if (file_size == -1) {
|
||||
// Read the input stream block by block and resize the output byte string as needed.
|
||||
uint8_t buffer[4096];
|
||||
while (!feof(f.get())) {
|
||||
size_t read_len = fread(buffer, 1, sizeof(buffer), f.get());
|
||||
content.append(buffer, read_len);
|
||||
if (ferror(f.get()))
|
||||
throw status { st::standard_error,
|
||||
"Could not read '"s + filename + "': " + strerror(errno) + "." };
|
||||
}
|
||||
} else {
|
||||
// Lucky! We know the file size, so let’s slurp it at once.
|
||||
content.resize(file_size);
|
||||
if (fread(content.data(), 1, file_size, f.get()) < file_size)
|
||||
throw status { st::standard_error,
|
||||
"Could not read '"s + filename + "': " + strerror(errno) + "." };
|
||||
}
|
||||
|
||||
return content;
|
||||
}
|
||||
|
3
t/cli.cc
3
t/cli.cc
@ -159,8 +159,7 @@ void check_bad_arguments()
|
||||
error_case({"opustags", "--derp=y"}, "Unrecognized option '--derp=y'.", "unrecognized long option with value");
|
||||
error_case({"opustags", "-aX=Y"}, "Exactly one input file must be specified.", "no input file");
|
||||
error_case({"opustags", "-i", "-o", "/dev/null", "-"}, "Cannot combine --in-place and --output.", "in-place + output");
|
||||
error_case({"opustags", "-S", "-"}, "Cannot use standard input as input file when --set-all is specified.",
|
||||
"set all and read opus from stdin");
|
||||
error_case({"opustags", "-S", "-"}, "Cannot use standard input more than once.", "set all and read opus from stdin");
|
||||
error_case({"opustags", "-i", "-"}, "Cannot modify standard input in place.", "write stdin in-place");
|
||||
error_case({"opustags", "-o", "x", "--output", "y", "z"},
|
||||
"Cannot specify --output more than once.", "double output");
|
||||
|
10
t/opustags.t
10
t/opustags.t
@ -4,7 +4,7 @@ use strict;
|
||||
use warnings;
|
||||
use utf8;
|
||||
|
||||
use Test::More tests => 58;
|
||||
use Test::More tests => 59;
|
||||
|
||||
use Digest::MD5;
|
||||
use File::Basename;
|
||||
@ -331,10 +331,14 @@ unlink('out.opus');
|
||||
####################################################################################################
|
||||
# Cover arts
|
||||
|
||||
is_deeply(opustags(qw(-D --set-cover pixel.png gobble.opus -o out.opus), ), ['', '', 0], 'set the cover');
|
||||
is_deeply(opustags(qw(--output-cover out.png out.opus), ), [<<'END_OUT', '', 0], 'extract the cover');
|
||||
is_deeply(opustags(qw(-D --set-cover pixel.png gobble.opus -o out.opus)), ['', '', 0], 'set the cover');
|
||||
is_deeply(opustags(qw(--output-cover out.png out.opus)), [<<'END_OUT', '', 0], 'extract the cover');
|
||||
METADATA_BLOCK_PICTURE=AAAAAwAAAAlpbWFnZS9wbmcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEWJUE5HDQoaCgAAAA1JSERSAAAAAQAAAAEIAgAAAJB3U94AAAAMSURBVAjXY/j//z8ABf4C/tzMWecAAAAASUVORK5CYII=
|
||||
END_OUT
|
||||
is(md5('out.png'), md5('pixel.png'), 'the extracted cover is identical to the one set');
|
||||
unlink('out.opus');
|
||||
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
|
||||
|
Loading…
x
Reference in New Issue
Block a user