initial functional file indexer, added Qt / C++ port of Cartographer
This commit is contained in:
parent
fae3d30dbd
commit
c52386abb4
19 changed files with 721 additions and 149 deletions
83
desktop/datalake/album.cpp
Normal file
83
desktop/datalake/album.cpp
Normal 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;
|
||||
}
|
60
desktop/datalake/artist.cpp
Normal file
60
desktop/datalake/artist.cpp
Normal 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};
|
||||
}
|
45
desktop/datalake/cartographer.cpp
Normal file
45
desktop/datalake/cartographer.cpp
Normal 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;
|
||||
}
|
30
desktop/datalake/cartographer.hpp
Normal file
30
desktop/datalake/cartographer.hpp
Normal 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;
|
||||
};
|
|
@ -1,6 +1,90 @@
|
|||
#include "indexer.hpp"
|
||||
|
||||
#include <KFileMetaData/ExtractorCollection>
|
||||
#include <QDebug>
|
||||
#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; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,19 +1,25 @@
|
|||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "cartographer.hpp"
|
||||
#include "config/library.hpp"
|
||||
#include "track.hpp"
|
||||
#include "structs.hpp"
|
||||
|
||||
class FileIndexer {
|
||||
public:
|
||||
FileIndexer(KotoLibraryConfig * config);
|
||||
~FileIndexer();
|
||||
public:
|
||||
FileIndexer(KotoLibraryConfig* config);
|
||||
~FileIndexer();
|
||||
|
||||
std::vector<KotoTrack *> getFiles();
|
||||
std::string getRoot();
|
||||
QList<KotoArtist*> getArtists();
|
||||
QList<KotoTrack*> getFiles();
|
||||
QString getRoot();
|
||||
|
||||
void index();
|
||||
protected:
|
||||
std::vector<KotoTrack *> i_tracks;
|
||||
std::string i_root;
|
||||
void index();
|
||||
|
||||
protected:
|
||||
void indexDirectory(QString path, int depth);
|
||||
QList<KotoArtist*> i_artists;
|
||||
QList<KotoTrack*> i_tracks;
|
||||
QString i_root;
|
||||
};
|
||||
|
|
125
desktop/datalake/structs.hpp
Normal file
125
desktop/datalake/structs.hpp
Normal 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;
|
||||
};
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
};
|
Loading…
Add table
Add a link
Reference in a new issue