multithreading

This commit is contained in:
Blue 2023-07-21 18:32:24 -03:00
parent aa65cc6851
commit c5eafe7d98
Signed by: blue
GPG Key ID: 9B203B252A63EE38
8 changed files with 210 additions and 37 deletions

8
CHANGELOG.md Normal file
View File

@ -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

View File

@ -1,7 +1,7 @@
cmake_minimum_required(VERSION 3.0) cmake_minimum_required(VERSION 3.0)
project( project(
mlc mlc
VERSION 0.0.1 VERSION 1.0.1
DESCRIPTION "Media Library Compiler: rips your media library to a lossy compilation" DESCRIPTION "Media Library Compiler: rips your media library to a lossy compilation"
LANGUAGES CXX LANGUAGES CXX
) )
@ -22,6 +22,7 @@ add_executable(mlc
decoded.cpp decoded.cpp
flactomp3.cpp flactomp3.cpp
collection.cpp collection.cpp
taskmanager.cpp
) )
target_link_libraries(mlc target_link_libraries(mlc

View File

@ -1,22 +1,24 @@
#include "collection.h" #include "collection.h"
#include "taskmanager.h"
namespace fs = std::filesystem; namespace fs = std::filesystem;
static const std::string flac(".flac"); static const std::string flac(".flac");
Collection::Collection(const std::string& path) : Collection::Collection(const std::string& path, TaskManager* tm) :
path(fs::canonical(path)), path(fs::canonical(path)),
countMusical(0), countMusical(0),
counted(false), 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)), path(fs::canonical(path)),
countMusical(0), countMusical(0),
counted(false), counted(false),
convertedSoFar(0) taskManager(tm)
{ {
} }
@ -60,12 +62,11 @@ uint32_t Collection::countMusicFiles() const {
return countMusical; return countMusical;
} }
void Collection::convert(const std::string& outPath, std::function<void()> progress) { void Collection::convert(const std::string& outPath) {
convertedSoFar = 0; if (taskManager == nullptr)
throw 6;
fs::path out = fs::absolute(outPath); fs::path out = fs::absolute(outPath);
if (progress == nullptr) {
progress = std::bind(&Collection::prg, this);
}
fs::create_directories(out); fs::create_directories(out);
out = fs::canonical(outPath); out = fs::canonical(outPath);
@ -76,22 +77,18 @@ void Collection::convert(const std::string& outPath, std::function<void()> progr
fs::path dstPath = out / sourcePath.filename(); fs::path dstPath = out / sourcePath.filename();
if (isMusic(sourcePath)) { if (isMusic(sourcePath)) {
dstPath.replace_extension(".mp3"); dstPath.replace_extension(".mp3");
FLACtoMP3 convertor; taskManager->queueJob(sourcePath, dstPath);
convertor.setInputFile(sourcePath);
convertor.setOutputFile(dstPath);
convertor.run();
progress();
} else { } else {
fs::copy_file(sourcePath, dstPath); fs::copy_file(sourcePath, dstPath, fs::copy_options::overwrite_existing);
} }
//std::cout << sourcePath << " => " << dstPath << std::endl; //std::cout << sourcePath << " => " << dstPath << std::endl;
} break; } break;
case fs::file_type::directory: { case fs::file_type::directory: {
fs::path sourcePath = entry.path(); fs::path sourcePath = entry.path();
Collection collection(sourcePath); Collection collection(sourcePath, taskManager);
fs::path::iterator itr = sourcePath.end(); fs::path::iterator itr = sourcePath.end();
--itr; --itr;
collection.convert(std::string(out / *itr), progress); collection.convert(std::string(out / *itr));
} break; } break;
default: default:
break; 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 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;
}

View File

@ -3,28 +3,28 @@
#include <string> #include <string>
#include <iostream> #include <iostream>
#include <filesystem> #include <filesystem>
#include <functional>
#include "flactomp3.h" #include "flactomp3.h"
class TaskManager;
class Collection { class Collection {
public: public:
Collection(const std::string& path); Collection(const std::string& path, TaskManager* tm = nullptr);
Collection(const std::filesystem::path& path); Collection(const std::filesystem::path& path, TaskManager* tm = nullptr);
~Collection(); ~Collection();
void list() const; void list() const;
uint32_t countMusicFiles() const; uint32_t countMusicFiles() const;
void convert(const std::string& outPath, std::function<void()> progress = nullptr); void convert(const std::string& outPath);
private: private:
static bool isMusic(const std::filesystem::path& path); static bool isMusic(const std::filesystem::path& path);
void prg() const;
private: private:
std::filesystem::path path; std::filesystem::path path;
mutable uint32_t countMusical; mutable uint32_t countMusical;
mutable bool counted; mutable bool counted;
mutable uint32_t convertedSoFar; TaskManager* taskManager;
}; };

