initial functional file indexer, added Qt / C++ port of Cartographer

This commit is contained in:
Joshua Strobl 2024-10-02 17:51:51 +03:00
parent fae3d30dbd
commit c52386abb4
19 changed files with 721 additions and 149 deletions

4
.gitignore vendored
View file

@ -72,10 +72,12 @@ CMakeLists.txt.user*
*.dll *.dll
*.exe *.exe
.ccls-cache/
.qt/ .qt/
.rcc/ .rcc/
bin/ bin/
build/ build*/
cmake-build-debug/
**/qmldir **/qmldir
**/meta_types **/meta_types
**/qmltypes **/qmltypes

View file

@ -17,6 +17,10 @@ qt_add_executable(koto
config/ui_prefs.cpp config/ui_prefs.cpp
datalake/indexer.cpp datalake/indexer.cpp
datalake/track.cpp datalake/track.cpp
datalake/album.cpp
datalake/artist.cpp
datalake/cartographer.cpp
datalake/cartographer.hpp
) )
ecm_add_qml_module(koto URI "com.github.joshstrobl.koto" GENERATE_PLUGIN_SOURCE) ecm_add_qml_module(koto URI "com.github.joshstrobl.koto" GENERATE_PLUGIN_SOURCE)

View file

@ -1,4 +1,5 @@
#include "config.hpp" #include "config.hpp"
#include <QDir> #include <QDir>
#include <QStandardPaths> #include <QStandardPaths>
#include <filesystem> #include <filesystem>
@ -31,7 +32,9 @@ KotoConfig::KotoConfig() {
} }
} }
KotoUiPreferences * KotoConfig::getUiPreferences() { KotoConfig::~KotoConfig() {}
KotoUiPreferences* KotoConfig::getUiPreferences() {
return this->i_uiPreferences; return this->i_uiPreferences;
} }

View file

@ -1,7 +1,17 @@
#include "library.hpp" #include "library.hpp"
#include <QDebug>
#include <string> #include <string>
KotoLibraryConfig::KotoLibraryConfig(const toml::value &v) { KotoLibraryConfig::KotoLibraryConfig(std::string name, fs::path path) {
this->i_name = name;
this->i_path = path;
qDebug() << "Library: " << this->i_name.c_str() << " at " << this->i_path.c_str();
}
KotoLibraryConfig::~KotoLibraryConfig() {}
KotoLibraryConfig::KotoLibraryConfig(const toml::value& v) {
this->i_name = toml::find<std::string>(v, "name"); this->i_name = toml::find<std::string>(v, "name");
this->i_path = toml::find<std::string>(v, "path"); this->i_path = toml::find<std::string>(v, "path");
} }

View file

