Skip to content

Commit

Permalink
Detect MIME type and deduce expected file extension and type
Browse files Browse the repository at this point in the history
Checking only the actual file extension is not sufficient.

Fixes: https://bugs.launchpad.net/mixxx/+bug/1445885
  • Loading branch information
uklotzde committed Oct 5, 2021
1 parent e086cdd commit 7b70a59
Show file tree
Hide file tree
Showing 6 changed files with 97 additions and 21 deletions.
49 changes: 45 additions & 4 deletions src/sources/soundsource.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
#include "sources/soundsource.h"

#include <QMimeDatabase>
#include <QMimeType>

#include "util/logger.h"

namespace mixxx {
Expand All @@ -8,7 +11,7 @@ namespace {

const Logger kLogger("SoundSource");

inline QUrl validateUrl(QUrl url) {
inline QUrl validateLocalFileUrl(QUrl url) {
DEBUG_ASSERT(url.isValid());
VERIFY_OR_DEBUG_ASSERT(url.isLocalFile()) {
kLogger.warning()
Expand All @@ -20,12 +23,50 @@ inline QUrl validateUrl(QUrl url) {

} // anonymous namespace

/*static*/ QString SoundSource::getFileExtensionFromUrl(const QUrl& url) {
return validateUrl(url).toString().section(".", -1).toLower().trimmed();
//static
QString SoundSource::getTypeFromUrl(const QUrl& url) {
const QString filePath = validateLocalFileUrl(url).toLocalFile();
return getTypeFromFile(filePath);
}

//static
QString SoundSource::getTypeFromFile(const QFileInfo& fileInfo) {
const QString fileSuffix = fileInfo.suffix();
DEBUG_ASSERT(!fileSuffix.isEmpty() || fileSuffix == QString{});
const QMimeType mimeType = QMimeDatabase().mimeTypeForFile(fileInfo);
if (!mimeType.isValid()) {
qWarning()
<< "Unknown MIME type for file" << fileInfo.filePath();
return fileSuffix;
}
const QString preferredSuffix = mimeType.preferredSuffix();
if (preferredSuffix.isEmpty()) {
DEBUG_ASSERT(mimeType.suffixes().isEmpty());
qInfo()
<< "MIME type" << mimeType
<< "has no preferred suffix";
return fileSuffix;
}
if (fileSuffix == preferredSuffix || mimeType.suffixes().contains(fileSuffix)) {
return fileSuffix;
}
if (fileSuffix.isEmpty()) {
qWarning()
<< "Using type" << preferredSuffix
<< "according to the detected MIME type" << mimeType
<< "of file" << fileInfo.filePath();
} else {
qWarning()
<< "Using type" << preferredSuffix
<< "instead of" << fileSuffix
<< "according to the detected MIME type" << mimeType
<< "of file" << fileInfo.filePath();
}
return preferredSuffix;
}

SoundSource::SoundSource(const QUrl& url, const QString& type)
: AudioSource(validateUrl(url)),
: AudioSource(validateLocalFileUrl(url)),
MetadataSourceTagLib(getLocalFileName()),
m_type(type) {
}
Expand Down
16 changes: 12 additions & 4 deletions src/sources/soundsource.h
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
#pragma once

#include <QDebug>
#include <QFileInfo>

#include "sources/audiosource.h"
#include "sources/metadatasourcetaglib.h"

#include "util/assert.h"

namespace mixxx {
Expand All @@ -15,8 +14,17 @@ class SoundSource
: public AudioSource,
public MetadataSourceTagLib {
public:
static QString getFileExtensionFromUrl(const QUrl& url);
/// Determine the type from an URL.
///
/// Only local file URLs are supported.
static QString getTypeFromUrl(const QUrl& url);

/// Determine the type from a (local) file.
static QString getTypeFromFile(const QFileInfo& fileInfo);

/// The type of the source.
///
/// The type equals the preferred suffix of the content's MIME type.
QString getType() const {
return m_type;
}
Expand All @@ -25,7 +33,7 @@ class SoundSource
// If no type is provided the file extension of the file referred
// by the URL will be used as the type of the SoundSource.
explicit SoundSource(const QUrl& url)
: SoundSource(url, getFileExtensionFromUrl(url)) {
: SoundSource(url, getTypeFromUrl(url)) {
}
SoundSource(const QUrl& url, const QString& type);

Expand Down
16 changes: 8 additions & 8 deletions src/sources/soundsourcemodplug.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,20 +32,20 @@ const QStringList kSupportedFileExtensions = {
constexpr SINT kChunkSizeInBytes = SINT(1) << 19;

QString getModPlugTypeFromUrl(const QUrl& url) {
const QString fileExtension(SoundSource::getFileExtensionFromUrl(url));
if (fileExtension == "mod") {
const QString fileType = SoundSource::getTypeFromUrl(url);
if (fileType == "mod") {
return "Protracker";
} else if (fileExtension == "med") {
} else if (fileType == "med") {
return "OctaMed";
} else if (fileExtension == "okt") {
} else if (fileType == "okt") {
return "Oktalyzer";
} else if (fileExtension == "s3m") {
} else if (fileType == "s3m") {
return "Scream Tracker 3";
} else if (fileExtension == "stm") {
} else if (fileType == "stm") {
return "Scream Tracker";
} else if (fileExtension == "xm") {
} else if (fileType == "xm") {
return "FastTracker2";
} else if (fileExtension == "it") {
} else if (fileType == "it") {
return "Impulse Tracker";
} else {
return "Module";
Expand Down
8 changes: 4 additions & 4 deletions src/sources/soundsourceproxy.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -288,17 +288,17 @@ SoundSourceProxy::allProviderRegistrationsForUrl(
// silently ignore empty URLs
return {};
}
const QString fileExtension =
mixxx::SoundSource::getFileExtensionFromUrl(url);
if (fileExtension.isEmpty()) {
const QString fileType =
mixxx::SoundSource::getTypeFromUrl(url);
if (fileType.isEmpty()) {
kLogger.warning()
<< "Unknown file type:"
<< url.toString();
return {};
}
const auto providerRegistrations =
allProviderRegistrationsForFileExtension(
fileExtension);
fileType);
if (providerRegistrations.isEmpty()) {
kLogger.warning()
<< "Unsupported file type:"
Expand Down
27 changes: 27 additions & 0 deletions src/test/soundproxy_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -708,3 +708,30 @@ TEST_F(SoundSourceProxyTest, regressionTestCachingReaderChunkJumpForward) {
}
}
}

TEST_F(SoundSourceProxyTest, getTypeFromFile) {
// Generate file names for the temporary file
const QString filePathWithoutSuffix =
mixxxtest::generateTemporaryFileName("file_with_no_file_suffix");
const QString filePathWithUnknownSuffix =
mixxxtest::generateTemporaryFileName("file_with.unknown_suffix");
const QString filePathWithWrongSuffix =
mixxxtest::generateTemporaryFileName("file_with_wrong_suffix.wav");

// Create the temporary files by copying an existing file
const QString validFilePath = kTestDir.absoluteFilePath(QStringLiteral("empty.mp3"));
mixxxtest::copyFile(validFilePath, filePathWithoutSuffix);
mixxxtest::copyFile(validFilePath, filePathWithUnknownSuffix);
mixxxtest::copyFile(validFilePath, filePathWithWrongSuffix);

ASSERT_STREQ(qPrintable("mp3"), qPrintable(mixxx::SoundSource::getTypeFromFile(validFilePath)));
EXPECT_STREQ(qPrintable("mp3"),
qPrintable(mixxx::SoundSource::getTypeFromFile(
filePathWithoutSuffix)));
EXPECT_STREQ(qPrintable("mp3"),
qPrintable(mixxx::SoundSource::getTypeFromFile(
filePathWithUnknownSuffix)));
EXPECT_STREQ(qPrintable("mp3"),
qPrintable(mixxx::SoundSource::getTypeFromFile(
filePathWithWrongSuffix)));
}
2 changes: 1 addition & 1 deletion src/track/serato/tags.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ namespace {

QString getPrimaryDecoderNameForFilePath(const QString& filePath) {
const QString fileExtension =
mixxx::SoundSource::getFileExtensionFromUrl(QUrl::fromLocalFile(filePath));
mixxx::SoundSource::getTypeFromFile(filePath);
const mixxx::SoundSourceProviderPointer pPrimaryProvider =
SoundSourceProxy::getPrimaryProviderForFileExtension(fileExtension);
if (pPrimaryProvider) {
Expand Down

0 comments on commit 7b70a59

Please sign in to comment.