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)
|
||||
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
|
||||
|
@ -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<void()> 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<void()> 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;
|
||||
}
|
||||
|
||||
|
12
collection.h
12
collection.h
@ -3,28 +3,28 @@
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
#include <filesystem>
|
||||
#include <functional>
|
||||
|
||||
#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<void()> 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;
|
||||
};
|
||||
|
||||
|
@ -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) {
|
||||
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) {
|
||||
|
26
main.cpp
26
main.cpp
@ -1,29 +1,43 @@
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <chrono>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "FLAC/stream_decoder.h"
|
||||
#include <lame/lame.h>
|
||||
|
||||
#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<double> seconds = end - start;
|
||||
std::cout << std::endl << "took " << seconds.count() << std::endl;
|
||||
|
||||
taskManager.stop();
|
||||
|
||||
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