@ -1,16 +1,19 @@
#pragma once #pragma once
#include "includes/toml.hpp"
#include <filesystem> #include <filesystem>
#include <string> #include <string>
#include "includes/toml.hpp"
namespace fs = std::filesystem; namespace fs = std::filesystem;
class KotoLibraryConfig { class KotoLibraryConfig {
public: public:
KotoLibraryConfig(const toml::value &v); KotoLibraryConfig(std::string name, fs::path path);
KotoLibraryConfig(const toml::value& v);
~KotoLibraryConfig(); ~KotoLibraryConfig();
std::string getName(); std::string getName();
fs::path getPath(); fs::path getPath();
private: private:
std::string i_name; std::string i_name;
fs::path i_path; fs::path i_path;

View file

@ -17,6 +17,8 @@ KotoUiPreferences::KotoUiPreferences(std::optional<toml::value> v) {
this->i_lastUsedVolume = toml::find_or<float>(uiPrefs, "last_used_volume", 0.5); this->i_lastUsedVolume = toml::find_or<float>(uiPrefs, "last_used_volume", 0.5);
} }
KotoUiPreferences::~KotoUiPreferences() {}
bool KotoUiPreferences::getAlbumInfoShowDescription() { bool KotoUiPreferences::getAlbumInfoShowDescription() {
return this->i_albumInfoShowDescription; return this->i_albumInfoShowDescription;
} }

View file

@ -1,10 +1,11 @@
#pragma once #pragma once
#include "includes/toml.hpp"
#include <optional> #include <optional>
#include <string> #include <string>
#include <string_view> #include <string_view>
#include <vector> #include <vector>
#include "includes/toml.hpp"
class KotoUiPreferences { class KotoUiPreferences {
public: public:
KotoUiPreferences(std::optional<toml::value> v); KotoUiPreferences(std::optional<toml::value> v);
@ -21,6 +22,7 @@ class KotoUiPreferences {
void setAlbumInfoShowNarrator(bool show); void setAlbumInfoShowNarrator(bool show);
void setAlbumInfoShowYear(bool show); void setAlbumInfoShowYear(bool show);
void setLastUsedVolume(float volume); void setLastUsedVolume(float volume);
private: private:
bool i_albumInfoShowDescription; bool i_albumInfoShowDescription;
bool i_albumInfoShowGenre; bool i_albumInfoShowGenre;

View file

@ -0,0 +1,83 @@
#include "structs.hpp"
KotoAlbum::KotoAlbum() {
this->uuid = QUuid::createUuid();
this->tracks = QList<KotoTrack*>();
}
KotoAlbum* KotoAlbum::fromDb() {
return new KotoAlbum();
}
KotoAlbum::~KotoAlbum() {
for (auto track : this->tracks) { delete track; }
this->tracks.clear();
}
void KotoAlbum::addTrack(KotoTrack* track) {
this->tracks.append(track);
}
QString KotoAlbum::getAlbumArtPath() {
return QString {this->album_art_path};
}
QString KotoAlbum::getDescription() {
return QString {this->description};
}
QList<QString> KotoAlbum::getGenres() {
return QList {this->genres};
}
QString KotoAlbum::getPath() {
return this->path;
}
QString KotoAlbum::getNarrator() {
return QString {this->narrator};
}
QString KotoAlbum::getTitle() {
return QString {this->title};
}
QList<KotoTrack*> KotoAlbum::getTracks() {
return QList {this->tracks};
}
int KotoAlbum::getYear() {
return this->year;
}
void KotoAlbum::removeTrack(KotoTrack* track) {
this->tracks.removeOne(track);
}
void KotoAlbum::setAlbumArtPath(QString str) {
this->album_art_path = QString {path};
}
void KotoAlbum::setDescription(QString str) {
this->description = QString {str};
}
void KotoAlbum::setGenres(QList<QString> list) {
this->genres = QList {list};
}
void KotoAlbum::setNarrator(QString str) {
this->narrator = QString {str};
}
void KotoAlbum::setPath(QString str) {
this->path = QString {str};
}
void KotoAlbum::setTitle(QString str) {
this->title = QString {str};
}
void KotoAlbum::setYear(int num) {
this->year = num;
}

View file

@ -0,0 +1,60 @@
#include "structs.hpp"
KotoArtist::KotoArtist() {
this->uuid = QUuid::createUuid();
}
KotoArtist* KotoArtist::fromDb() {
return new KotoArtist();
}
KotoArtist::~KotoArtist() {
for (auto album : this->albums) { delete album; }
for (auto track : this->tracks) { delete track; }
this->albums.clear();
this->tracks.clear();
}
void KotoArtist::addAlbum(KotoAlbum* album) {
this->albums.append(album);
}
void KotoArtist::addTrack(KotoTrack* track) {
this->tracks.append(track);
}
QList<KotoAlbum*> KotoArtist::getAlbums() {
return QList {this->albums};
}
std::optional<KotoAlbum*> KotoArtist::getAlbumByName(QString name) {
for (auto album : this->albums) {
if (album->getTitle().contains(name)) { return std::optional {album}; }
}
return std::nullopt;
}
QString KotoArtist::getName() {
return QString {this->name};
}
QList<KotoTrack*> KotoArtist::getTracks() {
return QList {this->tracks};
}
void KotoArtist::removeAlbum(KotoAlbum* album) {
this->albums.removeOne(album);
}
void KotoArtist::removeTrack(KotoTrack* track) {
this->tracks.removeOne(track);
}
void KotoArtist::setName(QString str) {
this->name = QString {str};
}
void KotoArtist::setPath(QString str) {
this->path = QString {str};
}

View file

@ -0,0 +1,45 @@
#include "cartographer.hpp"
Cartographer::Cartographer()
: i_albums(QHash<QUuid, KotoAlbum*>()),
i_artists(QHash<QUuid, KotoArtist*>()),
i_artists_by_name(QHash<QString, KotoArtist*>()),
i_tracks(QHash<QUuid, KotoTrack*>()) {}
Cartographer& Cartographer::instance() {
static Cartographer _instance;
return _instance;
}
void Cartographer::addAlbum(KotoAlbum* album) {
this->i_albums.insert(album->uuid, album);
}
void Cartographer::addArtist(KotoArtist* artist) {
this->i_artists.insert(artist->uuid, artist);
this->i_artists_by_name.insert(artist->getName(), artist);
}
void Cartographer::addTrack(KotoTrack* track) {
this->i_tracks.insert(track->uuid, track);
}
std::optional<KotoAlbum*> Cartographer::getAlbum(QUuid uuid) {
auto album = this->i_albums.value(uuid, nullptr);
return album ? std::optional {album} : std::nullopt;
}
std::optional<KotoArtist*> Cartographer::getArtist(QUuid uuid) {
auto artist = this->i_artists.value(uuid, nullptr);
return artist ? std::optional {artist} : std::nullopt;
}
std::optional<KotoArtist*> Cartographer::getArtist(QString name) {
auto artist = this->i_artists_by_name.value(name, nullptr);
return artist ? std::optional {artist} : std::nullopt;
}
std::optional<KotoTrack*> Cartographer::getTrack(QUuid uuid) {
auto track = this->i_tracks.value(uuid, nullptr);
return track ? std::optional {track} : std::nullopt;
}

View file

@ -0,0 +1,30 @@
#pragma once
#include <QHash>
#include <QString>
#include <QUuid>
#include <optional>
#include "structs.hpp"
class Cartographer {
public:
Cartographer();
static Cartographer& instance();
static Cartographer* create() { return &instance(); }
void addAlbum(KotoAlbum* album);
void addArtist(KotoArtist* artist);
void addTrack(KotoTrack* track);
std::optional<KotoAlbum*> getAlbum(QUuid uuid);
//.std::optional<KotoAlbum*> getAlbum(QString name);
std::optional<KotoArtist*> getArtist(QUuid uuid);
std::optional<KotoArtist*> getArtist(QString name);
std::optional<KotoTrack*> getTrack(QUuid uuid);
private:
QHash<QUuid, KotoAlbum*> i_albums;
QHash<QUuid, KotoArtist*> i_artists;
QHash<QString, KotoArtist*> i_artists_by_name;
QHash<QUuid, KotoTrack*> i_tracks;
};

View file

@ -1,6 +1,90 @@
#include "indexer.hpp" #include "indexer.hpp"
#include <KFileMetaData/ExtractorCollection>
#include <QDebug>
#include <QDirIterator> #include <QDirIterator>
#include <QMimeDatabase>
#include <iostream>
FileIndexer::FileIndexer(KotoLibraryConfig * config) { FileIndexer::FileIndexer(KotoLibraryConfig* config) {
this->i_root = QString {config->getPath().c_str()};
}
FileIndexer::~FileIndexer() = default;
void FileIndexer::index() {
QMimeDatabase db;
KFileMetaData::ExtractorCollection extractors;
QStringList root_dirs {this->i_root.split(QDir::separator())};
QDirIterator it {this->i_root, QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot, QDirIterator::Subdirectories};
// std::cout << "Indexing " << this->i_root.toStdString();
while (it.hasNext()) {
QString path = it.next();
QFileInfo info {path};
if (info.isDir()) {
auto diffPath = info.dir().relativeFilePath(this->i_root);
auto diffDirs = diffPath.split("..");
auto diffDirsSize = diffDirs.size() - 1;
// This is going to be an artist
if (diffDirsSize == 0) {
auto artist = new KotoArtist();
artist->setName(info.fileName());
artist->setPath(path);
this->i_artists.append(artist);
Cartographer::instance().addArtist(artist);
continue;
} else if (diffDirsSize == 1) {
auto album = new KotoAlbum();
album->setTitle(info.fileName());
auto artistDir = QDir(info.dir());
auto artistName = artistDir.dirName();
auto artistOptional = Cartographer::instance().getArtist(artistName);
if (artistOptional.has_value()) {
auto artist = artistOptional.value();
album->artist_uuid = artist->uuid;
artist->addAlbum(album);
}
Cartographer::instance().addAlbum(album);
continue;
}
}
// This is a file
QMimeType mime = db.mimeTypeForFile(info);
if (mime.name().startsWith("audio/")) {
auto extractorList = extractors.fetchExtractors(mime.name());
if (extractorList.isEmpty()) { continue; }
auto result = KFileMetaData::SimpleExtractionResult(path, mime.name(), KFileMetaData::ExtractionResult::ExtractMetaData);
extractorList.first()->extract(&result);
if (!result.types().contains(KFileMetaData::Type::Audio)) { continue; }
auto track = KotoTrack::fromMetadata(result);
track->setPath(path);
this->i_tracks.append(track);
Cartographer::instance().addTrack(track);
} else if (mime.name().startsWith("image/")) {
// This is an image, TODO add cover art to album
}
}
std::cout << "===== Summary =====" << std::endl;
for (auto artist : this->i_artists) {
std::cout << "Artist: " << artist->getName().toStdString() << std::endl;
for (auto album : artist->getAlbums()) {
std::cout << " Album: " << album->getTitle().toStdString() << std::endl;
for (auto track : album->getTracks()) { std::cout << " Track: " << track->getTitle().toStdString() << std::endl; }
}
}
} }

View file

@ -1,19 +1,25 @@
#pragma once #pragma once
#include <string> #include <string>
#include "cartographer.hpp"
#include "config/library.hpp" #include "config/library.hpp"
#include "track.hpp" #include "structs.hpp"
class FileIndexer { class FileIndexer {
public: public:
FileIndexer(KotoLibraryConfig * config); FileIndexer(KotoLibraryConfig* config);
~FileIndexer(); ~FileIndexer();
std::vector<KotoTrack *> getFiles(); QList<KotoArtist*> getArtists();
std::string getRoot(); QList<KotoTrack*> getFiles();
QString getRoot();
void index(); void index();
protected: protected:
std::vector<KotoTrack *> i_tracks; void indexDirectory(QString path, int depth);
std::string i_root; QList<KotoArtist*> i_artists;
QList<KotoTrack*> i_tracks;
QString i_root;
}; };

View file

@ -0,0 +1,125 @@
#pragma once
#include <KFileMetaData/SimpleExtractionResult>
#include <QList>
#include <QString>
#include <QUuid>
#include <filesystem>
namespace fs = std::filesystem;
class KotoArtist;
class KotoAlbum;
class KotoTrack;
class KotoArtist {
public:
KotoArtist();
static KotoArtist* fromDb();
~KotoArtist();
QUuid uuid;
void addAlbum(KotoAlbum* album);
void addTrack(KotoTrack* track);
QList<KotoAlbum*> getAlbums();
std::optional<KotoAlbum*> getAlbumByName(QString name);
QString getName();
QString getPath();
QList<KotoTrack*> getTracks();
void removeAlbum(KotoAlbum* album);
void removeTrack(KotoTrack* track);
void setName(QString str);
void setPath(QString str);
private:
QString path;
QString name;
QList<KotoAlbum*> albums;
QList<KotoTrack*> tracks;
};
class KotoAlbum {
public:
KotoAlbum();
static KotoAlbum* fromDb();
~KotoAlbum();
QUuid uuid;
QUuid artist_uuid;
QString getAlbumArtPath();
QString getDescription();
QList<QString> getGenres();
QString getNarrator();
QString getPath();
QString getTitle();
QList<KotoTrack*> getTracks();
int getYear();
void addTrack(KotoTrack* track);
void removeTrack(KotoTrack* track);
void setAlbumArtPath(QString str);
void setDescription(QString str);
void setGenres(QList<QString> list);
void setNarrator(QString str);
void setPath(QString str);
void setTitle(QString str);
void setYear(int num);
private:
QString title;
QString description;
QString narrator;
int year;
QList<QString> genres;
QList<KotoTrack*> tracks;
QString path;
QString album_art_path;
};
class KotoTrack {
public:
KotoTrack(); // No-op constructor
static KotoTrack* fromDb();
static KotoTrack* fromMetadata(const KFileMetaData::SimpleExtractionResult& metadata);
~KotoTrack();
std::optional<QUuid> album_uuid;
QUuid artist_uuid;
QUuid uuid;
int getDuration();
QStringList getGenres();
QString getLyrics();
QString getNarrator();
QString getPath();
QString getTitle();
int getTrackNumber();
int getYear();
void setAlbum(KotoAlbum* album);
void setArtist(KotoArtist* artist);
void setDiscNumber(int num);
void setDuration(int num);
void setGenres(QList<QString> list);
void setLyrics(QString str);
void setNarrator(QString str);
void setPath(QString path);
void setTitle(QString str);
void setTrackNumber(int num);
void setYear(int num);
private:
int disc_number;
int duration;
QStringList genres;
QString lyrics;
QString narrator;
QString path;
QString title;
int track_number;
int year;
};

View file

@ -0,0 +1,140 @@
#include <iostream>
#include "cartographer.hpp"
#include "structs.hpp"
KotoTrack::KotoTrack() {
this->uuid = QUuid::createUuid();
}
KotoTrack* KotoTrack::fromDb() {
return new KotoTrack();
}
KotoTrack::~KotoTrack() {}
KotoTrack* KotoTrack::fromMetadata(const KFileMetaData::SimpleExtractionResult& metadata) {
auto props = metadata.properties();
KotoTrack* track = new KotoTrack();
track->disc_number = props.value(KFileMetaData::Property::DiscNumber, 0).toInt();
track->duration = props.value(KFileMetaData::Property::Duration, 0).toInt();
QStringList genres;
for (auto v : props.values(KFileMetaData::Property::Genre)) { genres.append(v.toString()); }
track->genres = genres;
track->lyrics = props.value(KFileMetaData::Property::Lyrics).toString();
track->narrator = props.value(KFileMetaData::Property::Performer).toString();
track->title = props.value(KFileMetaData::Property::Title).toString();
track->track_number = props.value(KFileMetaData::Property::TrackNumber, 0).toInt();
track->year = props.value(KFileMetaData::Property::ReleaseYear, 0).toInt();
auto artistResult = props.value(KFileMetaData::Property::Artist);
auto artistOptional = std::optional<KotoArtist*>();
if (artistResult.isValid()) {
artistOptional = Cartographer::instance().getArtist(artistResult.toString());
if (artistOptional.has_value()) {
auto artist = artistOptional.value();
track->artist_uuid = QUuid(artist->uuid);
artist->addTrack(track);
}
}
auto albumResult = props.value(KFileMetaData::Property::Album);
if (albumResult.isValid() && artistOptional.has_value()) {
auto artist = artistOptional.value();
std::cout << "Album: " << albumResult.toString().toStdString() << std::endl;
auto albumMetaName = albumResult.toString();
auto albumOptional = artist->getAlbumByName(albumMetaName);
if (albumOptional.has_value()) {
auto album = albumOptional.value();
track->album_uuid = QUuid(album->uuid);
album->addTrack(track);
if (album->getTitle() != albumMetaName) album->setTitle(albumMetaName);
}
}
return track;
}
int KotoTrack::getDuration() {
return this->duration;
}
QList<QString> KotoTrack::getGenres() {
return QList {this->genres};
}
QString KotoTrack::getLyrics() {
return QString {this->lyrics};
}
QString KotoTrack::getNarrator() {
return QString {this->narrator};
}
QString KotoTrack::getPath() {
return QString {this->path};
}
QString KotoTrack::getTitle() {
return QString {this->title};
}
int KotoTrack::getTrackNumber() {
return this->track_number;
}
int KotoTrack::getYear() {
return this->year;
}
void KotoTrack::setAlbum(KotoAlbum* album) {
this->album_uuid = QUuid(album->uuid);
if (this->artist_uuid.isNull()) QUuid(album->artist_uuid);
}
void KotoTrack::setArtist(KotoArtist* artist) {
this->artist_uuid = QUuid(artist->uuid);
}
void KotoTrack::setDiscNumber(int num) {
this->disc_number = num;
}
void KotoTrack::setDuration(int num) {
this->duration = num;
}
void KotoTrack::setGenres(QList<QString> list) {
this->genres = QList {list};
}
void KotoTrack::setLyrics(QString str) {
this->lyrics = QString {str};
}
void KotoTrack::setNarrator(QString str) {
this->narrator = QString {str};
}
void KotoTrack::setPath(QString str) {
this->path = QString {str};
}
void KotoTrack::setTitle(QString str) {
this->title = QString {str};
}
void KotoTrack::setTrackNumber(int num) {
this->track_number = num;
}
void KotoTrack::setYear(int num) {
this->year = num;
}

View file

@ -1,26 +0,0 @@
#pragma once
#include <KFileMetaData/SimpleExtractionResult>
#include <string>
#include <vector>
class KotoTrack {
public:
KotoTrack(); // No-op constructor
static KotoTrack * fromDb();
static KotoTrack * fromMetadata(KFileMetaData::SimpleExtractionResult metadata);
~KotoTrack();
private:
std::string album;
std::string album_artist;
std::string artist;
int disc_number;
int duration;
std::vector<std::string> genres;
std::string lyrics;
std::string narrator;
std::string path;
std::string title;
int track_number;
int year;
};

View file

@ -9,18 +9,6 @@ last_used_volume = 0.5
jump_backwards_seconds = 30 jump_backwards_seconds = 30
jump_forward_seconds = 30 jump_forward_seconds = 30
[[libraries]]
name = "Audiobooks"
path = "/home/joshua/Audiobooks"
[[libraries]] [[libraries]]
name = "Music" name = "Music"
path = "/home/joshua/Music" path = "/home/joshua/Music"
[[libraries]]
name = "Streaming"
path = "/home/joshua/StreamingMusic"
[[libraries]]
name = "Podcasts"
path = "/home/joshua/Podcasts"

View file

@ -1,9 +1,12 @@
#include <QGuiApplication> #include <QGuiApplication>
#include <QQmlApplicationEngine> #include <QQmlApplicationEngine>
#include <QQuickStyle> #include <QQuickStyle>
#include <thread>
int main(int argc, char *argv[]) #include "config/library.hpp"
{ #include "datalake/indexer.hpp"
int main(int argc, char* argv[]) {
QQuickStyle::setStyle(QStringLiteral("org.kde.breeze")); QQuickStyle::setStyle(QStringLiteral("org.kde.breeze"));
QGuiApplication app(argc, argv); QGuiApplication app(argc, argv);
app.setApplicationDisplayName("Koto"); app.setApplicationDisplayName("Koto");
@ -13,9 +16,17 @@ int main(int argc, char *argv[])
engine.loadFromModule("com.github.joshstrobl.koto", "Main"); engine.loadFromModule("com.github.joshstrobl.koto", "Main");
if (engine.rootObjects().isEmpty()) { if (engine.rootObjects().isEmpty()) { return -1; }
return -1;
}
// std::thread([]() {
// auto config = KotoLibraryConfig("Music", "/home/joshua/Music");
//
// auto indexExample = FileIndexer(&config);
// indexExample.index();
// }).detach();
Cartographer::create();
auto config = KotoLibraryConfig("Music", "/home/joshua/Music");
auto indexExample = FileIndexer(&config);
indexExample.index();
return app.exec(); return app.exec();
} }