Compare commits
No commits in common. "master" and "1.3.3" have entirely different histories.
20 changed files with 125 additions and 258 deletions
10
CHANGELOG.md
10
CHANGELOG.md
|
@ -1,11 +1,5 @@
|
||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
## MLC 1.3.4 (March 30, 2025)
|
|
||||||
- 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)
|
## MLC 1.3.3 (October 13, 2023)
|
||||||
- Regex to specify non-music files to copy
|
- Regex to specify non-music files to copy
|
||||||
- Encoding settings (VBR/CBR, encoding quality, output quality)
|
- Encoding settings (VBR/CBR, encoding quality, output quality)
|
||||||
|
@ -29,9 +23,9 @@
|
||||||
|
|
||||||
## MLC 1.1.0 (July 23, 2023)
|
## MLC 1.1.0 (July 23, 2023)
|
||||||
- New logging system
|
- New logging system
|
||||||
- Artist, Album artist, Album and Title are now utf16 encoded, should fix broken titles
|
- 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
|
- 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
|
- Date bug fix, it was MMDD instead of standard DDMM
|
||||||
|
|
||||||
## MLC 1.0.1 (July 21, 2023)
|
## MLC 1.0.1 (July 21, 2023)
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
cmake_minimum_required(VERSION 3.5)
|
cmake_minimum_required(VERSION 3.5)
|
||||||
project(
|
project(
|
||||||
mlc
|
mlc
|
||||||
VERSION 1.3.4
|
VERSION 1.3.3
|
||||||
DESCRIPTION "Media Library Compiler: converts your media library to a lossy compilation"
|
DESCRIPTION "Media Library Compiler: rips your media library to a lossy compilation"
|
||||||
LANGUAGES CXX
|
LANGUAGES CXX
|
||||||
)
|
)
|
||||||
cmake_policy(SET CMP0076 NEW)
|
cmake_policy(SET CMP0076 NEW)
|
||||||
|
@ -25,25 +25,24 @@ message("Compilation options: " ${COMPILE_OPTIONS})
|
||||||
set(CMAKE_CXX_STANDARD 17)
|
set(CMAKE_CXX_STANDARD 17)
|
||||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake")
|
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake")
|
||||||
set(THREADS_PREFER_PTHREAD_FLAG ON)
|
|
||||||
|
|
||||||
|
find_package(PkgConfig REQUIRED)
|
||||||
find_package(FLAC REQUIRED)
|
find_package(FLAC REQUIRED)
|
||||||
find_package(JPEG REQUIRED)
|
find_package(JPEG REQUIRED)
|
||||||
find_package(LAME REQUIRED)
|
|
||||||
find_package(TAGLIB REQUIRED)
|
|
||||||
find_package(Threads REQUIRED)
|
|
||||||
|
|
||||||
add_executable(${PROJECT_NAME})
|
pkg_check_modules(LAME REQUIRED IMPORTED_TARGET lame)
|
||||||
|
pkg_check_modules(TAGLIB REQUIRED IMPORTED_TARGET taglib)
|
||||||
|
|
||||||
|
add_executable(mlc)
|
||||||
target_compile_options(${PROJECT_NAME} PRIVATE ${COMPILE_OPTIONS})
|
target_compile_options(${PROJECT_NAME} PRIVATE ${COMPILE_OPTIONS})
|
||||||
|
|
||||||
add_subdirectory(src)
|
add_subdirectory(src)
|
||||||
|
|
||||||
target_link_libraries(${PROJECT_NAME}
|
target_link_libraries(mlc
|
||||||
FLAC::FLAC
|
FLAC::FLAC
|
||||||
LAME::LAME
|
PkgConfig::LAME
|
||||||
JPEG::JPEG
|
JPEG::JPEG
|
||||||
TAGLIB::TAGLIB
|
PkgConfig::TAGLIB
|
||||||
Threads::Threads
|
|
||||||
)
|
)
|
||||||
|
|
||||||
install(TARGETS ${PROJECT_NAME} RUNTIME DESTINATION bin)
|
install(TARGETS mlc RUNTIME DESTINATION bin)
|
||||||
|
|
66
README.md
66
README.md
|
@ -1,8 +1,6 @@
|
||||||
# MLC - Music Library Compiler
|
# MLC - Music Library Compiler
|
||||||
|
|
||||||
[](https://aur.archlinux.org/packages/mlc/)
|
This is a program for compilation of your loseless music library to lossy formats
|
||||||
|
|
||||||
This is a program to compile your local lossless music library to lossy formats
|
|
||||||
|
|
||||||
### Prerequisites
|
### Prerequisites
|
||||||
|
|
||||||
|
@ -13,61 +11,23 @@ This is a program to compile your local lossless music library to lossy formats
|
||||||
|
|
||||||
### Building
|
### Building
|
||||||
|
|
||||||
```shell
|
```
|
||||||
git clone https://git.macaw.me/blue/mlc
|
$ git clone https://git.macaw.me/blue/mlc
|
||||||
cd mlc
|
$ cd mlc
|
||||||
mkdir build
|
$ mkdir build
|
||||||
cd build
|
$ cd build
|
||||||
cmake ..
|
$ cmake ..
|
||||||
cmake --build .
|
$ cmake --build .
|
||||||
```
|
```
|
||||||
|
|
||||||
### Usage
|
### Usage
|
||||||
|
|
||||||
Just to compile lossless library to lossy you can use this command
|
```
|
||||||
assuming you are in the same directory you have just built `MLC`:
|
./mlc path/to/loseless/library path/to/store/lossy/library
|
||||||
|
|
||||||
```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:
|
For now the program is very primitive, it only works to convert `.flac` files (it trusts the suffix) to `.mp3` VBR best possible quality slowest possible conversion algorithm.
|
||||||
|
|
||||||
```shell
|
MLC keeps file structure, copies all the non `.flac` files, tries to adapt some `.flac` (vorbis) tags to id3v1 and id3v2 of destination `.mp3` file.
|
||||||
./mlc help
|
|
||||||
# or
|
|
||||||
./mlc --help
|
|
||||||
#
|
|
||||||
# or
|
|
||||||
./mlc -h
|
|
||||||
```
|
|
||||||
|
|
||||||
`MLC` has a way to configure conversion process, it will generate global for you user config on the first launch.
|
For now it's siglethread, no interrupt controll, not even sure converted files are valid, no exotic cases handled, no conversion options can be passed. May be will improve in the futer.
|
||||||
It will be located in `~/.config/mlc.conf` and `MLC` will notify you when it's generated.
|
|
||||||
|
|
||||||
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
|
|
||||||
```
|
|
||||||
|
|
||||||
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
|
|
||||||
```
|
|
||||||
|
|
||||||
### About
|
|
||||||
|
|
||||||
For now the program is very primitive, it only works to convert `.flac` files (it trusts the suffix) to `.mp3`.
|
|
||||||
I'm planing to add more lossy formats first then, may be, more lossless.
|
|
||||||
|
|
||||||
`MLC` keeps file structure, copies all the non `.flac` files, tries to adapt some `.flac` (vorbis) tags to id3v1 and id3v2 of destination `.mp3` file.
|
|
||||||
You can set up output format and quality and which kind of files should it skip in the config.
|
|
||||||
|
|
||||||
By default `MLC` runs on as many threads as your system can provide to speed up conversion process.
|
|
||||||
You can set up correct amount of threads in the config.
|
|
||||||
|
|
|
@ -1,23 +0,0 @@
|
||||||
find_path(FLAC_INCLUDE_DIR FLAC/stream_decoder.h)
|
|
||||||
find_library(FLAC_LIBRARIES FLAC NAMES flac)
|
|
||||||
|
|
||||||
if(FLAC_INCLUDE_DIR AND FLAC_LIBRARIES)
|
|
||||||
set(FLAC_FOUND TRUE)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if(FLAC_FOUND)
|
|
||||||
add_library(FLAC::FLAC SHARED IMPORTED)
|
|
||||||
set_target_properties(FLAC::FLAC PROPERTIES
|
|
||||||
IMPORTED_LOCATION "${FLAC_LIBRARIES}"
|
|
||||||
INTERFACE_INCLUDE_DIRECTORIES "${FLAC_INCLUDE_DIR}/FLAC"
|
|
||||||
INTERFACE_LINK_LIBRARIES "${FLAC_LIBRARIES}"
|
|
||||||
)
|
|
||||||
if (NOT FLAC_FIND_QUIETLY)
|
|
||||||
message(STATUS "Found FLAC includes: ${FLAC_INCLUDE_DIR}/FLAC")
|
|
||||||
message(STATUS "Found FLAC library: ${FLAC_LIBRARIES}")
|
|
||||||
endif ()
|
|
||||||
else()
|
|
||||||
if (FLAC_FIND_REQUIRED)
|
|
||||||
message(FATAL_ERROR "Could NOT find FLAC development files")
|
|
||||||
endif ()
|
|
||||||
endif()
|
|
|
@ -1,26 +1,20 @@
|
||||||
#copied from here, thank you
|
#copied from here, thank you
|
||||||
#https://github.com/sipwise/sems/blob/master/cmake/FindLame.cmake
|
#https://github.com/sipwise/sems/blob/master/cmake/FindLame.cmake
|
||||||
|
|
||||||
find_path(LAME_INCLUDE_DIR lame/lame.h)
|
FIND_PATH(LAME_INCLUDE_DIR lame/lame.h)
|
||||||
find_library(LAME_LIBRARIES lame NAMES mp3lame)
|
FIND_LIBRARY(LAME_LIBRARIES NAMES mp3lame)
|
||||||
|
|
||||||
if(LAME_INCLUDE_DIR AND LAME_LIBRARIES)
|
IF(LAME_INCLUDE_DIR AND LAME_LIBRARIES)
|
||||||
set(LAME_FOUND TRUE)
|
SET(LAME_FOUND TRUE)
|
||||||
endif(LAME_INCLUDE_DIR AND LAME_LIBRARIES)
|
ENDIF(LAME_INCLUDE_DIR AND LAME_LIBRARIES)
|
||||||
|
|
||||||
if(LAME_FOUND)
|
IF(LAME_FOUND)
|
||||||
add_library(LAME::LAME SHARED IMPORTED)
|
IF (NOT Lame_FIND_QUIETLY)
|
||||||
set_target_properties(LAME::LAME PROPERTIES
|
MESSAGE(STATUS "Found lame includes: ${LAME_INCLUDE_DIR}/lame/lame.h")
|
||||||
IMPORTED_LOCATION "${LAME_LIBRARIES}"
|
MESSAGE(STATUS "Found lame library: ${LAME_LIBRARIES}")
|
||||||
INTERFACE_INCLUDE_DIRECTORIES "${LAME_INCLUDE_DIR}/lame"
|
ENDIF (NOT Lame_FIND_QUIETLY)
|
||||||
INTERFACE_LINK_LIBRARIES "${LAME_LIBRARIES}"
|
ELSE(LAME_FOUND)
|
||||||
)
|
IF (Lame_FIND_REQUIRED)
|
||||||
if (NOT Lame_FIND_QUIETLY)
|
MESSAGE(FATAL_ERROR "Could NOT find lame development files")
|
||||||
message(STATUS "Found lame includes: ${LAME_INCLUDE_DIR}/lame")
|
ENDIF (Lame_FIND_REQUIRED)
|
||||||
message(STATUS "Found lame library: ${LAME_LIBRARIES}")
|
ENDIF(LAME_FOUND)
|
||||||
endif (NOT Lame_FIND_QUIETLY)
|
|
||||||
else(LAME_FOUND)
|
|
||||||
if (Lame_FIND_REQUIRED)
|
|
||||||
message(FATAL_ERROR "Could NOT find lame development files")
|
|
||||||
endif (Lame_FIND_REQUIRED)
|
|
||||||
endif(LAME_FOUND)
|
|
||||||
|
|
|
@ -1,24 +0,0 @@
|
||||||
find_path(TAGLIB_INCLUDE_DIR taglib/id3v2tag.h)
|
|
||||||
find_library(TAGLIB_LIBRARIES taglib NAMES TAGLIB tag)
|
|
||||||
|
|
||||||
if(TAGLIB_INCLUDE_DIR AND TAGLIB_LIBRARIES)
|
|
||||||
set(TAGLIB_FOUND TRUE)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if(TAGLIB_FOUND)
|
|
||||||
add_library(TAGLIB::TAGLIB SHARED IMPORTED)
|
|
||||||
set_target_properties(TAGLIB::TAGLIB PROPERTIES
|
|
||||||
IMPORTED_LOCATION "${TAGLIB_LIBRARIES}"
|
|
||||||
INTERFACE_INCLUDE_DIRECTORIES "${TAGLIB_INCLUDE_DIR}/taglib"
|
|
||||||
INTERFACE_LINK_LIBRARIES "${TAGLIB_LIBRARIES}"
|
|
||||||
)
|
|
||||||
if (NOT TAGLIB_FIND_QUIETLY)
|
|
||||||
message(STATUS "Found TAGLIB includes: ${TAGLIB_INCLUDE_DIR}/taglib")
|
|
||||||
message(STATUS "Found TAGLIB library: ${TAGLIB_LIBRARIES}")
|
|
||||||
endif ()
|
|
||||||
else()
|
|
||||||
if (TAGLIB_FIND_REQUIRED)
|
|
||||||
message(FATAL_ERROR "Could NOT find TAGLIB development files")
|
|
||||||
endif ()
|
|
||||||
endif()
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
# Maintainer: Yury Gubich <blue@macaw.me>
|
# Maintainer: Yury Gubich <blue@macaw.me>
|
||||||
pkgname=mlc
|
pkgname=mlc
|
||||||
pkgver=1.3.4
|
pkgver=1.3.3
|
||||||
pkgrel=1
|
pkgrel=1
|
||||||
pkgdesc="Media Library Compiler: rips your media library to a lossy compilation"
|
pkgdesc="Media Library Compiler: rips your media library to a lossy compilation"
|
||||||
arch=('i686' 'x86_64')
|
arch=('i686' 'x86_64')
|
||||||
|
|
|
@ -6,12 +6,11 @@ namespace fs = std::filesystem;
|
||||||
|
|
||||||
static const std::string flac(".flac");
|
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),
|
path(path),
|
||||||
countMusical(0),
|
countMusical(0),
|
||||||
counted(false),
|
counted(false),
|
||||||
taskManager(tm),
|
taskManager(tm)
|
||||||
settings(st)
|
|
||||||
{}
|
{}
|
||||||
|
|
||||||
Collection::~Collection()
|
Collection::~Collection()
|
||||||
|
@ -34,16 +33,13 @@ uint32_t Collection::countMusicFiles() const {
|
||||||
++countMusical;
|
++countMusical;
|
||||||
} else if (fs::is_directory(path)) {
|
} else if (fs::is_directory(path)) {
|
||||||
for (const fs::directory_entry& entry : fs::directory_iterator(path)) {
|
for (const fs::directory_entry& entry : fs::directory_iterator(path)) {
|
||||||
if (settings->isExcluded(entry.path()))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
switch (entry.status().type()) {
|
switch (entry.status().type()) {
|
||||||
case fs::file_type::regular:
|
case fs::file_type::regular:
|
||||||
if (isMusic(entry.path()))
|
if (isMusic(entry.path()))
|
||||||
++countMusical;
|
++countMusical;
|
||||||
break;
|
break;
|
||||||
case fs::file_type::directory: {
|
case fs::file_type::directory: {
|
||||||
Collection collection(entry.path(), taskManager, settings);
|
Collection collection(entry.path());
|
||||||
countMusical += collection.countMusicFiles();
|
countMusical += collection.countMusicFiles();
|
||||||
} break;
|
} break;
|
||||||
default:
|
default:
|
||||||
|
@ -57,24 +53,25 @@ uint32_t Collection::countMusicFiles() const {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Collection::convert(const std::string& outPath) {
|
void Collection::convert(const std::string& outPath) {
|
||||||
|
if (taskManager == nullptr)
|
||||||
|
throw 6;
|
||||||
|
|
||||||
fs::path out = fs::absolute(outPath);
|
fs::path out = fs::absolute(outPath);
|
||||||
|
|
||||||
fs::create_directories(out);
|
fs::create_directories(out);
|
||||||
out = fs::canonical(outPath);
|
out = fs::canonical(outPath);
|
||||||
for (const fs::directory_entry& entry : fs::directory_iterator(path)) {
|
for (const fs::directory_entry& entry : fs::directory_iterator(path)) {
|
||||||
fs::path sourcePath = entry.path();
|
|
||||||
if (settings->isExcluded(sourcePath))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
switch (entry.status().type()) {
|
switch (entry.status().type()) {
|
||||||
case fs::file_type::regular: {
|
case fs::file_type::regular: {
|
||||||
|
fs::path sourcePath = entry.path();
|
||||||
if (isMusic(sourcePath))
|
if (isMusic(sourcePath))
|
||||||
taskManager->queueConvert(sourcePath, out / sourcePath.stem());
|
taskManager->queueConvert(sourcePath, out / sourcePath.stem());
|
||||||
else
|
else
|
||||||
taskManager->queueCopy(sourcePath, out / sourcePath.filename());
|
taskManager->queueCopy(sourcePath, out / sourcePath.filename());
|
||||||
} break;
|
} break;
|
||||||
case fs::file_type::directory: {
|
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();
|
fs::path::iterator itr = sourcePath.end();
|
||||||
--itr;
|
--itr;
|
||||||
collection.convert(std::string(out / *itr));
|
collection.convert(std::string(out / *itr));
|
||||||
|
|
|
@ -3,16 +3,14 @@
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
#include <memory>
|
|
||||||
|
|
||||||
#include "settings.h"
|
|
||||||
#include "flactomp3.h"
|
#include "flactomp3.h"
|
||||||
|
|
||||||
class TaskManager;
|
class TaskManager;
|
||||||
|
|
||||||
class Collection {
|
class Collection {
|
||||||
public:
|
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();
|
~Collection();
|
||||||
|
|
||||||
void list() const;
|
void list() const;
|
||||||
|
@ -26,7 +24,6 @@ private:
|
||||||
std::filesystem::path path;
|
std::filesystem::path path;
|
||||||
mutable uint32_t countMusical;
|
mutable uint32_t countMusical;
|
||||||
mutable bool counted;
|
mutable bool counted;
|
||||||
std::shared_ptr<TaskManager> taskManager;
|
TaskManager* taskManager;
|
||||||
std::shared_ptr<Settings> settings;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -5,12 +5,12 @@
|
||||||
#
|
#
|
||||||
# Use # sign for comments
|
# Use # sign for comments
|
||||||
#
|
#
|
||||||
# All printed commented out values are default values
|
# All printed comented out values are default values
|
||||||
#
|
#
|
||||||
# You should have one of this files as ~/.config/mlc.conf,
|
# You should have one of this files as ~/.config/mlc.conf,
|
||||||
# if you have started mlc at least once.
|
# if you have started mlc at least once.
|
||||||
# This is going to be used every time you launch mlc,
|
# This is going to be used every time you launch mlc,
|
||||||
# so, edit it if you wish to change default behavior.
|
# so, edit it if you wish to change default behaviour.
|
||||||
#
|
#
|
||||||
# Alternatively, you can launch `mlc -c customConfig.conf`
|
# Alternatively, you can launch `mlc -c customConfig.conf`
|
||||||
# if you wish to override ~/.config/mlc.conf file
|
# if you wish to override ~/.config/mlc.conf file
|
||||||
|
@ -20,26 +20,26 @@
|
||||||
#level info
|
#level info
|
||||||
|
|
||||||
# Output type
|
# Output type
|
||||||
# Allowed values are: [mp3] (more coming soon)
|
# Allowed values are: [mp3] (more comming soon)
|
||||||
#type mp3
|
#type mp3
|
||||||
|
|
||||||
# Source collection path
|
# Source collection path
|
||||||
# This is a default path to your music collection source.
|
# This is a default path to your music collection source.
|
||||||
# It's useful to set it when you always encode the same collection
|
# It's usefull to set it when you always encode the same collection
|
||||||
# Leaving this empty (as it is by default) will make you always
|
# Leaving this empty (as it is by default) will make you always
|
||||||
# specify source in the command line
|
# specify source in the command line
|
||||||
#source
|
#source
|
||||||
|
|
||||||
# Destination path
|
# Destingation path
|
||||||
# This is a default path for your encoding destination
|
# This is a default path for your encoding destination
|
||||||
# It's useful to set it when you often encode your collection
|
# It's usefull to set it when you often encode your collection
|
||||||
# to the same output
|
# to the same output
|
||||||
# Leaving this empty (as it is by default) will make you always
|
# Leaving this empty (as it is by default) will make you always
|
||||||
# specify destination in the command line
|
# specify destination in the command line
|
||||||
#destination
|
#destination
|
||||||
|
|
||||||
# Parallel tasks
|
# Parallel tasks
|
||||||
# Defines how many threads are going to be started in parallel
|
# Defines how many threads are going to be started in parralel
|
||||||
# Allowed values are [0, 1, 2, 3 ...] etc
|
# Allowed values are [0, 1, 2, 3 ...] etc
|
||||||
# If it's set to 0 - amount of threads is going to be
|
# If it's set to 0 - amount of threads is going to be
|
||||||
# as high as your processor can effectively handle
|
# as high as your processor can effectively handle
|
||||||
|
@ -62,9 +62,9 @@
|
||||||
|
|
||||||
# Output quality
|
# Output quality
|
||||||
# Sets up output quality
|
# Sets up output quality
|
||||||
# The higher quality the less information is lost in compression
|
# The higher quality the less information is lonst in compression
|
||||||
# 0 is the highest possible quality for selected mode and type and results in the biggest file
|
# 0 is the highest possible quality for selected mode and type and results in the biggest file
|
||||||
# 9 is the lowest quality but results in the smallest file
|
# 9 is the lowest quality but results in the smalest file
|
||||||
# Allowed values are [0, 1, 2, ... 9]
|
# Allowed values are [0, 1, 2, ... 9]
|
||||||
# For the constant bitrate modes (CBR) the following table is valid
|
# For the constant bitrate modes (CBR) the following table is valid
|
||||||
# Quality | MP3 |
|
# Quality | MP3 |
|
||||||
|
@ -92,15 +92,7 @@
|
||||||
|
|
||||||
# Variable bitrate
|
# Variable bitrate
|
||||||
# Switches on or off 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
|
# in terms of quality. VBR files might be a bit more tricky for the player
|
||||||
# Allowed values are: [true, false]
|
# Allowedvalues are: [true, false]
|
||||||
#vbr true
|
#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
|
|
|
@ -1,6 +1,6 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <stream_decoder.h>
|
#include <FLAC/stream_decoder.h>
|
||||||
#include <lame.h>
|
#include <lame.h>
|
||||||
#include <jpeglib.h>
|
#include <jpeglib.h>
|
||||||
#include <id3v2tag.h>
|
#include <id3v2tag.h>
|
||||||
|
|
16
src/help
16
src/help
|
@ -20,23 +20,23 @@ Flags:
|
||||||
- sets action to help, and prints this page
|
- sets action to help, and prints this page
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
`mlc ~/Music compile/latest`
|
`mlc ~/Music comile/latest`
|
||||||
- reads config file from `~/.config/mlc.conf`
|
- reads config file from `~/.config/mlc.conf`
|
||||||
- if there was not - creates default that changes nothing
|
- if there was not - creates default that changes nothing
|
||||||
- creates directory `compile` in the CURRENT directory if it was not created before
|
- creates directory `conpile` in the CURRENT directory if it was not created before
|
||||||
- create `latest` directory in `compile` directory if it was not created before
|
- create `latest` directory in `compile` dirrectory if it was not created before
|
||||||
- searches for music in `~/Music` directory
|
- searches for music in `~/Music` directory
|
||||||
- converts all music to `compile/latest` directory
|
- converts all music to `comile/latest` directory
|
||||||
- copies all other files found in `~/Music` to `compile/latest`
|
- copies all other files found in `~/Music` to `comile/latest`
|
||||||
- any file name overlap will be overridden
|
- any file name overlap will be overriden
|
||||||
|
|
||||||
`mlc config > myConfig.conf`
|
`mlc config > myConfig.conf`
|
||||||
- prints default config to standard output
|
- prints default config to standard output
|
||||||
- unix operator `>` redirects output to a file `myConfig.conf`
|
- unix operator `>` redirects output to a file `myConfig.conf`
|
||||||
|
|
||||||
`mlc convert -c myConfig.conf`
|
`mlc rip -c myConfig.conf`
|
||||||
- reads file `myConfig.conf` from current directory
|
- reads file `myConfig.conf` from current directory
|
||||||
- if `myConfig.conf` has a valid `source` entry - uses it and uses `rip` as destination
|
- if `myConfig.conf` has a valid `source` entry - uses it and uses `rip` as destination
|
||||||
- if `myConfig.conf` has a valid `destination` entry - uses it and uses `rip` as source
|
- if `myConfig.conf` has a valid `destination` entry - uses it and uses `rip` as source
|
||||||
- if both present (don't do this way) - it depends on the order, who comes first - defines behavior
|
- if both present (don't do this way) - it depends on the order, who comes first - defines behaviour
|
||||||
- the rest is the same from the first example apart from default config
|
- the rest is the same from the first example apart from default config
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
|
||||||
constexpr std::array<std::string_view, static_cast<int>(Logger::Severity::_severitySize)> levels({
|
constexpr std::array<std::string_view, static_cast<int>(Logger::Severity::_sevetirySize)> levels({
|
||||||
"debug",
|
"debug",
|
||||||
"info",
|
"info",
|
||||||
"minor",
|
"minor",
|
||||||
|
@ -46,8 +46,8 @@ void Logger::fatal(const std::string& comment, bool colored) const {
|
||||||
|
|
||||||
Logger::Severity Logger::stringToSeverity(const std::string& line) {
|
Logger::Severity Logger::stringToSeverity(const std::string& line) {
|
||||||
unsigned char dist = std::distance(levels.begin(), std::find(levels.begin(), levels.end(), line));
|
unsigned char dist = std::distance(levels.begin(), std::find(levels.begin(), levels.end(), line));
|
||||||
if (dist < static_cast<unsigned char>(Severity::_severitySize))
|
if (dist < static_cast<unsigned char>(Severity::_sevetirySize))
|
||||||
return static_cast<Severity>(dist);
|
return static_cast<Severity>(dist);
|
||||||
|
|
||||||
return Severity::_severitySize;
|
return Severity::_sevetirySize;
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,7 @@ public:
|
||||||
warning,
|
warning,
|
||||||
error,
|
error,
|
||||||
fatal,
|
fatal,
|
||||||
_severitySize
|
_sevetirySize
|
||||||
};
|
};
|
||||||
using Message = std::pair<Severity, std::string>;
|
using Message = std::pair<Severity, std::string>;
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
||||||
constexpr const std::array<std::string_view, static_cast<int>(Logger::Severity::_severitySize)> logSettings({
|
constexpr const std::array<std::string_view, static_cast<int>(Logger::Severity::_sevetirySize)> logSettings({
|
||||||
/*debug*/ "\e[90m",
|
/*debug*/ "\e[90m",
|
||||||
/*info*/ "\e[32m",
|
/*info*/ "\e[32m",
|
||||||
/*minor*/ "\e[34m",
|
/*minor*/ "\e[34m",
|
||||||
|
@ -14,7 +14,7 @@ constexpr const std::array<std::string_view, static_cast<int>(Logger::Severity::
|
||||||
/*fatal*/ "\e[91m"
|
/*fatal*/ "\e[91m"
|
||||||
});
|
});
|
||||||
|
|
||||||
constexpr const std::array<std::string_view, static_cast<int>(Logger::Severity::_severitySize)> logHeaders({
|
constexpr const std::array<std::string_view, static_cast<int>(Logger::Severity::_sevetirySize)> logHeaders({
|
||||||
/*debug*/ "DEBUG: ",
|
/*debug*/ "DEBUG: ",
|
||||||
/*info*/ "INFO: ",
|
/*info*/ "INFO: ",
|
||||||
/*minor*/ "MINOR: ",
|
/*minor*/ "MINOR: ",
|
||||||
|
|
10
src/main.cpp
10
src/main.cpp
|
@ -57,16 +57,16 @@ int main(int argc, char **argv) {
|
||||||
}
|
}
|
||||||
|
|
||||||
logger->setSeverity(settings->getLogLevel());
|
logger->setSeverity(settings->getLogLevel());
|
||||||
std::shared_ptr<TaskManager> taskManager = std::make_shared<TaskManager>(settings, logger);
|
TaskManager taskManager(settings, logger);
|
||||||
taskManager->start();
|
taskManager.start();
|
||||||
|
|
||||||
std::chrono::time_point start = std::chrono::system_clock::now();
|
std::chrono::time_point start = std::chrono::system_clock::now();
|
||||||
Collection collection(input, taskManager, settings);
|
Collection collection(input, &taskManager);
|
||||||
collection.convert(output);
|
collection.convert(output);
|
||||||
|
|
||||||
taskManager->wait();
|
taskManager.wait();
|
||||||
std::cout << std::endl;
|
std::cout << std::endl;
|
||||||
taskManager->stop();
|
taskManager.stop();
|
||||||
|
|
||||||
std::chrono::time_point end = std::chrono::system_clock::now();
|
std::chrono::time_point end = std::chrono::system_clock::now();
|
||||||
std::chrono::duration<double> seconds = end - start;
|
std::chrono::duration<double> seconds = end - start;
|
||||||
|
|
|
@ -17,7 +17,6 @@ enum class Option {
|
||||||
destination,
|
destination,
|
||||||
parallel,
|
parallel,
|
||||||
filesToCopy,
|
filesToCopy,
|
||||||
exclude,
|
|
||||||
encodingQuality,
|
encodingQuality,
|
||||||
outputQuality,
|
outputQuality,
|
||||||
vbr,
|
vbr,
|
||||||
|
@ -43,7 +42,6 @@ constexpr std::array<std::string_view, static_cast<int>(Option::_optionsSize)> o
|
||||||
"destination",
|
"destination",
|
||||||
"parallel",
|
"parallel",
|
||||||
"filesToCopy",
|
"filesToCopy",
|
||||||
"exclude",
|
|
||||||
"encodingQuality",
|
"encodingQuality",
|
||||||
"outputQuality",
|
"outputQuality",
|
||||||
"vbr"
|
"vbr"
|
||||||
|
@ -205,14 +203,14 @@ unsigned char Settings::getOutputQuality() const {
|
||||||
if (outputQuality.has_value())
|
if (outputQuality.has_value())
|
||||||
return outputQuality.value();
|
return outputQuality.value();
|
||||||
else
|
else
|
||||||
return minQuality; //it means max possible quality, min is for min enum value
|
return minQuality; //actually, it means max possible quality
|
||||||
}
|
}
|
||||||
|
|
||||||
unsigned char Settings::getEncodingQuality() const {
|
unsigned char Settings::getEncodingQuality() const {
|
||||||
if (encodingQuality.has_value())
|
if (encodingQuality.has_value())
|
||||||
return encodingQuality.value();
|
return encodingQuality.value();
|
||||||
else
|
else
|
||||||
return minQuality; //it means max possible quality, min is for min enum value
|
return minQuality; //actually, it means max possible quality
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Settings::getVBR() const {
|
bool Settings::getVBR() const {
|
||||||
|
@ -266,74 +264,69 @@ void Settings::readConfigLine(const std::string& line) {
|
||||||
if (option == Option::_optionsSize)
|
if (option == Option::_optionsSize)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
std::string value;
|
|
||||||
std::getline(stream >> std::ws, value);
|
|
||||||
strip(value);
|
|
||||||
if (value.empty())
|
|
||||||
return;
|
|
||||||
|
|
||||||
switch (option) {
|
switch (option) {
|
||||||
case Option::level: {
|
case Option::level: {
|
||||||
std::string lv;
|
std::string lv;
|
||||||
if (!logLevel.has_value() && std::istringstream(value) >> lv) {
|
if (!logLevel.has_value() && stream >> lv) {
|
||||||
Logger::Severity level = Logger::stringToSeverity(lv);
|
Logger::Severity level = Logger::stringToSeverity(lv);
|
||||||
if (level < Logger::Severity::_severitySize)
|
if (level < Logger::Severity::_sevetirySize)
|
||||||
logLevel = level;
|
logLevel = level;
|
||||||
}
|
}
|
||||||
} break;
|
} break;
|
||||||
case Option::type: {
|
case Option::type: {
|
||||||
std::string tp;
|
std::string lv;
|
||||||
if (!outputType.has_value() && std::istringstream(value) >> tp) {
|
if (!outputType.has_value() && stream >> lv) {
|
||||||
Type type = stringToType(tp);
|
Type type = stringToType(lv);
|
||||||
if (type < _typesSize)
|
if (type < _typesSize)
|
||||||
outputType = type;
|
outputType = type;
|
||||||
}
|
}
|
||||||
} break;
|
} break;
|
||||||
case Option::source: {
|
case Option::source: {
|
||||||
|
std::string path;
|
||||||
|
if (stream >> path) {
|
||||||
if (!input.has_value()) {
|
if (!input.has_value()) {
|
||||||
input = value;
|
input = path;
|
||||||
} else if (!output.has_value()) {
|
} else if (!output.has_value()) {
|
||||||
output = input;
|
output = input;
|
||||||
input = value;
|
input = path;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} break;
|
} break;
|
||||||
case Option::destination: {
|
case Option::destination: {
|
||||||
if (!output.has_value())
|
std::string path;
|
||||||
output = value;
|
if (!output.has_value() && stream >> path)
|
||||||
|
output = path;
|
||||||
} break;
|
} break;
|
||||||
case Option::parallel: {
|
case Option::parallel: {
|
||||||
unsigned int count;
|
unsigned int count;
|
||||||
if (!threads.has_value() && std::istringstream(value) >> count)
|
if (!threads.has_value() && stream >> count)
|
||||||
threads = count;
|
threads = count;
|
||||||
} break;
|
} break;
|
||||||
case Option::filesToCopy: {
|
case Option::filesToCopy: {
|
||||||
if (!nonMusic.has_value()) {
|
std::string regex;
|
||||||
if (value == "all")
|
if (!nonMusic.has_value() && stream >> regex) {
|
||||||
value = "";
|
if (regex == "all")
|
||||||
else if (value == "none")
|
regex = "";
|
||||||
value = "a^";
|
else if (regex == "none")
|
||||||
|
regex = "a^";
|
||||||
|
|
||||||
nonMusic = value;
|
nonMusic = regex;
|
||||||
}
|
}
|
||||||
} break;
|
} break;
|
||||||
case Option::exclude: {
|
|
||||||
if (!excluded.has_value())
|
|
||||||
excluded = value;
|
|
||||||
} break;
|
|
||||||
case Option::outputQuality: {
|
case Option::outputQuality: {
|
||||||
unsigned int quality;
|
unsigned int value;
|
||||||
if (!outputQuality.has_value() && std::istringstream(value) >> quality)
|
if (!outputQuality.has_value() && stream >> value)
|
||||||
outputQuality = std::clamp(quality, minQuality, maxQuality);
|
outputQuality = std::clamp(value, minQuality, maxQuality);
|
||||||
} break;
|
} break;
|
||||||
case Option::encodingQuality: {
|
case Option::encodingQuality: {
|
||||||
unsigned int quality;
|
unsigned int value;
|
||||||
if (!encodingQuality.has_value() && std::istringstream(value) >> quality)
|
if (!encodingQuality.has_value() && stream >> value)
|
||||||
encodingQuality = std::clamp(quality, minQuality, maxQuality);
|
encodingQuality = std::clamp(value, minQuality, maxQuality);
|
||||||
} break;
|
} break;
|
||||||
case Option::vbr: {
|
case Option::vbr: {
|
||||||
bool vb;
|
bool value;
|
||||||
if (!vbr.has_value() && std::istringstream(value) >> std::boolalpha >> vb)
|
if (!vbr.has_value() && stream >> std::boolalpha >> value)
|
||||||
vbr = vb;
|
vbr = value;
|
||||||
} break;
|
} break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
|
@ -372,16 +365,9 @@ std::string Settings::resolvePath(const std::string& line) {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Settings::matchNonMusic(const std::string& fileName) const {
|
bool Settings::matchNonMusic(const std::string& fileName) const {
|
||||||
if (!nonMusic.has_value())
|
if (nonMusic.has_value())
|
||||||
return true;
|
|
||||||
|
|
||||||
return std::regex_search(fileName, nonMusic.value());
|
return std::regex_search(fileName, nonMusic.value());
|
||||||
}
|
else
|
||||||
|
return true;
|
||||||
bool Settings::isExcluded(const std::string& path) const {
|
|
||||||
if (!excluded.has_value())
|
|
||||||
return false;
|
|
||||||
|
|
||||||
return std::regex_search(path, excluded.value());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -40,7 +40,6 @@ public:
|
||||||
Action getAction() const;
|
Action getAction() const;
|
||||||
unsigned int getThreads() const;
|
unsigned int getThreads() const;
|
||||||
bool matchNonMusic(const std::string& fileName) const;
|
bool matchNonMusic(const std::string& fileName) const;
|
||||||
bool isExcluded(const std::string& path) const;
|
|
||||||
unsigned char getEncodingQuality() const;
|
unsigned char getEncodingQuality() const;
|
||||||
unsigned char getOutputQuality() const;
|
unsigned char getOutputQuality() const;
|
||||||
bool getVBR() const;
|
bool getVBR() const;
|
||||||
|
@ -70,7 +69,6 @@ private:
|
||||||
std::optional<std::string> configPath;
|
std::optional<std::string> configPath;
|
||||||
std::optional<unsigned int> threads;
|
std::optional<unsigned int> threads;
|
||||||
std::optional<std::regex> nonMusic;
|
std::optional<std::regex> nonMusic;
|
||||||
std::optional<std::regex> excluded;
|
|
||||||
std::optional<unsigned char> encodingQuality;
|
std::optional<unsigned char> encodingQuality;
|
||||||
std::optional<unsigned char> outputQuality;
|
std::optional<unsigned char> outputQuality;
|
||||||
std::optional<bool> vbr;
|
std::optional<bool> vbr;
|
||||||
|
|
|
@ -22,9 +22,6 @@ TaskManager::~TaskManager() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void TaskManager::queueConvert(const std::filesystem::path& source, const std::filesystem::path& destination) {
|
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);
|
std::unique_lock<std::mutex> lock(queueMutex);
|
||||||
jobs.emplace(Job::convert, source, destination);
|
jobs.emplace(Job::convert, source, destination);
|
||||||
|
|
||||||
|
@ -86,7 +83,7 @@ void TaskManager::loop() {
|
||||||
|
|
||||||
lock.lock();
|
lock.lock();
|
||||||
++completeTasks;
|
++completeTasks;
|
||||||
printResult(job, result);
|
printResilt(job, result);
|
||||||
--busyThreads;
|
--busyThreads;
|
||||||
lock.unlock();
|
lock.unlock();
|
||||||
waitConditional.notify_all();
|
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;
|
std::string msg;
|
||||||
switch (job.type) {
|
switch (job.type) {
|
||||||
case Job::copy:
|
case Job::copy:
|
||||||
|
|
|
@ -36,7 +36,7 @@ public:
|
||||||
private:
|
private:
|
||||||
void loop();
|
void loop();
|
||||||
JobResult execute(Job& job);
|
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 mp3Job(const Job& job, const std::shared_ptr<Settings>& settings);
|
||||||
static JobResult copyJob(const Job& job, const std::shared_ptr<Settings>& settings);
|
static JobResult copyJob(const Job& job, const std::shared_ptr<Settings>& settings);
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue