cli: Revamp `extract` to allow specifying index of value to extract

This commit is contained in:
Martchus 2022-03-16 20:37:35 +01:00
parent 7f1e234f36
commit df1c949f88
4 changed files with 112 additions and 78 deletions

View File

@ -13,8 +13,8 @@ set(META_APP_DESCRIPTION
set(META_GUI_OPTIONAL true) set(META_GUI_OPTIONAL true)
set(META_JS_SRC_DIR renamingutility) set(META_JS_SRC_DIR renamingutility)
set(META_VERSION_MAJOR 3) set(META_VERSION_MAJOR 3)
set(META_VERSION_MINOR 6) set(META_VERSION_MINOR 7)
set(META_VERSION_PATCH 2) set(META_VERSION_PATCH 0)
set(META_ADD_DEFAULT_CPP_UNIT_TEST_APPLICATION ON) set(META_ADD_DEFAULT_CPP_UNIT_TEST_APPLICATION ON)
# add project files # add project files

View File

@ -186,13 +186,14 @@ int main(int argc, char *argv[])
ConfigValueArgument fieldArg("field", 'n', "specifies the field to be extracted", { "field name" }); ConfigValueArgument fieldArg("field", 'n', "specifies the field to be extracted", { "field name" });
fieldArg.setImplicit(true); fieldArg.setImplicit(true);
ConfigValueArgument attachmentArg("attachment", 'a', "specifies the attachment to be extracted", { "id=..." }); ConfigValueArgument attachmentArg("attachment", 'a', "specifies the attachment to be extracted", { "id=..." });
ConfigValueArgument indexArg("index", 'i', "specifies the value/attachment to extract by its index, e.g. 0 for the first value", { "0/1/2/..." });
OperationArgument extractFieldArg("extract", 'e', OperationArgument extractFieldArg("extract", 'e',
"saves the value of the specified field (eg. cover or other binary field) or attachment to the specified file or writes it to stdout if no " "saves the value of the specified field (e.g. cover or other binary field) or attachment to the specified file or writes it to stdout if no "
"output file has been specified"); "output file has been specified");
extractFieldArg.setSubArguments({ &fieldArg, &attachmentArg, &fileArg, &outputFileArg, &verboseArg }); extractFieldArg.setSubArguments({ &fieldArg, &attachmentArg, &indexArg, &fileArg, &outputFileArg, &verboseArg });
extractFieldArg.setExample(PROJECT_NAME " extract cover --output-file the-cover.jpg --file some-file.opus"); extractFieldArg.setExample(PROJECT_NAME " extract cover --output-file the-cover.jpg --file some-file.opus");
extractFieldArg.setCallback(std::bind( extractFieldArg.setCallback(std::bind(Cli::extractField, std::cref(fieldArg), std::cref(attachmentArg), std::cref(fileArg),
Cli::extractField, std::cref(fieldArg), std::cref(attachmentArg), std::cref(fileArg), std::cref(outputFileArg), std::cref(verboseArg))); std::cref(outputFileArg), std::cref(indexArg), std::cref(verboseArg)));
// export to JSON // export to JSON
ConfigValueArgument prettyArg("pretty", '\0', "prints with indentation and spacing"); ConfigValueArgument prettyArg("pretty", '\0', "prints with indentation and spacing");
OperationArgument exportArg("export", 'j', "exports the tag information for the specified files to JSON"); OperationArgument exportArg("export", 'j', "exports the tag information for the specified files to JSON");

View File

@ -925,14 +925,14 @@ void setTagInfo(const SetTagInfoArgs &args)
} }
} }
void extractField( void extractField(const Argument &fieldArg, const Argument &attachmentArg, const Argument &inputFilesArg, const Argument &outputFileArg,
const Argument &fieldArg, const Argument &attachmentArg, const Argument &inputFilesArg, const Argument &outputFileArg, const Argument &verboseArg) const Argument &indexArg, const Argument &verboseArg)
{ {
CMD_UTILS_START_CONSOLE; CMD_UTILS_START_CONSOLE;
// parse specified field and attachment // parse specified field and attachment
const auto fieldDenotations = parseFieldDenotations(fieldArg, true); const auto fieldDenotations = parseFieldDenotations(fieldArg, true);
AttachmentInfo attachmentInfo; auto attachmentInfo = AttachmentInfo();
if (attachmentArg.isPresent()) { if (attachmentArg.isPresent()) {
attachmentInfo.parseDenotation(attachmentArg.values().front()); attachmentInfo.parseDenotation(attachmentArg.values().front());
} }
@ -946,13 +946,29 @@ void extractField(
exit(-1); exit(-1);
} }
MediaFileInfo inputFileInfo; static constexpr auto noIndex = std::numeric_limits<std::size_t>::max();
auto index = noIndex;
if (indexArg.isPresent()) {
try {
if ((index = stringToNumber<std::size_t>(indexArg.firstValue())) == noIndex) {
throw ConversionException();
}
} catch (const ConversionException &) {
cerr << Phrases::Error << "Specified index is no valid unsigned integer." << Phrases::EndFlush;
exit(-1);
}
}
// read values/attachments
auto inputFileInfo = MediaFileInfo();
auto values = std::vector<std::pair<const TagValue *, std::string>>();
auto attachments = std::vector<std::pair<const AbstractAttachment *, std::string>>();
auto diag = Diagnostics();
for (const char *file : inputFilesArg.values()) { for (const char *file : inputFilesArg.values()) {
Diagnostics diag; auto progress = AbortableProgressFeedback(); // FIXME: actually use the progress object
AbortableProgressFeedback progress; // FIXME: actually use the progress object
try { try {
// setup media file info // setup media file info
inputFileInfo.setPath(std::string(file)); inputFileInfo.setPath(std::string_view(file));
inputFileInfo.open(true); inputFileInfo.open(true);
// extract either tag field or attachment // extract either tag field or attachment
@ -962,7 +978,6 @@ void extractField(
inputFileInfo.parseContainerFormat(diag, progress); inputFileInfo.parseContainerFormat(diag, progress);
inputFileInfo.parseTags(diag, progress); inputFileInfo.parseTags(diag, progress);
auto tags = inputFileInfo.tags(); auto tags = inputFileInfo.tags();
vector<pair<const TagValue *, string>> values;
// iterate through all tags // iterate through all tags
for (const Tag *tag : tags) { for (const Tag *tag : tags) {
const TagType tagType = tag->type(); const TagType tagType = tag->type();
@ -983,36 +998,6 @@ void extractField(
} }
} }
} }
if (values.empty()) {
cerr << " - " << Phrases::Error << "None of the specified files has a (supported) " << fieldArg.values().front() << " field."
<< Phrases::End;
} else if (outputFileArg.isPresent()) {
string outputFilePathWithoutExtension, outputFileExtension;
if (values.size() > 1) {
outputFilePathWithoutExtension = BasicFileInfo::pathWithoutExtension(outputFileArg.values().front());
outputFileExtension = BasicFileInfo::extension(outputFileArg.values().front());
}
for (const auto &value : values) {
NativeFileStream outputFileStream;
outputFileStream.exceptions(ios_base::failbit | ios_base::badbit);
auto path = values.size() > 1 ? joinStrings({ outputFilePathWithoutExtension, "-", value.second, outputFileExtension })
: outputFileArg.values().front();
try {
outputFileStream.open(path, ios_base::out | ios_base::binary);
outputFileStream.write(value.first->dataPointer(), static_cast<std::streamsize>(value.first->dataSize()));
outputFileStream.flush();
cout << " - Value has been saved to \"" << path << "\"." << endl;
} catch (const std::ios_base::failure &e) {
cerr << " - " << Phrases::Error << "An IO error occurred when writing the file \"" << path << "\": " << e.what()
<< Phrases::End;
}
}
} else {
// write data to stdout if no output file has been specified
for (const auto &value : values) {
cout.write(value.first->dataPointer(), static_cast<std::streamsize>(value.first->dataSize()));
}
}
} else { } else {
// extract attachment // extract attachment
auto &logStream = (outputFileArg.isPresent() ? cout : cerr); auto &logStream = (outputFileArg.isPresent() ? cout : cerr);
@ -1026,52 +1011,100 @@ void extractField(
inputFileInfo.parseContainerFormat(diag, progress); inputFileInfo.parseContainerFormat(diag, progress);
inputFileInfo.parseAttachments(diag, progress); inputFileInfo.parseAttachments(diag, progress);
vector<pair<const AbstractAttachment *, string>> attachments;
// iterate through all attachments // iterate through all attachments
for (const AbstractAttachment *attachment : inputFileInfo.attachments()) { for (const AbstractAttachment *attachment : inputFileInfo.attachments()) {
if ((attachmentInfo.hasId && attachment->id() == attachmentInfo.id) || (attachment->name() == attachmentInfo.name)) { if ((attachmentInfo.hasId && attachment->id() == attachmentInfo.id) || (attachment->name() == attachmentInfo.name)) {
attachments.emplace_back(attachment, joinStrings({ attachment->name(), numberToString(attachments.size()) }, "-", true)); attachments.emplace_back(attachment, joinStrings({ attachment->name(), numberToString(attachments.size()) }, "-", true));
} }
} }
if (attachments.empty()) {
cerr << " - " << Phrases::Error << "None of the specified files has a (supported) attachment with the specified ID/name."
<< Phrases::End;
} else if (outputFileArg.isPresent()) {
string outputFilePathWithoutExtension, outputFileExtension;
if (attachments.size() > 1) {
outputFilePathWithoutExtension = BasicFileInfo::pathWithoutExtension(outputFileArg.values().front());
outputFileExtension = BasicFileInfo::extension(outputFileArg.values().front());
}
for (const auto &attachment : attachments) {
NativeFileStream outputFileStream;
outputFileStream.exceptions(ios_base::failbit | ios_base::badbit);
auto path = attachments.size() > 1
? joinStrings({ outputFilePathWithoutExtension, "-", attachment.second, outputFileExtension })
: outputFileArg.values().front();
try {
outputFileStream.open(path, ios_base::out | ios_base::binary);
attachment.first->data()->copyTo(outputFileStream);
outputFileStream.flush();
cout << " - Value has been saved to \"" << path << "\"." << endl;
} catch (const std::ios_base::failure &e) {
cerr << " - " << Phrases::Error << "An IO error occurred when writing the file \"" << path << "\": " << e.what()
<< Phrases::EndFlush;
}
}
} else {
for (const auto &attachment : attachments) {
attachment.first->data()->copyTo(cout);
}
}
} }
} catch (const TagParser::Failure &) { } catch (const TagParser::Failure &) {
cerr << Phrases::Error << "A parsing failure occurred when reading the file \"" << file << "\"." << Phrases::End; cerr << Phrases::Error << "A parsing failure occurred when reading the file \"" << file << "\"." << Phrases::End;
} catch (const std::ios_base::failure &e) { } catch (const std::ios_base::failure &e) {
cerr << Phrases::Error << "An IO error occurred when reading the file \"" << file << "\": " << e.what() << Phrases::End; cerr << Phrases::Error << "An IO error occurred when reading the file \"" << file << "\": " << e.what() << Phrases::End;
} }
printDiagMessages(diag, "Diagnostic messages:", verboseArg.isPresent());
} }
// write values/attachments
if (!fieldDenotations.empty()) {
if (values.empty()) {
cerr << Phrases::Error << "None of the specified files has a (supported) " << fieldArg.values().front() << " field." << Phrases::End;
} else if (index != noIndex && index >= values.size()) {
cerr << Phrases::Error << "The specified index is out of range as the specified files/fields have only " << values.size() << " values."
<< Phrases::End;
} else if (outputFileArg.isPresent()) {
if (index != noIndex) {
if (index) {
values[index].swap(values.front());
}
values.resize(1);
}
auto outputFilePathWithoutExtension = std::string(), outputFileExtension = std::string();
if (values.size() > 1) {
outputFilePathWithoutExtension = BasicFileInfo::pathWithoutExtension(outputFileArg.values().front());
outputFileExtension = BasicFileInfo::extension(outputFileArg.values().front());
}
for (const auto &value : values) {
auto outputFileStream = NativeFileStream();
outputFileStream.exceptions(ios_base::failbit | ios_base::badbit);
auto path = values.size() > 1 ? joinStrings({ outputFilePathWithoutExtension, "-", value.second, outputFileExtension })
: outputFileArg.values().front();
try {
outputFileStream.open(path, ios_base::out | ios_base::binary);
outputFileStream.write(value.first->dataPointer(), static_cast<std::streamsize>(value.first->dataSize()));
outputFileStream.flush();
cout << "Value has been saved to \"" << path << "\"." << endl;
} catch (const std::ios_base::failure &e) {
cerr << Phrases::Error << "An IO error occurred when writing the file \"" << path << "\": " << e.what() << Phrases::End;
}
}
} else {
// write data to stdout if no output file has been specified
for (const auto &value : values) {
cout.write(value.first->dataPointer(), static_cast<std::streamsize>(value.first->dataSize()));
}
}
} else {
if (attachments.empty()) {
cerr << Phrases::Error << "None of the specified files has a (supported) attachment with the specified ID/name." << Phrases::End;
} else if (index != noIndex && index >= values.size()) {
cerr << Phrases::Error << "The specified index is out of range as the specified files have only " << attachments.size() << " attachments."
<< Phrases::End;
} else if (outputFileArg.isPresent()) {
if (index != noIndex) {
if (index) {
attachments[index].swap(attachments.front());
}
attachments.resize(1);
}
auto outputFilePathWithoutExtension = std::string(), outputFileExtension = std::string();
if (attachments.size() > 1) {
outputFilePathWithoutExtension = BasicFileInfo::pathWithoutExtension(outputFileArg.values().front());
outputFileExtension = BasicFileInfo::extension(outputFileArg.values().front());
}
for (const auto &attachment : attachments) {
auto outputFileStream = NativeFileStream();
outputFileStream.exceptions(ios_base::failbit | ios_base::badbit);
auto path = attachments.size() > 1 ? joinStrings({ outputFilePathWithoutExtension, "-", attachment.second, outputFileExtension })
: outputFileArg.values().front();
try {
outputFileStream.open(path, ios_base::out | ios_base::binary);
attachment.first->data()->copyTo(outputFileStream);
outputFileStream.flush();
cout << "Value has been saved to \"" << path << "\"." << endl;
} catch (const std::ios_base::failure &e) {
cerr << Phrases::Error << "An IO error occurred when writing the file \"" << path << "\": " << e.what() << Phrases::EndFlush;
}
}
} else {
for (const auto &attachment : attachments) {
attachment.first->data()->copyTo(cout);
}
}
}
printDiagMessages(diag, "Diagnostic messages:", verboseArg.isPresent());
} }
void exportToJson(const ArgumentOccurrence &, const Argument &filesArg, const Argument &prettyArg) void exportToJson(const ArgumentOccurrence &, const Argument &filesArg, const Argument &prettyArg)

View File

@ -59,7 +59,7 @@ void displayTagInfo(const CppUtilities::Argument &fieldsArg, const CppUtilities:
const CppUtilities::Argument &verboseArg); const CppUtilities::Argument &verboseArg);
void setTagInfo(const Cli::SetTagInfoArgs &args); void setTagInfo(const Cli::SetTagInfoArgs &args);
void extractField(const CppUtilities::Argument &fieldArg, const CppUtilities::Argument &attachmentArg, const CppUtilities::Argument &inputFilesArg, void extractField(const CppUtilities::Argument &fieldArg, const CppUtilities::Argument &attachmentArg, const CppUtilities::Argument &inputFilesArg,
const CppUtilities::Argument &outputFileArg, const CppUtilities::Argument &verboseArg); const CppUtilities::Argument &outputFileArg, const CppUtilities::Argument &indexArg, const CppUtilities::Argument &verboseArg);
void exportToJson(const CppUtilities::ArgumentOccurrence &, const CppUtilities::Argument &filesArg, const CppUtilities::Argument &prettyArg); void exportToJson(const CppUtilities::ArgumentOccurrence &, const CppUtilities::Argument &filesArg, const CppUtilities::Argument &prettyArg);
} // namespace Cli } // namespace Cli