diff --git a/CHANGELOG.md b/CHANGELOG.md index acae7f2..9fbc7bd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,7 @@ # Changelog -## MLC 1.3.4 (March 30, 2025) +## MLC 1.3.4 (UNRELEASED) - Build fixes -- Source and Destination paths now can contain spaces -- Regex to copy non music files can have spaces -- A feature to define a regex to exclude paths from rendering ## MLC 1.3.3 (October 13, 2023) - Regex to specify non-music files to copy @@ -31,7 +28,7 @@ - New logging system - Artist, Album artist, Album and Title are now utf16 encoded, should fix broken titles - BPM tag is now rounded, as it supposed to be by spec -- Lyrics is not set now for USLT tag for unsynchronized lyrics is not supported in LAME +- 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) diff --git a/README.md b/README.md index c08255a..4beaa8b 100644 --- a/README.md +++ b/README.md @@ -13,13 +13,13 @@ This is a program to compile your local lossless music library to lossy formats ### Building -```shell -git clone https://git.macaw.me/blue/mlc -cd mlc -mkdir build -cd build -cmake .. -cmake --build . +```sh +$ git clone https://git.macaw.me/blue/mlc +$ cd mlc +$ mkdir build +$ cd build +$ cmake .. +$ cmake --build . ``` ### Usage @@ -27,19 +27,19 @@ cmake --build . Just to compile lossless library to lossy you can use this command assuming you are in the same directory you have just built `MLC`: -```shell +``` ./mlc path/to/lossless/library path/to/store/lossy/library ``` There are more ways to use `MLC`, please refer to help for more options: -```shell -./mlc help +```sh +$ ./mlc help # or -./mlc --help +$ ./mlc --help # # or -./mlc -h +$ ./mlc -h ``` `MLC` has a way to configure conversion process, it will generate global for you user config on the first launch. @@ -49,16 +49,14 @@ You can also make local configs for each directory you launch mlc from. To output the default config run the following, assuming you are in the same directory you have just built `MLC`: -```shell -./mlc config #to print -#or -./mlc config > config.conf #to save to config.conf in current directory +```sh +$ ./mlc config ``` To use non default config run the following, assuming you are in the same directory you have just built `MLC`: -```shell -./mlc path/to/lossless/library path/to/store/lossy/library -c path/to/config/file +```sh +$ ./mlc path/to/lossless/library path/to/store/lossy/library -c path/to/config/file ``` ### About diff --git a/src/collection.cpp b/src/collection.cpp index c47e8ed..dad6991 100644 --- a/src/collection.cpp +++ b/src/collection.cpp @@ -6,12 +6,11 @@ namespace fs = std::filesystem; static const std::string flac(".flac"); -Collection::Collection(const std::filesystem::path& path, const std::shared_ptr<TaskManager>& tm, const std::shared_ptr<Settings>& st): +Collection::Collection(const std::filesystem::path& path, TaskManager* tm) : path(path), countMusical(0), counted(false), - taskManager(tm), - settings(st) + taskManager(tm) {} Collection::~Collection() @@ -34,16 +33,13 @@ uint32_t Collection::countMusicFiles() const { ++countMusical; } else if (fs::is_directory(path)) { for (const fs::directory_entry& entry : fs::directory_iterator(path)) { - if (settings->isExcluded(entry.path())) - continue; - switch (entry.status().type()) { case fs::file_type::regular: if (isMusic(entry.path())) ++countMusical; break; case fs::file_type::directory: { - Collection collection(entry.path(), taskManager, settings); + Collection collection(entry.path()); countMusical += collection.countMusicFiles(); } break; default: @@ -57,24 +53,25 @@ uint32_t Collection::countMusicFiles() const { } void Collection::convert(const std::string& outPath) { + if (taskManager == nullptr) + throw 6; + fs::path out = fs::absolute(outPath); fs::create_directories(out); out = fs::canonical(outPath); for (const fs::directory_entry& entry : fs::directory_iterator(path)) { - fs::path sourcePath = entry.path(); - if (settings->isExcluded(sourcePath)) - continue; - switch (entry.status().type()) { case fs::file_type::regular: { + fs::path sourcePath = entry.path(); if (isMusic(sourcePath)) taskManager->queueConvert(sourcePath, out / sourcePath.stem()); else taskManager->queueCopy(sourcePath, out / sourcePath.filename()); } break; case fs::file_type::directory: { - Collection collection(sourcePath, taskManager, settings); + fs::path sourcePath = entry.path(); + Collection collection(sourcePath, taskManager); fs::path::iterator itr = sourcePath.end(); --itr; collection.convert(std::string(out / *itr)); diff --git a/src/collection.h b/src/collection.h index bb107fb..1a3c254 100644 --- a/src/collection.h +++ b/src/collection.h @@ -3,16 +3,14 @@ #include <string> #include <iostream> #include <filesystem> -#include <memory> -#include "settings.h" #include "flactomp3.h" class TaskManager; class Collection { public: - Collection(const std::filesystem::path& path, const std::shared_ptr<TaskManager>& tm, const std::shared_ptr<Settings>& st); + Collection(const std::filesystem::path& path, TaskManager* tm = nullptr); ~Collection(); void list() const; @@ -26,7 +24,6 @@ private: std::filesystem::path path; mutable uint32_t countMusical; mutable bool counted; - std::shared_ptr<TaskManager> taskManager; - std::shared_ptr<Settings> settings; + TaskManager* taskManager; }; diff --git a/src/default.conf b/src/default.conf index 97f6a56..d7ef46d 100644 --- a/src/default.conf +++ b/src/default.conf @@ -92,15 +92,7 @@ # Variable bitrate # Switches on or off variable bitrate -# VBR files are usually smaller, but not supposed to be worse +# VBR files are usually smaller, but not supped to be worse # in terms of quality. VBR files might be a bit more tricky for the player # Allowed values are: [true, false] #vbr true - -# Exclude -# MLC renders any music file it finds in source directory -# UNLESS its path matches the following regex -# Allowed value is the regex without any additional syntax, -# for example: exclude [Ss]hamefull?\s[Ss]ong[Ss] -# If you don't want to exclude anything leave this option blank -#exclude \ No newline at end of file diff --git a/src/flactomp3.cpp b/src/flactomp3.cpp index 2f7d67c..bb9b27b 100644 --- a/src/flactomp3.cpp +++ b/src/flactomp3.cpp @@ -294,16 +294,28 @@ bool FLACtoMP3::scaleJPEG(const FLAC__StreamMetadata_Picture& picture) { return true; } -bool FLACtoMP3::decodeFrame(const int32_t * const buffer[], uint32_t size) { +bool FLACtoMP3::decodeFrame(const int32_t * const buffer[], uint32_t size, const uint32_t bitsPerSample) { if (!outputInitilized) { bool success = initializeOutput(); if (!success) return false; } + if (bitsPerSample >= 16) + return decodeFrameImpl(buffer, size, 1 << (bitsPerSample - 16)); + + return false; +} + +bool FLACtoMP3::decodeFrameImpl(const int32_t * const buffer[], uint32_t size, const uint32_t divisor) +{ for (size_t i = 0; i < size; ++i) { - pcm[pcmCounter++] = (int16_t)buffer[0][i]; - pcm[pcmCounter++] = (int16_t)buffer[1][i]; + int32_t left = buffer[0][i]; + int32_t right = buffer[1][i]; + left /= divisor; + right /= divisor; + pcm[pcmCounter++] = (int16_t)left; + pcm[pcmCounter++] = (int16_t)right; if (pcmCounter == pcmSize) return flush(); @@ -394,8 +406,8 @@ FLAC__StreamDecoderWriteStatus FLACtoMP3::write( self->logger.fatal("ERROR: buffer [1] is NULL"); return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT; } - - bool result = self->decodeFrame(buffer, frame->header.blocksize); + + bool result = self->decodeFrame(buffer, frame->header.blocksize, frame->header.bits_per_sample); if (result) return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE; diff --git a/src/flactomp3.h b/src/flactomp3.h index 0bf1a8a..d246b5a 100644 --- a/src/flactomp3.h +++ b/src/flactomp3.h @@ -29,7 +29,8 @@ private: void processTags(const FLAC__StreamMetadata_VorbisComment& tags); void processInfo(const FLAC__StreamMetadata_StreamInfo& info); void processPicture(const FLAC__StreamMetadata_Picture& picture); - bool decodeFrame(const int32_t * const buffer[], uint32_t size); + bool decodeFrame(const int32_t * const buffer[], uint32_t size, const uint32_t bitsPerSample); + bool decodeFrameImpl(const int32_t * const buffer[], uint32_t size, const uint32_t divisor); bool flush(); bool initializeOutput(); bool scaleJPEG(const FLAC__StreamMetadata_Picture& picture); diff --git a/src/main.cpp b/src/main.cpp index 50bded3..dedcae5 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -57,16 +57,16 @@ int main(int argc, char **argv) { } logger->setSeverity(settings->getLogLevel()); - std::shared_ptr<TaskManager> taskManager = std::make_shared<TaskManager>(settings, logger); - taskManager->start(); + TaskManager taskManager(settings, logger); + taskManager.start(); std::chrono::time_point start = std::chrono::system_clock::now(); - Collection collection(input, taskManager, settings); + Collection collection(input, &taskManager); collection.convert(output); - taskManager->wait(); + taskManager.wait(); std::cout << std::endl; - taskManager->stop(); + taskManager.stop(); std::chrono::time_point end = std::chrono::system_clock::now(); std::chrono::duration<double> seconds = end - start; diff --git a/src/settings.cpp b/src/settings.cpp index 9ecb3c0..39be0a7 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -17,7 +17,6 @@ enum class Option { destination, parallel, filesToCopy, - exclude, encodingQuality, outputQuality, vbr, @@ -43,7 +42,6 @@ constexpr std::array<std::string_view, static_cast<int>(Option::_optionsSize)> o "destination", "parallel", "filesToCopy", - "exclude", "encodingQuality", "outputQuality", "vbr" @@ -266,74 +264,69 @@ void Settings::readConfigLine(const std::string& line) { if (option == Option::_optionsSize) return; - std::string value; - std::getline(stream >> std::ws, value); - strip(value); - if (value.empty()) - return; - switch (option) { case Option::level: { std::string lv; - if (!logLevel.has_value() && std::istringstream(value) >> lv) { + if (!logLevel.has_value() && stream >> lv) { Logger::Severity level = Logger::stringToSeverity(lv); if (level < Logger::Severity::_severitySize) logLevel = level; } } break; case Option::type: { - std::string tp; - if (!outputType.has_value() && std::istringstream(value) >> tp) { - Type type = stringToType(tp); + std::string lv; + if (!outputType.has_value() && stream >> lv) { + Type type = stringToType(lv); if (type < _typesSize) outputType = type; } } break; case Option::source: { - if (!input.has_value()) { - input = value; - } else if (!output.has_value()) { - output = input; - input = value; + std::string path; + if (stream >> path) { + if (!input.has_value()) { + input = path; + } else if (!output.has_value()) { + output = input; + input = path; + } } } break; case Option::destination: { - if (!output.has_value()) - output = value; + std::string path; + if (!output.has_value() && stream >> path) + output = path; } break; case Option::parallel: { unsigned int count; - if (!threads.has_value() && std::istringstream(value) >> count) + if (!threads.has_value() && stream >> count) threads = count; } break; case Option::filesToCopy: { - if (!nonMusic.has_value()) { - if (value == "all") - value = ""; - else if (value == "none") - value = "a^"; + std::string regex; + if (!nonMusic.has_value() && stream >> regex) { + if (regex == "all") + regex = ""; + else if (regex == "none") + regex = "a^"; - nonMusic = value; + nonMusic = regex; } } break; - case Option::exclude: { - if (!excluded.has_value()) - excluded = value; - } break; case Option::outputQuality: { - unsigned int quality; - if (!outputQuality.has_value() && std::istringstream(value) >> quality) - outputQuality = std::clamp(quality, minQuality, maxQuality); + unsigned int value; + if (!outputQuality.has_value() && stream >> value) + outputQuality = std::clamp(value, minQuality, maxQuality); } break; case Option::encodingQuality: { - unsigned int quality; - if (!encodingQuality.has_value() && std::istringstream(value) >> quality) - encodingQuality = std::clamp(quality, minQuality, maxQuality); + unsigned int value; + if (!encodingQuality.has_value() && stream >> value) + encodingQuality = std::clamp(value, minQuality, maxQuality); } break; case Option::vbr: { - bool vb; - if (!vbr.has_value() && std::istringstream(value) >> std::boolalpha >> vb) - vbr = vb; + bool value; + if (!vbr.has_value() && stream >> std::boolalpha >> value) + vbr = value; } break; default: break; @@ -372,16 +365,9 @@ std::string Settings::resolvePath(const std::string& line) { } bool Settings::matchNonMusic(const std::string& fileName) const { - if (!nonMusic.has_value()) + if (nonMusic.has_value()) + return std::regex_search(fileName, nonMusic.value()); + else return true; - - return std::regex_search(fileName, nonMusic.value()); -} - -bool Settings::isExcluded(const std::string& path) const { - if (!excluded.has_value()) - return false; - - return std::regex_search(path, excluded.value()); } diff --git a/src/settings.h b/src/settings.h index de9fddc..d13b7f8 100644 --- a/src/settings.h +++ b/src/settings.h @@ -40,7 +40,6 @@ public: Action getAction() const; unsigned int getThreads() const; bool matchNonMusic(const std::string& fileName) const; - bool isExcluded(const std::string& path) const; unsigned char getEncodingQuality() const; unsigned char getOutputQuality() const; bool getVBR() const; @@ -70,7 +69,6 @@ private: std::optional<std::string> configPath; std::optional<unsigned int> threads; std::optional<std::regex> nonMusic; - std::optional<std::regex> excluded; std::optional<unsigned char> encodingQuality; std::optional<unsigned char> outputQuality; std::optional<bool> vbr; diff --git a/src/taskmanager.cpp b/src/taskmanager.cpp index 03693d4..81cd97f 100644 --- a/src/taskmanager.cpp +++ b/src/taskmanager.cpp @@ -22,9 +22,6 @@ TaskManager::~TaskManager() { } void TaskManager::queueConvert(const std::filesystem::path& source, const std::filesystem::path& destination) { - if (settings->isExcluded(source)) - return; - std::unique_lock<std::mutex> lock(queueMutex); jobs.emplace(Job::convert, source, destination); @@ -86,7 +83,7 @@ void TaskManager::loop() { lock.lock(); ++completeTasks; - printResult(job, result); + printResilt(job, result); --busyThreads; lock.unlock(); waitConditional.notify_all(); @@ -141,7 +138,7 @@ TaskManager::JobResult TaskManager::execute(Job& job) { }}; } -void TaskManager::printResult(const TaskManager::Job& job, const TaskManager::JobResult& result) { +void TaskManager::printResilt(const TaskManager::Job& job, const TaskManager::JobResult& result) { std::string msg; switch (job.type) { case Job::copy: diff --git a/src/taskmanager.h b/src/taskmanager.h index 4955ece..022c016 100644 --- a/src/taskmanager.h +++ b/src/taskmanager.h @@ -36,7 +36,7 @@ public: private: void loop(); JobResult execute(Job& job); - void printResult(const Job& job, const JobResult& result); + void printResilt(const Job& job, const JobResult& result); static JobResult mp3Job(const Job& job, const std::shared_ptr<Settings>& settings); static JobResult copyJob(const Job& job, const std::shared_ptr<Settings>& settings);