Improve CLI
* Use formatting * Use more consistent format * Show track summary
This commit is contained in:
parent
25d570d394
commit
5ffa9b7d2c
121
cli/helper.cpp
121
cli/helper.cpp
|
@ -8,6 +8,7 @@
|
|||
|
||||
#include <c++utilities/application/argumentparser.h>
|
||||
#include <c++utilities/conversion/stringbuilder.h>
|
||||
#include <c++utilities/io/ansiescapecodes.h>
|
||||
|
||||
#include <iostream>
|
||||
#include <cstring>
|
||||
|
@ -64,37 +65,37 @@ void printNotifications(NotificationList ¬ifications, const char *head, bool
|
|||
if(!notifications.empty()) {
|
||||
printNotifications:
|
||||
if(head) {
|
||||
cout << head << endl;
|
||||
cout << " - " << head << endl;
|
||||
}
|
||||
Notification::sortByTime(notifications);
|
||||
for(const auto ¬ification : notifications) {
|
||||
switch(notification.type()) {
|
||||
case NotificationType::Debug:
|
||||
if(beVerbose) {
|
||||
cout << "Debug ";
|
||||
cout << " Debug ";
|
||||
break;
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
case NotificationType::Information:
|
||||
if(beVerbose) {
|
||||
cout << "Information ";
|
||||
cout << " Information ";
|
||||
break;
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
case NotificationType::Warning:
|
||||
cout << "Warning ";
|
||||
cout << " Warning ";
|
||||
break;
|
||||
case NotificationType::Critical:
|
||||
cout << "Error ";
|
||||
cout << " Error ";
|
||||
break;
|
||||
default:
|
||||
;
|
||||
}
|
||||
cout << notification.creationTime().toString(DateTimeOutputFormat::TimeOnly) << " ";
|
||||
cout << notification.context() << ": ";
|
||||
cout << notification.message() << endl;
|
||||
cout << notification.message() << '\n';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -137,7 +138,7 @@ void printProperty(const char *propName, ElementPosition elementPosition, const
|
|||
|
||||
void printFieldName(const char *fieldName, size_t fieldNameLen)
|
||||
{
|
||||
cout << ' ' << fieldName;
|
||||
cout << " " << fieldName;
|
||||
// also write padding
|
||||
for(auto i = fieldNameLen; i < 18; ++i) {
|
||||
cout << ' ';
|
||||
|
@ -151,30 +152,35 @@ void printField(const FieldScope &scope, const Tag *tag, TagType tagType, bool s
|
|||
const auto fieldNameLen = strlen(fieldName);
|
||||
|
||||
try {
|
||||
// parse field denotation
|
||||
const auto &values = scope.field.values(tag, tagType);
|
||||
if(!skipEmpty || !values.empty()) {
|
||||
// write value
|
||||
if(values.empty()) {
|
||||
printFieldName(fieldName, fieldNameLen);
|
||||
cout << "none\n";
|
||||
} else {
|
||||
for(const auto &value : values) {
|
||||
printFieldName(fieldName, fieldNameLen);
|
||||
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 << '\n';
|
||||
}
|
||||
}
|
||||
|
||||
// skip empty values (unless prevented)
|
||||
if(skipEmpty && values.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// print empty value (if not prevented)
|
||||
if(values.empty()) {
|
||||
printFieldName(fieldName, fieldNameLen);
|
||||
cout << "none\n";
|
||||
return;
|
||||
}
|
||||
|
||||
// print values
|
||||
for(const auto &value : values) {
|
||||
printFieldName(fieldName, fieldNameLen);
|
||||
try {
|
||||
cout << value->toString(TagTextEncoding::Utf8);
|
||||
} catch(const ConversionException &) {
|
||||
// handle case when value can not be displayed as string
|
||||
cout << "can't display as string (see --extract)";
|
||||
}
|
||||
cout << '\n';
|
||||
}
|
||||
|
||||
} catch(const ConversionException &e) {
|
||||
// handle conversion error which might happen when parsing field denotation
|
||||
printFieldName(fieldName, fieldNameLen);
|
||||
cout << "unable to parse - " << e.what() << '\n';
|
||||
}
|
||||
|
@ -585,4 +591,63 @@ bool stringToBool(const string &str)
|
|||
throw ConversionException(argsToString('\"', str, " is not yes or no"));
|
||||
}
|
||||
|
||||
bool logLineFinalized = true;
|
||||
void logStatus(const StatusProvider &statusProvider)
|
||||
{
|
||||
static string lastStatus;
|
||||
|
||||
if(statusProvider.currentStatus() != lastStatus) {
|
||||
// the ongoing operation ("status") has changed
|
||||
// -> finalize previous line and make new line
|
||||
if(!logLineFinalized) {
|
||||
cout << "\r - [100%] " << lastStatus << endl;
|
||||
logLineFinalized = true;
|
||||
}
|
||||
// -> update lastStatus
|
||||
lastStatus = statusProvider.currentStatus();
|
||||
}
|
||||
|
||||
// update current line if an operation is ongoing (status is not empty)
|
||||
if(!lastStatus.empty()) {
|
||||
int percentage = static_cast<int>(statusProvider.currentPercentage() * 100);
|
||||
if(percentage < 0) {
|
||||
percentage = 0;
|
||||
}
|
||||
cout << "\r - [" << setw(3) << percentage << "%] " << lastStatus << flush;
|
||||
logLineFinalized = false;
|
||||
}
|
||||
}
|
||||
|
||||
void finalizeLog()
|
||||
{
|
||||
if(!logLineFinalized) {
|
||||
cout << '\n';
|
||||
logLineFinalized = true;
|
||||
}
|
||||
}
|
||||
|
||||
std::ostream &operator<< (std::ostream &stream, Phrases phrase)
|
||||
{
|
||||
using namespace EscapeCodes;
|
||||
switch(phrase) {
|
||||
case Phrases::Error:
|
||||
setStyle(stream, Color::Red, ColorContext::Foreground, TextAttribute::Bold);
|
||||
stream << "Error: ";
|
||||
setStyle(stream, TextAttribute::Reset);
|
||||
setStyle(stream, TextAttribute::Bold);
|
||||
break;
|
||||
case Phrases::Warning:
|
||||
setStyle(stream, Color::Yellow, ColorContext::Foreground, TextAttribute::Bold);
|
||||
stream << "Warning: ";
|
||||
setStyle(stream, TextAttribute::Reset);
|
||||
setStyle(stream, TextAttribute::Bold);
|
||||
break;
|
||||
case Phrases::End:
|
||||
setStyle(stream, TextAttribute::Reset);
|
||||
stream << '\n';
|
||||
break;
|
||||
}
|
||||
return stream;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
18
cli/helper.h
18
cli/helper.h
|
@ -9,10 +9,12 @@
|
|||
#include <c++utilities/chrono/datetime.h>
|
||||
#include <c++utilities/chrono/timespan.h>
|
||||
#include <c++utilities/conversion/stringconversion.h>
|
||||
#include <c++utilities/misc/traits.h>
|
||||
|
||||
#include <vector>
|
||||
#include <unordered_map>
|
||||
#include <functional>
|
||||
#include <type_traits>
|
||||
|
||||
namespace ApplicationUtilities {
|
||||
class Argument;
|
||||
|
@ -272,11 +274,11 @@ inline void printProperty(const char *propName, ChronoUtilities::DateTime dateTi
|
|||
}
|
||||
}
|
||||
|
||||
template<typename intType>
|
||||
inline void printProperty(const char *propName, const intType value, const char *suffix = nullptr, bool force = false, ApplicationUtilities::Indentation indentation = 4)
|
||||
template<typename NumberType, Traits::EnableIfAny<std::is_integral<NumberType>, std::is_floating_point<NumberType>>...>
|
||||
inline void printProperty(const char *propName, const NumberType value, const char *suffix = nullptr, bool force = false, ApplicationUtilities::Indentation indentation = 4)
|
||||
{
|
||||
if(value != 0 || force) {
|
||||
printProperty(propName, ConversionUtilities::numberToString<intType>(value), suffix, indentation);
|
||||
printProperty(propName, ConversionUtilities::numberToString<NumberType>(value), suffix, indentation);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -291,6 +293,16 @@ bool applyTargetConfiguration(TagTarget &target, const std::string &configStr);
|
|||
FieldDenotations parseFieldDenotations(const ApplicationUtilities::Argument &fieldsArg, bool readOnly);
|
||||
std::string tagName(const Tag *tag);
|
||||
bool stringToBool(const std::string &str);
|
||||
extern bool logLineFinalized;
|
||||
void logStatus(const StatusProvider &statusProvider);
|
||||
void finalizeLog();
|
||||
|
||||
enum class Phrases {
|
||||
Error,
|
||||
Warning,
|
||||
End,
|
||||
};
|
||||
std::ostream &operator<<(std::ostream &stream, Phrases phrase);
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -30,6 +30,7 @@
|
|||
#endif
|
||||
|
||||
#include <iostream>
|
||||
#include <iomanip>
|
||||
#include <cstring>
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
|
@ -89,22 +90,22 @@ void generateFileInfo(const ArgumentOccurrence &, const Argument &inputFileArg,
|
|||
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;
|
||||
cerr << Phrases::Error << "An IO error occured when writing the file \"" << outputFileArg.values().front() << "\"." << Phrases::End;
|
||||
}
|
||||
} else {
|
||||
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;
|
||||
cerr << Phrases::Error << "A parsing failure occured when reading the file \"" << inputFileArg.values().front() << "\"." << Phrases::End;
|
||||
} catch(...) {
|
||||
::IoUtilities::catchIoFailure();
|
||||
cerr << "Error: An IO failure occured when reading the file \"" << inputFileArg.values().front() << "\"." << endl;
|
||||
cerr << Phrases::Error << "An IO failure occured when reading the file \"" << inputFileArg.values().front() << "\"." << Phrases::End;
|
||||
}
|
||||
#else
|
||||
VAR_UNUSED(inputFileArg);
|
||||
VAR_UNUSED(outputFileArg);
|
||||
VAR_UNUSED(validateArg);
|
||||
cerr << "Error: Generating HTML info is only available if built with Qt support." << endl;
|
||||
cerr << Phrases::Error << "Generating HTML info is only available if built with Qt support." << endl;
|
||||
#endif
|
||||
}
|
||||
|
||||
|
@ -112,7 +113,7 @@ void displayFileInfo(const ArgumentOccurrence &, const Argument &filesArg, const
|
|||
{
|
||||
CMD_UTILS_START_CONSOLE;
|
||||
if(!filesArg.isPresent() || filesArg.values().empty()) {
|
||||
cerr << "Error: No files have been specified." << endl;
|
||||
cerr << Phrases::Error << "No files have been specified." << Phrases::End;
|
||||
return;
|
||||
}
|
||||
MediaFileInfo fileInfo;
|
||||
|
@ -125,145 +126,147 @@ void displayFileInfo(const ArgumentOccurrence &, const Argument &filesArg, const
|
|||
fileInfo.parseTracks();
|
||||
fileInfo.parseAttachments();
|
||||
fileInfo.parseChapters();
|
||||
cout << "Technical information for \"" << file << "\":" << endl;
|
||||
cout << " Container format: " << fileInfo.containerFormatName() << endl;
|
||||
{
|
||||
if(const auto container = fileInfo.container()) {
|
||||
size_t segmentIndex = 0;
|
||||
for(const auto &title : container->titles()) {
|
||||
if(segmentIndex) {
|
||||
printProperty("Title", title % " (segment " % ++segmentIndex + ")");
|
||||
} else {
|
||||
++segmentIndex;
|
||||
printProperty("Title", title);
|
||||
}
|
||||
}
|
||||
printProperty("Document type", container->documentType());
|
||||
printProperty("Read version", container->readVersion());
|
||||
printProperty("Version", container->version());
|
||||
printProperty("Document read version", container->doctypeReadVersion());
|
||||
printProperty("Document version", container->doctypeVersion());
|
||||
printProperty("Duration", container->duration());
|
||||
printProperty("Creation time", container->creationTime());
|
||||
printProperty("Modification time", container->modificationTime());
|
||||
printProperty("Tag position", container->determineTagPosition());
|
||||
printProperty("Index position", container->determineIndexPosition());
|
||||
}
|
||||
if(fileInfo.paddingSize()) {
|
||||
printProperty("Padding", dataSizeToString(fileInfo.paddingSize()));
|
||||
}
|
||||
}
|
||||
{ // tracks
|
||||
const auto tracks = fileInfo.tracks();
|
||||
if(!tracks.empty()) {
|
||||
cout << " Tracks:" << endl;
|
||||
for(const auto *track : tracks) {
|
||||
printProperty("ID", track->id(), nullptr, true);
|
||||
printProperty("Name", track->name());
|
||||
printProperty("Type", track->mediaTypeName());
|
||||
if(track->language() != "und") {
|
||||
printProperty("Language", track->language());
|
||||
}
|
||||
const char *fmtName = track->formatName(), *fmtAbbr = track->formatAbbreviation();
|
||||
printProperty("Format", fmtName);
|
||||
if(strcmp(fmtName, fmtAbbr)) {
|
||||
printProperty("Abbreviation", fmtAbbr);
|
||||
}
|
||||
printProperty("Extensions", track->format().extensionName());
|
||||
printProperty("Raw format ID", track->formatId());
|
||||
if(track->size()) {
|
||||
printProperty("Size", dataSizeToString(track->size(), true));
|
||||
}
|
||||
printProperty("Duration", track->duration());
|
||||
printProperty("FPS", track->fps());
|
||||
if(track->channelConfigString()) {
|
||||
printProperty("Channel config", track->channelConfigString());
|
||||
} else {
|
||||
printProperty("Channel count", track->channelCount());
|
||||
}
|
||||
if(track->extensionChannelConfigString()) {
|
||||
printProperty("Extension channel config", track->extensionChannelConfigString());
|
||||
}
|
||||
printProperty("Bitrate", track->bitrate(), "kbit/s");
|
||||
printProperty("Bits per sample", track->bitsPerSample());
|
||||
printProperty("Sampling frequency", track->samplingFrequency(), "Hz");
|
||||
printProperty("Extension sampling frequency", track->extensionSamplingFrequency(), "Hz");
|
||||
printProperty(track->mediaType() == MediaType::Video
|
||||
? "Frame count"
|
||||
: "Sample count",
|
||||
track->sampleCount());
|
||||
printProperty("Creation time", track->creationTime());
|
||||
printProperty("Modification time", track->modificationTime());
|
||||
vector<string> labels;
|
||||
labels.reserve(7);
|
||||
if(track->isInterlaced()) {
|
||||
labels.emplace_back("interlaced");
|
||||
}
|
||||
if(!track->isEnabled()) {
|
||||
labels.emplace_back("disabled");
|
||||
}
|
||||
if(track->isDefault()) {
|
||||
labels.emplace_back("default");
|
||||
}
|
||||
if(track->isForced()) {
|
||||
labels.emplace_back("forced");
|
||||
}
|
||||
if(track->hasLacing()) {
|
||||
labels.emplace_back("has lacing");
|
||||
}
|
||||
if(track->isEncrypted()) {
|
||||
labels.emplace_back("encrypted");
|
||||
}
|
||||
if(!labels.empty()) {
|
||||
printProperty("Labeled as", joinStrings(labels, ", "));
|
||||
}
|
||||
cout << endl;
|
||||
}
|
||||
} else {
|
||||
cout << " File has no (supported) tracks." << endl;
|
||||
}
|
||||
}
|
||||
{ // attachments
|
||||
const auto attachments = fileInfo.attachments();
|
||||
if(!attachments.empty()) {
|
||||
cout << "Attachments:" << endl;
|
||||
for(const auto *attachment : attachments) {
|
||||
printProperty("ID", attachment->id());
|
||||
printProperty("Name", attachment->name());
|
||||
printProperty("MIME-type", attachment->mimeType());
|
||||
printProperty("Description", attachment->description());
|
||||
if(attachment->data()) {
|
||||
printProperty("Size", dataSizeToString(attachment->data()->size(), true));
|
||||
}
|
||||
cout << endl;
|
||||
|
||||
// print general/container-related info
|
||||
cout << "Technical information for \"" << file << "\":\n";
|
||||
cout << " - " << TextAttribute::Bold << "Container format: " << fileInfo.containerFormatName() << Phrases::End;
|
||||
if(const auto container = fileInfo.container()) {
|
||||
size_t segmentIndex = 0;
|
||||
for(const auto &title : container->titles()) {
|
||||
if(segmentIndex) {
|
||||
printProperty("Title", title % " (segment " % ++segmentIndex + ")");
|
||||
} else {
|
||||
++segmentIndex;
|
||||
printProperty("Title", title);
|
||||
}
|
||||
}
|
||||
printProperty("Document type", container->documentType());
|
||||
printProperty("Read version", container->readVersion());
|
||||
printProperty("Version", container->version());
|
||||
printProperty("Document read version", container->doctypeReadVersion());
|
||||
printProperty("Document version", container->doctypeVersion());
|
||||
printProperty("Duration", container->duration());
|
||||
printProperty("Creation time", container->creationTime());
|
||||
printProperty("Modification time", container->modificationTime());
|
||||
printProperty("Tag position", container->determineTagPosition());
|
||||
printProperty("Index position", container->determineIndexPosition());
|
||||
}
|
||||
{ // chapters
|
||||
const auto chapters = fileInfo.chapters();
|
||||
if(!chapters.empty()) {
|
||||
cout << "Chapters:" << endl;
|
||||
for(const auto *chapter : chapters) {
|
||||
printProperty("ID", chapter->id());
|
||||
if(!chapter->names().empty()) {
|
||||
printProperty("Name", static_cast<string>(chapter->names().front()));
|
||||
}
|
||||
if(!chapter->startTime().isNegative()) {
|
||||
printProperty("Start time", chapter->startTime().toString());
|
||||
}
|
||||
if(!chapter->endTime().isNegative()) {
|
||||
printProperty("End time", chapter->endTime().toString());
|
||||
}
|
||||
cout << endl;
|
||||
if(fileInfo.paddingSize()) {
|
||||
printProperty("Padding", dataSizeToString(fileInfo.paddingSize()));
|
||||
}
|
||||
|
||||
// print tracks
|
||||
const auto tracks = fileInfo.tracks();
|
||||
if(!tracks.empty()) {
|
||||
cout << " - " << TextAttribute::Bold << "Tracks: " << fileInfo.technicalSummary() << Phrases::End;
|
||||
for(const auto *track : tracks) {
|
||||
printProperty("ID", track->id(), nullptr, true);
|
||||
printProperty("Name", track->name());
|
||||
printProperty("Type", track->mediaTypeName());
|
||||
if(track->language() != "und") {
|
||||
printProperty("Language", track->language());
|
||||
}
|
||||
const char *fmtName = track->formatName(), *fmtAbbr = track->formatAbbreviation();
|
||||
printProperty("Format", fmtName);
|
||||
if(strcmp(fmtName, fmtAbbr)) {
|
||||
printProperty("Abbreviation", fmtAbbr);
|
||||
}
|
||||
printProperty("Extensions", track->format().extensionName());
|
||||
printProperty("Raw format ID", track->formatId());
|
||||
if(track->size()) {
|
||||
printProperty("Size", dataSizeToString(track->size(), true));
|
||||
}
|
||||
printProperty("Duration", track->duration());
|
||||
printProperty("FPS", track->fps());
|
||||
if(track->channelConfigString()) {
|
||||
printProperty("Channel config", track->channelConfigString());
|
||||
} else {
|
||||
printProperty("Channel count", track->channelCount());
|
||||
}
|
||||
if(track->extensionChannelConfigString()) {
|
||||
printProperty("Extension channel config", track->extensionChannelConfigString());
|
||||
}
|
||||
printProperty("Bitrate", track->bitrate(), "kbit/s");
|
||||
printProperty("Bits per sample", track->bitsPerSample());
|
||||
printProperty("Sampling frequency", track->samplingFrequency(), "Hz");
|
||||
printProperty("Extension sampling frequency", track->extensionSamplingFrequency(), "Hz");
|
||||
printProperty(track->mediaType() == MediaType::Video
|
||||
? "Frame count"
|
||||
: "Sample count",
|
||||
track->sampleCount());
|
||||
printProperty("Creation time", track->creationTime());
|
||||
printProperty("Modification time", track->modificationTime());
|
||||
vector<string> labels;
|
||||
labels.reserve(7);
|
||||
if(track->isInterlaced()) {
|
||||
labels.emplace_back("interlaced");
|
||||
}
|
||||
if(!track->isEnabled()) {
|
||||
labels.emplace_back("disabled");
|
||||
}
|
||||
if(track->isDefault()) {
|
||||
labels.emplace_back("default");
|
||||
}
|
||||
if(track->isForced()) {
|
||||
labels.emplace_back("forced");
|
||||
}
|
||||
if(track->hasLacing()) {
|
||||
labels.emplace_back("has lacing");
|
||||
}
|
||||
if(track->isEncrypted()) {
|
||||
labels.emplace_back("encrypted");
|
||||
}
|
||||
if(!labels.empty()) {
|
||||
printProperty("Labeled as", joinStrings(labels, ", "));
|
||||
}
|
||||
cout << '\n';
|
||||
}
|
||||
} else {
|
||||
cout << " - File has no (supported) tracks.\n";
|
||||
}
|
||||
|
||||
// print attachments
|
||||
const auto attachments = fileInfo.attachments();
|
||||
if(!attachments.empty()) {
|
||||
cout << " - " << TextAttribute::Bold << "Attachments:" << TextAttribute::Reset << '\n';
|
||||
for(const auto *attachment : attachments) {
|
||||
printProperty("ID", attachment->id());
|
||||
printProperty("Name", attachment->name());
|
||||
printProperty("MIME-type", attachment->mimeType());
|
||||
printProperty("Description", attachment->description());
|
||||
if(attachment->data()) {
|
||||
printProperty("Size", dataSizeToString(static_cast<uint64>(attachment->data()->size()), true));
|
||||
}
|
||||
cout << '\n';
|
||||
}
|
||||
}
|
||||
|
||||
// print chapters
|
||||
const auto chapters = fileInfo.chapters();
|
||||
if(!chapters.empty()) {
|
||||
cout << " - " << TextAttribute::Bold << "Chapters:" << TextAttribute::Reset << '\n';
|
||||
for(const auto *chapter : chapters) {
|
||||
printProperty("ID", chapter->id());
|
||||
if(!chapter->names().empty()) {
|
||||
printProperty("Name", static_cast<string>(chapter->names().front()));
|
||||
}
|
||||
if(!chapter->startTime().isNegative()) {
|
||||
printProperty("Start time", chapter->startTime().toString());
|
||||
}
|
||||
if(!chapter->endTime().isNegative()) {
|
||||
printProperty("End time", chapter->endTime().toString());
|
||||
}
|
||||
cout << '\n';
|
||||
}
|
||||
}
|
||||
|
||||
} catch(const ApplicationUtilities::Failure &) {
|
||||
cerr << "Error: A parsing failure occured when reading the file \"" << file << "\"." << endl;
|
||||
cerr << Phrases::Error << "A parsing failure occured when reading the file \"" << file << "\"." << Phrases::End;
|
||||
} catch(...) {
|
||||
::IoUtilities::catchIoFailure();
|
||||
cerr << "Error: An IO failure occured when reading the file \"" << file << "\"." << endl;
|
||||
cerr << Phrases::Error << "An IO failure occured when reading the file \"" << file << "\"." << Phrases::End;
|
||||
}
|
||||
|
||||
printNotifications(fileInfo, "Parsing notifications:", verboseArg.isPresent());
|
||||
cout << endl;
|
||||
}
|
||||
|
@ -273,7 +276,7 @@ void displayTagInfo(const Argument &fieldsArg, const Argument &filesArg, const A
|
|||
{
|
||||
CMD_UTILS_START_CONSOLE;
|
||||
if(!filesArg.isPresent() || filesArg.values().empty()) {
|
||||
cerr << "Error: No files have been specified." << endl;
|
||||
cerr << Phrases::Error << "No files have been specified." << Phrases::End;
|
||||
return;
|
||||
}
|
||||
const auto fields = parseFieldDenotations(fieldsArg, true);
|
||||
|
@ -285,8 +288,7 @@ void displayTagInfo(const Argument &fieldsArg, const Argument &filesArg, const A
|
|||
fileInfo.open(true);
|
||||
fileInfo.parseContainerFormat();
|
||||
fileInfo.parseTags();
|
||||
cout << file << endl;
|
||||
cout << "Tag information for \"" << file << "\":" << endl;
|
||||
cout << "Tag information for \"" << file << "\":\n";
|
||||
const auto tags = fileInfo.tags();
|
||||
if(!tags.empty()) {
|
||||
// iterate through all tags
|
||||
|
@ -294,7 +296,7 @@ void displayTagInfo(const Argument &fieldsArg, const Argument &filesArg, const A
|
|||
// determine tag type
|
||||
const TagType tagType = tag->type();
|
||||
// write tag name and target, eg. MP4/iTunes tag
|
||||
cout << tagName(tag) << endl;
|
||||
cout << " - " << TextAttribute::Bold << tagName(tag) << TextAttribute::Reset << '\n';
|
||||
// iterate through fields specified by the user
|
||||
if(fields.empty()) {
|
||||
for(auto field = firstKnownField; field != KnownField::Invalid; field = nextKnownField(field)) {
|
||||
|
@ -310,13 +312,13 @@ void displayTagInfo(const Argument &fieldsArg, const Argument &filesArg, const A
|
|||
}
|
||||
}
|
||||
} else {
|
||||
cout << " File has no (supported) tag information." << endl;
|
||||
cout << " - File has no (supported) tag information.\n";
|
||||
}
|
||||
} catch(const ApplicationUtilities::Failure &) {
|
||||
cerr << "Error: A parsing failure occured when reading the file \"" << file << "\"." << endl;
|
||||
cerr << Phrases::Error << "A parsing failure occured when reading the file \"" << file << "\"." << Phrases::End;
|
||||
} catch(...) {
|
||||
::IoUtilities::catchIoFailure();
|
||||
cerr << "Error: An IO failure occured when reading the file \"" << file << "\"." << endl;
|
||||
cerr << Phrases::Error << "An IO failure occured when reading the file \"" << file << "\"." << Phrases::End;
|
||||
}
|
||||
printNotifications(fileInfo, "Parsing notifications:", verboseArg.isPresent());
|
||||
cout << endl;
|
||||
|
@ -327,11 +329,11 @@ void setTagInfo(const SetTagInfoArgs &args)
|
|||
{
|
||||
CMD_UTILS_START_CONSOLE;
|
||||
if(!args.filesArg.isPresent() || args.filesArg.values().empty()) {
|
||||
cerr << "Error: No files have been specified." << endl;
|
||||
cerr << Phrases::Error << "No files have been specified." << Phrases::End;
|
||||
return;
|
||||
}
|
||||
if(args.outputFilesArg.isPresent() && args.outputFilesArg.values().size() != args.filesArg.values().size()) {
|
||||
cerr << "Error: The number of output files does not match the number of input files." << endl;
|
||||
cerr << Phrases::Error << "The number of output files does not match the number of input files." << Phrases::End;
|
||||
return;
|
||||
}
|
||||
auto &outputFiles = args.outputFilesArg.isPresent() ? args.outputFilesArg.values() : vector<const char *>();
|
||||
|
@ -346,7 +348,7 @@ void setTagInfo(const SetTagInfoArgs &args)
|
|||
&& !args.id3v1UsageArg.isPresent()
|
||||
&& !args.id3v2UsageArg.isPresent()
|
||||
&& !args.id3v2VersionArg.isPresent()) {
|
||||
cerr << "Warning: No fields/attachments have been specified." << endl;
|
||||
cerr << Phrases::Warning << "No fields/attachments have been specified." << Phrases::End;
|
||||
}
|
||||
// determine required targets
|
||||
vector<TagTarget> requiredTargets;
|
||||
|
@ -369,7 +371,7 @@ void setTagInfo(const SetTagInfoArgs &args)
|
|||
} else if(applyTargetConfiguration(targetsToRemove.back(), targetDenotation)) {
|
||||
validRemoveTargetsSpecified = true;
|
||||
} else {
|
||||
cerr << "Warning: The given target specification \"" << targetDenotation << "\" is invalid and will be ignored." << endl;
|
||||
cerr << Phrases::Warning << "The given target specification \"" << targetDenotation << "\" is invalid and will be ignored." << Phrases::End;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -383,7 +385,7 @@ void setTagInfo(const SetTagInfoArgs &args)
|
|||
}
|
||||
} catch (const ConversionException &) {
|
||||
id3v2Version = 3;
|
||||
cerr << "Warning: The specified ID3v2 version \"" << args.id3v2VersionArg.values().front() << "\" is invalid and will be ingored." << endl;
|
||||
cerr << Phrases::Warning << "The specified ID3v2 version \"" << args.id3v2VersionArg.values().front() << "\" is invalid and will be ingored." << Phrases::End;
|
||||
}
|
||||
}
|
||||
const TagTextEncoding denotedEncoding = parseEncodingDenotation(args.encodingArg, TagTextEncoding::Utf8);
|
||||
|
@ -405,7 +407,7 @@ void setTagInfo(const SetTagInfoArgs &args)
|
|||
for(const char *file : args.filesArg.values()) {
|
||||
try {
|
||||
// parse tags
|
||||
cout << "Setting tag information for \"" << file << "\" ..." << endl;
|
||||
cout << TextAttribute::Bold << "Setting tag information for \"" << file << "\" ..." << TextAttribute::Reset << endl;
|
||||
notifications.clear();
|
||||
fileInfo.setPath(file);
|
||||
fileInfo.parseContainerFormat();
|
||||
|
@ -432,12 +434,12 @@ void setTagInfo(const SetTagInfoArgs &args)
|
|||
if(segmentIndex < segmentCount) {
|
||||
container->setTitle(newTitle, segmentIndex);
|
||||
} else {
|
||||
cerr << "Warning: The specified document title \"" << newTitle << "\" can not be set because the file has not that many segments." << endl;
|
||||
cerr << Phrases::Warning << "The specified document title \"" << newTitle << "\" can not be set because the file has not that many segments." << Phrases::End;
|
||||
}
|
||||
++segmentIndex;
|
||||
}
|
||||
} else {
|
||||
cerr << "Warning: Setting the document title is not supported for the file." << endl;
|
||||
cerr << Phrases::Warning << "Setting the document title is not supported for the file." << Phrases::End;
|
||||
}
|
||||
}
|
||||
// select the relevant values for the current file index
|
||||
|
@ -619,17 +621,22 @@ void setTagInfo(const SetTagInfoArgs &args)
|
|||
fileInfo.setSaveFilePath(currentOutputFile != noMoreOutputFiles ? string(*currentOutputFile) : string());
|
||||
fileInfo.gatherRelatedNotifications(notifications);
|
||||
fileInfo.invalidateNotifications();
|
||||
fileInfo.registerCallback(logStatus);
|
||||
fileInfo.applyChanges();
|
||||
fileInfo.gatherRelatedNotifications(notifications);
|
||||
cout << "Changes have been applied." << endl;
|
||||
finalizeLog();
|
||||
cout << " - Changes have been applied." << endl;
|
||||
} catch(const ApplicationUtilities::Failure &) {
|
||||
cerr << "Error: Failed to apply changes." << endl;
|
||||
finalizeLog();
|
||||
cerr << " - " << Phrases::Error << "Failed to apply changes." << endl;
|
||||
}
|
||||
} catch(const ApplicationUtilities::Failure &) {
|
||||
cerr << "Error: A parsing failure occured when reading/writing the file \"" << file << "\"." << endl;
|
||||
finalizeLog();
|
||||
cerr << " - " << Phrases::Error << "A parsing failure occured when reading/writing the file \"" << file << "\"." << endl;
|
||||
} catch(...) {
|
||||
::IoUtilities::catchIoFailure();
|
||||
cerr << "Error: An IO failure occured when reading/writing the file \"" << file << "\"." << endl;
|
||||
finalizeLog();
|
||||
cerr << " - " << Phrases::Error << "An IO failure occured when reading/writing the file \"" << file << "\"." << endl;
|
||||
}
|
||||
printNotifications(notifications, "Notifications:", args.verboseArg.isPresent());
|
||||
|
||||
|
@ -652,11 +659,11 @@ void extractField(const Argument &fieldArg, const Argument &attachmentArg, const
|
|||
}
|
||||
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;
|
||||
cerr << Phrases::Error << "Excactly one field or attachment needs to be specified." << Phrases::End;
|
||||
return;
|
||||
}
|
||||
if(!inputFilesArg.isPresent() || inputFilesArg.values().empty()) {
|
||||
cerr << "Error: No files have been specified." << endl;
|
||||
cerr << Phrases::Error << "No files have been specified." << Phrases::End;
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -688,7 +695,7 @@ void extractField(const Argument &fieldArg, const Argument &attachmentArg, const
|
|||
}
|
||||
}
|
||||
if(values.empty()) {
|
||||
cerr << " None of the specified files has a (supported) " << fieldArg.values().front() << " field." << endl;
|
||||
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) {
|
||||
|
@ -703,10 +710,10 @@ void extractField(const Argument &fieldArg, const Argument &attachmentArg, const
|
|||
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;
|
||||
cout << " - Value has been saved to \"" << path << "\"." << endl;
|
||||
} catch(...) {
|
||||
::IoUtilities::catchIoFailure();
|
||||
cerr << "Error: An IO error occured when writing the file \"" << path << "\"." << endl;
|
||||
cerr << " - " << Phrases::Error << "An IO error occured when writing the file \"" << path << "\"." << Phrases::End;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
@ -736,7 +743,7 @@ void extractField(const Argument &fieldArg, const Argument &attachmentArg, const
|
|||
}
|
||||
}
|
||||
if(attachments.empty()) {
|
||||
cerr << " None of the specified files has a (supported) attachment with the specified ID/name." << endl;
|
||||
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) {
|
||||
|
@ -751,10 +758,10 @@ void extractField(const Argument &fieldArg, const Argument &attachmentArg, const
|
|||
outputFileStream.open(path, ios_base::out | ios_base::binary);
|
||||
attachment.first->data()->copyTo(outputFileStream);
|
||||
outputFileStream.flush();
|
||||
cout << "Value has been saved to \"" << path << "\"." << endl;
|
||||
cout << " - Value has been saved to \"" << path << "\"." << endl;
|
||||
} catch(...) {
|
||||
::IoUtilities::catchIoFailure();
|
||||
cerr << "Error: An IO error occured when writing the file \"" << path << "\"." << endl;
|
||||
cerr << " - " << Phrases::Error << "An IO error occured when writing the file \"" << path << "\"." << Phrases::End;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
@ -765,10 +772,10 @@ void extractField(const Argument &fieldArg, const Argument &attachmentArg, const
|
|||
}
|
||||
|
||||
} catch(const ApplicationUtilities::Failure &) {
|
||||
cerr << "Error: A parsing failure occured when reading the file \"" << file << "\"." << endl;
|
||||
cerr << Phrases::Error << "A parsing failure occured when reading the file \"" << file << "\"." << Phrases::End;
|
||||
} catch(...) {
|
||||
::IoUtilities::catchIoFailure();
|
||||
cerr << "Error: An IO failure occured when reading the file \"" << file << "\"." << endl;
|
||||
cerr << Phrases::Error << "An IO failure occured when reading the file \"" << file << "\"." << Phrases::End;
|
||||
}
|
||||
printNotifications(inputFileInfo, "Parsing notifications:", verboseArg.isPresent());
|
||||
}
|
||||
|
|
213
tests/cli.cpp
213
tests/cli.cpp
|
@ -86,7 +86,16 @@ void CliTests::tearDown()
|
|||
template <typename StringType, bool negateErrorCond = false>
|
||||
bool testContainsSubstrings(const StringType &str, std::initializer_list<const typename StringType::value_type *> substrings)
|
||||
{
|
||||
bool res = containsSubstrings(str, substrings);
|
||||
vector<const typename StringType::value_type *> failedSubstrings;
|
||||
typename StringType::size_type currentPos = 0;
|
||||
for (const auto *substr : substrings) {
|
||||
if ((currentPos = str.find(substr, currentPos)) == StringType::npos) {
|
||||
failedSubstrings.emplace_back(substr);
|
||||
}
|
||||
currentPos += std::strlen(substr);
|
||||
}
|
||||
|
||||
bool res = failedSubstrings.empty();
|
||||
if(negateErrorCond) {
|
||||
res = !res;
|
||||
}
|
||||
|
@ -97,8 +106,8 @@ bool testContainsSubstrings(const StringType &str, std::initializer_list<const t
|
|||
cout << " - test failed: output DOES contain substrings it shouldn't\n";
|
||||
}
|
||||
cout << "Output:\n" << str;
|
||||
cout << "Substrings:\n";
|
||||
for(const auto &substr : substrings) {
|
||||
cout << "Failed substrings:\n";
|
||||
for(const auto &substr : failedSubstrings) {
|
||||
cout << substr << "\n";
|
||||
}
|
||||
}
|
||||
|
@ -268,64 +277,68 @@ void CliTests::testId3SpecificOptions()
|
|||
// verify both ID3 tags are detected
|
||||
const char *const args1[] = {"tageditor", "get", "-f", mp3File1.data(), nullptr};
|
||||
TESTUTILS_ASSERT_EXEC(args1);
|
||||
CPPUNIT_ASSERT(stdout.find("ID3v1 tag\n"
|
||||
" Title Cohesion\n"
|
||||
" Album Double Nickels On The Dime\n"
|
||||
" Artist Minutemen\n"
|
||||
" Genre Punk Rock\n"
|
||||
" Year 1984\n"
|
||||
" Comment ExactAudioCopy v0.95b4\n"
|
||||
" Track 4\n"
|
||||
"ID3v2 tag (version 2.3.0)\n"
|
||||
" Title Cohesion\n"
|
||||
" Album Double Nickels On The Dime\n"
|
||||
" Artist Minutemen\n"
|
||||
" Genre Punk Rock\n"
|
||||
" Year 1984\n"
|
||||
" Comment ExactAudioCopy v0.95b4\n"
|
||||
" Track 4/43\n"
|
||||
" Duration 00:00:00\n"
|
||||
" Encoder settings LAME 64bits version 3.99 (http://lame.sf.net)") != string::npos);
|
||||
CPPUNIT_ASSERT(testContainsSubstrings(stdout, {
|
||||
" - \e[1mID3v1 tag\e[0m\n"
|
||||
" Title Cohesion\n"
|
||||
" Album Double Nickels On The Dime\n"
|
||||
" Artist Minutemen\n"
|
||||
" Genre Punk Rock\n"
|
||||
" Year 1984\n"
|
||||
" Comment ExactAudioCopy v0.95b4\n"
|
||||
" Track 4\n",
|
||||
" - \e[1mID3v2 tag (version 2.3.0)\e[0m\n"
|
||||
" Title Cohesion\n"
|
||||
" Album Double Nickels On The Dime\n"
|
||||
" Artist Minutemen\n"
|
||||
" Genre Punk Rock\n"
|
||||
" Year 1984\n"
|
||||
" Comment ExactAudioCopy v0.95b4\n"
|
||||
" Track 4/43\n"
|
||||
" Duration 00:00:00\n"
|
||||
" Encoder settings LAME 64bits version 3.99 (http://lame.sf.net)"
|
||||
}));
|
||||
|
||||
// remove ID3v1 tag, convert ID3v2 tag to version 4
|
||||
const char *const args2[] = {"tageditor", "set", "--id3v1-usage", "never", "--id3v2-version", "4", "-f", mp3File1.data(), nullptr};
|
||||
TESTUTILS_ASSERT_EXEC(args2);
|
||||
TESTUTILS_ASSERT_EXEC(args1);
|
||||
CPPUNIT_ASSERT(stdout.find("ID3v1 tag") == string::npos);
|
||||
CPPUNIT_ASSERT(stdout.find("ID3v2 tag (version 2.4.0)\n"
|
||||
" Title Cohesion\n"
|
||||
" Album Double Nickels On The Dime\n"
|
||||
" Artist Minutemen\n"
|
||||
" Genre Punk Rock\n"
|
||||
" Year 1984\n"
|
||||
" Comment ExactAudioCopy v0.95b4\n"
|
||||
" Track 4/43\n"
|
||||
" Duration 00:00:00\n"
|
||||
" Encoder settings LAME 64bits version 3.99 (http://lame.sf.net)") != string::npos);
|
||||
CPPUNIT_ASSERT(testContainsSubstrings(stdout, {
|
||||
" - \e[1mID3v2 tag (version 2.4.0)\e[0m\n"
|
||||
" Title Cohesion\n"
|
||||
" Album Double Nickels On The Dime\n"
|
||||
" Artist Minutemen\n"
|
||||
" Genre Punk Rock\n"
|
||||
" Year 1984\n"
|
||||
" Comment ExactAudioCopy v0.95b4\n"
|
||||
" Track 4/43\n"
|
||||
" Duration 00:00:00\n"
|
||||
" Encoder settings LAME 64bits version 3.99 (http://lame.sf.net)"
|
||||
}));
|
||||
remove(mp3File1Backup.data());
|
||||
|
||||
// convert remaining ID3v2 tag to version 2, add an ID3v1 tag again and set a field with unicode char by the way
|
||||
const char *const args3[] = {"tageditor", "set", "album=Dóuble Nickels On The Dime", "--id3v1-usage", "always", "--id3v2-version", "2", "--id3-init-on-create", "-f", mp3File1.data(), nullptr};
|
||||
CPPUNIT_ASSERT_EQUAL(0, execApp(args3, stdout, stderr));
|
||||
CPPUNIT_ASSERT_EQUAL(0, execApp(args1, stdout, stderr));
|
||||
CPPUNIT_ASSERT(stdout.find("ID3v1 tag\n"
|
||||
" Title Cohesion\n"
|
||||
" Album Dóuble Nickels On The Dime\n"
|
||||
" Artist Minutemen\n"
|
||||
" Genre Punk Rock\n"
|
||||
" Year 1984\n"
|
||||
" Comment ExactAudioCopy v0.95b4\n"
|
||||
" Track 4\n"
|
||||
"ID3v2 tag (version 2.2.0)\n"
|
||||
" Title Cohesion\n"
|
||||
" Album Dóuble Nickels On The Dime\n"
|
||||
" Artist Minutemen\n"
|
||||
" Genre Punk Rock\n"
|
||||
" Year 1984\n"
|
||||
" Comment ExactAudioCopy v0.95b4\n"
|
||||
" Track 4/43\n"
|
||||
" Duration 00:00:00\n"
|
||||
" Encoder settings LAME 64bits version 3.99 (http://lame.sf.net)") != string::npos);
|
||||
CPPUNIT_ASSERT(testContainsSubstrings(stdout, {
|
||||
" - \e[1mID3v1 tag\e[0m\n"
|
||||
" Title Cohesion\n"
|
||||
" Album Dóuble Nickels On The Dime\n"
|
||||
" Artist Minutemen\n"
|
||||
" Genre Punk Rock\n"
|
||||
" Year 1984\n"
|
||||
" Comment ExactAudioCopy v0.95b4\n"
|
||||
" Track 4\n",
|
||||
" - \e[1mID3v2 tag (version 2.2.0)\e[0m\n"
|
||||
" Title Cohesion\n"
|
||||
" Album Dóuble Nickels On The Dime\n"
|
||||
" Artist Minutemen\n"
|
||||
" Genre Punk Rock\n"
|
||||
" Year 1984\n"
|
||||
" Comment ExactAudioCopy v0.95b4\n"
|
||||
" Track 4/43\n"
|
||||
" Duration 00:00:00\n"
|
||||
" Encoder settings LAME 64bits version 3.99 (http://lame.sf.net)"}));
|
||||
remove(mp3File1.data());
|
||||
remove(mp3File1Backup.data());
|
||||
}
|
||||
|
@ -411,30 +424,30 @@ void CliTests::testMultipleFiles()
|
|||
TESTUTILS_ASSERT_EXEC(args2);
|
||||
TESTUTILS_ASSERT_EXEC(args1);
|
||||
CPPUNIT_ASSERT(testContainsSubstrings(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"
|
||||
" - \e[1mMatroska tag targeting \"level 50 'album, opera, concert, movie, episode'\"\e[0m\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"
|
||||
" - \e[1mMatroska tag targeting \"level 30 'track, song, chapter'\"\e[0m\n"
|
||||
" Title test1\n"
|
||||
" Part 1",
|
||||
" - \e[1mMatroska tag targeting \"level 50 'album, opera, concert, movie, episode'\"\e[0m\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"
|
||||
" - \e[1mMatroska tag targeting \"level 30 'track, song, chapter'\"\e[0m\n"
|
||||
" Title test2\n"
|
||||
" Part 2",
|
||||
" - \e[1mMatroska tag targeting \"level 50 'album, opera, concert, movie, episode'\"\e[0m\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",
|
||||
" - \e[1mMatroska tag targeting \"level 30 'track, song, chapter'\"\e[0m\n"
|
||||
" Title test3\n"
|
||||
" Part 3"
|
||||
}));
|
||||
|
||||
// clear working copies if all tests have been
|
||||
|
@ -466,10 +479,10 @@ void CliTests::testOutputFile()
|
|||
const char *const args3[] = {"tageditor", "get", "-f", "/tmp/test1.mkv", "/tmp/test2.mkv", nullptr};
|
||||
TESTUTILS_ASSERT_EXEC(args3);
|
||||
CPPUNIT_ASSERT(testContainsSubstrings(stdout, {
|
||||
"Matroska tag targeting \"level 30 'track, song, chapter'\"\n"
|
||||
" Title test1\n",
|
||||
"Matroska tag targeting \"level 30 'track, song, chapter'\"\n"
|
||||
" Title test2\n"
|
||||
" - \e[1mMatroska tag targeting \"level 30 'track, song, chapter'\"\e[0m\n"
|
||||
" Title test1\n",
|
||||
" - \e[1mMatroska tag targeting \"level 30 'track, song, chapter'\"\e[0m\n"
|
||||
" Title test2\n"
|
||||
}));
|
||||
|
||||
remove(mkvFile1.data()), remove(mkvFile2.data());
|
||||
|
@ -586,7 +599,7 @@ void CliTests::testDisplayingInfo()
|
|||
const char *const args1[] = {"tageditor", "info", "-f", mkvFile.data(), nullptr};
|
||||
TESTUTILS_ASSERT_EXEC(args1);
|
||||
CPPUNIT_ASSERT(testContainsSubstrings(stdout, {
|
||||
" Container format: Matroska\n"
|
||||
" - \e[1mContainer format: Matroska\e[0m\n"
|
||||
" Document type matroska\n"
|
||||
" Read version 1\n"
|
||||
" Version 1\n"
|
||||
|
@ -595,7 +608,7 @@ void CliTests::testDisplayingInfo()
|
|||
" Duration 47 s 509 ms\n"
|
||||
" Tag position before data\n"
|
||||
" Index position before data\n",
|
||||
" Tracks:\n"
|
||||
" - \e[1mTracks: H.264-576p / AAC-LC-2ch\e[0m\n"
|
||||
" ID 1863976627\n"
|
||||
" Type Video\n"
|
||||
" Format Advanced Video Coding Main Profile\n"
|
||||
|
@ -615,12 +628,12 @@ void CliTests::testDisplayingInfo()
|
|||
const char *const args2[] = {"tageditor", "info", "-f", mp4File.data(), nullptr};
|
||||
TESTUTILS_ASSERT_EXEC(args2);
|
||||
CPPUNIT_ASSERT(testContainsSubstrings(stdout, {
|
||||
" Container format: MPEG-4 Part 14\n"
|
||||
" - \e[1mContainer format: MPEG-4 Part 14\e[0m\n"
|
||||
" Document type mp42\n"
|
||||
" Duration 3 min\n"
|
||||
" Creation time 2014-12-10 16:22:41\n"
|
||||
" Modification time 2014-12-10 16:22:41\n",
|
||||
" Tracks:\n"
|
||||
" - \e[1mTracks: HE-AAC-2ch\e[0m\n"
|
||||
" ID 1\n"
|
||||
" Name soun\n"
|
||||
" Type Audio\n"
|
||||
|
@ -663,7 +676,7 @@ void CliTests::testSettingTrackMetaData()
|
|||
TESTUTILS_ASSERT_EXEC(args1);
|
||||
TESTUTILS_ASSERT_EXEC(args2);
|
||||
CPPUNIT_ASSERT(testContainsSubstrings(stdout, {
|
||||
" Container format: Matroska\n"
|
||||
" - \e[1mContainer format: Matroska\e[0m\n"
|
||||
" Document type matroska\n"
|
||||
" Read version 1\n"
|
||||
" Version 1\n"
|
||||
|
@ -672,7 +685,7 @@ void CliTests::testSettingTrackMetaData()
|
|||
" Duration 47 s 509 ms\n"
|
||||
" Tag position before data\n"
|
||||
" Index position before data\n",
|
||||
" Tracks:\n"
|
||||
" - \e[1mTracks: H.264-576p / AAC-LC-2ch-ger\e[0m\n"
|
||||
" ID 1863976627\n"
|
||||
" Name video track\n"
|
||||
" Type Video\n"
|
||||
|
@ -692,22 +705,22 @@ void CliTests::testSettingTrackMetaData()
|
|||
" Labeled as default, forced"}));
|
||||
TESTUTILS_ASSERT_EXEC(args3);
|
||||
CPPUNIT_ASSERT(testContainsSubstrings(stdout, {
|
||||
"Matroska tag targeting \"level 50 'album, opera, concert, movie, episode'\"\n"
|
||||
" Title title of tag\n"
|
||||
" Artist setting tag value again\n"
|
||||
" Year 2010\n"
|
||||
" Comment Matroska Validation File 2, 100,000 timecode scale, odd aspect ratio, and CRC-32. Codecs are AVC and AAC"
|
||||
" - \e[1mMatroska tag targeting \"level 50 'album, opera, concert, movie, episode'\"\e[0m\n"
|
||||
" Title title of tag\n"
|
||||
" Artist setting tag value again\n"
|
||||
" Year 2010\n"
|
||||
" Comment Matroska Validation File 2, 100,000 timecode scale, odd aspect ratio, and CRC-32. Codecs are AVC and AAC"
|
||||
}));
|
||||
|
||||
const char *const args4[] = {"tageditor", "info", "-f", mp4File.data(), nullptr};
|
||||
TESTUTILS_ASSERT_EXEC(args4);
|
||||
CPPUNIT_ASSERT(testContainsSubstrings(stdout, {
|
||||
" Container format: MPEG-4 Part 14\n"
|
||||
" - \e[1mContainer format: MPEG-4 Part 14\e[0m\n"
|
||||
" Document type mp42\n"
|
||||
" Duration 3 min\n"
|
||||
" Creation time 2014-12-10 16:22:41\n"
|
||||
" Modification time 2014-12-10 16:22:41\n",
|
||||
" Tracks:\n"
|
||||
" - \e[1mTracks: HE-AAC-2ch-eng\e[0m\n"
|
||||
" ID 1\n"
|
||||
" Name sbr and ps\n"
|
||||
" Type Audio\n"
|
||||
|
@ -815,15 +828,15 @@ void CliTests::testFileLayoutOptions()
|
|||
|
||||
const char *const args5[] = {"tageditor", "get", "-f", mp4File2.data(), nullptr};
|
||||
TESTUTILS_ASSERT_EXEC(args5);
|
||||
CPPUNIT_ASSERT(stdout.find("MP4/iTunes tag\n"
|
||||
" Title You Shook Me All Night Long\n"
|
||||
" Album Who Made Who\n"
|
||||
" Artist ACDC\n"
|
||||
" Genre Rock\n"
|
||||
" Year 1986\n"
|
||||
" Track 2/9\n"
|
||||
" Encoder Nero AAC codec / 1.5.3.0, remuxed with Lavf57.56.100\n"
|
||||
" Encoder settings ndaudio 1.5.3.0 / -q 0.34") != string::npos);
|
||||
CPPUNIT_ASSERT(stdout.find(" - \e[1mMP4/iTunes tag\e[0m\n"
|
||||
" Title You Shook Me All Night Long\n"
|
||||
" Album Who Made Who\n"
|
||||
" Artist ACDC\n"
|
||||
" Genre Rock\n"
|
||||
" Year 1986\n"
|
||||
" Track 2/9\n"
|
||||
" Encoder Nero AAC codec / 1.5.3.0, remuxed with Lavf57.56.100\n"
|
||||
" Encoder settings ndaudio 1.5.3.0 / -q 0.34") != string::npos);
|
||||
remove((mp4File2 + ".bak").data());
|
||||
|
||||
const char *const args6[] = {"tageditor", "set", "--index-pos", "front", "--force", "-f", mp4File2.data(), nullptr};
|
||||
|
|
Loading…
Reference in New Issue