From 2c913d5f54c2dd37eaba0cd7be2d03869172b3ed Mon Sep 17 00:00:00 2001 From: blue Date: Sun, 23 Jul 2023 09:04:26 -0300 Subject: [PATCH] new way of logging, some tags fixes --- CHANGELOG.md | 13 +++- CMakeLists.txt | 1 + flactomp3.cpp | 173 +++++++++++++++++++++++++++++++++--------------- flactomp3.h | 16 ++++- loggable.cpp | 18 +++++ loggable.h | 28 ++++++++ main.cpp | 6 +- taskmanager.cpp | 54 +++++++++++++-- taskmanager.h | 12 +++- 9 files changed, 253 insertions(+), 68 deletions(-) create mode 100644 loggable.cpp create mode 100644 loggable.h diff --git a/CHANGELOG.md b/CHANGELOG.md index 183734f..4bd6df2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,15 @@ # Changelog +## MLC 1.1.0 (July 23, 2023) +- New logging system +- Artist, Album artist, Album and Title are now utf16 encoded, should fix broten titles +- BPM tag is now rounded, as it supposed to be by spec +- Lyrics is not set now for USLT tag for unsychronized lyrics is not supported in LAME +- Date bug fix, it was MMDD instead of standard DDMM + ## MLC 1.0.1 (July 21, 2023) -### Added multithreaded encoding -### Only the first artist vorbis tag is left in the id3 tags +- 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 +- Initial release, only core functionality diff --git a/CMakeLists.txt b/CMakeLists.txt index 315d28c..140bcec 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -23,6 +23,7 @@ add_executable(mlc flactomp3.cpp collection.cpp taskmanager.cpp + loggable.cpp ) target_link_libraries(mlc diff --git a/flactomp3.cpp b/flactomp3.cpp index d40f2f9..6f07d51 100644 --- a/flactomp3.cpp +++ b/flactomp3.cpp @@ -1,16 +1,21 @@ #include "flactomp3.h" +#include + constexpr uint16_t flacDefaultMaxBlockSize = 4096; +constexpr uint16_t id3v1TagSize = 128; +// constexpr std::wstring_view BOM ({(0xFEFF}); //one day... -static const std::string_view title ("TITLE="); -static const std::string_view artist ("ARTIST="); -static const std::string_view album ("ALBUM="); -static const std::string_view comment ("COMMENT="); -static const std::string_view genre ("GENRE="); -static const std::string_view track ("TRACKNUMBER="); -static const std::string_view date ("DATE="); +constexpr std::string_view title ("TITLE="); +constexpr std::string_view artist ("ARTIST="); +constexpr std::string_view album ("ALBUM="); +constexpr std::string_view comment ("COMMENT="); +constexpr std::string_view genre ("GENRE="); +constexpr std::string_view track ("TRACKNUMBER="); +constexpr std::string_view date ("DATE="); + +constexpr std::string_view jpeg ("image/jpeg"); -static const std::string_view jpeg ("image/jpeg"); static const std::map knownTags { {"ALBUMARTIST=", "TPE2="}, @@ -18,10 +23,12 @@ static const std::map knownTags { {"LENGTH=", "TLEN="}, {"ISRC=", "TSRC="}, {"DISCNUMBER=", "TPOS="}, - {"BPM=", "TBPM="} + {"BPM=", "TBPM="}, + {"LYRICS=", "USLT="} //but it's not supported in LAME }; -FLACtoMP3::FLACtoMP3(uint8_t size) : +FLACtoMP3::FLACtoMP3(Severity severity, uint8_t size) : + Loggable(severity), inPath(), outPath(), decoder(FLAC__stream_decoder_new()), @@ -36,7 +43,8 @@ FLACtoMP3::FLACtoMP3(uint8_t size) : outputBuffer(nullptr), outputBufferSize(0), outputInitilized(false), - downscaleAlbumArt(false) + downscaleAlbumArt(false), + usc2convertor() { } @@ -47,6 +55,7 @@ FLACtoMP3::~FLACtoMP3() { bool FLACtoMP3::run() { FLAC__bool ok = FLAC__stream_decoder_process_until_end_of_stream(decoder); + uint32_t fileSize; if (ok) { if (pcmCounter > 0) flush(); @@ -55,24 +64,26 @@ bool FLACtoMP3::run() { fwrite((char*)outputBuffer, nwrite, 1, output); if (downscaleAlbumArt) { + fileSize = ftell(output); lame_mp3_tags_fid(encoder, output); } else { - int tag1Size = lame_get_id3v1_tag(encoder, outputBuffer, 128); - if (tag1Size > 128) - std::cout << std::endl << "couldn't write id3v1 tag"; + int tag1Size = lame_get_id3v1_tag(encoder, outputBuffer, id3v1TagSize); + if (tag1Size > id3v1TagSize) + log(warning, "couldn't write id3v1 tag"); else fwrite((char*)outputBuffer, tag1Size, 1, output); + fileSize = ftell(output); fseek(output, 0, SEEK_SET); int tag2Size = lame_get_id3v2_tag(encoder, outputBuffer, outputBufferSize); if (tag2Size > outputBufferSize) { - std::cout << std::endl << "couldn't write id3v1 tag"; + log(Loggable::error, "couldn't write id3v2 tag"); } else fwrite((char*)outputBuffer, tag2Size, 1, output); int vbrTagSize = lame_get_lametag_frame(encoder, outputBuffer, outputBufferSize); if (vbrTagSize > outputBufferSize) - std::cout << std::endl << "couldn't write vbr tag"; + log(Loggable::error, "couldn't write vbr tag"); fwrite((char*)outputBuffer, vbrTagSize, 1, output); } @@ -92,6 +103,12 @@ bool FLACtoMP3::run() { pcmSize = 0; flacMaxBlockSize = 0; outputBufferSize = 0; + if (ok) { + float MBytes = (float)fileSize / 1024 / 1024; + std::string strMBytes = std::to_string(MBytes); + strMBytes = strMBytes.substr(0, strMBytes.find(".") + 3) + " MiB"; + log(info, "resulting file size: " + strMBytes); + } return ok; } @@ -130,13 +147,13 @@ bool FLACtoMP3::initializeOutput() { output = fopen(outPath.c_str(), "w+b"); if (output == 0) { output = nullptr; - std::cout << "Error opening file " << outPath << std::endl; + log(fatal, "Error opening file " + outPath); return false; } int ret = lame_init_params(encoder); if (ret < 0) { - std::cout << "Error occurred during parameters initializing. Code = " << ret << std::endl; + log(fatal, "Error initializing LAME parameters. Code = " + std::to_string(ret)); fclose(output); output = nullptr; return false; @@ -169,7 +186,9 @@ void FLACtoMP3::processInfo(const FLAC__StreamMetadata_StreamInfo& info) { lame_set_in_samplerate(encoder, info.sample_rate); lame_set_num_channels(encoder, info.channels); flacMaxBlockSize = info.max_blocksize; - //std::cout << "bits per sample: " << info.bits_per_sample << std::endl;; + log(Loggable::info, "sample rate: " + std::to_string(info.sample_rate)); + log(Loggable::info, "channels: " + std::to_string(info.channels)); + log(Loggable::info, "bits per sample: " + std::to_string(info.bits_per_sample)); } void FLACtoMP3::processTags(const FLAC__StreamMetadata_VorbisComment& tags) { @@ -178,14 +197,16 @@ void FLACtoMP3::processTags(const FLAC__StreamMetadata_VorbisComment& tags) { 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()); + setTagTitle(comm.substr(title.size())); } else if (comm.find(artist) == 0) { if (!artistSet) { - id3tag_set_artist(encoder, comm.substr(artist.size()).data()); + setTagArtist(comm.substr(artist.size())); artistSet = true; + } else { + log(minor, "more than one artist tag, ignoring " + std::string(comm.substr(artist.size()))); } } else if (comm.find(album) == 0) { - id3tag_set_album(encoder, comm.substr(album.size()).data()); + setTagAlbum(comm.substr(album.size())); } else if (comm.find(comment) == 0) { id3tag_set_comment(encoder, comm.substr(comment.size()).data()); } else if (comm.find(genre) == 0) { @@ -194,13 +215,13 @@ void FLACtoMP3::processTags(const FLAC__StreamMetadata_VorbisComment& tags) { id3tag_set_track(encoder, comm.substr(track.size()).data()); } else if (comm.find(date) == 0) { std::string_view fullDate = comm.substr(date.size()); - if (fullDate.size() == 10) { + if (fullDate.size() == 10) { //1999-11-11 kind of string std::string_view month = fullDate.substr(5, std::size_t(2)); std::string_view day = fullDate.substr(8); - std::string md = "TDAT=" + std::string(month) + std::string(day); + std::string md = "TDAT=" + std::string(day) + std::string(month); int res = id3tag_set_fieldvalue(encoder, md.c_str()); if (res != 0) - std::cout << "wasn't able to set the date tag (" << md << ")" << std::endl; + log(warning, "wasn't able to set the date tag (" + md + ")"); fullDate = fullDate.substr(0, 4); //year; } @@ -209,27 +230,25 @@ void FLACtoMP3::processTags(const FLAC__StreamMetadata_VorbisComment& tags) { std::string tag = "TXXX=" + std::string(comm); int res = id3tag_set_fieldvalue(encoder, tag.c_str()); if (res != 0) - std::cout << "wasn't able to set user tag (" << comm << ")" << std::endl; + log(warning, "wasn't able to set user tag (" + tag + ")"); } } } void FLACtoMP3::processPicture(const FLAC__StreamMetadata_Picture& picture) { if (downscaleAlbumArt && picture.data_length > LAME_MAXALBUMART) { - std::cout << "embeded picture is too big (" << picture.data_length << " bytes) " << std::endl; - std::cout << "mime type is " << picture.mime_type << std::endl; + log(info, "embeded album art is too big (" + std::to_string(picture.data_length) + " bytes), rescaling"); + log(debug, "mime type is " + std::string(picture.mime_type)); if (picture.mime_type == jpeg) { - if (scaleJPEG(picture)) { - std::cout << "successfully scaled album art" << std::endl; - } else { - std::cout << "failed to album art" << std::endl; - } + if (scaleJPEG(picture)) + log(debug, "successfully rescaled album art"); + else + log(warning, "failed to rescale album art"); } } else { int result = id3tag_set_albumart(encoder, (const char*)picture.data, picture.data_length); - if (result != 0) { - std::cout << "couldn't set album art tag, errcode: " << result << std::endl; - } + if (result != 0) + log(warning, "couldn't set album art tag, errcode: " + std::to_string(result)); } } @@ -243,11 +262,11 @@ bool FLACtoMP3::scaleJPEG(const FLAC__StreamMetadata_Picture& picture) { int rc = jpeg_read_header(&dinfo, TRUE); if (rc != 1) { - std::cout << "error reading jpeg header" << std::endl; + log(Loggable::error, "error reading jpeg header"); return false; } - uint64_t mem_size = LAME_MAXALBUMART; - uint8_t *mem = new uint8_t[mem_size]; + uint64_t mem_size = 0; + uint8_t *mem = new uint8_t[LAME_MAXALBUMART + 1024 * 4]; //I allocate a little bit more not to corrupt someone else's the memory dinfo.scale_num = 2; //need to tune it, feels like 500 by 500 is a good size dinfo.scale_denom = 3; @@ -273,6 +292,12 @@ bool FLACtoMP3::scaleJPEG(const FLAC__StreamMetadata_Picture& picture) { while (dinfo.output_scanline < dinfo.output_height) { jpeg_read_scanlines(&dinfo, &row, 1); jpeg_write_scanlines(&cinfo, &row, 1); + + if (mem_size > LAME_MAXALBUMART) { + delete[] mem; + log(Loggable::error, "resized album art exceeded reserved buffer size, stopping before memory corrution"); + return false; + } } jpeg_finish_decompress(&dinfo); jpeg_destroy_decompress(&dinfo); @@ -291,10 +316,24 @@ bool FLACtoMP3::scaleJPEG(const FLAC__StreamMetadata_Picture& picture) { bool FLACtoMP3::tryKnownTag(std::string_view source) { for (const std::pair& pair : knownTags) { if (source.find(pair.first) == 0) { - std::string tag = pair.second + std::string(source.substr(pair.first.size())); - int res = id3tag_set_fieldvalue(encoder, tag.c_str()); - if (res != 0) - std::cout << "wasn't able to set tag (" << source << ")" << std::endl; + if (pair.first == "LYRICS=") { + log(warning, "lyrics tag was not transfered, it is not supported currently"); + } else if (pair.first == "BPM=") { + float bpm = std::atof(source.substr(pair.first.size()).data()); + bpm = std::round(bpm); //id3 bpm must be integer + int result = id3tag_set_fieldvalue(encoder, std::string(pair.second + std::to_string((int)bpm)).c_str()); + if (result != 0) + log(warning, "wasn't able to set BPM tag. Code = " + std::to_string(result)); + } else if (pair.first == "ALBUMARTIST=") { + int res = setTagUSC2("TPE2", source.substr(pair.first.size())); + if (res != 0) + log(warning, "wasn't able to album artist tag. Code = " + std::to_string(res)); + } else { + std::string tag = pair.second + std::string(source.substr(pair.first.size())); + int res = id3tag_set_fieldvalue(encoder, tag.c_str()); + if (res != 0) + log(warning, "wasn't able to set tag (" + std::string(source) + "). Code = " + std::to_string(res)); + } return true; } @@ -331,11 +370,11 @@ bool FLACtoMP3::flush() { outputBufferSize ); while (nwrite == -1) { //-1 is returned when there was not enough space in the given buffer - std::cout << outputBufferSize << " bytes in the output buffer wasn't enough" << std::endl; + log(major, std::to_string(outputBufferSize) + " bytes in the output buffer wasn't enough");; outputBufferSize = outputBufferSize * 2; delete[] outputBuffer; outputBuffer = new uint8_t[outputBufferSize]; - std::cout << "allocating " << outputBufferSize << " bytes" << std::endl; + log(major, "allocating " + std::to_string(outputBufferSize) + " bytes"); nwrite = lame_encode_buffer_interleaved( encoder, @@ -352,10 +391,10 @@ bool FLACtoMP3::flush() { return actuallyWritten == 1; } else { if (nwrite == 0) { - //std::cout << "encoding flush encoded 0 bytes, skipping write" << std::endl; + log(minor, "encoding flush encoded 0 bytes, skipping write"); return true; } else { - std::cout << "encoding flush failed, error: " << nwrite << std::endl; + log(fatal, "encoding flush failed. Code = : " + std::to_string(nwrite)); return false; } } @@ -391,20 +430,20 @@ FLAC__StreamDecoderWriteStatus FLACtoMP3::write( // std::cout << "ERROR: this example only supports 16bit stereo streams" << std::endl; // return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT; // } + FLACtoMP3* self = static_cast(client_data); if (frame->header.channels != 2) { - std::cout << "ERROR: This frame contains " << frame->header.channels << " channels (should be 2)" << std::endl; + self->log(fatal, "ERROR: This frame contains " + std::to_string(frame->header.channels) + " channels (should be 2)"); return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT; } if (buffer[0] == NULL) { - std::cout << "ERROR: buffer [0] is NULL" << std::endl; + self->log(fatal, "ERROR: buffer [0] is NULL"); return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT; } if (buffer[1] == NULL) { - std::cout << "ERROR: buffer [1] is NULL" << std::endl; + self->log(fatal, "ERROR: buffer [1] is NULL"); return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT; } - FLACtoMP3* self = static_cast(client_data); bool result = self->decodeFrame(buffer, frame->header.blocksize); if (result) @@ -414,6 +453,34 @@ FLAC__StreamDecoderWriteStatus FLACtoMP3::write( } void FLACtoMP3::error(const FLAC__StreamDecoder* decoder, FLAC__StreamDecoderErrorStatus status, void* client_data) { - (void)decoder, (void)client_data; - std::cout << "Got error callback: " << FLAC__StreamDecoderErrorStatusString[status] << std::endl; + (void)decoder; + FLACtoMP3* self = static_cast(client_data); + std::string errText(FLAC__StreamDecoderErrorStatusString[status]); + self->log(Loggable::error, "Got error callback: " + errText); +} + +void FLACtoMP3::setTagTitle(const std::string_view& title) { + id3tag_set_title(encoder, title.data()); //to set at least some possibly encoding broken id3v1 tag + int res = setTagUSC2("TIT2", title); + if (res != 0) + log(warning, "Couldn't set Title tag. Code = " + std::to_string(res)); +} + +void FLACtoMP3::setTagAlbum(const std::string_view& album) { + id3tag_set_album(encoder, album.data()); //to set at least some possibly encoding broken id3v1 tag + int res = setTagUSC2("TALB", album); + if (res != 0) + log(warning, "Couldn't set Album tag. Code = " + std::to_string(res)); +} + +void FLACtoMP3::setTagArtist(const std::string_view& artist) { + id3tag_set_artist(encoder, artist.data()); //to set at least some possibly encoding broken id3v1 tag + int res = setTagUSC2("TPE1", artist); + if (res != 0) + log(warning, "Couldn't set Artist tag. Code = " + std::to_string(res)); +} + +int FLACtoMP3::setTagUSC2(const std::string_view& field, const std::string_view& value) { + std::u16string utf16 = std::u16string({0xFEFF}) + usc2convertor.from_bytes(value.data()); + return id3tag_set_textinfo_utf16(encoder, field.data(), (unsigned short*)utf16.c_str()); } diff --git a/flactomp3.h b/flactomp3.h index f59f9da..e44859a 100644 --- a/flactomp3.h +++ b/flactomp3.h @@ -6,13 +6,16 @@ #include #include -#include +#include +#include #include #include -class FLACtoMP3 { +#include "loggable.h" + +class FLACtoMP3 : public Loggable { public: - FLACtoMP3(uint8_t size = 4); + FLACtoMP3(Severity severity = info, uint8_t size = 4); ~FLACtoMP3(); void setInputFile(const std::string& path); @@ -29,6 +32,12 @@ private: bool tryKnownTag(std::string_view source); bool scaleJPEG(const FLAC__StreamMetadata_Picture& picture); + void setTagTitle(const std::string_view& title); + void setTagAlbum(const std::string_view& album); + void setTagArtist(const std::string_view& artist); + + int setTagUSC2(const std::string_view& field, const std::string_view& value); + static void error(const FLAC__StreamDecoder *decoder, FLAC__StreamDecoderErrorStatus status, void *client_data); static void metadata(const FLAC__StreamDecoder *decoder, const FLAC__StreamMetadata *metadata, void *client_data); static FLAC__StreamDecoderWriteStatus write( @@ -56,4 +65,5 @@ private: uint32_t outputBufferSize; bool outputInitilized; bool downscaleAlbumArt; + std::wstring_convert, char16_t> usc2convertor; }; diff --git a/loggable.cpp b/loggable.cpp new file mode 100644 index 0000000..da551c9 --- /dev/null +++ b/loggable.cpp @@ -0,0 +1,18 @@ +#include "loggable.h" + +Loggable::Loggable(Loggable::Severity severity): + currentSeverity(severity), + history() +{} + +Loggable::~Loggable() +{} + +void Loggable::log(Loggable::Severity severity, const std::string& comment) const { + if (severity >= currentSeverity) + history.emplace_back(severity, comment); +} + +std::list> Loggable::getHistory() const { + return history; +} diff --git a/loggable.h b/loggable.h new file mode 100644 index 0000000..9b7ba78 --- /dev/null +++ b/loggable.h @@ -0,0 +1,28 @@ +#pragma once + +#include +#include + +class Loggable { +public: + enum Severity { + debug, + info, + minor, + major, + warning, + error, + fatal + }; + typedef std::pair Message; + + Loggable(Severity severity); + ~Loggable(); + + void log (Severity severity, const std::string& comment) const; + std::list getHistory() const; + +private: + const Severity currentSeverity; + mutable std::list history; +}; diff --git a/main.cpp b/main.cpp index 618fa0a..a002979 100644 --- a/main.cpp +++ b/main.cpp @@ -17,13 +17,13 @@ int main(int argc, char **argv) { } const std::string firstArgument(argv[1]); - const std::string secondArgument(argv[2]); - if (firstArgument == "--help") { printHelp(); return 0; } + const std::string secondArgument(argv[2]); + TaskManager taskManager; taskManager.start(); @@ -35,7 +35,7 @@ int main(int argc, char **argv) { 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; + std::cout << std::endl << "Encoding is done, it took " << seconds.count() << " seconds in total, enjoy!" << std::endl; taskManager.stop(); diff --git a/taskmanager.cpp b/taskmanager.cpp index a491927..4d34fb4 100644 --- a/taskmanager.cpp +++ b/taskmanager.cpp @@ -2,11 +2,32 @@ #include "flactomp3.h" +constexpr const std::array logSettings({ + /*debug*/ "\e[90m", + /*info*/ "\e[32m", + /*minor*/ "\e[34m", + /*major*/ "\e[94m", + /*warning*/ "\e[33m", + /*error*/ "\e[31m", + /*fatal*/ "\e[91m" +}); + +constexpr const std::array logHeaders({ + /*debug*/ "DEBUG: ", + /*info*/ "INFO: ", + /*minor*/ "MINOR: ", + /*major*/ "MAJOR: ", + /*warning*/ "WARNING: ", + /*error*/ "ERROR: ", + /*fatal*/ "FATAL: " +}); + TaskManager::TaskManager(): busyThreads(0), maxTasks(0), completeTasks(0), terminate(false), + printMutex(), queueMutex(), busyMutex(), loopConditional(), @@ -62,9 +83,9 @@ void TaskManager::loop() { jobs.pop(); } - job(pair.first, pair.second); + JobResult result = job(pair.first, pair.second); ++completeTasks; - printProgress(); + printProgress(result, pair.first, pair.second); --busyThreads; waitConditional.notify_all(); } @@ -96,13 +117,38 @@ void TaskManager::wait() { waitConditional.wait(lock, boundWaitCondition); } -bool TaskManager::job(const std::string& source, const std::string& destination) { +TaskManager::JobResult TaskManager::job(const std::string& source, const std::string& destination) { FLACtoMP3 convertor; convertor.setInputFile(source); convertor.setOutputFile(destination); - return convertor.run(); + bool result = convertor.run(); + return {result, convertor.getHistory()}; } void TaskManager::printProgress() const { + std::unique_lock lock(printMutex); std::cout << "\r" << completeTasks << "/" << maxTasks << std::flush; } + +void TaskManager::printProgress(const TaskManager::JobResult& result, const std::string& source, const std::string& destination) const { + std::unique_lock lock(printMutex); + if (result.first) { + if (result.second.size() > 0) { + std::cout << "\r\e[1m" << "Encoding complete but there are messages about it" << "\e[0m" << std::endl; + printLog(result, source, destination); + } + } else { + std::cout << "\r\e[1m" << "Encoding failed!" << "\e[0m" << std::endl; + printLog(result, source, destination); + } + std::cout << "\r" << completeTasks << "/" << maxTasks << std::flush; +} + +void TaskManager::printLog(const TaskManager::JobResult& result, const std::string& source, const std::string& destination) { + std::cout << "Source: \t" << source << std::endl; + std::cout << "Destination: \t" << destination << std::endl; + for (const Loggable::Message& msg : result.second) { + std::cout << "\t" << logSettings[msg.first] << "\e[1m" << logHeaders[msg.first] << "\e[22m" << msg.second << std::endl; + } + std::cout << "\e[0m" << std::endl; +} diff --git a/taskmanager.h b/taskmanager.h index fd23c6c..4546344 100644 --- a/taskmanager.h +++ b/taskmanager.h @@ -5,11 +5,17 @@ #include #include #include +#include #include #include #include +#include +#include + +#include "loggable.h" class TaskManager { + typedef std::pair> JobResult; public: TaskManager(); ~TaskManager(); @@ -20,18 +26,20 @@ public: bool busy() const; void wait(); void printProgress() const; + void printProgress(const JobResult& result, const std::string& source, const std::string& destination) const; private: void loop(); bool loopCondition() const; bool waitCondition() const; - static bool job(const std::string& source, const std::string& destination); - + static JobResult job(const std::string& source, const std::string& destination); + static void printLog(const JobResult& result, const std::string& source, const std::string& destination); private: std::atomic busyThreads; std::atomic maxTasks; std::atomic completeTasks; bool terminate; + mutable std::mutex printMutex; mutable std::mutex queueMutex; std::mutex busyMutex; std::condition_variable loopConditional;