View File

@ -173,13 +173,17 @@ void FLACtoMP3::processInfo(const FLAC__StreamMetadata_StreamInfo& info) {
} }
void FLACtoMP3::processTags(const FLAC__StreamMetadata_VorbisComment& tags) { void FLACtoMP3::processTags(const FLAC__StreamMetadata_VorbisComment& tags) {
for (FLAC__uint32 i = 0; i < tags.num_comments; ++i) { bool artistSet = false; //in vorbis comment there could be more then 1 artist tag
const FLAC__StreamMetadata_VorbisComment_Entry& entry = tags.comments[i]; 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); std::string_view comm((const char*)entry.entry);
if (comm.find(title) == 0) { if (comm.find(title) == 0) {
id3tag_set_title(encoder, comm.substr(title.size()).data()); id3tag_set_title(encoder, comm.substr(title.size()).data());
} else if (comm.find(artist) == 0) { } 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) { } else if (comm.find(album) == 0) {
id3tag_set_album(encoder, comm.substr(album.size()).data()); id3tag_set_album(encoder, comm.substr(album.size()).data());
} else if (comm.find(comment) == 0) { } else if (comm.find(comment) == 0) {

View File

@ -1,29 +1,43 @@
#include <iostream> #include <iostream>
#include <string> #include <string>
#include <chrono>
#include <unistd.h>
#include "FLAC/stream_decoder.h" #include "FLAC/stream_decoder.h"
#include <lame/lame.h> #include <lame/lame.h>
#include "help.h" #include "help.h"
#include "flactomp3.h" #include "collection.h"
#include "taskmanager.h"
int main(int argc, char **argv) { 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; std::cout << "Insufficient amount of arguments, launch with \"--help\" argument to see usage" << std::endl;
return 1; return 1;
} }
const std::string firstArgument(argv[1]); const std::string firstArgument(argv[1]);
const std::string secondArgument(argv[2]);
if (firstArgument == "--help") { if (firstArgument == "--help") {
printHelp(); printHelp();
return 0; return 0;
} }
FLACtoMP3 pipe; TaskManager taskManager;
pipe.setInputFile(firstArgument); taskManager.start();
pipe.setOutputFile("out.mp3");
pipe.run(); 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<double> seconds = end - start;
std::cout << std::endl << "took " << seconds.count() << std::endl;
taskManager.stop();
return 0; return 0;
} }

108
taskmanager.cpp Normal file
View File

@ -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<std::mutex> lock(queueMutex);
jobs.emplace(source, destination);
}
++maxTasks;
loopConditional.notify_one();
waitConditional.notify_all();
}
bool TaskManager::busy() const {
bool result;
{
std::unique_lock<std::mutex> 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<std::string, std::string> pair;
{
std::unique_lock<std::mutex> 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<std::mutex> lock(queueMutex);
terminate = true;
}
loopConditional.notify_all();
for (std::thread& thread : threads)
thread.join();
threads.clear();
}
void TaskManager::wait() {
std::unique_lock<std::mutex> 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;
}

45
taskmanager.h Normal file
View File

@ -0,0 +1,45 @@
#pragma once
#include <mutex>
#include <condition_variable>
#include <thread>
#include <functional>
#include <vector>
#include <queue>
#include <string>
#include <atomic>
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<uint32_t> busyThreads;
std::atomic<uint32_t> maxTasks;
std::atomic<uint32_t> completeTasks;
bool terminate;
mutable std::mutex queueMutex;
std::mutex busyMutex;
std::condition_variable loopConditional;
std::condition_variable waitConditional;
std::vector<std::thread> threads;
std::queue<std::pair<std::string, std::string>> jobs;
std::function<bool()> boundLoopCondition;
std::function<bool()> boundWaitCondition;
};