2015-09-06 20:20:00 +02:00
|
|
|
#include "./utility.h"
|
2015-04-22 19:33:53 +02:00
|
|
|
|
2015-09-06 20:20:00 +02:00
|
|
|
#include "../application/settings.h"
|
2015-04-22 19:33:53 +02:00
|
|
|
|
|
|
|
#include <tagparser/exceptions.h>
|
|
|
|
#include <tagparser/id3/id3v1tag.h>
|
|
|
|
#include <tagparser/id3/id3v2tag.h>
|
2018-03-11 18:58:50 +01:00
|
|
|
#include <tagparser/mediafileinfo.h>
|
|
|
|
#include <tagparser/signature.h>
|
|
|
|
#include <tagparser/tag.h>
|
2015-04-22 19:33:53 +02:00
|
|
|
|
2020-09-04 00:59:22 +02:00
|
|
|
#include <qtutilities/misc/compat.h>
|
|
|
|
|
2019-06-01 12:47:08 +02:00
|
|
|
#include <c++utilities/conversion/binaryconversion.h>
|
2020-05-13 18:41:56 +02:00
|
|
|
#include <c++utilities/conversion/stringconversion.h>
|
2015-04-22 19:33:53 +02:00
|
|
|
#include <c++utilities/io/path.h>
|
|
|
|
|
2018-03-11 18:58:50 +01:00
|
|
|
#include <QAbstractItemModel>
|
2018-01-31 21:04:15 +01:00
|
|
|
#include <QCoreApplication>
|
2015-04-22 19:33:53 +02:00
|
|
|
#include <QDir>
|
|
|
|
#include <QDirIterator>
|
2018-03-11 18:58:50 +01:00
|
|
|
#include <QFileInfo>
|
2015-04-22 19:33:53 +02:00
|
|
|
|
|
|
|
#include <ios>
|
|
|
|
#include <iostream>
|
2018-03-11 18:58:50 +01:00
|
|
|
#include <stdexcept>
|
2015-04-22 19:33:53 +02:00
|
|
|
|
|
|
|
using namespace std;
|
2018-03-06 23:10:13 +01:00
|
|
|
using namespace TagParser;
|
2015-04-22 19:33:53 +02:00
|
|
|
|
2018-03-11 18:58:50 +01:00
|
|
|
namespace Utility {
|
2015-04-22 19:33:53 +02:00
|
|
|
|
|
|
|
const char *textEncodingToCodecName(TagTextEncoding textEncoding)
|
|
|
|
{
|
2018-03-11 18:58:50 +01:00
|
|
|
switch (textEncoding) {
|
2015-04-22 19:33:53 +02:00
|
|
|
case TagTextEncoding::Latin1:
|
|
|
|
return "ISO 8859-1";
|
|
|
|
case TagTextEncoding::Utf8:
|
|
|
|
return "UTF-8";
|
|
|
|
case TagTextEncoding::Utf16BigEndian:
|
|
|
|
return "UTF-16BE";
|
|
|
|
case TagTextEncoding::Utf16LittleEndian:
|
|
|
|
return "UTF-16LE";
|
|
|
|
case TagTextEncoding::Unspecified:
|
|
|
|
return "ISO 8859-1"; // assumption
|
|
|
|
}
|
2018-01-31 21:04:15 +01:00
|
|
|
return nullptr;
|
2015-04-22 19:33:53 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
QString tagValueToQString(const TagValue &value)
|
|
|
|
{
|
2019-06-01 12:47:08 +02:00
|
|
|
if (value.isEmpty()) {
|
|
|
|
return QString();
|
|
|
|
}
|
|
|
|
switch (value.type()) {
|
|
|
|
case TagDataType::Text:
|
|
|
|
return dataToQString(value.dataPointer(), value.dataSize(), value.dataEncoding());
|
|
|
|
case TagDataType::Integer:
|
|
|
|
return QString::number(value.toInteger());
|
|
|
|
case TagDataType::StandardGenreIndex:
|
|
|
|
case TagDataType::TimeSpan:
|
2020-04-24 23:08:53 +02:00
|
|
|
case TagDataType::DateTime:
|
2019-06-01 12:47:08 +02:00
|
|
|
case TagDataType::PositionInSet:
|
|
|
|
return QString::fromStdString(value.toString());
|
|
|
|
default:;
|
2015-04-22 19:33:53 +02:00
|
|
|
}
|
|
|
|
return QString();
|
|
|
|
}
|
|
|
|
|
|
|
|
QString dataToQString(const char *data, size_t dataSize, TagTextEncoding encoding)
|
|
|
|
{
|
2019-06-01 12:47:08 +02:00
|
|
|
if (!data || !dataSize) {
|
|
|
|
return QString();
|
2015-04-22 19:33:53 +02:00
|
|
|
}
|
2019-06-01 12:47:08 +02:00
|
|
|
|
|
|
|
switch (encoding) {
|
|
|
|
case TagTextEncoding::Latin1:
|
|
|
|
case TagTextEncoding::Unspecified:
|
|
|
|
return QString::fromLatin1(data, static_cast<int>(dataSize));
|
|
|
|
case TagTextEncoding::Utf8:
|
|
|
|
return QString::fromUtf8(data, static_cast<int>(dataSize));
|
|
|
|
#if defined(CONVERSION_UTILITIES_BYTE_ORDER_LITTLE_ENDIAN)
|
|
|
|
case TagTextEncoding::Utf16LittleEndian:
|
|
|
|
#else
|
|
|
|
case TagTextEncoding::Utf16BigEndian:
|
|
|
|
#endif
|
2020-09-04 00:59:22 +02:00
|
|
|
return QString::fromUtf16(
|
|
|
|
reinterpret_cast<const QtUtilities::Utf16CharType *>(data), static_cast<int>(dataSize / (sizeof(ushort) / sizeof(char))));
|
2019-06-01 12:47:08 +02:00
|
|
|
default:;
|
|
|
|
}
|
|
|
|
|
2020-05-13 18:41:56 +02:00
|
|
|
const auto utf16Data = CppUtilities::convertString(textEncodingToCodecName(encoding),
|
|
|
|
#if defined(CONVERSION_UTILITIES_BYTE_ORDER_LITTLE_ENDIAN)
|
|
|
|
"UTF-16LE",
|
|
|
|
#else
|
|
|
|
"UTF-16BE",
|
|
|
|
#endif
|
|
|
|
data, dataSize, 2.0f);
|
2020-09-04 00:59:22 +02:00
|
|
|
return QString::fromUtf16(reinterpret_cast<const QtUtilities::Utf16CharType *>(utf16Data.first.get()),
|
|
|
|
static_cast<int>(utf16Data.second / (sizeof(ushort) / sizeof(char))));
|
2015-04-22 19:33:53 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
QString stringToQString(const string &value, TagTextEncoding textEncoding)
|
|
|
|
{
|
2019-06-01 12:47:08 +02:00
|
|
|
return dataToQString(value.data(), value.size(), textEncoding);
|
2015-04-22 19:33:53 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
string qstringToString(const QString &value, TagTextEncoding textEncoding)
|
|
|
|
{
|
2019-06-01 12:47:08 +02:00
|
|
|
if (value.isEmpty()) {
|
|
|
|
return string();
|
|
|
|
}
|
|
|
|
QByteArray encodedString;
|
|
|
|
switch (textEncoding) {
|
|
|
|
case TagTextEncoding::Latin1:
|
|
|
|
case TagTextEncoding::Unspecified:
|
|
|
|
encodedString = value.toLatin1();
|
|
|
|
break;
|
|
|
|
case TagTextEncoding::Utf8:
|
|
|
|
encodedString = value.toUtf8();
|
|
|
|
break;
|
|
|
|
#if defined(CONVERSION_UTILITIES_BYTE_ORDER_LITTLE_ENDIAN)
|
|
|
|
case TagTextEncoding::Utf16LittleEndian:
|
|
|
|
#else
|
|
|
|
case TagTextEncoding::Utf16BigEndian:
|
|
|
|
#endif
|
2021-03-20 21:59:49 +01:00
|
|
|
encodedString = QByteArray(reinterpret_cast<const char *>(value.utf16()),
|
|
|
|
static_cast<QByteArray::size_type>(value.size()) * static_cast<QByteArray::size_type>(sizeof(ushort) / sizeof(char)));
|
2019-06-01 12:47:08 +02:00
|
|
|
break;
|
2020-05-13 18:41:56 +02:00
|
|
|
#if defined(CONVERSION_UTILITIES_BYTE_ORDER_LITTLE_ENDIAN)
|
|
|
|
case TagTextEncoding::Utf16BigEndian: {
|
|
|
|
#else
|
|
|
|
case TagTextEncoding::Utf16LittleEndian: {
|
|
|
|
#endif
|
|
|
|
const auto utf16Data = CppUtilities::convertString(
|
|
|
|
#if defined(CONVERSION_UTILITIES_BYTE_ORDER_LITTLE_ENDIAN)
|
|
|
|
"UTF-16LE",
|
|
|
|
#else
|
|
|
|
"UTF-16BE",
|
|
|
|
#endif
|
|
|
|
textEncodingToCodecName(textEncoding), reinterpret_cast<const char *>(value.utf16()),
|
2021-03-20 21:59:49 +01:00
|
|
|
static_cast<std::size_t>(value.size()) * (sizeof(ushort) / sizeof(char)), 2.0f);
|
2020-05-13 18:41:56 +02:00
|
|
|
return string(utf16Data.first.get(), utf16Data.second);
|
2019-06-01 12:47:08 +02:00
|
|
|
}
|
2015-04-22 19:33:53 +02:00
|
|
|
}
|
2019-06-01 12:47:08 +02:00
|
|
|
return string(encodedString.data(), static_cast<string::size_type>(encodedString.size()));
|
2015-04-22 19:33:53 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
TagValue qstringToTagValue(const QString &value, TagTextEncoding textEncoding)
|
|
|
|
{
|
2015-08-07 23:48:14 +02:00
|
|
|
return value.isEmpty() ? TagValue() : TagValue(qstringToString(value, textEncoding), textEncoding);
|
2015-04-22 19:33:53 +02:00
|
|
|
}
|
|
|
|
|
2018-01-31 21:04:15 +01:00
|
|
|
QString elementPositionToQString(ElementPosition elementPosition)
|
|
|
|
{
|
2018-03-11 18:58:50 +01:00
|
|
|
switch (elementPosition) {
|
2018-01-31 21:04:15 +01:00
|
|
|
case ElementPosition::BeforeData:
|
|
|
|
return QCoreApplication::translate("Utility", "before data");
|
|
|
|
case ElementPosition::AfterData:
|
|
|
|
return QCoreApplication::translate("Utility", "after data");
|
2018-03-11 18:58:50 +01:00
|
|
|
case ElementPosition::Keep:;
|
2018-01-31 21:04:15 +01:00
|
|
|
}
|
|
|
|
return QString();
|
|
|
|
}
|
|
|
|
|
2015-04-22 19:33:53 +02:00
|
|
|
QString formatName(const QString &str, bool underscoreToWhitespace)
|
|
|
|
{
|
|
|
|
QString res;
|
2020-09-26 20:49:57 +02:00
|
|
|
res.reserve(str.size());
|
2015-04-22 19:33:53 +02:00
|
|
|
bool whitespace = true;
|
2018-03-11 18:58:50 +01:00
|
|
|
for (int i = 0, size = str.size(); i != size; ++i) {
|
2017-01-12 22:07:04 +01:00
|
|
|
const QChar current = str.at(i);
|
2018-03-11 18:58:50 +01:00
|
|
|
if (current.isSpace() || current == QChar('(') || current == QChar('[')) {
|
2015-04-22 19:33:53 +02:00
|
|
|
whitespace = true;
|
|
|
|
res += current;
|
2018-03-11 18:58:50 +01:00
|
|
|
} else if (underscoreToWhitespace && current == QChar('_')) {
|
2015-04-22 19:33:53 +02:00
|
|
|
whitespace = true;
|
|
|
|
res += ' ';
|
2018-03-11 18:58:50 +01:00
|
|
|
} else if (whitespace) {
|
|
|
|
if (i) {
|
2020-09-04 00:59:22 +02:00
|
|
|
auto rest = QtUtilities::midRef(str, i);
|
2020-09-26 20:49:57 +02:00
|
|
|
static const char *const connectingWords[]
|
|
|
|
= { "the ", "a ", "an ", "of ", "or ", "and ", "in ", "to ", "at ", "on ", "as ", "vs ", "vs. " };
|
2018-03-11 18:58:50 +01:00
|
|
|
for (const char *word : connectingWords) {
|
|
|
|
if (rest.startsWith(QLatin1String(word), Qt::CaseInsensitive)) {
|
2015-04-22 19:33:53 +02:00
|
|
|
res += current.toLower();
|
|
|
|
whitespace = false;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2018-03-11 18:58:50 +01:00
|
|
|
if (whitespace) {
|
2015-04-22 19:33:53 +02:00
|
|
|
res += current.toUpper();
|
|
|
|
whitespace = false;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
res += current.toLower();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
|
|
|
QString fixUmlauts(const QString &str)
|
|
|
|
{
|
2015-08-07 23:48:14 +02:00
|
|
|
auto words = str.split(QChar(' '));
|
2018-03-11 18:58:50 +01:00
|
|
|
static const QLatin1String exceptions[] = { QLatin1String("reggae"), QLatin1String("blues"), QLatin1String("auer"), QLatin1String("aues"),
|
|
|
|
QLatin1String("manuel"), QLatin1String("duet"), QLatin1String("neue"), QLatin1String("prologue") };
|
|
|
|
static const QLatin1String pairs[6][2] = { { QLatin1String("ae"), QLatin1String("\xe4") }, { QLatin1String("ue"), QLatin1String("\xfc") },
|
|
|
|
{ QLatin1String("oe"), QLatin1String("\xf6") }, { QLatin1String("Ae"), QLatin1String("\xc4") },
|
|
|
|
{ QLatin1String("Ue"), QLatin1String("\xdc") }, { QLatin1String("Oe"), QLatin1String("\xd6") } };
|
|
|
|
for (auto &word : words) {
|
2015-08-07 23:48:14 +02:00
|
|
|
// preserve words containing any of the exceptions
|
2018-03-11 18:58:50 +01:00
|
|
|
for (const auto &exception : exceptions) {
|
|
|
|
if (word.contains(exception, Qt::CaseInsensitive)) {
|
2015-08-07 23:48:14 +02:00
|
|
|
goto continueOuterLoop;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// fix all umlauts
|
2018-03-11 18:58:50 +01:00
|
|
|
for (const auto *pair : pairs) {
|
2015-08-07 23:48:14 +02:00
|
|
|
word = word.replace(pair[0], pair[1], Qt::CaseSensitive);
|
|
|
|
}
|
2018-03-11 18:58:50 +01:00
|
|
|
continueOuterLoop:;
|
2015-04-22 19:33:53 +02:00
|
|
|
}
|
2015-08-07 23:48:14 +02:00
|
|
|
return words.join(' ');
|
2015-04-22 19:33:53 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
void parseFileName(const QString &fileName, QString &title, int &trackNumber)
|
|
|
|
{
|
|
|
|
title = fileName.trimmed();
|
|
|
|
trackNumber = 0;
|
|
|
|
int lastPoint = title.lastIndexOf(QChar('.'));
|
2018-03-11 18:58:50 +01:00
|
|
|
if (lastPoint > 0) {
|
2015-04-22 19:33:53 +02:00
|
|
|
title.truncate(lastPoint);
|
2018-03-11 18:58:50 +01:00
|
|
|
} else if (lastPoint == 0) {
|
2015-04-22 19:33:53 +02:00
|
|
|
title.remove(0, 1);
|
|
|
|
}
|
2018-03-11 18:58:50 +01:00
|
|
|
static const QLatin1String delims[] = { QLatin1String(" - "), QLatin1String(", "), QLatin1String("-"), QLatin1String(" ") };
|
|
|
|
for (const auto &delim : delims) {
|
2015-04-22 19:33:53 +02:00
|
|
|
int lastDelimIndex = 0;
|
|
|
|
int delimIndex = title.indexOf(delim);
|
2018-03-11 18:58:50 +01:00
|
|
|
while (delimIndex > lastDelimIndex) {
|
2015-04-22 19:33:53 +02:00
|
|
|
bool ok = false;
|
2020-09-04 00:59:22 +02:00
|
|
|
trackNumber = QtUtilities::midRef(title, lastDelimIndex, delimIndex - lastDelimIndex).toInt(&ok);
|
2018-03-11 18:58:50 +01:00
|
|
|
if (ok) {
|
2015-04-22 19:33:53 +02:00
|
|
|
int titleStart = delimIndex + delim.size();
|
2021-03-20 21:59:49 +01:00
|
|
|
for (const auto &delim2 : delims) {
|
|
|
|
if (QtUtilities::midRef(title, titleStart).startsWith(delim2)) {
|
|
|
|
titleStart += delim2.size();
|
2015-04-22 19:33:53 +02:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
title.remove(0, titleStart);
|
2015-10-07 23:50:14 +02:00
|
|
|
} else {
|
|
|
|
delimIndex = title.indexOf(delim, lastDelimIndex = delimIndex + delim.size());
|
2015-04-22 19:33:53 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
QString printModel(QAbstractItemModel *model)
|
|
|
|
{
|
2015-08-07 23:48:14 +02:00
|
|
|
const auto index = model->index(0, 0);
|
2015-04-22 19:33:53 +02:00
|
|
|
QString res;
|
|
|
|
printModelIndex(index, res, 0);
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
|
|
|
void printModelIndex(const QModelIndex &index, QString &res, int level)
|
|
|
|
{
|
2018-03-11 18:58:50 +01:00
|
|
|
if (index.isValid()) {
|
2015-08-07 23:48:14 +02:00
|
|
|
const auto data = index.data().toString();
|
2018-03-11 18:58:50 +01:00
|
|
|
if (!data.isEmpty()) {
|
|
|
|
switch (index.column()) {
|
2015-04-22 19:33:53 +02:00
|
|
|
case 0:
|
2018-03-11 18:58:50 +01:00
|
|
|
for (int i = 0; i < level; ++i) {
|
2015-04-22 19:33:53 +02:00
|
|
|
res += "\t";
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
res += "\t";
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
res += data;
|
|
|
|
}
|
2015-08-07 23:48:14 +02:00
|
|
|
const auto nextInCol = index.sibling(index.row(), index.column() + 1);
|
2019-05-04 22:17:28 +02:00
|
|
|
const auto child = index.model()->index(0, 0, index);
|
2015-08-07 23:48:14 +02:00
|
|
|
const auto next = index.sibling(index.row() + 1, 0);
|
2018-03-11 18:58:50 +01:00
|
|
|
if (nextInCol.isValid()) {
|
2015-04-22 19:33:53 +02:00
|
|
|
printModelIndex(nextInCol, res, level);
|
|
|
|
} else {
|
|
|
|
res += "\n";
|
|
|
|
}
|
2018-03-11 18:58:50 +01:00
|
|
|
if (index.column() == 0) {
|
2015-04-22 19:33:53 +02:00
|
|
|
printModelIndex(child, res, level + 1);
|
|
|
|
printModelIndex(next, res, level);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-03-11 18:58:50 +01:00
|
|
|
} // namespace Utility
|