Refactor tagging and add library scanning
- Replace `Tag` class with utility helpers for hashing and file type parsing - Change `Track.artwork` from `std::string` to `TagLib::VariantMap` - Add `importArtwork`, rename `importFolder` to `scanFolder` - Add `scanLibrary`, `updateLibrary`, `editMetadata`, and `seek` methods to `Player` hierarchy - Remove obsolete `Tag.h`/`Tag.cpp` files
This commit is contained in:
@@ -31,7 +31,7 @@ struct Track {
|
|||||||
unsigned int length;
|
unsigned int length;
|
||||||
unsigned int year;
|
unsigned int year;
|
||||||
std::string filePath;
|
std::string filePath;
|
||||||
std::string artwork;
|
TagLib::VariantMap artwork;
|
||||||
TagLib::String genre;
|
TagLib::String genre;
|
||||||
std::string format;
|
std::string format;
|
||||||
uint64_t hash;
|
uint64_t hash;
|
||||||
|
|||||||
+3
-1
@@ -22,10 +22,12 @@ class Database {
|
|||||||
std::optional<int> getArtistId( const std::string& artistName );
|
std::optional<int> getArtistId( const std::string& artistName );
|
||||||
std::optional<int> getAlbumId( const std::string& albumTitle );
|
std::optional<int> getAlbumId( const std::string& albumTitle );
|
||||||
std::optional<int> getGenreId( const std::string& genre );
|
std::optional<int> getGenreId( const std::string& genre );
|
||||||
|
|
||||||
void addTrack( Track t);
|
void addTrack( Track t);
|
||||||
void addArtist( const std::string& artistName );
|
void addArtist( const std::string& artistName );
|
||||||
void addAlbum( Track t);
|
void addAlbum( Track t);
|
||||||
void addGenre( std::string& genre );
|
void addGenre( const std::string& genre );
|
||||||
|
|
||||||
void setActive( bool active );
|
void setActive( bool active );
|
||||||
void updateFilePath( const std::string& filePath );
|
void updateFilePath( const std::string& filePath );
|
||||||
|
|
||||||
|
|||||||
+2
-1
@@ -11,8 +11,9 @@ namespace fs = std::filesystem;
|
|||||||
class File {
|
class File {
|
||||||
public:
|
public:
|
||||||
Track importFile( const std::string& filePath );
|
Track importFile( const std::string& filePath );
|
||||||
void importFolder( const std::string& folderPath, std::vector<Track>& tracks );
|
void scanFolder( const std::string& folderPath, std::vector<Track>& tracks );
|
||||||
void readFileTag( Track& t );
|
void readFileTag( Track& t );
|
||||||
|
TagLib::VariantMap importArtwork( Track& t );
|
||||||
std::string parseFiletype( const std::string& path );
|
std::string parseFiletype( const std::string& path );
|
||||||
void updateLibrary();
|
void updateLibrary();
|
||||||
uint64_t hashString( Track t );
|
uint64_t hashString( Track t );
|
||||||
|
|||||||
@@ -17,6 +17,9 @@ class Player {
|
|||||||
|
|
||||||
virtual void initPlayer ();
|
virtual void initPlayer ();
|
||||||
virtual void loadFile ( Track track );
|
virtual void loadFile ( Track track );
|
||||||
|
virtual void scanLibrary();
|
||||||
|
virtual void updateLibrary();
|
||||||
|
virtual void editMetadata();
|
||||||
|
|
||||||
// Transport Control
|
// Transport Control
|
||||||
virtual void play ();
|
virtual void play ();
|
||||||
@@ -24,6 +27,7 @@ class Player {
|
|||||||
virtual void pause ();
|
virtual void pause ();
|
||||||
virtual void previous ();
|
virtual void previous ();
|
||||||
virtual void next ();
|
virtual void next ();
|
||||||
|
virtual void seek();
|
||||||
|
|
||||||
virtual ma_uint64 getPlayheadPosition ();
|
virtual ma_uint64 getPlayheadPosition ();
|
||||||
virtual void setPlayheadPosition () const;
|
virtual void setPlayheadPosition () const;
|
||||||
@@ -44,6 +48,7 @@ class Player {
|
|||||||
protected:
|
protected:
|
||||||
PlaybackEngine PE_;
|
PlaybackEngine PE_;
|
||||||
trackVec queue_;
|
trackVec queue_;
|
||||||
|
std::vector<Track> library;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::string artist_;
|
std::string artist_;
|
||||||
|
|||||||
+17
-1
@@ -1,17 +1,33 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "Player.h"
|
#include "Player.h"
|
||||||
|
#include "Database.h"
|
||||||
|
|
||||||
|
using db = Database;
|
||||||
|
|
||||||
class PlayerLocal : public Player {
|
class PlayerLocal : public Player {
|
||||||
public:
|
public:
|
||||||
|
// Library Controls
|
||||||
|
void loadFile ( Track track ) override;
|
||||||
|
void scanLibrary() override;
|
||||||
|
void updateLibrary() override;
|
||||||
|
void editMetadata() override;
|
||||||
|
|
||||||
|
// Transport Controls
|
||||||
|
void play() override;
|
||||||
|
void pause() override;
|
||||||
|
void stop() override;
|
||||||
void next() override;
|
void next() override;
|
||||||
void previous() override;
|
void previous() override;
|
||||||
|
void seek() override;
|
||||||
ma_uint64 getPlayheadPosition () override;
|
ma_uint64 getPlayheadPosition () override;
|
||||||
void loadFile ( Track track ) override;
|
|
||||||
|
// Queue
|
||||||
void addToQueue( Track& track );
|
void addToQueue( Track& track );
|
||||||
void playQueue();
|
void playQueue();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
unsigned int currentTrack_;
|
unsigned int currentTrack_;
|
||||||
|
db db_;
|
||||||
|
std::string musicFolder_ = "data/";
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,26 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "containers.h"
|
|
||||||
//#include "file.h"
|
|
||||||
|
|
||||||
#include <filesystem>
|
|
||||||
#include <memory>
|
|
||||||
#include <taglib/tstring.h>
|
|
||||||
#include <taglib/fileref.h>
|
|
||||||
#include <taglib/tvariant.h>
|
|
||||||
|
|
||||||
|
|
||||||
namespace fs = std::filesystem;
|
|
||||||
|
|
||||||
class tag {
|
|
||||||
public:
|
|
||||||
void readtag( TagLib::filename filename );
|
|
||||||
metadata getmetadata() const;
|
|
||||||
|
|
||||||
const std::string& getfilepath() const;
|
|
||||||
const auto getartwork() const;
|
|
||||||
private:
|
|
||||||
metadata metadata_;
|
|
||||||
std::string filepath_;
|
|
||||||
fs::path musicdir = music_folder;
|
|
||||||
};
|
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Containers.h"
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
namespace utility {
|
||||||
|
|
||||||
|
uint64_t hashString( const std::string& s );
|
||||||
|
std::string parseFiletype( const std::string& filePath );
|
||||||
|
|
||||||
|
}
|
||||||
+43
-23
@@ -1,11 +1,17 @@
|
|||||||
#include "../include/File.h"
|
#include "../include/File.h"
|
||||||
#include "../include/Database.h"
|
#include "../include/Database.h"
|
||||||
#include "../include/miniaudio.h"
|
#include "../include/miniaudio.h"
|
||||||
|
#include "../include/utility.h"
|
||||||
|
|
||||||
|
|
||||||
#include <cstdint>
|
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
|
#include <fstream>
|
||||||
|
#include <iostream>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <taglib/tag.h>
|
||||||
|
#include <taglib/tbytevector.h>
|
||||||
|
#include <taglib/tstring.h>
|
||||||
|
#include <taglib/tvariant.h>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <set>
|
#include <set>
|
||||||
#include <taglib/fileref.h>
|
#include <taglib/fileref.h>
|
||||||
@@ -15,28 +21,18 @@ namespace fs = std::filesystem;
|
|||||||
Track File::importFile( const std::string& filePath ) {
|
Track File::importFile( const std::string& filePath ) {
|
||||||
Track t;
|
Track t;
|
||||||
t.filePath = filePath;
|
t.filePath = filePath;
|
||||||
t.artwork = "./cover.png";
|
t.artwork = importArtwork( t );
|
||||||
readFileTag( t );
|
readFileTag( t );
|
||||||
return t;
|
return t;
|
||||||
}
|
}
|
||||||
|
|
||||||
void File::importFolder(const std::string& folderPath, std::vector<Track>& tracks ) {
|
void File::scanFolder(const std::string& folderPath, std::vector<Track>& tracks ) {
|
||||||
for ( const auto& audioFile : fs::directory_iterator{ folderPath }) {
|
for ( const auto& audioFile : fs::directory_iterator{ folderPath }) {
|
||||||
Track t = importFile( audioFile.path());
|
Track t = importFile( audioFile.path());
|
||||||
tracks.push_back( t );
|
tracks.push_back( t );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
uint64_t hashString( Track t ) {
|
|
||||||
std::string s = (t.artist + t.title + t.album).toCString();
|
|
||||||
uint64_t hash = 14695981039346656037ULL;
|
|
||||||
for ( char c : s ) {
|
|
||||||
hash ^= static_cast<uint8_t> ( c );
|
|
||||||
hash *= 1099511628211ULL;
|
|
||||||
}
|
|
||||||
return hash;
|
|
||||||
}
|
|
||||||
|
|
||||||
void File::readFileTag( Track& t ) {
|
void File::readFileTag( Track& t ) {
|
||||||
TagLib::FileRef f( reinterpret_cast<const char*>(&t.filePath ));
|
TagLib::FileRef f( reinterpret_cast<const char*>(&t.filePath ));
|
||||||
if ( !f.isNull() ) {
|
if ( !f.isNull() ) {
|
||||||
@@ -46,19 +42,12 @@ void File::readFileTag( Track& t ) {
|
|||||||
t.trackNr = ( f.tag()->track() );
|
t.trackNr = ( f.tag()->track() );
|
||||||
t.year = ( f.tag()->track() );
|
t.year = ( f.tag()->track() );
|
||||||
t.genre = ( f.tag()->genre().isEmpty() ) ? "" : ( f.tag()->genre() );
|
t.genre = ( f.tag()->genre().isEmpty() ) ? "" : ( f.tag()->genre() );
|
||||||
t.format = parseFiletype( t.filePath );
|
t.format = utility::parseFiletype( t.filePath );
|
||||||
t.length = ( f.audioProperties()->lengthInSeconds() ) ? f.audioProperties()->lengthInSeconds() : 0;
|
t.length = ( f.audioProperties()->lengthInSeconds() ) ? f.audioProperties()->lengthInSeconds() : 0;
|
||||||
t.hash = hashString( t );
|
t.hash = utility::hashString( (t.artist + t.title + t.album).toCString() );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string File::parseFiletype( const std::string& path ){
|
|
||||||
|
|
||||||
size_t length = path.size();
|
|
||||||
size_t dot = path.find_last_of( ".");
|
|
||||||
|
|
||||||
return path.substr( dot + 1, length - dot );
|
|
||||||
}
|
|
||||||
|
|
||||||
void File::updateLibrary() {
|
void File::updateLibrary() {
|
||||||
Database database( "sound_serve.db");
|
Database database( "sound_serve.db");
|
||||||
@@ -67,7 +56,7 @@ void File::updateLibrary() {
|
|||||||
std::set<const char*> db_filePaths;
|
std::set<const char*> db_filePaths;
|
||||||
std::set<unsigned long> hashes;
|
std::set<unsigned long> hashes;
|
||||||
|
|
||||||
importFolder( musicFolder_, musicFolder);
|
scanFolder( musicFolder_, musicFolder);
|
||||||
database.fetchFilePaths();
|
database.fetchFilePaths();
|
||||||
database.fetchHashes( hashes );
|
database.fetchHashes( hashes );
|
||||||
|
|
||||||
@@ -87,3 +76,34 @@ void File::updateLibrary() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TagLib::VariantMap File::importArtwork( Track& t ) {
|
||||||
|
auto path = t.filePath;
|
||||||
|
TagLib::String mimeType;
|
||||||
|
TagLib::ByteVector imgData;
|
||||||
|
TagLib::VariantMap picture;
|
||||||
|
|
||||||
|
std::ifstream in(path, std::ios::binary);
|
||||||
|
if (!in) throw std::runtime_error("Could not open image: " + path);
|
||||||
|
|
||||||
|
std::vector<char> bytes{
|
||||||
|
std::istreambuf_iterator<char>(in),
|
||||||
|
std::istreambuf_iterator<char>()
|
||||||
|
};
|
||||||
|
|
||||||
|
imgData = (TagLib::ByteVector( bytes.data(), static_cast<unsigned int>( bytes.size()) ));
|
||||||
|
|
||||||
|
std::string ext = utility::parseFiletype( path );
|
||||||
|
|
||||||
|
if (ext == "jpg" || ext == "jpeg") mimeType = "image/jpeg";
|
||||||
|
if (ext == "png") mimeType = "image/png";
|
||||||
|
|
||||||
|
throw std::runtime_error("Only JPEG/PNG cover art is handled here");
|
||||||
|
|
||||||
|
picture["data"] = imgData;
|
||||||
|
picture["pictureType"] = TagLib::String( "Front Cover" );
|
||||||
|
picture["mimeType"] = mimeType;
|
||||||
|
picture["description"] = TagLib::String( "Cover" );
|
||||||
|
|
||||||
|
return picture;
|
||||||
|
}
|
||||||
|
|||||||
+21
-1
@@ -1,5 +1,10 @@
|
|||||||
#include "../include/PlayerLocal.h"
|
#include "../include/PlayerLocal.h"
|
||||||
#include "../include/Player.h"
|
#include "../include/Player.h"
|
||||||
|
#include "../include/File.h"
|
||||||
|
#include "../include/Database.h"
|
||||||
|
#include "../include/Containers.h"
|
||||||
|
|
||||||
|
using db = Database;
|
||||||
|
|
||||||
void Player::play () { PE_.startPlayback(); }
|
void Player::play () { PE_.startPlayback(); }
|
||||||
void Player::stop () { PE_.stopPlayback(); }
|
void Player::stop () { PE_.stopPlayback(); }
|
||||||
@@ -20,7 +25,7 @@ void PlayerLocal::previous() {
|
|||||||
ma_uint64 Player::getPlayheadPosition () { return PE_.getPosition(); };
|
ma_uint64 Player::getPlayheadPosition () { return PE_.getPosition(); };
|
||||||
|
|
||||||
void PlayerLocal::loadFile ( Track track ) {
|
void PlayerLocal::loadFile ( Track track ) {
|
||||||
const char *fp = track.filePath;
|
const char *fp = track.filePath.c_str();
|
||||||
PE_.initDecoder ( fp );
|
PE_.initDecoder ( fp );
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -35,3 +40,18 @@ void::PlayerLocal::playQueue() {
|
|||||||
++currentTrack_;
|
++currentTrack_;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void PlayerLocal::scanLibrary(){
|
||||||
|
File f;
|
||||||
|
Track t;
|
||||||
|
std::vector<Track> tracks;
|
||||||
|
|
||||||
|
f.scanFolder( musicFolder_, tracks );
|
||||||
|
|
||||||
|
for ( auto t : tracks ) {
|
||||||
|
db_.addArtist( t.artist.toCString() );
|
||||||
|
db_.addGenre( t.genre.toCString() );
|
||||||
|
db_.addAlbum( t );
|
||||||
|
db_.addTrack( t );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
-29
@@ -1,29 +0,0 @@
|
|||||||
#include "../include/Tag.h"
|
|
||||||
#include "../include/File.h"
|
|
||||||
|
|
||||||
#include <taglib/tag.h>
|
|
||||||
#include <taglib/fileref.h>
|
|
||||||
|
|
||||||
|
|
||||||
void Tag::readTag( TagLib::FileName fileName ){
|
|
||||||
TagLib::FileRef f( fileName );
|
|
||||||
if ( !f.isNull() ) {
|
|
||||||
metadata_.artist = ( f.tag()->artist().isEmpty() ) ? "" : f.tag()->artist();
|
|
||||||
metadata_.title = ( f.tag()->title().isEmpty() ) ? "" : f.tag()->title();
|
|
||||||
metadata_.album = ( f.tag()->album().isEmpty() ) ? "" : f.tag()->album();
|
|
||||||
metadata_.trackNr = f.tag()->track();
|
|
||||||
metadata_.year = f.tag()->year();
|
|
||||||
metadata_.genre = f.tag()->genre();
|
|
||||||
metadata_.sampleRate = f.audioProperties()->sampleRate();
|
|
||||||
metadata_.bitRate = f.audioProperties()->bitrate();
|
|
||||||
metadata_.length = f.audioProperties()->lengthInSeconds();
|
|
||||||
metadata_.format = parseFiletype( fileName );
|
|
||||||
// TODO
|
|
||||||
// file format
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
Metadata Tag::getMetadata() const {
|
|
||||||
return metadata_;
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
#include "../include/utility.h"
|
||||||
|
|
||||||
|
uint64_t utility::hashString( const std::string& s ) {
|
||||||
|
uint64_t hash = 14695981039346656037ULL;
|
||||||
|
for ( char c : s ) {
|
||||||
|
hash ^= static_cast<uint8_t> ( c );
|
||||||
|
hash *= 1099511628211ULL;
|
||||||
|
}
|
||||||
|
return hash;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string utility::parseFiletype( const std::string& path ){
|
||||||
|
|
||||||
|
size_t length = path.size();
|
||||||
|
size_t dot = path.find_last_of( ".");
|
||||||
|
|
||||||
|
return path.substr( dot + 1, length - dot );
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user