diff --git a/include/ArgumentParser.h b/include/ArgumentParser.h index b4f4823..5ca9074 100644 --- a/include/ArgumentParser.h +++ b/include/ArgumentParser.h @@ -1,11 +1,13 @@ #include #include #include -#include "boost/program_options.hpp" +#include +#include using std::vector; using std::string; namespace po = boost::program_options; +namespace fs = boost::filesystem; class ArgumentParser { public: @@ -33,6 +35,6 @@ class ConcatenatorArgParse : public ArgumentParser { vector get_analyses() { return (*this)["analyses"].as>(); } string get_source_db() { return (*this)["source"].as(); } string get_target_db() { return (*this)["target"].as(); } - string get_tar_audio_dir() { return ((*this)["tar_db"].empty() ? (*this)["target"].as() : (*this)["tar_db"].as()); } - string get_src_audio_dir() { return ((*this)["src_db"].empty() ? (*this)["source"].as() : (*this)["src_db"].as()); } + fs::path get_tar_audio_dir() { return ((*this)["tar_audio"].empty() ? fs::path("") : fs::path((*this)["tar_audio"].as())); } + fs::path get_src_audio_dir() { return ((*this)["src_audio"].empty() ? fs::path("") : fs::path((*this)["src_audio"].as())); } }; diff --git a/include/AudioDatabase.h b/include/AudioDatabase.h index 1b3cd91..0ba6e3c 100644 --- a/include/AudioDatabase.h +++ b/include/AudioDatabase.h @@ -3,6 +3,7 @@ #include #include #include +#include #include "logger.h" using std::string; @@ -18,17 +19,28 @@ using std::vector; class AudioDatabase { public: - AudioDatabase(const std::string database_dir, vector& analyses, Logger* log, const std::string audio_dir=""); + AudioDatabase( + const std::string database_dir, + vector& analyses, + Logger* log + ); + void load_database(boost::filesystem::path source_dir, bool reanalyse=false); + private: - std::string database_dir = ""; - std::string audio_dir = ""; + boost::filesystem::path database_dir; + boost::filesystem::path audio_dir; // Define a set that stores the locations of audiofiles in the database. - std::set audio_file_set; + std::set audio_file_set; + std::map database_dirs; Logger* log; + void validate_analysis_list(); + bool validate_filetype(const boost::filesystem::path& filepath); + void create_subdirs(); + void organise_audio(boost::filesystem::path source_dir, bool symlink=true); + void register_audio(); }; - /*! A function that determines whether a string value is found in the container. */ template diff --git a/src/ArgumentParser.cpp b/src/ArgumentParser.cpp index 6cb4820..24eefeb 100644 --- a/src/ArgumentParser.cpp +++ b/src/ArgumentParser.cpp @@ -18,12 +18,12 @@ ArgumentParser::ArgumentParser() : desc("Allowed options") { ("output", po::value()->required(), "Output location") ("analyses,a", po::value>()->multitoken(), "Analysis " "strings specifying analyses to use for database comparison.") - ("tar_db", po::value(), "Specifies the " - "directory to create the target database and store analyses in. If " - "not specified then the target directory will be used directly.") - ("src_db", po::value(), "Specifies the " + ("src_audio", po::value(), "Specifies the " "directory to create the source database and store analyses in. If " "not specified then the " "source directory will be used directly.") + ("tar_audio", po::value(), "Specifies the " + "directory to create the target database and store analyses in. If " + "not specified then the target directory will be used directly.") ; } diff --git a/src/AudioDatabase.cpp b/src/AudioDatabase.cpp index 2069931..60cc7db 100644 --- a/src/AudioDatabase.cpp +++ b/src/AudioDatabase.cpp @@ -6,20 +6,23 @@ #include #include #include +#include +#include + +namespace fs = boost::filesystem; using namespace std; AudioDatabase::AudioDatabase( const string database_dir, vector& analyses, - Logger* log, - const string audio_dir + Logger* log ) { this->log = log; log->info("Database directory: " + database_dir); - log->info("Audio directory: " + audio_dir); + // Remove duplicate strings from vector of analyses. std::vector::iterator it; it = std::unique (analyses.begin(), analyses.end()); @@ -33,6 +36,167 @@ AudioDatabase::AudioDatabase( throw std::runtime_error(err + invalid_strings); } - this->database_dir = database_dir; - this->audio_dir = audio_dir; + database_dirs.insert({"root", fs::path(database_dir)}); + this->audio_dir = fs::path(audio_dir); +} + +void AudioDatabase::validate_analysis_list() +{ +} + +void AudioDatabase::load_database(fs::path source_dir, bool reanalyse) +{ + // Make sure the database root directory exists. + try + { + if(create_directory(database_dirs["root"])) + { + log->debug("Database directory created: " + database_dir.string()); + } + else if(exists(database_dirs["root"])) + { + log->debug("Database directory already exists: " + database_dir.string()); + } + } + catch(boost::filesystem::filesystem_error &e) + { + throw std::runtime_error("Database directory could not be created: " + database_dir.string()); + } + + + // Create a folder hierachy used to store audio and analysis data used by the database. + create_subdirs(); + + if(source_dir.empty()) { + source_dir = database_dirs["audio"]; + log->debug("Source directory not provided. Setting to:" + source_dir.string()); + } + + if(!exists(source_dir)) { + throw std::runtime_error("Source audio directory does not exist: " + source_dir.string()); + } + + // Only organise audio if new audio is to be added from a new location. + if(source_dir != database_dirs["audio"]) { + // Copy/create links to audio files that are to be used as part of the database. + organise_audio(source_dir); + } + + // Find all audio in the database and store references for use later... + register_audio(); + +} + +void AudioDatabase::create_subdirs() +{ + array directory_names = {{ + fs::path("audio"), + fs::path("data") + }}; + + for(const auto& name : directory_names) { + fs::path subdir = database_dirs["root"]/name; + try + { + if(create_directory(subdir)) { + log->info("Subdirectory created: " + subdir.string()); + } + else if(exists(database_dirs["root"])) + { + log->info("Subdirectory already exists: " + subdir.string()); + } + } + catch(boost::filesystem::filesystem_error &e) + { + throw std::runtime_error("Subdirectory could not be created: " + database_dirs["root"].string()); + } + database_dirs.insert({name.string(), subdir}); + } +} + +bool AudioDatabase::validate_filetype(const fs::path& filepath) +{ + // Define patterns to validate files found based on their file extensions. + static array valid_filetypes = {{ + ".wav", + ".aif", + ".aiff", + ".flac" + }}; + + for(const auto& filetype : valid_filetypes) + { + // compare file extension with valid extension strings to find match. + bool valid = (filetype.compare(filepath.extension().string()) == 0); + if(valid) { + return true; + } + } + return false; +} + +void AudioDatabase::organise_audio(fs::path source_dir, bool symlink) +{ + + log->info("Organising audio directory at: " + database_dirs["audio"].string()); + // Define the destination for copying/linking all valid audio files found. + for(fs::recursive_directory_iterator iter(source_dir), end; iter != end; ++iter) + { + // Don't search the audio directory of the database if this is a subdirectory of the source directory provided. + if (iter->path() == database_dirs["audio"]) + { + iter.disable_recursion_pending(); + } + + if(!validate_filetype(iter->path())) { + log->debug("File: " + iter->path().string() + " isn't a supported audiofile. Skipping..."); + continue; + } + + + fs::path destination_file = database_dirs["audio"]/iter->path().filename(); + + if(symlink) { + // Try to symlink the file to the audio directory of the database. + try { + fs::create_symlink(iter->path(), destination_file); + log->debug("Linked: " + iter->path().string() + " to: " + destination_file.string()); + } + catch(boost::filesystem::filesystem_error &e){ + // If symbolic linking fails then the file probably already exists at the location. + log->debug("Failed to link: " + iter->path().string() + " to " + destination_file.string() + " File may already exists."); + } + } + else { + // If it is in the database as a symlink, but a full copy is required + if(fs::exists(destination_file) && !fs::is_symlink(destination_file)) { + log->debug("File already exists: " + iter->path().string()); + continue; + } + + // Copy file, overwriting any previously created symbolic links. + try { + fs::remove(destination_file); + fs::copy_file(iter->path(), destination_file, fs::copy_option::overwrite_if_exists); + log->debug("Copied: " + iter->path().string() + " to: " + destination_file.string()); + } + catch(boost::filesystem::filesystem_error &e){ + // If symbolic linking fails then the file probably already exists at the location. + log->debug("Failed to copy source file to: " + destination_file.string() + " File may already exists."); + } + } + } +} + +void AudioDatabase::register_audio() +{ + // Clear any previous entries from set. + audio_file_set.clear(); + for(auto& entry : boost::make_iterator_range(fs::directory_iterator(database_dirs["audio"]), {})) + { + if(validate_filetype(entry.path())) { + log->info("Registered audio file: " + entry.path().string()); + audio_file_set.insert(entry.path()); + } + } } diff --git a/src/concatenator.cpp b/src/concatenator.cpp index 0852d70..a770858 100644 --- a/src/concatenator.cpp +++ b/src/concatenator.cpp @@ -23,32 +23,35 @@ int main(int argc, char** argv) { try { */ - // Initialize object to parse arguments supplied by user from command - // line - ConcatenatorArgParse argparse = ConcatenatorArgParse(); - // Parse arguments and exit program if specified (through use of --help - // or -h flag) - if(argparse.parseargs(argc, argv)) { - return SUCCESS; - } - - vector analyses = argparse.get_analyses(); - // Initialize the source audio database object with arguments provided from the command line. - AudioDatabase source_db = AudioDatabase( - argparse.get_source_db(), - analyses, - &log, - argparse.get_src_audio_dir() - ); + // Initialize object to parse arguments supplied by user from command + // line + ConcatenatorArgParse argparse = ConcatenatorArgParse(); + // Parse arguments and exit program if specified (through use of --help + // or -h flag) + if(argparse.parseargs(argc, argv)) { + return SUCCESS; + } + + vector analyses = argparse.get_analyses(); - // Initialize the target audio database object with arguments provided from the command line. - AudioDatabase target_db = AudioDatabase( - argparse.get_target_db(), - analyses, - &log, - argparse.get_tar_audio_dir() - ); + // Initialize the source audio database object with arguments provided from the command line. + AudioDatabase source_db = AudioDatabase( + argparse.get_source_db(), + analyses, + &log + ); + source_db.load_database(argparse.get_src_audio_dir()); + + /* + // Initialize the target audio database object with arguments provided from the command line. + AudioDatabase target_db = AudioDatabase( + argparse.get_target_db(), + analyses, + &log + ); + target_db.load_database(argparse.get_tar_audio_dir()); + */ /* } @@ -58,7 +61,7 @@ int main(int argc, char** argv) { error.append(e.what()); log.error(error); - return ERROR_UNHANDLED_EXCEPTION; + throw; } */ return SUCCESS;