cli: Revamp `extract` to allow specifying index of value to extract
This commit is contained in:
parent
7f1e234f36
commit
df1c949f88
|
@ -13,8 +13,8 @@ set(META_APP_DESCRIPTION
|
|||
set(META_GUI_OPTIONAL true)
|
||||
set(META_JS_SRC_DIR renamingutility)
|
||||
set(META_VERSION_MAJOR 3)
|
||||
set(META_VERSION_MINOR 6)
|
||||
set(META_VERSION_PATCH 2)
|
||||
set(META_VERSION_MINOR 7)
|
||||
set(META_VERSION_PATCH 0)
|
||||
set(META_ADD_DEFAULT_CPP_UNIT_TEST_APPLICATION ON)
|
||||
|
||||
# add project files
|
||||
|
|
|
@ -186,13 +186,14 @@ int main(int argc, char *argv[])
|
|||
ConfigValueArgument fieldArg("field", 'n', "specifies the field to be extracted", { "field name" });
|
||||
fieldArg.setImplicit(true);
|
||||
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',
|
||||
"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");
|
||||
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.setCallback(std::bind(
|
||||
Cli::extractField, std::cref(fieldArg), std::cref(attachmentArg), std::cref(fileArg), std::cref(outputFileArg), std::cref(verboseArg)));
|
||||
extractFieldArg.setCallback(std::bind(Cli::extractField, std::cref(fieldArg), std::cref(attachmentArg), std::cref(fileArg),
|
||||
std::cref(outputFileArg), std::cref(indexArg), std::cref(verboseArg)));
|
||||
// export to JSON
|
||||
ConfigValueArgument prettyArg("pretty", '\0', "prints with indentation and spacing");
|
||||
OperationArgument exportArg("export", 'j', "exports the tag information for the specified files to JSON");
|
||||
|
|
|
@ -925,14 +925,14 @@ void setTagInfo(const SetTagInfoArgs &args)
|
|||
}
|
||||
}
|
||||
|
||||
void extractField(
|
||||
const Argument &fieldArg, const Argument &attachmentArg, const Argument &inputFilesArg, const Argument &outputFileArg, const Argument &verboseArg)
|
||||
void extractField(const Argument &fieldArg, const Argument &attachmentArg, const Argument &inputFilesArg, const Argument &outputFileArg,
|
||||
const Argument &indexArg, const Argument &verboseArg)
|
||||
{
|
||||
CMD_UTILS_START_CONSOLE;
|
||||
|
||||
// parse specified field and attachment
|
||||
const auto fieldDenotations = parseFieldDenotations(fieldArg, true);
|
||||
AttachmentInfo attachmentInfo;
|
||||
auto attachmentInfo = AttachmentInfo();
|
||||
if (attachmentArg.isPresent()) {
|
||||
attachmentInfo.parseDenotation(attachmentArg.values().front());
|
||||
}
|
||||
|
@ -946,13 +946,29 @@ void extractField(
|
|||
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()) {
|
||||
Diagnostics diag;
|
||||
AbortableProgressFeedback progress; // FIXME: actually use the progress object
|
||||
auto progress = AbortableProgressFeedback(); // FIXME: actually use the progress object
|
||||
try {
|
||||
// setup media file info
|
||||
inputFileInfo.setPath(std::string(file));
|
||||
inputFileInfo.setPath(std::string_view(file));
|
||||
inputFileInfo.open(true);
|
||||
|
||||
// extract either tag field or attachment
|
||||
|
@ -962,7 +978,6 @@ void extractField(
|
|||
inputFileInfo.parseContainerFormat(diag, progress);
|
||||
inputFileInfo.parseTags(diag, progress);
|
||||
auto tags = inputFileInfo.tags();
|
||||
vector<pair<const TagValue *, string>> values;
|
||||
// iterate through all tags
|
||||
for (const Tag *tag : tags) {
|
||||
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 {
|
||||
// extract attachment
|
||||
auto &logStream = (outputFileArg.isPresent() ? cout : cerr);
|
||||
|
@ -1026,52 +1011,100 @@ void extractField(
|
|||
|
||||
inputFileInfo.parseContainerFormat(diag, progress);
|
||||
inputFileInfo.parseAttachments(diag, progress);
|
||||
vector<pair<const AbstractAttachment *, string>> attachments;
|
||||
|
||||
// iterate through all attachments
|
||||
for (const AbstractAttachment *attachment : inputFileInfo.attachments()) {
|
||||
if ((attachmentInfo.hasId && attachment->id() == attachmentInfo.id) || (attachment->name() == attachmentInfo.name)) {
|
||||
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 &) {
|
||||
cerr << Phrases::Error << "A parsing failure occurred when reading the file \"" << file << "\"." << Phrases::End;
|
||||
} catch (const std::ios_base::failure &e) {
|
||||
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)
|
||||
|
|
|
@ -59,7 +59,7 @@ void displayTagInfo(const CppUtilities::Argument &fieldsArg, const CppUtilities:
|
|||
const CppUtilities::Argument &verboseArg);
|
||||
void setTagInfo(const Cli::SetTagInfoArgs &args);
|
||||
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);
|
||||
|
||||
} // namespace Cli
|
||||
|
|
Loading…
Reference in New Issue