Add more CLI tests
This commit is contained in:
parent
1a20eea65b
commit
46f006f289
|
@ -200,6 +200,7 @@ endif()
|
|||
# find tagparser
|
||||
find_package(tagparser 6.0.0 REQUIRED)
|
||||
use_tag_parser()
|
||||
list(APPEND TEST_LIBRARIES ${TAG_PARSER_SHARED_LIB})
|
||||
|
||||
# add Qt modules which can currently not be detected automatically
|
||||
list(APPEND ADDITIONAL_QT_MODULES Concurrent Network)
|
||||
|
|
|
@ -118,6 +118,9 @@ Here are some Bash examples which illustrate getting and setting tag information
|
|||
- The same scheme is used for the track numbers.
|
||||
- All files will get the album name *The Album*, the artist *The Artist* and the cover image from the file */path/to/image*.
|
||||
|
||||
**Note:** The current version v1.4.0 has a bug so tagging multiple files at once doesn't work as shown above. As a workaround
|
||||
use either the Git version or use title0=... title1=... title2=... to specify the different titles for the files.
|
||||
|
||||
* *Sets* title of both specified files and the album of the second specified file:
|
||||
```
|
||||
tageditor set title0="Title for both files" album1="Album for 2nd file" \
|
||||
|
@ -184,7 +187,6 @@ To build without GUI, add the following parameters to the CMake call:
|
|||
|
||||
## Bugs
|
||||
- Large file information is not shown when using Qt WebEngine.
|
||||
- It is recommend you to create backups before editing because I can not test whether the
|
||||
library works with all kind of files. If you force rewriting a backup is always created.
|
||||
- underlying library: Matroska files composed of more than one segment aren't tested yet and might not work.
|
||||
- underlying library: To add new features I've had to revise a lot of code since the last release. I always test the library with
|
||||
files produced by mkvmerge and ffmpeg and several other file but can't verify that it will work with all
|
||||
files. Hence I recommend to create backups of your files.
|
||||
|
|
|
@ -147,13 +147,12 @@ int main(int argc, char *argv[])
|
|||
// verbose option
|
||||
Argument verboseArg("verbose", 'v', "be verbose");
|
||||
verboseArg.setCombinable(true);
|
||||
// recursive option
|
||||
Argument recursiveArg("recursive", 'r', "includes subdirectories");
|
||||
// input/output file/files
|
||||
Argument fileArg("file", 'f', "specifies the path of the file to be opened");
|
||||
fileArg.setValueNames({"path"});
|
||||
fileArg.setRequiredValueCount(1);
|
||||
fileArg.setCombinable(true);
|
||||
fileArg.setRequired(true);
|
||||
Argument defaultFileArg(fileArg);
|
||||
defaultFileArg.setImplicit(true);
|
||||
Argument filesArg("files", 'f', "specifies the path of the file(s) to be opened");
|
||||
|
@ -185,14 +184,17 @@ int main(int argc, char *argv[])
|
|||
// set tag info
|
||||
Cli::SetTagInfoArgs setTagInfoArgs(filesArg, verboseArg);
|
||||
// extract cover
|
||||
Argument fieldArg("fields", 'n', "specifies the field to be extracted");
|
||||
Argument fieldArg("field", 'n', "specifies the field to be extracted");
|
||||
fieldArg.setValueNames({"field name"});
|
||||
fieldArg.setRequiredValueCount(1);
|
||||
fieldArg.setImplicit(true);
|
||||
Argument extractFieldArg("extract", 'e', "saves the value of the specified field (eg. cover or other binary field) to the specified file or writes it to stdout if no output file has been specified");
|
||||
extractFieldArg.setSubArguments({&fieldArg, &fileArg, &outputFileArg, &verboseArg});
|
||||
Argument attachmentArg("attachment", 'a', "specifies the attachment to be extracted");
|
||||
attachmentArg.setValueNames({"id=..."});
|
||||
attachmentArg.setRequiredValueCount(1);
|
||||
Argument 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 output file has been specified");
|
||||
extractFieldArg.setSubArguments({&fieldArg, &attachmentArg, &fileArg, &outputFileArg, &verboseArg});
|
||||
extractFieldArg.setDenotesOperation(true);
|
||||
extractFieldArg.setCallback(std::bind(Cli::extractField, std::cref(fieldsArg), 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(verboseArg)));
|
||||
// file info
|
||||
Argument validateArg("validate", 'c', "validates the file integrity as accurately as possible; the structure of the file will be parsed completely");
|
||||
validateArg.setCombinable(true);
|
||||
|
|
|
@ -238,12 +238,12 @@ void printNotifications(const MediaFileInfo &fileInfo, const char *head = nullpt
|
|||
}
|
||||
|
||||
#define FIELD_NAMES "title album artist genre year comment bpm bps lyricist track disk part totalparts encoder\n" \
|
||||
"recorddate performers duration language encodersettings lyrics synchronizedlyrics grouping\n" \
|
||||
"recordlabel cover composer rating description"
|
||||
"recorddate performers duration language encodersettings lyrics synchronizedlyrics grouping\n" \
|
||||
"recordlabel cover composer rating description"
|
||||
|
||||
#define TAG_MODIFIER "tag=id3v1 tag=id3v2 tag=id3 tag=itunes tag=vorbis tag=matroska tag=all"
|
||||
#define TARGET_MODIFIER "target-level target-levelname target-tracks target-tracks\n" \
|
||||
"target-chapters target-editions target-attachments target-reset"
|
||||
"target-chapters target-editions target-attachments target-reset"
|
||||
|
||||
const char *const fieldNames = FIELD_NAMES;
|
||||
const char *const fieldNamesForSet = FIELD_NAMES " " TAG_MODIFIER " " TARGET_MODIFIER;
|
||||
|
@ -252,7 +252,7 @@ void printFieldNames(const ArgumentOccurrence &occurrence)
|
|||
{
|
||||
CMD_UTILS_START_CONSOLE;
|
||||
VAR_UNUSED(occurrence)
|
||||
cout << fieldNames;
|
||||
cout << fieldNames;
|
||||
cout << "\nTag modifier: " << TAG_MODIFIER;
|
||||
cout << "\nTarget modifier: " << TARGET_MODIFIER << endl;
|
||||
}
|
||||
|
@ -701,13 +701,17 @@ void generateFileInfo(const ArgumentOccurrence &, const Argument &inputFileArg,
|
|||
inputFileInfo.setForceFullParse(validateArg.isPresent());
|
||||
inputFileInfo.open(true);
|
||||
inputFileInfo.parseEverything();
|
||||
cout << "Saving file info of \"" << inputFileArg.values().front() << "\" ..." << endl;
|
||||
(outputFileArg.isPresent() ? cout : cerr) << "Saving file info for \"" << inputFileArg.values().front() << "\" ..." << endl;
|
||||
NotificationList origNotify;
|
||||
QFile file(QString::fromLocal8Bit(outputFileArg.values().front()));
|
||||
if(file.open(QFile::WriteOnly) && file.write(HtmlInfo::generateInfo(inputFileInfo, origNotify)) && file.flush()) {
|
||||
cout << "File information has been saved to \"" << outputFileArg.values().front() << "\"." << endl;
|
||||
if(outputFileArg.isPresent()) {
|
||||
QFile file(QString::fromLocal8Bit(outputFileArg.values().front()));
|
||||
if(file.open(QFile::WriteOnly) && file.write(HtmlInfo::generateInfo(inputFileInfo, origNotify)) && file.flush()) {
|
||||
cout << "File information has been saved to \"" << outputFileArg.values().front() << "\"." << endl;
|
||||
} else {
|
||||
cerr << "Error: An IO error occured when writing the file \"" << outputFileArg.values().front() << "\"." << endl;
|
||||
}
|
||||
} else {
|
||||
cerr << "Error: An IO error occured when writing the file \"" << outputFileArg.values().front() << "\"." << endl;
|
||||
cout << HtmlInfo::generateInfo(inputFileInfo, origNotify).data() << endl;
|
||||
}
|
||||
} catch(const ApplicationUtilities::Failure &) {
|
||||
cerr << "Error: A parsing failure occured when reading the file \"" << inputFileArg.values().front() << "\"." << endl;
|
||||
|
@ -776,11 +780,12 @@ void displayFileInfo(const ArgumentOccurrence &, const Argument &filesArg, const
|
|||
return;
|
||||
}
|
||||
MediaFileInfo fileInfo;
|
||||
for(const auto &file : filesArg.values()) {
|
||||
for(const char *file : filesArg.values()) {
|
||||
try {
|
||||
// parse tags
|
||||
fileInfo.setPath(file);
|
||||
fileInfo.open(true);
|
||||
fileInfo.parseContainerFormat();
|
||||
fileInfo.parseTracks();
|
||||
fileInfo.parseAttachments();
|
||||
fileInfo.parseChapters();
|
||||
|
@ -906,69 +911,43 @@ void displayTagInfo(const Argument &fieldsArg, const Argument &filesArg, const A
|
|||
}
|
||||
const auto fields = parseFieldDenotations(fieldsArg, true);
|
||||
MediaFileInfo fileInfo;
|
||||
for(const auto &file : filesArg.values()) {
|
||||
for(const char *file : filesArg.values()) {
|
||||
try {
|
||||
// parse tags
|
||||
fileInfo.setPath(file);
|
||||
fileInfo.open(true);
|
||||
fileInfo.parseContainerFormat();
|
||||
fileInfo.parseTags();
|
||||
cout << "Tag information for \"" << file << "\":" << endl;
|
||||
const auto tags = fileInfo.tags();
|
||||
if(tags.size()) {
|
||||
if(!tags.empty()) {
|
||||
// iterate through all tags
|
||||
for(const auto *tag : tags) {
|
||||
// determine tag type
|
||||
TagType tagType = tag->type();
|
||||
const TagType tagType = tag->type();
|
||||
// write tag name and target, eg. MP4/iTunes tag
|
||||
cout << tag->typeName();
|
||||
if(tagType == TagType::MatroskaTag || !tag->target().isEmpty()) {
|
||||
cout << " targeting \"" << tag->targetString() << "\"";
|
||||
cout << " targeting \"" << tag->targetString() << '\"';
|
||||
}
|
||||
cout << endl;
|
||||
// iterate through fields specified by the user
|
||||
if(fields.empty()) {
|
||||
for(auto field = firstKnownField; field != KnownField::Invalid; field = nextKnownField(field)) {
|
||||
const auto &value = tag->value(field);
|
||||
if(!value.isEmpty()) {
|
||||
// write field name
|
||||
const auto &values = tag->values(field);
|
||||
if(!values.empty()) {
|
||||
const char *fieldName = KnownFieldModel::fieldName(field);
|
||||
cout << ' ' << fieldName;
|
||||
// write padding
|
||||
for(auto i = strlen(fieldName); i < 18; ++i) {
|
||||
cout << ' ';
|
||||
}
|
||||
// write value
|
||||
try {
|
||||
const auto textValue = value.toString(TagTextEncoding::Utf8);
|
||||
if(textValue.empty()) {
|
||||
cout << "can't display here (see --extract)";
|
||||
} else {
|
||||
cout << textValue;
|
||||
const auto fieldNameLen = strlen(fieldName);
|
||||
for(const auto &value : values) {
|
||||
// write field name
|
||||
cout << ' ' << fieldName;
|
||||
// write padding
|
||||
for(auto i = fieldNameLen; i < 18; ++i) {
|
||||
cout << ' ';
|
||||
}
|
||||
} catch(const ConversionException &) {
|
||||
cout << "conversion error";
|
||||
}
|
||||
cout << endl;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for(const auto &fieldDenotation : fields) {
|
||||
const FieldScope &denotedScope = fieldDenotation.first;
|
||||
const TagValue &value = tag->value(denotedScope.field);
|
||||
if(denotedScope.tagType == TagType::Unspecified || (denotedScope.tagType | tagType) != TagType::Unspecified) {
|
||||
// write field name
|
||||
const char *fieldName = KnownFieldModel::fieldName(denotedScope.field);
|
||||
cout << ' ' << fieldName;
|
||||
// write padding
|
||||
for(auto i = strlen(fieldName); i < 18; ++i) {
|
||||
cout << ' ';
|
||||
}
|
||||
// write value
|
||||
if(value.isEmpty()) {
|
||||
cout << "none";
|
||||
} else {
|
||||
// write value
|
||||
try {
|
||||
const auto textValue = value.toString(TagTextEncoding::Utf8);
|
||||
const auto textValue = value->toString(TagTextEncoding::Utf8);
|
||||
if(textValue.empty()) {
|
||||
cout << "can't display here (see --extract)";
|
||||
} else {
|
||||
|
@ -977,8 +956,43 @@ void displayTagInfo(const Argument &fieldsArg, const Argument &filesArg, const A
|
|||
} catch(const ConversionException &) {
|
||||
cout << "conversion error";
|
||||
}
|
||||
cout << endl;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
} else {
|
||||
for(const auto &fieldDenotation : fields) {
|
||||
const FieldScope &denotedScope = fieldDenotation.first;
|
||||
if(denotedScope.tagType == TagType::Unspecified || (denotedScope.tagType | tagType) != TagType::Unspecified) {
|
||||
const auto &values = tag->values(denotedScope.field);
|
||||
const char *fieldName = KnownFieldModel::fieldName(denotedScope.field);
|
||||
const auto fieldNameLen = strlen(fieldName);
|
||||
if(values.empty()) {
|
||||
cout << "none";
|
||||
} else {
|
||||
for(const auto &value : values) {
|
||||
// write field name
|
||||
const char *fieldName = KnownFieldModel::fieldName(denotedScope.field);
|
||||
cout << ' ' << fieldName;
|
||||
// write padding
|
||||
for(auto i = fieldNameLen; i < 18; ++i) {
|
||||
cout << ' ';
|
||||
}
|
||||
// write value
|
||||
try {
|
||||
const auto textValue = value->toString(TagTextEncoding::Utf8);
|
||||
if(textValue.empty()) {
|
||||
cout << "can't display here (see --extract)";
|
||||
} else {
|
||||
cout << textValue;
|
||||
}
|
||||
} catch(const ConversionException &) {
|
||||
cout << "conversion error";
|
||||
}
|
||||
cout << endl;
|
||||
}
|
||||
}
|
||||
cout << endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1068,12 +1082,13 @@ void setTagInfo(const SetTagInfoArgs &args)
|
|||
unsigned int fileIndex = 0;
|
||||
static const string context("setting tags");
|
||||
NotificationList notifications;
|
||||
for(const auto &file : args.filesArg.values()) {
|
||||
for(const char *file : args.filesArg.values()) {
|
||||
try {
|
||||
// parse tags
|
||||
cout << "Setting tag information for \"" << file << "\" ..." << endl;
|
||||
notifications.clear();
|
||||
fileInfo.setPath(file);
|
||||
fileInfo.parseContainerFormat();
|
||||
fileInfo.parseTags();
|
||||
fileInfo.parseTracks();
|
||||
vector<Tag *> tags;
|
||||
|
@ -1246,67 +1261,135 @@ void setTagInfo(const SetTagInfoArgs &args)
|
|||
}
|
||||
}
|
||||
|
||||
void extractField(const Argument &fieldsArg, const Argument &inputFileArg, const Argument &outputFileArg, const Argument &verboseArg)
|
||||
void extractField(const Argument &fieldArg, const Argument &attachmentArg, const Argument &inputFilesArg, const Argument &outputFileArg, const Argument &verboseArg)
|
||||
{
|
||||
CMD_UTILS_START_CONSOLE;
|
||||
const auto fields = parseFieldDenotations(fieldsArg, true);
|
||||
if(fields.size() != 1) {
|
||||
cerr << "Error: Excactly one field needs to be specified." << endl;
|
||||
|
||||
// parse specified field and attachment
|
||||
const auto fieldDenotations = parseFieldDenotations(fieldArg, true);
|
||||
AttachmentInfo attachmentInfo;
|
||||
if(attachmentArg.isPresent()) {
|
||||
attachmentInfo.parseDenotation(attachmentArg.values().front());
|
||||
}
|
||||
if(((fieldDenotations.size() != 1) || (!attachmentInfo.hasId && !attachmentInfo.name))
|
||||
&& ((fieldDenotations.size() == 1) && (attachmentInfo.hasId || attachmentInfo.name))) {
|
||||
cerr << "Error: Excactly one field or attachment needs to be specified." << endl;
|
||||
return;
|
||||
}
|
||||
MediaFileInfo inputFileInfo;
|
||||
try {
|
||||
// parse tags
|
||||
inputFileInfo.setPath(inputFileArg.values().front());
|
||||
inputFileInfo.open(true);
|
||||
inputFileInfo.parseTags();
|
||||
(outputFileArg.isPresent() ? cout : cerr) << "Extracting " << fieldsArg.values().front() << " of \"" << inputFileArg.values().front() << "\" ..." << endl;
|
||||
auto tags = inputFileInfo.tags();
|
||||
vector<pair<const TagValue *, string> > values;
|
||||
// iterate through all tags
|
||||
for(const Tag *tag : tags) {
|
||||
for(const auto &fieldDenotation : fields) {
|
||||
const auto &value = tag->value(fieldDenotation.first.field);
|
||||
if(!value.isEmpty()) {
|
||||
values.emplace_back(&value, joinStrings({tag->typeName(), numberToString(values.size())}, "-"));
|
||||
}
|
||||
}
|
||||
}
|
||||
if(values.empty()) {
|
||||
cerr << "File has no (supported) " << fieldsArg.values().front() << " field." << endl;
|
||||
} 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) {
|
||||
fstream 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(), value.first->dataSize());
|
||||
outputFileStream.flush();
|
||||
cout << "Value has been saved to \"" << path << "\"." << endl;
|
||||
} catch(...) {
|
||||
::IoUtilities::catchIoFailure();
|
||||
cerr << "Error: An IO error occured when writing the file \"" << path << "\"." << endl;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// write data to stdout if no output file has been specified
|
||||
for(const auto &value : values) {
|
||||
cout.write(value.first->dataPointer(), value.first->dataSize());
|
||||
}
|
||||
}
|
||||
} catch(const ApplicationUtilities::Failure &) {
|
||||
cerr << "Error: A parsing failure occured when reading the file \"" << inputFileArg.values().front() << "\"." << endl;
|
||||
} catch(...) {
|
||||
::IoUtilities::catchIoFailure();
|
||||
cerr << "Error: An IO failure occured when reading the file \"" << inputFileArg.values().front() << "\"." << endl;
|
||||
if(!inputFilesArg.isPresent() || inputFilesArg.values().empty()) {
|
||||
cerr << "Error: No files have been specified." << endl;
|
||||
return;
|
||||
}
|
||||
|
||||
MediaFileInfo inputFileInfo;
|
||||
for(const char *file : inputFilesArg.values()) {
|
||||
try {
|
||||
// parse tags
|
||||
inputFileInfo.setPath(file);
|
||||
inputFileInfo.open(true);
|
||||
|
||||
if(!fieldDenotations.empty()) {
|
||||
// extract tag field
|
||||
(outputFileArg.isPresent() ? cout : cerr) << "Extracting field " << fieldArg.values().front() << " of \"" << file << "\" ..." << endl;
|
||||
inputFileInfo.parseContainerFormat();
|
||||
inputFileInfo.parseTags();
|
||||
auto tags = inputFileInfo.tags();
|
||||
vector<pair<const TagValue *, string> > values;
|
||||
// iterate through all tags
|
||||
for(const Tag *tag : tags) {
|
||||
for(const auto &fieldDenotation : fieldDenotations) {
|
||||
const auto &value = tag->value(fieldDenotation.first.field);
|
||||
if(!value.isEmpty()) {
|
||||
values.emplace_back(&value, joinStrings({tag->typeName(), numberToString(values.size())}, "-", true));
|
||||
}
|
||||
}
|
||||
}
|
||||
if(values.empty()) {
|
||||
cerr << " None of the specified files has a (supported) " << fieldArg.values().front() << " field." << endl;
|
||||
} 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) {
|
||||
fstream 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(), value.first->dataSize());
|
||||
outputFileStream.flush();
|
||||
cout << "Value has been saved to \"" << path << "\"." << endl;
|
||||
} catch(...) {
|
||||
::IoUtilities::catchIoFailure();
|
||||
cerr << "Error: An IO error occured when writing the file \"" << path << "\"." << endl;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// write data to stdout if no output file has been specified
|
||||
for(const auto &value : values) {
|
||||
cout.write(value.first->dataPointer(), value.first->dataSize());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// extract attachment
|
||||
auto &logStream = (outputFileArg.isPresent() ? cout : cerr);
|
||||
logStream << "Extracting attachment with ";
|
||||
if(attachmentInfo.hasId) {
|
||||
logStream << "ID " << attachmentInfo.id;
|
||||
} else {
|
||||
logStream << "name \"" << attachmentInfo.name << '\"';
|
||||
}
|
||||
logStream << " of \"" << file << "\" ..." << endl;
|
||||
|
||||
inputFileInfo.parseContainerFormat();
|
||||
inputFileInfo.parseAttachments();
|
||||
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 << " None of the specified files has a (supported) attachment with the specified ID/name." << endl;
|
||||
} 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) {
|
||||
fstream 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(...) {
|
||||
::IoUtilities::catchIoFailure();
|
||||
cerr << "Error: An IO error occured when writing the file \"" << path << "\"." << endl;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for(const auto &attachment : attachments) {
|
||||
attachment.first->data()->copyTo(cout);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} catch(const ApplicationUtilities::Failure &) {
|
||||
cerr << "Error: A parsing failure occured when reading the file \"" << file << "\"." << endl;
|
||||
} catch(...) {
|
||||
::IoUtilities::catchIoFailure();
|
||||
cerr << "Error: An IO failure occured when reading the file \"" << file << "\"." << endl;
|
||||
}
|
||||
printNotifications(inputFileInfo, "Parsing notifications:", verboseArg.isPresent());
|
||||
}
|
||||
printNotifications(inputFileInfo, "Parsing notifications:", verboseArg.isPresent());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -52,7 +52,7 @@ void displayFileInfo(const ApplicationUtilities::ArgumentOccurrence &, const App
|
|||
void generateFileInfo(const ApplicationUtilities::ArgumentOccurrence &, const ApplicationUtilities::Argument &inputFileArg, const ApplicationUtilities::Argument &outputFileArg, const ApplicationUtilities::Argument &validateArg);
|
||||
void displayTagInfo(const ApplicationUtilities::Argument &fieldsArg, const ApplicationUtilities::Argument &filesArg, const ApplicationUtilities::Argument &verboseArg);
|
||||
void setTagInfo(const Cli::SetTagInfoArgs &args);
|
||||
void extractField(const ApplicationUtilities::Argument &fieldsArg, const ApplicationUtilities::Argument &inputFileArg, const ApplicationUtilities::Argument &outputFileArg, const ApplicationUtilities::Argument &verboseArg);
|
||||
void extractField(const ApplicationUtilities::Argument &fieldArg, const ApplicationUtilities::Argument &attachmentArg, const ApplicationUtilities::Argument &inputFilesArg, const ApplicationUtilities::Argument &outputFileArg, const ApplicationUtilities::Argument &verboseArg);
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -130,22 +130,24 @@ bool TagEdit::hasField(KnownField field) const
|
|||
QString TagEdit::generateLabel() const
|
||||
{
|
||||
if(!m_tags.isEmpty()) {
|
||||
TagTarget target = m_tags.at(0)->target();
|
||||
bool differentTargets = false;
|
||||
const TagTarget &target = m_tags.at(0)->target();
|
||||
bool differentTargets = false, haveMatroskaTags = false;
|
||||
QStringList tagNames;
|
||||
for(Tag *tag : m_tags) {
|
||||
tagNames.reserve(m_tags.size());
|
||||
for(const Tag *tag : m_tags) {
|
||||
tagNames << QString::fromLocal8Bit(tag->typeName());
|
||||
if(!differentTargets && !(target == tag->target())) {
|
||||
differentTargets = true;
|
||||
}
|
||||
if(tag->type() == TagType::MatroskaTag) {
|
||||
haveMatroskaTags = true;
|
||||
}
|
||||
}
|
||||
QString res = tagNames.join(QStringLiteral(", "));
|
||||
if(differentTargets) {
|
||||
res.append(tr(" with different targets"));
|
||||
} else {
|
||||
if(!target.isEmpty()) {
|
||||
res.append(tr(" targeting %1").arg(QString::fromLocal8Bit(m_tags.front()->targetString().c_str())));
|
||||
}
|
||||
} else if(haveMatroskaTags || !target.isEmpty()) {
|
||||
res.append(tr(" targeting %1").arg(QString::fromLocal8Bit(m_tags.front()->targetString().c_str())));
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
|
245
tests/cli.cpp
245
tests/cli.cpp
|
@ -1,13 +1,19 @@
|
|||
#include <c++utilities/conversion/stringconversion.h>
|
||||
#include <c++utilities/io/catchiofailure.h>
|
||||
#include <c++utilities/tests/testutils.h>
|
||||
|
||||
#include <tagparser/mediafileinfo.h>
|
||||
|
||||
#include <cppunit/extensions/HelperMacros.h>
|
||||
#include <cppunit/TestFixture.h>
|
||||
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
|
||||
using namespace std;
|
||||
using namespace TestUtilities;
|
||||
using namespace ConversionUtilities;
|
||||
using namespace Media;
|
||||
|
||||
using namespace CPPUNIT_NS;
|
||||
|
||||
|
@ -74,21 +80,26 @@ void CliTests::testBasicReadingAndWriting()
|
|||
string stdout, stderr;
|
||||
// get specific field
|
||||
const string mkvFile(workingCopyPath("matroska_wave1/test2.mkv"));
|
||||
const string mkvFileBackup(mkvFile + ".bak");
|
||||
const char *const args1[] = {"tageditor", "get", "title", "-f", mkvFile.data(), nullptr};
|
||||
CPPUNIT_ASSERT_EQUAL(0, execApp(args1, stdout, stderr));
|
||||
CPPUNIT_ASSERT(stderr.empty());
|
||||
// context of the following fields is the album (so "Title" means the title of the album)
|
||||
CPPUNIT_ASSERT(stdout.find("album") != string::npos);
|
||||
CPPUNIT_ASSERT(stdout.find("Title Elephant Dream - test 2") != string::npos);
|
||||
CPPUNIT_ASSERT(containsSubstrings(stdout, {
|
||||
"album",
|
||||
"Title Elephant Dream - test 2"
|
||||
}));
|
||||
CPPUNIT_ASSERT(stdout.find("Year 2010") == string::npos);
|
||||
|
||||
// get all fields
|
||||
const char *const args2[] = {"tageditor", "get", "-f", mkvFile.data(), nullptr};
|
||||
CPPUNIT_ASSERT_EQUAL(0, execApp(args2, stdout, stderr));
|
||||
CPPUNIT_ASSERT(stderr.empty());
|
||||
CPPUNIT_ASSERT(stdout.find("Title Elephant Dream - test 2") != string::npos);
|
||||
CPPUNIT_ASSERT(stdout.find("Year 2010") != string::npos);
|
||||
CPPUNIT_ASSERT(stdout.find("Comment Matroska Validation File 2, 100,000 timecode scale, odd aspect ratio, and CRC-32. Codecs are AVC and AAC") != string::npos);
|
||||
CPPUNIT_ASSERT(containsSubstrings(stdout, {
|
||||
"Title Elephant Dream - test 2",
|
||||
"Year 2010",
|
||||
"Comment Matroska Validation File 2, 100,000 timecode scale, odd aspect ratio, and CRC-32. Codecs are AVC and AAC"
|
||||
}));
|
||||
|
||||
// set some fields, keep other field
|
||||
const char *const args3[] = {"tageditor", "set", "title=A new title", "genre=Testfile", "-f", mkvFile.data(), nullptr};
|
||||
|
@ -96,21 +107,31 @@ void CliTests::testBasicReadingAndWriting()
|
|||
CPPUNIT_ASSERT(stdout.find("Changes have been applied") != string::npos);
|
||||
CPPUNIT_ASSERT_EQUAL(0, execApp(args2, stdout, stderr));
|
||||
CPPUNIT_ASSERT(stderr.empty());
|
||||
CPPUNIT_ASSERT(stdout.find("Title A new title") != string::npos);
|
||||
CPPUNIT_ASSERT(stdout.find("Year 2010") != string::npos);
|
||||
CPPUNIT_ASSERT(stdout.find("Comment Matroska Validation File 2, 100,000 timecode scale, odd aspect ratio, and CRC-32. Codecs are AVC and AAC") != string::npos);
|
||||
CPPUNIT_ASSERT(stdout.find("Genre Testfile") != string::npos);
|
||||
CPPUNIT_ASSERT(containsSubstrings(stdout, {
|
||||
"Title A new title",
|
||||
"Genre Testfile",
|
||||
"Year 2010",
|
||||
"Comment Matroska Validation File 2, 100,000 timecode scale, odd aspect ratio, and CRC-32. Codecs are AVC and AAC"
|
||||
}));
|
||||
// clear backup file
|
||||
remove(mkvFileBackup.data());
|
||||
|
||||
// set some fields, discard other
|
||||
const char *const args4[] = {"tageditor", "set", "title=Foo", "artist=Bar", "--remove-other-fields", "-f", mkvFile.data(), nullptr};
|
||||
CPPUNIT_ASSERT_EQUAL(0, execApp(args4, stdout, stderr));
|
||||
CPPUNIT_ASSERT_EQUAL(0, execApp(args2, stdout, stderr));
|
||||
CPPUNIT_ASSERT(stderr.empty());
|
||||
CPPUNIT_ASSERT(stdout.find("Title Foo") != string::npos);
|
||||
CPPUNIT_ASSERT(stdout.find("Artist Bar") != string::npos);
|
||||
CPPUNIT_ASSERT(containsSubstrings(stdout, {
|
||||
"Title Foo",
|
||||
"Artist Bar"
|
||||
}));
|
||||
CPPUNIT_ASSERT(stdout.find("Year") == string::npos);
|
||||
CPPUNIT_ASSERT(stdout.find("Comment") == string::npos);
|
||||
CPPUNIT_ASSERT(stdout.find("Genre") == string::npos);
|
||||
|
||||
// clear working copies if all tests have been
|
||||
remove(mkvFile.c_str());
|
||||
remove(mkvFileBackup.data());
|
||||
}
|
||||
|
||||
/*!
|
||||
|
@ -120,30 +141,41 @@ void CliTests::testHandlingOfTargets()
|
|||
{
|
||||
string stdout, stderr;
|
||||
const string mkvFile(workingCopyPath("matroska_wave1/test2.mkv"));
|
||||
const string mkvFileBackup(mkvFile + ".bak");
|
||||
const char *const args1[] = {"tageditor", "get", "-f", mkvFile.data(), nullptr};
|
||||
|
||||
// add song title (title field for tag with level 30)
|
||||
const char *const args2[] = {"tageditor", "set", "target-level=30", "title=The song title", "genre=The song genre", "target-level=50", "genre=The album genre", "-f", mkvFile.data(), nullptr};
|
||||
CPPUNIT_ASSERT_EQUAL(0, execApp(args2, stdout, stderr));
|
||||
CPPUNIT_ASSERT_EQUAL(0, execApp(args1, stdout, stderr));
|
||||
size_t songPos, albumPos;
|
||||
CPPUNIT_ASSERT((songPos = stdout.find("song")) != string::npos);
|
||||
CPPUNIT_ASSERT((albumPos = stdout.find("album")) != string::npos);
|
||||
CPPUNIT_ASSERT(stdout.find("Title The song title") > songPos);
|
||||
CPPUNIT_ASSERT(stdout.find("Genre The song genre") > songPos);
|
||||
CPPUNIT_ASSERT(stdout.find("Title Elephant Dream - test 2") > albumPos);
|
||||
CPPUNIT_ASSERT(stdout.find("Genre The album genre") > albumPos);
|
||||
CPPUNIT_ASSERT(containsSubstrings(stdout, {
|
||||
"song",
|
||||
"Title The song title",
|
||||
"Genre The song genre"
|
||||
}));
|
||||
CPPUNIT_ASSERT(containsSubstrings(stdout, {
|
||||
"album",
|
||||
"Title Elephant Dream - test 2",
|
||||
"Genre The album genre"
|
||||
}));
|
||||
remove(mkvFileBackup.data());
|
||||
|
||||
// remove tags targeting level 30 and 50 and add new tag targeting level 30 and the audio track
|
||||
const char *const args3[] = {"tageditor", "set", "target-level=30", "target-tracks=3134325680", "title=The audio track", "encoder=likely some AAC encoder", "--remove-target", "target-level=30", "--remove-target", "target-level=50", "-f", mkvFile.data(), nullptr};
|
||||
CPPUNIT_ASSERT_EQUAL(0, execApp(args3, stdout, stderr));
|
||||
CPPUNIT_ASSERT_EQUAL(0, execApp(args1, stdout, stderr));
|
||||
CPPUNIT_ASSERT((songPos = stdout.find("song")) != string::npos);
|
||||
CPPUNIT_ASSERT(stdout.find("song", songPos + 1) == string::npos);
|
||||
CPPUNIT_ASSERT(stdout.find("3134325680") != string::npos);
|
||||
CPPUNIT_ASSERT((albumPos = stdout.find("album")) == string::npos);
|
||||
CPPUNIT_ASSERT(stdout.find("Title The audio track") != string::npos);
|
||||
CPPUNIT_ASSERT(stdout.find("Encoder likely some AAC encoder") != string::npos);
|
||||
CPPUNIT_ASSERT(containsSubstrings(stdout, {"song"}));
|
||||
CPPUNIT_ASSERT(!containsSubstrings(stdout, {"song", "song"}));
|
||||
CPPUNIT_ASSERT(stdout.find("album") == string::npos);
|
||||
CPPUNIT_ASSERT(containsSubstrings(stdout, {
|
||||
"3134325680",
|
||||
"Title The audio track",
|
||||
"Encoder likely some AAC encoder"
|
||||
}));
|
||||
remove(mkvFileBackup.data());
|
||||
|
||||
// clear working copies if all tests have been
|
||||
remove(mkvFile.c_str());
|
||||
}
|
||||
|
||||
/*!
|
||||
|
@ -167,49 +199,73 @@ void CliTests::testMultipleFiles()
|
|||
// get tags of 3 files at once
|
||||
const char *const args1[] = {"tageditor", "get", "-f", mkvFile1.data(), mkvFile2.data(), mkvFile3.data(), nullptr};
|
||||
CPPUNIT_ASSERT_EQUAL(0, execApp(args1, stdout, stderr));
|
||||
size_t pos1 = stdout.find("Title Big Buck Bunny - test 1");
|
||||
size_t pos2 = stdout.find("Title Elephant Dream - test 2");
|
||||
size_t pos3 = stdout.find("Title Elephant Dream - test 3");
|
||||
CPPUNIT_ASSERT(pos1 != string::npos);
|
||||
CPPUNIT_ASSERT(pos2 > pos1);
|
||||
CPPUNIT_ASSERT(pos3 > pos2);
|
||||
CPPUNIT_ASSERT(containsSubstrings(stdout, {
|
||||
"Title Big Buck Bunny - test 1",
|
||||
"Title Elephant Dream - test 2",
|
||||
"Title Elephant Dream - test 3"
|
||||
}));
|
||||
// clear backup files
|
||||
remove((mkvFile1 + ".bak").c_str()), remove((mkvFile2 + ".bak").c_str()), remove((mkvFile3 + ".bak").c_str());
|
||||
|
||||
// set title and part number of 3 files at once
|
||||
const char *const args2[] = {"tageditor", "set", "target-level=30", "title=test1", "title=test2", "title=test3", "part+=1", "target-level=50", "title=MKV testfiles", "totalparts=3", "-f", mkvFile1.data(), mkvFile2.data(), mkvFile3.data(), nullptr};
|
||||
CPPUNIT_ASSERT_EQUAL(0, execApp(args2, stdout, stderr));
|
||||
CPPUNIT_ASSERT_EQUAL(0, execApp(args1, stdout, stderr));
|
||||
CPPUNIT_ASSERT((pos1 = stdout.find("Matroska tag targeting \"level 50 'album, opera, concert, movie, episode'\"\n"
|
||||
" Title MKV testfiles\n"
|
||||
" Year 2010\n"
|
||||
" Comment Matroska Validation File1, basic MPEG4.2 and MP3 with only SimpleBlock\n"
|
||||
" Total parts 3\n"
|
||||
"Matroska tag targeting \"level 30 'track, song, chapter'\"\n"
|
||||
" Title test1\n"
|
||||
" Part 1")) != string::npos);
|
||||
CPPUNIT_ASSERT((pos2 = stdout.find("Matroska tag targeting \"level 50 'album, opera, concert, movie, episode'\"\n"
|
||||
" Title MKV testfiles\n"
|
||||
" Year 2010\n"
|
||||
" Comment Matroska Validation File 2, 100,000 timecode scale, odd aspect ratio, and CRC-32. Codecs are AVC and AAC\n"
|
||||
" Total parts 3\n"
|
||||
"Matroska tag targeting \"level 30 'track, song, chapter'\"\n"
|
||||
" Title test2\n"
|
||||
" Part 2")) > pos1);
|
||||
CPPUNIT_ASSERT((stdout.find("Matroska tag targeting \"level 50 'album, opera, concert, movie, episode'\"\n"
|
||||
" Title MKV testfiles\n"
|
||||
" Year 2010\n"
|
||||
" Comment Matroska Validation File 3, header stripping on the video track and no SimpleBlock\n"
|
||||
" Total parts 3\n"
|
||||
"Matroska tag targeting \"level 30 'track, song, chapter'\"\n"
|
||||
" Title test3\n"
|
||||
" Part 3")) > pos2);
|
||||
CPPUNIT_ASSERT(containsSubstrings(stdout, {
|
||||
"Matroska tag targeting \"level 50 'album, opera, concert, movie, episode'\"\n"
|
||||
" Title MKV testfiles\n"
|
||||
" Year 2010\n"
|
||||
" Comment Matroska Validation File1, basic MPEG4.2 and MP3 with only SimpleBlock\n"
|
||||
" Total parts 3\n"
|
||||
"Matroska tag targeting \"level 30 'track, song, chapter'\"\n"
|
||||
" Title test1\n"
|
||||
" Part 1",
|
||||
"Matroska tag targeting \"level 50 'album, opera, concert, movie, episode'\"\n"
|
||||
" Title MKV testfiles\n"
|
||||
" Year 2010\n"
|
||||
" Comment Matroska Validation File 2, 100,000 timecode scale, odd aspect ratio, and CRC-32. Codecs are AVC and AAC\n"
|
||||
" Total parts 3\n"
|
||||
"Matroska tag targeting \"level 30 'track, song, chapter'\"\n"
|
||||
" Title test2\n"
|
||||
" Part 2",
|
||||
"Matroska tag targeting \"level 50 'album, opera, concert, movie, episode'\"\n"
|
||||
" Title MKV testfiles\n"
|
||||
" Year 2010\n"
|
||||
" Comment Matroska Validation File 3, header stripping on the video track and no SimpleBlock\n"
|
||||
" Total parts 3\n"
|
||||
"Matroska tag targeting \"level 30 'track, song, chapter'\"\n"
|
||||
" Title test3\n"
|
||||
" Part 3"
|
||||
}));
|
||||
|
||||
// clear working copies if all tests have been
|
||||
remove(mkvFile1.c_str()), remove(mkvFile2.c_str()), remove(mkvFile3.c_str());
|
||||
remove((mkvFile1 + ".bak").c_str()), remove((mkvFile2 + ".bak").c_str()), remove((mkvFile3 + ".bak").c_str());
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Tests tagging multiple values per field.
|
||||
* \remarks Fails because feature has not been implemented yet.
|
||||
*/
|
||||
void CliTests::testMultipleValuesPerField()
|
||||
{
|
||||
// TODO (feature not implemented yet)
|
||||
string stdout, stderr;
|
||||
const string mkvFile(workingCopyPath("matroska_wave1/test1.mkv"));
|
||||
const char *const args1[] = {"tageditor", "get", "-f", mkvFile.data(), nullptr};
|
||||
const char *const args2[] = {"tageditor", "set", "artist=test1", "+artist=test2", "+artist=test3", "-f", mkvFile.data(), nullptr};
|
||||
CPPUNIT_ASSERT_EQUAL(0, execApp(args2, stdout, stderr));
|
||||
CPPUNIT_ASSERT_EQUAL(0, execApp(args1, stdout, stderr));
|
||||
//cout << stdout << endl;
|
||||
//cerr << stderr << endl;
|
||||
CPPUNIT_ASSERT(containsSubstrings(stdout, {
|
||||
"Artist test1",
|
||||
"Artist test2",
|
||||
"Artist test3"
|
||||
}));
|
||||
|
||||
// clear working copies if all tests have been
|
||||
remove(mkvFile.c_str());
|
||||
remove((mkvFile + ".bak").c_str());
|
||||
}
|
||||
|
||||
/*!
|
||||
|
@ -219,6 +275,7 @@ void CliTests::testHandlingAttachments()
|
|||
{
|
||||
string stdout, stderr;
|
||||
const string mkvFile1(workingCopyPath("matroska_wave1/test1.mkv"));
|
||||
const string mkvFile1Backup(mkvFile1 + ".bak");
|
||||
const string mkvFile2("path=" + testFilePath("matroska_wave1/test2.mkv"));
|
||||
|
||||
// add attachment
|
||||
|
@ -226,24 +283,43 @@ void CliTests::testHandlingAttachments()
|
|||
CPPUNIT_ASSERT_EQUAL(0, execApp(args2, stdout, stderr));
|
||||
const char *const args1[] = {"tageditor", "info", "-f", mkvFile1.data(), nullptr};
|
||||
CPPUNIT_ASSERT_EQUAL(0, execApp(args1, stdout, stderr));
|
||||
size_t pos1;
|
||||
CPPUNIT_ASSERT((pos1 = stdout.find("Attachments:")) != string::npos);
|
||||
CPPUNIT_ASSERT(stdout.find("Name test2.mkv") > pos1);
|
||||
CPPUNIT_ASSERT(stdout.find("MIME-type video/x-matroska") > pos1);
|
||||
CPPUNIT_ASSERT(stdout.find("Description Test attachment") > pos1);
|
||||
CPPUNIT_ASSERT(stdout.find("Size 20.16 MiB (21142764 byte)") > pos1);
|
||||
CPPUNIT_ASSERT(containsSubstrings(stdout, {
|
||||
"Attachments:",
|
||||
"Name test2.mkv",
|
||||
"MIME-type video/x-matroska",
|
||||
"Description Test attachment",
|
||||
"Size 20.16 MiB (21142764 byte)"
|
||||
}));
|
||||
// clear backup file
|
||||
remove(mkvFile1Backup.data());
|
||||
|
||||
// update attachment
|
||||
const char *const args3[] = {"tageditor", "set", "--update-attachment", "name=test2.mkv", "desc=Updated test attachment", "-f", mkvFile1.data(), nullptr};
|
||||
CPPUNIT_ASSERT_EQUAL(0, execApp(args3, stdout, stderr));
|
||||
CPPUNIT_ASSERT_EQUAL(0, execApp(args1, stdout, stderr));
|
||||
CPPUNIT_ASSERT((pos1 = stdout.find("Attachments:")) != string::npos);
|
||||
CPPUNIT_ASSERT(stdout.find("Name test2.mkv") > pos1);
|
||||
CPPUNIT_ASSERT(stdout.find("MIME-type video/x-matroska") > pos1);
|
||||
CPPUNIT_ASSERT(stdout.find("Description Updated test attachment") > pos1);
|
||||
CPPUNIT_ASSERT(stdout.find("Size 20.16 MiB (21142764 byte)") > pos1);
|
||||
CPPUNIT_ASSERT(containsSubstrings(stdout, {
|
||||
"Attachments:",
|
||||
"Name test2.mkv",
|
||||
"MIME-type video/x-matroska",
|
||||
"Description Updated test attachment",
|
||||
"Size 20.16 MiB (21142764 byte)"
|
||||
}));
|
||||
// clear backup file
|
||||
remove(mkvFile1Backup.data());
|
||||
|
||||
// TODO: extract assigned attachment (feature not implemented yet)
|
||||
// extract assigned attachment again
|
||||
const char *const args4[] = {"tageditor", "extract", "--attachment", "name=test2.mkv", "-f", mkvFile1.data(), "-o", "/tmp/extracted.mkv", nullptr};
|
||||
CPPUNIT_ASSERT_EQUAL(0, execApp(args4, stdout, stderr));
|
||||
fstream origFile, extFile;
|
||||
origFile.exceptions(ios_base::failbit | ios_base::badbit), extFile.exceptions(ios_base::failbit | ios_base::badbit);
|
||||
origFile.open(mkvFile2.data() + 5, ios_base::in | ios_base::binary), extFile.open("/tmp/extracted.mkv", ios_base::in | ios_base::binary);
|
||||
origFile.seekg(0, ios_base::end), extFile.seekg(0, ios_base::end);
|
||||
int64 origFileSize = origFile.tellg(), extFileSize = extFile.tellg();
|
||||
CPPUNIT_ASSERT_EQUAL(origFileSize, extFileSize);
|
||||
for(origFile.seekg(0), extFile.seekg(0); origFileSize > 0; --origFileSize) {
|
||||
CPPUNIT_ASSERT_EQUAL(origFile.get(), extFile.get());
|
||||
}
|
||||
remove("/tmp/extracted.mkv");
|
||||
|
||||
// remove assigned attachment
|
||||
const char *const args5[] = {"tageditor", "set", "--remove-attachment", "name=test2.mkv", "-f", mkvFile1.data(), nullptr};
|
||||
|
@ -251,6 +327,10 @@ void CliTests::testHandlingAttachments()
|
|||
CPPUNIT_ASSERT_EQUAL(0, execApp(args1, stdout, stderr));
|
||||
CPPUNIT_ASSERT(stdout.find("Attachments:") == string::npos);
|
||||
CPPUNIT_ASSERT(stdout.find("Name test2.mkv") == string::npos);
|
||||
|
||||
// clear working copies if all tests have been
|
||||
remove(mkvFile1.data());
|
||||
remove(mkvFile1Backup.data());
|
||||
}
|
||||
|
||||
/*!
|
||||
|
@ -262,11 +342,38 @@ void CliTests::testDisplayingInfo()
|
|||
}
|
||||
|
||||
/*!
|
||||
* \brief Tests extraction (used for cover or other binary fields).
|
||||
* \brief Tests extraction of field values (used to extract cover or other binary fields).
|
||||
* \remarks Extraction of attachments is already tested in testHandlingAttachments().
|
||||
*/
|
||||
void CliTests::testExtraction()
|
||||
{
|
||||
// TODO
|
||||
string stdout, stderr;
|
||||
const string mp41File(testFilePath("mtx-test-data/alac/othertest-itunes.m4a"));
|
||||
|
||||
// test extraction of cover
|
||||
const char *const args1[] = {"tageditor", "extract", "cover", "-f", mp41File.data(), "-o", "/tmp/extracted.jpeg", nullptr};
|
||||
CPPUNIT_ASSERT_EQUAL(0, execApp(args1, stdout, stderr));
|
||||
MediaFileInfo extractedInfo("/tmp/extracted.jpeg");
|
||||
extractedInfo.open(true);
|
||||
extractedInfo.parseContainerFormat();
|
||||
CPPUNIT_ASSERT_EQUAL(22771ul, extractedInfo.size());
|
||||
CPPUNIT_ASSERT(ContainerFormat::Jpeg == extractedInfo.containerFormat());
|
||||
extractedInfo.invalidate();
|
||||
|
||||
// test assignment of cover by the way
|
||||
const string mp4File2(workingCopyPath("mtx-test-data/aac/he-aacv2-ps.m4a"));
|
||||
const char *const args2[] = {"tageditor", "set", "cover=/tmp/extracted.jpeg", "-f", mp4File2.data(), nullptr};
|
||||
CPPUNIT_ASSERT_EQUAL(0, execApp(args2, stdout, stderr));
|
||||
const char *const args3[] = {"tageditor", "extract", "cover", "-f", mp4File2.data(), "-o", "/tmp/extracted.jpeg", nullptr};
|
||||
remove("/tmp/extracted.jpeg");
|
||||
CPPUNIT_ASSERT_EQUAL(0, execApp(args3, stdout, stderr));
|
||||
extractedInfo.open(true);
|
||||
extractedInfo.parseContainerFormat();
|
||||
CPPUNIT_ASSERT_EQUAL(22771ul, extractedInfo.size());
|
||||
CPPUNIT_ASSERT(ContainerFormat::Jpeg == extractedInfo.containerFormat());
|
||||
remove("/tmp/extracted.jpeg");
|
||||
remove(mp4File2.data());
|
||||
remove((mp4File2 + ".bak").data());
|
||||
}
|
||||
|
||||
/*!
|
||||
|
|
Loading…
Reference in New Issue