diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..183734f --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,8 @@ +# Changelog + +## MLC 1.0.1 (July 21, 2023) +### Added multithreaded encoding +### Only the first artist vorbis tag is left in the id3 tags + +## MLC 1.0.0 (July 19, 2023) +### Initial release, only core functionality diff --git a/CMakeLists.txt b/CMakeLists.txt index 5949bb0..315d28c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,7 +1,7 @@ cmake_minimum_required(VERSION 3.0) project( mlc - VERSION 0.0.1 + VERSION 1.0.1 DESCRIPTION "Media Library Compiler: rips your media library to a lossy compilation" LANGUAGES CXX ) @@ -22,6 +22,7 @@ add_executable(mlc decoded.cpp flactomp3.cpp collection.cpp + taskmanager.cpp ) target_link_libraries(mlc diff --git a/collection.cpp b/collection.cpp index c319a00..cc2868d 100644 --- a/collection.cpp +++ b/collection.cpp @@ -1,22 +1,24 @@ #include "collection.h" +#include "taskmanager.h" + namespace fs = std::filesystem; static const std::string flac(".flac"); -Collection::Collection(const std::string& path) : +Collection::Collection(const std::string& path, TaskManager* tm) : path(fs::canonical(path)), countMusical(0), counted(false), - convertedSoFar(0) + taskManager(tm) { } -Collection::Collection(const std::filesystem::path& path): +Collection::Collection(const std::filesystem::path& path, TaskManager* tm): path(fs::canonical(path)), countMusical(0), counted(false), - convertedSoFar(0) + taskManager(tm) { } @@ -60,12 +62,11 @@ uint32_t Collection::countMusicFiles() const { return countMusical; } -void Collection::convert(const std::string& outPath, std::function progress) { - convertedSoFar = 0; +void Collection::convert(const std::string& outPath) { + if (taskManager == nullptr) + throw 6; + fs::path out = fs::absolute(outPath); - if (progress == nullptr) { - progress = std::bind(&Collection::prg, this); - } fs::create_directories(out); out = fs::canonical(outPath); @@ -76,22 +77,18 @@ void Collection::convert(const std::string& outPath, std::function progr fs::path dstPath = out / sourcePath.filename(); if (isMusic(sourcePath)) { dstPath.replace_extension(".mp3"); - FLACtoMP3 convertor; - convertor.setInputFile(sourcePath); - convertor.setOutputFile(dstPath); - convertor.run(); - progress(); + taskManager->queueJob(sourcePath, dstPath); } else { - fs::copy_file(sourcePath, dstPath); + fs::copy_file(sourcePath, dstPath, fs::copy_options::overwrite_existing); } //std::cout << sourcePath << " => " << dstPath << std::endl; } break; case fs::file_type::directory: { fs::path sourcePath = entry.path(); - Collection collection(sourcePath); + Collection collection(sourcePath, taskManager); fs::path::iterator itr = sourcePath.end(); --itr; - collection.convert(std::string(out / *itr), progress); + collection.convert(std::string(out / *itr)); } break; default: break; @@ -103,7 +100,3 @@ bool Collection::isMusic(const std::filesystem::path& path) { return path.extension() == flac; //I know, it's primitive yet, but it's the fastest } -void Collection::prg() const { - std::cout << "\r" << ++convertedSoFar << "/" << countMusicFiles() << std::flush; -} - diff --git a/collection.h b/collection.h index cda8d68..b531f4b 100644 --- a/collection.h +++ b/collection.h @@ -3,28 +3,28 @@ #include #include #include -#include #include "flactomp3.h" +class TaskManager; + class Collection { public: - Collection(const std::string& path); - Collection(const std::filesystem::path& path); + Collection(const std::string& path, TaskManager* tm = nullptr); + Collection(const std::filesystem::path& path, TaskManager* tm = nullptr); ~Collection(); void list() const; uint32_t countMusicFiles() const; - void convert(const std::string& outPath, std::function progress = nullptr); + void convert(const std::string& outPath); private: static bool isMusic(const std::filesystem::path& path); - void prg() const; private: std::filesystem::path path; mutable uint32_t countMusical; mutable bool counted; - mutable uint32_t convertedSoFar; + TaskManager* taskManager; }; diff --git a/flactomp3.cpp b/flactomp3.cpp index 9f206d0..d40f2f9 100644 --- a/flactomp3.cpp +++ b/flactomp3.cpp @@ -173,13 +173,17 @@ void FLACtoMP3::processInfo(const FLAC__StreamMetadata_StreamInfo& info) { } void FLACtoMP3::processTags(const FLAC__StreamMetadata_VorbisComment& tags) { - for (FLAC__uint32 i = 0; i < tags.num_comments; ++i) { - const FLAC__StreamMetadata_VorbisComment_Entry& entry = tags.comments[i]; + bool artistSet = false; //in vorbis comment there could be more then 1 artist tag + for (FLAC__uint32 i = 0; i < tags.num_comments; ++i) { //usualy it's about guested and fetured artists + const FLAC__StreamMetadata_VorbisComment_Entry& entry = tags.comments[i]; //gonna keep only the first one for now std::string_view comm((const char*)entry.entry); if (comm.find(title) == 0) { id3tag_set_title(encoder, comm.substr(title.size()).data()); } else if (comm.find(artist) == 0) { - id3tag_set_artist(encoder, comm.substr(artist.size()).data()); + if (!artistSet) { + id3tag_set_artist(encoder, comm.substr(artist.size()).data()); + artistSet = true; + } } else if (comm.find(album) == 0) { id3tag_set_album(encoder, comm.substr(album.size()).data()); } else if (comm.find(comment) == 0) { diff --git a/main.cpp b/main.cpp index 56cb002..618fa0a 100644 --- a/main.cpp +++ b/main.cpp @@ -1,29 +1,43 @@ #include #include +#include +#include #include "FLAC/stream_decoder.h" #include #include "help.h" -#include "flactomp3.h" +#include "collection.h" +#include "taskmanager.h" int main(int argc, char **argv) { - if (argc < 2) { + if (argc < 3) { std::cout << "Insufficient amount of arguments, launch with \"--help\" argument to see usage" << std::endl; return 1; } const std::string firstArgument(argv[1]); + const std::string secondArgument(argv[2]); if (firstArgument == "--help") { printHelp(); return 0; } - FLACtoMP3 pipe; - pipe.setInputFile(firstArgument); - pipe.setOutputFile("out.mp3"); - pipe.run(); + TaskManager taskManager; + taskManager.start(); + + std::chrono::time_point start = std::chrono::system_clock::now(); + Collection collection(firstArgument, &taskManager); + collection.convert(secondArgument); + + taskManager.printProgress(); + taskManager.wait(); + std::chrono::time_point end = std::chrono::system_clock::now(); + std::chrono::duration seconds = end - start; + std::cout << std::endl << "took " << seconds.count() << std::endl; + + taskManager.stop(); return 0; } diff --git a/taskmanager.cpp b/taskmanager.cpp new file mode 100644 index 0000000..a491927 --- /dev/null +++ b/taskmanager.cpp @@ -0,0 +1,108 @@ +#include "taskmanager.h" + +#include "flactomp3.h" + +TaskManager::TaskManager(): + busyThreads(0), + maxTasks(0), + completeTasks(0), + terminate(false), + queueMutex(), + busyMutex(), + loopConditional(), + waitConditional(), + threads(), + jobs(), + boundLoopCondition(std::bind(&TaskManager::loopCondition, this)), + boundWaitCondition(std::bind(&TaskManager::waitCondition, this)) +{ +} + +TaskManager::~TaskManager() { +} + +void TaskManager::queueJob(const std::string& source, const std::string& destination) { + { + std::unique_lock lock(queueMutex); + jobs.emplace(source, destination); + } + ++maxTasks; + loopConditional.notify_one(); + waitConditional.notify_all(); +} + +bool TaskManager::busy() const { + bool result; + { + std::unique_lock lock(queueMutex); + result = !jobs.empty(); + } + + return result; +} + +void TaskManager::start() { + const uint32_t num_threads = std::thread::hardware_concurrency(); + for (uint32_t ii = 0; ii < num_threads; ++ii) + threads.emplace_back(std::thread(&TaskManager::loop, this)); +} + +void TaskManager::loop() { + while (true) { + std::pair pair; + { + std::unique_lock lock(queueMutex); + loopConditional.wait(lock, boundLoopCondition); + if (terminate) + return; + + pair = jobs.front(); + ++busyThreads; + waitConditional.notify_all(); + jobs.pop(); + } + + job(pair.first, pair.second); + ++completeTasks; + printProgress(); + --busyThreads; + waitConditional.notify_all(); + } +} + +bool TaskManager::loopCondition() const { + return !jobs.empty() || terminate; +} + +bool TaskManager::waitCondition() const { + return busyThreads == 0 && !busy(); +} + +void TaskManager::stop() { + { + std::unique_lock lock(queueMutex); + terminate = true; + } + + loopConditional.notify_all(); + for (std::thread& thread : threads) + thread.join(); + + threads.clear(); +} + +void TaskManager::wait() { + std::unique_lock lock(busyMutex); + waitConditional.wait(lock, boundWaitCondition); +} + +bool TaskManager::job(const std::string& source, const std::string& destination) { + FLACtoMP3 convertor; + convertor.setInputFile(source); + convertor.setOutputFile(destination); + return convertor.run(); +} + +void TaskManager::printProgress() const { + std::cout << "\r" << completeTasks << "/" << maxTasks << std::flush; +} diff --git a/taskmanager.h b/taskmanager.h new file mode 100644 index 0000000..fd23c6c --- /dev/null +++ b/taskmanager.h @@ -0,0 +1,45 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +class TaskManager { +public: + TaskManager(); + ~TaskManager(); + + void start(); + void queueJob(const std::string& source, const std::string& destination); + void stop(); + bool busy() const; + void wait(); + void printProgress() const; + +private: + void loop(); + bool loopCondition() const; + bool waitCondition() const; + static bool job(const std::string& source, const std::string& destination); + +private: + std::atomic busyThreads; + std::atomic maxTasks; + std::atomic completeTasks; + bool terminate; + mutable std::mutex queueMutex; + std::mutex busyMutex; + std::condition_variable loopConditional; + std::condition_variable waitConditional; + std::vector threads; + std::queue> jobs; + std::function boundLoopCondition; + std::function boundWaitCondition; + +}; +