multithreading
This commit is contained in:
parent
aa65cc6851
commit
c5eafe7d98
8
CHANGELOG.md
Normal file
8
CHANGELOG.md
Normal 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
|
@ -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
|
||||||
|
@ -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;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
12
collection.h
12
collection.h
@ -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;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -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) {
|
||||||
|
if (!artistSet) {
|
||||||
id3tag_set_artist(encoder, comm.substr(artist.size()).data());
|
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) {
|
||||||
|
26
main.cpp
26
main.cpp
@ -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
108
taskmanager.cpp
Normal 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
45
taskmanager.h
Normal 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;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
Loading…
Reference in New Issue
Block a user