Compare commits
23 Commits
Author | SHA1 | Date | |
---|---|---|---|
ceab08a26d | |||
3971a5b662 | |||
2cce5f52f0 | |||
5c3a4a592e | |||
03e7f29d84 | |||
eb85b71651 | |||
a337cc7ec3 | |||
97ffe45b24 | |||
ca9f67c223 | |||
45f3e1d6dd | |||
9b726e37fc | |||
7edbc32377 | |||
ed4d7365ac | |||
18489476af | |||
7685dd95f6 | |||
e6068b1837 | |||
633d08fd4b | |||
e9a1e8cb1d | |||
d8644e5d70 | |||
e266008e11 | |||
0b268a7245 | |||
6c7356598a | |||
870842f63d |
45
.gitea/workflows/release.yml
Normal file
45
.gitea/workflows/release.yml
Normal file
@ -0,0 +1,45 @@
|
||||
name: MLC Release workflow
|
||||
run-name: ${{ gitea.actor }} is running MLC Release workflow on release ${{ gitea.event.release.tag_name }}
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
jobs:
|
||||
Archlinux:
|
||||
runs-on: archlinux
|
||||
steps:
|
||||
- name: Download the release tarball
|
||||
run: curl -sL ${{ gitea.server_url }}/${{ gitea.repository }}/archive/${{ gitea.event.release.tag_name }}.tar.gz --output tarball.tar.gz
|
||||
|
||||
- name: Calculate SHA256 for the tarball
|
||||
run: echo "tbSum=$(sha256sum tarball.tar.gz | cut -d ' ' -f 1)" >> $GITHUB_ENV
|
||||
|
||||
- name: Unarchive tarball
|
||||
run: tar -xvzf tarball.tar.gz
|
||||
|
||||
- name: Clone the AUR repository
|
||||
run: |
|
||||
echo "${{ secrets.DEPLOY_TO_AUR_PRIVATE_KEY }}" > key
|
||||
chmod 600 key
|
||||
GIT_SSH_COMMAND="ssh -i key -o 'IdentitiesOnly yes' -o 'StrictHostKeyChecking no'" git clone ssh://aur@aur.archlinux.org/mlc.git aur
|
||||
chmod 777 -R aur
|
||||
cd aur
|
||||
git config user.name ${{ secrets.DEPLOY_TO_AUR_USER_NAME }}
|
||||
git config user.email ${{ secrets.DEPLOY_TO_AUR_EMAIL }}
|
||||
|
||||
|
||||
- name: Copy PKGBUILD to the directory
|
||||
run: cp mlc/packaging/Archlinux/PKGBUILD aur/
|
||||
|
||||
- name: Put SHA256 sum to PKGBUILD file, and generate .SRCINFO
|
||||
working-directory: aur
|
||||
run: |
|
||||
sed -i "/sha256sums=/c\sha256sums=('${{ env.tbSum }}')" PKGBUILD
|
||||
sudo -u build makepkg --printsrcinfo > .SRCINFO
|
||||
|
||||
- name: Commit package to aur
|
||||
working-directory: aur
|
||||
run: |
|
||||
git add PKGBUILD .SRCINFO
|
||||
git commit -m "${{ gitea.event.release.body }}"
|
||||
GIT_SSH_COMMAND="ssh -i ../key -o 'IdentitiesOnly yes' -o 'StrictHostKeyChecking no'" git push
|
23
CHANGELOG.md
23
CHANGELOG.md
@ -1,11 +1,32 @@
|
||||
# Changelog
|
||||
|
||||
## MLC 1.3.4 (UNRELEASED)
|
||||
- Build fixes
|
||||
|
||||
## MLC 1.3.3 (October 13, 2023)
|
||||
- Regex to specify non-music files to copy
|
||||
- Encoding settings (VBR/CBR, encoding quality, output quality)
|
||||
|
||||
## MLC 1.3.2 (October 10, 2023)
|
||||
- A release purely for CI
|
||||
|
||||
## MLC 1.3.1 (October 10, 2023)
|
||||
- Release build with optimizations
|
||||
- Removed unused files from build
|
||||
- Suppressed warnings
|
||||
- CI to release to AUR
|
||||
|
||||
## MLC 1.3.0 (October 09, 2023)
|
||||
- Config file to control the process
|
||||
- First help page
|
||||
- Program modes concept to implement config print and help
|
||||
|
||||
## MLC 1.2.0 (August 11, 2023)
|
||||
- Better way of handling tags using TagLib
|
||||
|
||||
## MLC 1.1.0 (July 23, 2023)
|
||||
- New logging system
|
||||
- Artist, Album artist, Album and Title are now utf16 encoded, should fix broten titles
|
||||
- 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 unsychronized lyrics is not supported in LAME
|
||||
- Date bug fix, it was MMDD instead of standard DDMM
|
||||
|
@ -1,39 +1,49 @@
|
||||
cmake_minimum_required(VERSION 3.0)
|
||||
cmake_minimum_required(VERSION 3.5)
|
||||
project(
|
||||
mlc
|
||||
VERSION 1.2.0
|
||||
DESCRIPTION "Media Library Compiler: rips your media library to a lossy compilation"
|
||||
VERSION 1.3.4
|
||||
DESCRIPTION "Media Library Compiler: converts your media library to a lossy compilation"
|
||||
LANGUAGES CXX
|
||||
)
|
||||
cmake_policy(SET CMP0076 NEW)
|
||||
cmake_policy(SET CMP0079 NEW)
|
||||
|
||||
if (NOT CMAKE_BUILD_TYPE)
|
||||
set(CMAKE_BUILD_TYPE Debug)
|
||||
endif ()
|
||||
|
||||
if (CMAKE_BUILD_TYPE STREQUAL "Release")
|
||||
list(APPEND COMPILE_OPTIONS -O3)
|
||||
elseif (CMAKE_BUILD_TYPE STREQUAL "Debug")
|
||||
list(APPEND COMPILE_OPTIONS -g)
|
||||
list(APPEND COMPILE_OPTIONS -Wall)
|
||||
list(APPEND COMPILE_OPTIONS -Wextra)
|
||||
endif()
|
||||
|
||||
message("Compilation options: " ${COMPILE_OPTIONS})
|
||||
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
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(JPEG REQUIRED)
|
||||
find_package(LAME REQUIRED)
|
||||
find_package(TAGLIB REQUIRED)
|
||||
find_package(Threads REQUIRED)
|
||||
|
||||
pkg_check_modules(LAME REQUIRED IMPORTED_TARGET lame)
|
||||
pkg_check_modules(TAGLIB REQUIRED IMPORTED_TARGET taglib)
|
||||
add_executable(${PROJECT_NAME})
|
||||
target_compile_options(${PROJECT_NAME} PRIVATE ${COMPILE_OPTIONS})
|
||||
|
||||
add_executable(mlc
|
||||
main.cpp
|
||||
help.cpp
|
||||
decoded.cpp
|
||||
flactomp3.cpp
|
||||
collection.cpp
|
||||
taskmanager.cpp
|
||||
loggable.cpp
|
||||
)
|
||||
add_subdirectory(src)
|
||||
|
||||
target_link_libraries(mlc
|
||||
target_link_libraries(${PROJECT_NAME}
|
||||
FLAC::FLAC
|
||||
PkgConfig::LAME
|
||||
LAME::LAME
|
||||
JPEG::JPEG
|
||||
PkgConfig::TAGLIB
|
||||
TAGLIB::TAGLIB
|
||||
Threads::Threads
|
||||
)
|
||||
|
||||
install(TARGETS mlc RUNTIME DESTINATION bin)
|
||||
install(TARGETS ${PROJECT_NAME} RUNTIME DESTINATION bin)
|
||||
|
51
README.md
51
README.md
@ -1,16 +1,19 @@
|
||||
# MLC - Music Library Compiler
|
||||
|
||||
This is a program for compilation of your loseless music library to lossy formats
|
||||
[![AUR version](https://img.shields.io/aur/version/mlc?style=flat-square)](https://aur.archlinux.org/packages/mlc/)
|
||||
|
||||
This is a program to compile your local lossless music library to lossy formats
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- flac
|
||||
- lame
|
||||
- jpeg
|
||||
- taglib
|
||||
|
||||
### Building
|
||||
|
||||
```
|
||||
```sh
|
||||
$ git clone https://git.macaw.me/blue/mlc
|
||||
$ cd mlc
|
||||
$ mkdir build
|
||||
@ -21,12 +24,48 @@ $ cmake --build .
|
||||
|
||||
### 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
|
||||
./mlc path/to/lossless/library path/to/store/lossy/library
|
||||
```
|
||||
|
||||
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.
|
||||
There are more ways to use `MLC`, please refer to help for more options:
|
||||
|
||||
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.
|
||||
```sh
|
||||
$ ./mlc help
|
||||
# or
|
||||
$ ./mlc --help
|
||||
#
|
||||
# or
|
||||
$ ./mlc -h
|
||||
```
|
||||
|
||||
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.
|
||||
`MLC` has a way to configure conversion process, it will generate global for you user config on the first launch.
|
||||
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`:
|
||||
|
||||
```sh
|
||||
$ ./mlc config
|
||||
```
|
||||
|
||||
To use non default config run the following, assuming you are in the same directory you have just built `MLC`:
|
||||
|
||||
```sh
|
||||
$ ./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.
|
||||
|
23
cmake/FindFLAC.cmake
Normal file
23
cmake/FindFLAC.cmake
Normal file
@ -0,0 +1,23 @@
|
||||
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,20 +1,26 @@
|
||||
#copied from here, thank you
|
||||
#https://github.com/sipwise/sems/blob/master/cmake/FindLame.cmake
|
||||
|
||||
FIND_PATH(LAME_INCLUDE_DIR lame/lame.h)
|
||||
FIND_LIBRARY(LAME_LIBRARIES NAMES mp3lame)
|
||||
find_path(LAME_INCLUDE_DIR lame/lame.h)
|
||||
find_library(LAME_LIBRARIES lame NAMES mp3lame)
|
||||
|
||||
IF(LAME_INCLUDE_DIR AND LAME_LIBRARIES)
|
||||
SET(LAME_FOUND TRUE)
|
||||
ENDIF(LAME_INCLUDE_DIR AND LAME_LIBRARIES)
|
||||
if(LAME_INCLUDE_DIR AND LAME_LIBRARIES)
|
||||
set(LAME_FOUND TRUE)
|
||||
endif(LAME_INCLUDE_DIR AND LAME_LIBRARIES)
|
||||
|
||||
IF(LAME_FOUND)
|
||||
IF (NOT Lame_FIND_QUIETLY)
|
||||
MESSAGE(STATUS "Found lame includes: ${LAME_INCLUDE_DIR}/lame/lame.h")
|
||||
MESSAGE(STATUS "Found lame library: ${LAME_LIBRARIES}")
|
||||
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)
|
||||
if(LAME_FOUND)
|
||||
add_library(LAME::LAME SHARED IMPORTED)
|
||||
set_target_properties(LAME::LAME PROPERTIES
|
||||
IMPORTED_LOCATION "${LAME_LIBRARIES}"
|
||||
INTERFACE_INCLUDE_DIRECTORIES "${LAME_INCLUDE_DIR}/lame"
|
||||
INTERFACE_LINK_LIBRARIES "${LAME_LIBRARIES}"
|
||||
)
|
||||
if (NOT Lame_FIND_QUIETLY)
|
||||
message(STATUS "Found lame includes: ${LAME_INCLUDE_DIR}/lame")
|
||||
message(STATUS "Found lame library: ${LAME_LIBRARIES}")
|
||||
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)
|
||||
|
24
cmake/FindTAGLIB.cmake
Normal file
24
cmake/FindTAGLIB.cmake
Normal file
@ -0,0 +1,24 @@
|
||||
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()
|
||||
|
7
help.cpp
7
help.cpp
@ -1,7 +0,0 @@
|
||||
#include "help.h"
|
||||
|
||||
#include "iostream"
|
||||
|
||||
void printHelp() {
|
||||
std::cout << "Sorry, there is no help yet. It will arrive one day... I hope" << std::endl;
|
||||
}
|
18
loggable.cpp
18
loggable.cpp
@ -1,18 +0,0 @@
|
||||
#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<std::pair<Loggable::Severity, std::string>> Loggable::getHistory() const {
|
||||
return history;
|
||||
}
|
28
loggable.h
28
loggable.h
@ -1,28 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <list>
|
||||
#include <string>
|
||||
|
||||
class Loggable {
|
||||
public:
|
||||
enum Severity {
|
||||
debug,
|
||||
info,
|
||||
minor,
|
||||
major,
|
||||
warning,
|
||||
error,
|
||||
fatal
|
||||
};
|
||||
typedef std::pair<Severity, std::string> Message;
|
||||
|
||||
Loggable(Severity severity);
|
||||
~Loggable();
|
||||
|
||||
void log (Severity severity, const std::string& comment) const;
|
||||
std::list<Message> getHistory() const;
|
||||
|
||||
private:
|
||||
const Severity currentSeverity;
|
||||
mutable std::list<Message> history;
|
||||
};
|
43
main.cpp
43
main.cpp
@ -1,43 +0,0 @@
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <chrono>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "FLAC/stream_decoder.h"
|
||||
#include <lame/lame.h>
|
||||
|
||||
#include "help.h"
|
||||
#include "collection.h"
|
||||
#include "taskmanager.h"
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
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]);
|
||||
if (firstArgument == "--help") {
|
||||
printHelp();
|
||||
return 0;
|
||||
}
|
||||
|
||||
const std::string secondArgument(argv[2]);
|
||||
|
||||
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 << "Encoding is done, it took " << seconds.count() << " seconds in total, enjoy!" << std::endl;
|
||||
|
||||
taskManager.stop();
|
||||
|
||||
return 0;
|
||||
}
|
24
packaging/Archlinux/PKGBUILD
Normal file
24
packaging/Archlinux/PKGBUILD
Normal file
@ -0,0 +1,24 @@
|
||||
# Maintainer: Yury Gubich <blue@macaw.me>
|
||||
pkgname=mlc
|
||||
pkgver=1.3.4
|
||||
pkgrel=1
|
||||
pkgdesc="Media Library Compiler: rips your media library to a lossy compilation"
|
||||
arch=('i686' 'x86_64')
|
||||
url="https://git.macaw.me/blue/mlc"
|
||||
license=('GPL3')
|
||||
depends=('flac' 'lame' 'libjpeg' 'taglib')
|
||||
makedepends=('cmake>=3.5' 'gcc>=7.0')
|
||||
optdepends=()
|
||||
|
||||
source=("$pkgname-$pkgver.tar.gz::https://git.macaw.me/blue/$pkgname/archive/$pkgver.tar.gz")
|
||||
sha256sums=('SKIP')
|
||||
build() {
|
||||
cd "$srcdir/$pkgname"
|
||||
cmake . -D CMAKE_INSTALL_PREFIX=/usr -D CMAKE_BUILD_TYPE=Release
|
||||
cmake --build .
|
||||
}
|
||||
package() {
|
||||
cd "$srcdir/$pkgname"
|
||||
DESTDIR="$pkgdir/" cmake --install .
|
||||
}
|
||||
|
33
src/CMakeLists.txt
Normal file
33
src/CMakeLists.txt
Normal file
@ -0,0 +1,33 @@
|
||||
function(make_includable input_file output_file)
|
||||
file(READ ${input_file} content)
|
||||
set(delim "for_c++_include")
|
||||
set(content "R\"${delim}(\n${content})${delim}\"")
|
||||
file(WRITE ${output_file} "${content}")
|
||||
endfunction(make_includable)
|
||||
|
||||
set(SOURCES
|
||||
main.cpp
|
||||
help.cpp
|
||||
#decoded.cpp
|
||||
flactomp3.cpp
|
||||
collection.cpp
|
||||
taskmanager.cpp
|
||||
settings.cpp
|
||||
)
|
||||
|
||||
set(HEADERS
|
||||
help.h
|
||||
#decoded.h
|
||||
flactomp3.h
|
||||
collection.h
|
||||
taskmanager.h
|
||||
settings.h
|
||||
)
|
||||
|
||||
target_sources(${PROJECT_NAME} PRIVATE ${SOURCES})
|
||||
|
||||
add_subdirectory(logger)
|
||||
|
||||
make_includable(default.conf ${CMAKE_BINARY_DIR}/generated/default.conf)
|
||||
make_includable(help ${CMAKE_BINARY_DIR}/generated/help)
|
||||
target_include_directories(${PROJECT_NAME} PRIVATE ${CMAKE_BINARY_DIR})
|
@ -6,25 +6,15 @@ namespace fs = std::filesystem;
|
||||
|
||||
static const std::string flac(".flac");
|
||||
|
||||
Collection::Collection(const std::string& path, TaskManager* tm) :
|
||||
path(fs::canonical(path)),
|
||||
Collection::Collection(const std::filesystem::path& path, TaskManager* tm) :
|
||||
path(path),
|
||||
countMusical(0),
|
||||
counted(false),
|
||||
taskManager(tm)
|
||||
{
|
||||
}
|
||||
{}
|
||||
|
||||
Collection::Collection(const std::filesystem::path& path, TaskManager* tm):
|
||||
path(fs::canonical(path)),
|
||||
countMusical(0),
|
||||
counted(false),
|
||||
taskManager(tm)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
Collection::~Collection() {
|
||||
}
|
||||
Collection::~Collection()
|
||||
{}
|
||||
|
||||
void Collection::list() const {
|
||||
if (fs::is_regular_file(path))
|
||||
@ -74,14 +64,10 @@ void Collection::convert(const std::string& outPath) {
|
||||
switch (entry.status().type()) {
|
||||
case fs::file_type::regular: {
|
||||
fs::path sourcePath = entry.path();
|
||||
fs::path dstPath = out / sourcePath.filename();
|
||||
if (isMusic(sourcePath)) {
|
||||
dstPath.replace_extension(".mp3");
|
||||
taskManager->queueJob(sourcePath, dstPath);
|
||||
} else {
|
||||
fs::copy_file(sourcePath, dstPath, fs::copy_options::overwrite_existing);
|
||||
}
|
||||
//std::cout << sourcePath << " => " << dstPath << std::endl;
|
||||
if (isMusic(sourcePath))
|
||||
taskManager->queueConvert(sourcePath, out / sourcePath.stem());
|
||||
else
|
||||
taskManager->queueCopy(sourcePath, out / sourcePath.filename());
|
||||
} break;
|
||||
case fs::file_type::directory: {
|
||||
fs::path sourcePath = entry.path();
|
@ -10,7 +10,6 @@ class TaskManager;
|
||||
|
||||
class Collection {
|
||||
public:
|
||||
Collection(const std::string& path, TaskManager* tm = nullptr);
|
||||
Collection(const std::filesystem::path& path, TaskManager* tm = nullptr);
|
||||
~Collection();
|
||||
|
98
src/default.conf
Normal file
98
src/default.conf
Normal file
@ -0,0 +1,98 @@
|
||||
# This is a default autogenerated MLC config
|
||||
# The syntax goes like this:
|
||||
# key value
|
||||
# Only the first occasion of the valid key value pair is taken to consideration
|
||||
#
|
||||
# Use # sign for comments
|
||||
#
|
||||
# All printed commented out values are default values
|
||||
#
|
||||
# You should have one of this files as ~/.config/mlc.conf,
|
||||
# if you have started mlc at least once.
|
||||
# This is going to be used every time you launch mlc,
|
||||
# so, edit it if you wish to change default behavior.
|
||||
#
|
||||
# Alternatively, you can launch `mlc -c customConfig.conf`
|
||||
# if you wish to override ~/.config/mlc.conf file
|
||||
|
||||
# Log level, regulates minimal message severity to be printed
|
||||
# Allowed values are: [debug, info, minor, major, warning, error, fatal]
|
||||
#level info
|
||||
|
||||
# Output type
|
||||
# Allowed values are: [mp3] (more coming soon)
|
||||
#type mp3
|
||||
|
||||
# Source collection path
|
||||
# This is a default path to your music collection source.
|
||||
# It's useful to set it when you always encode the same collection
|
||||
# Leaving this empty (as it is by default) will make you always
|
||||
# specify source in the command line
|
||||
#source
|
||||
|
||||
# Destination path
|
||||
# This is a default path for your encoding destination
|
||||
# It's useful to set it when you often encode your collection
|
||||
# to the same output
|
||||
# Leaving this empty (as it is by default) will make you always
|
||||
# specify destination in the command line
|
||||
#destination
|
||||
|
||||
# Parallel tasks
|
||||
# Defines how many threads are going to be started in parallel
|
||||
# Allowed values are [0, 1, 2, 3 ...] etc
|
||||
# If it's set to 0 - amount of threads is going to be
|
||||
# as high as your processor can effectively handle
|
||||
#parallel 0
|
||||
|
||||
# Non music files
|
||||
# MLC copies any non-music file it finds in source directory
|
||||
# if it matches the following regex
|
||||
# Allowed value are: [all, none] or regex without any additional syntax,
|
||||
# for example: filesToCopy cover\.jpe?g
|
||||
#filesToCopy all
|
||||
|
||||
# Encoding quality
|
||||
# Sets up encoding quality (NOT OUTPUT QUALITY)
|
||||
# The higher quality the slower the encoding process
|
||||
# 0 is the highest quality and slowest process
|
||||
# 9 is the lowest quality and the fastest process fastest
|
||||
# Allowed values are: [0, 1, 2, ... 9]
|
||||
#encodingQuality 0
|
||||
|
||||
# Output quality
|
||||
# Sets up output quality
|
||||
# The higher quality the less information is lost in compression
|
||||
# 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
|
||||
# Allowed values are [0, 1, 2, ... 9]
|
||||
# For the constant bitrate modes (CBR) the following table is valid
|
||||
# Quality | MP3 |
|
||||
# --------+-----+--
|
||||
# 0 | 320 |
|
||||
# --------+-----+--
|
||||
# 1 | 288 |
|
||||
# --------+-----+--
|
||||
# 2 | 256 |
|
||||
# --------+-----+--
|
||||
# 3 | 224 |
|
||||
# --------+-----+--
|
||||
# 4 | 192 |
|
||||
# --------+-----+--
|
||||
# 5 | 160 |
|
||||
# --------+-----+--
|
||||
# 6 | 128 |
|
||||
# --------+-----+--
|
||||
# 7 | 96 |
|
||||
# --------+-----+--
|
||||
# 8 | 64 |
|
||||
# --------+-----+--
|
||||
# 9 | 32 |
|
||||
#outputQuality 0
|
||||
|
||||
# Variable bitrate
|
||||
# Switches on or off variable bitrate
|
||||
# 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
|
@ -12,9 +12,21 @@ constexpr std::string_view jpeg ("image/jpeg");
|
||||
const std::map<std::string, std::string> textIdentificationReplacements({
|
||||
{"PUBLISHER", "TPUB"}
|
||||
});
|
||||
constexpr std::array<int, 10> bitrates({
|
||||
320,
|
||||
288,
|
||||
256,
|
||||
224,
|
||||
192,
|
||||
160,
|
||||
128,
|
||||
96,
|
||||
64,
|
||||
32
|
||||
});
|
||||
|
||||
FLACtoMP3::FLACtoMP3(Severity severity, uint8_t size) :
|
||||
Loggable(severity),
|
||||
FLACtoMP3::FLACtoMP3(Logger::Severity severity, uint8_t size) :
|
||||
logger(severity),
|
||||
inPath(),
|
||||
outPath(),
|
||||
decoder(FLAC__stream_decoder_new()),
|
||||
@ -46,7 +58,7 @@ bool FLACtoMP3::run() {
|
||||
if (pcmCounter > 0)
|
||||
flush();
|
||||
|
||||
int nwrite = lame_encode_flush(encoder, outputBuffer, pcmSize * 2);
|
||||
int nwrite = lame_encode_flush(encoder, outputBuffer, outputBufferSize);
|
||||
fwrite((char*)outputBuffer, nwrite, 1, output);
|
||||
|
||||
|
||||
@ -72,7 +84,7 @@ bool FLACtoMP3::run() {
|
||||
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);
|
||||
logger.info("resulting file size: " + strMBytes);
|
||||
}
|
||||
|
||||
return ok;
|
||||
@ -98,9 +110,21 @@ void FLACtoMP3::setOutputFile(const std::string& path) {
|
||||
|
||||
outPath = path;
|
||||
|
||||
}
|
||||
|
||||
void FLACtoMP3::setParameters(unsigned char encodingQuality, unsigned char outputQuality, bool vbr) {
|
||||
if (vbr) {
|
||||
logger.info("Encoding to VBR with quality " + std::to_string(outputQuality));
|
||||
lame_set_VBR(encoder, vbr_default);
|
||||
lame_set_VBR_quality(encoder, 0);
|
||||
lame_set_quality(encoder, 0);
|
||||
lame_set_VBR_quality(encoder, outputQuality);
|
||||
} else {
|
||||
int bitrate = bitrates[outputQuality];
|
||||
logger.info("Encoding to CBR " + std::to_string(bitrate));
|
||||
lame_set_VBR(encoder, vbr_off);
|
||||
lame_set_brate(encoder, bitrate);
|
||||
}
|
||||
|
||||
lame_set_quality(encoder, encodingQuality);
|
||||
}
|
||||
|
||||
bool FLACtoMP3::initializeOutput() {
|
||||
@ -110,13 +134,13 @@ bool FLACtoMP3::initializeOutput() {
|
||||
output = fopen(outPath.c_str(), "w+b");
|
||||
if (output == 0) {
|
||||
output = nullptr;
|
||||
log(fatal, "Error opening file " + outPath);
|
||||
logger.fatal("Error opening file " + outPath);
|
||||
return false;
|
||||
}
|
||||
|
||||
int ret = lame_init_params(encoder);
|
||||
if (ret < 0) {
|
||||
log(fatal, "Error initializing LAME parameters. Code = " + std::to_string(ret));
|
||||
logger.fatal("Error initializing LAME parameters. Code = " + std::to_string(ret));
|
||||
fclose(output);
|
||||
output = nullptr;
|
||||
return false;
|
||||
@ -143,9 +167,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;
|
||||
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));
|
||||
logger.info("sample rate: " + std::to_string(info.sample_rate));
|
||||
logger.info("channels: " + std::to_string(info.channels));
|
||||
logger.info("bits per sample: " + std::to_string(info.bits_per_sample));
|
||||
}
|
||||
|
||||
void FLACtoMP3::processTags(const FLAC__StreamMetadata_VorbisComment& tags) {
|
||||
@ -156,7 +180,7 @@ void FLACtoMP3::processTags(const FLAC__StreamMetadata_VorbisComment& tags) {
|
||||
std::string_view comm((const char*)entry.entry);
|
||||
std::string_view::size_type ePos = comm.find("=");
|
||||
if (ePos == std::string_view::npos) {
|
||||
log(warning, "couldn't understand tag (" + std::string(comm) + "), symbol '=' is missing, skipping");
|
||||
logger.warn("couldn't understand tag (" + std::string(comm) + "), symbol '=' is missing, skipping");
|
||||
continue;
|
||||
}
|
||||
std::string key(comm.substr(0, ePos));
|
||||
@ -174,17 +198,17 @@ void FLACtoMP3::processTags(const FLAC__StreamMetadata_VorbisComment& tags) {
|
||||
TagLib::ID3v2::TextIdentificationFrame* frame = new TagLib::ID3v2::TextIdentificationFrame(itr->second.c_str());
|
||||
frame->setText(value);
|
||||
customFrames.push_back(frame);
|
||||
log(debug, "tag \"" + key + "\" was remapped to \"" + itr->second + "\"");
|
||||
logger.debug("tag \"" + key + "\" was remapped to \"" + itr->second + "\"");
|
||||
} else {
|
||||
success = props.insert(key, TagLib::String(value, TagLib::String::UTF8));
|
||||
}
|
||||
|
||||
if (!success)
|
||||
log(warning, "couldn't understand tag (" + key + "), skipping");
|
||||
logger.warn("couldn't understand tag (" + key + "), skipping");
|
||||
}
|
||||
TagLib::StringList unsupported = props.unsupportedData();
|
||||
for (const TagLib::String& key : unsupported)
|
||||
log(minor, "tag \"" + key.to8Bit() + "\", is not supported, probably won't display well");
|
||||
logger.minor("tag \"" + key.to8Bit() + "\", is not supported, probably won't display well");
|
||||
|
||||
id3v2tag.setProperties(props);
|
||||
|
||||
@ -195,13 +219,13 @@ void FLACtoMP3::processTags(const FLAC__StreamMetadata_VorbisComment& tags) {
|
||||
|
||||
void FLACtoMP3::processPicture(const FLAC__StreamMetadata_Picture& picture) {
|
||||
if (downscaleAlbumArt && picture.data_length > LAME_MAXALBUMART) {
|
||||
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));
|
||||
logger.info("embeded album art is too big (" + std::to_string(picture.data_length) + " bytes), rescaling");
|
||||
logger.debug("mime type is " + std::string(picture.mime_type));
|
||||
if (picture.mime_type == jpeg) {
|
||||
if (scaleJPEG(picture))
|
||||
log(debug, "successfully rescaled album art");
|
||||
logger.debug("successfully rescaled album art");
|
||||
else
|
||||
log(warning, "failed to rescale album art");
|
||||
logger.warn("failed to rescale album art");
|
||||
}
|
||||
} else {
|
||||
//auch, sorry for copying so much, but I haven't found a way around it yet
|
||||
@ -220,7 +244,7 @@ bool FLACtoMP3::scaleJPEG(const FLAC__StreamMetadata_Picture& picture) {
|
||||
int rc = jpeg_read_header(&dinfo, TRUE);
|
||||
|
||||
if (rc != 1) {
|
||||
log(Loggable::error, "error reading jpeg header");
|
||||
logger.error("error reading jpeg header");
|
||||
return false;
|
||||
}
|
||||
TagLib::ByteVector vector (picture.data_length + 1024 * 4); //I allocate a little bit more not to corrupt someone else's the memory
|
||||
@ -251,7 +275,7 @@ bool FLACtoMP3::scaleJPEG(const FLAC__StreamMetadata_Picture& picture) {
|
||||
while (dinfo.output_scanline < dinfo.output_height) {
|
||||
if (mem_size + rowSize > vector.size()) {
|
||||
vector.resize(vector.size() + rowSize);
|
||||
log(Loggable::major, "allocated memory for resising the image wasn't enougth, resising");
|
||||
logger.major("allocated memory for resising the image wasn't enougth, resising");
|
||||
}
|
||||
jpeg_read_scanlines(&dinfo, &row, 1);
|
||||
jpeg_write_scanlines(&cinfo, &row, 1);
|
||||
@ -297,16 +321,16 @@ bool FLACtoMP3::flush() {
|
||||
outputBufferSize
|
||||
);
|
||||
while (nwrite == -1) { //-1 is returned when there was not enough space in the given buffer
|
||||
log(major, std::to_string(outputBufferSize) + " bytes in the output buffer wasn't enough");;
|
||||
logger.major(std::to_string(outputBufferSize) + " bytes in the output buffer wasn't enough");;
|
||||
outputBufferSize = outputBufferSize * 2;
|
||||
delete[] outputBuffer;
|
||||
outputBuffer = new uint8_t[outputBufferSize];
|
||||
log(major, "allocating " + std::to_string(outputBufferSize) + " bytes");
|
||||
logger.major("allocating " + std::to_string(outputBufferSize) + " bytes");
|
||||
|
||||
nwrite = lame_encode_buffer_interleaved(
|
||||
encoder,
|
||||
pcm,
|
||||
pcmCounter,
|
||||
pcmCounter / 2,
|
||||
outputBuffer,
|
||||
outputBufferSize
|
||||
);
|
||||
@ -318,10 +342,10 @@ bool FLACtoMP3::flush() {
|
||||
return actuallyWritten == 1;
|
||||
} else {
|
||||
if (nwrite == 0) {
|
||||
log(minor, "encoding flush encoded 0 bytes, skipping write");
|
||||
logger.minor("encoding flush encoded 0 bytes, skipping write");
|
||||
return true;
|
||||
} else {
|
||||
log(fatal, "encoding flush failed. Code = : " + std::to_string(nwrite));
|
||||
logger.fatal("encoding flush failed. Code = : " + std::to_string(nwrite));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@ -359,15 +383,15 @@ FLAC__StreamDecoderWriteStatus FLACtoMP3::write(
|
||||
// }
|
||||
FLACtoMP3* self = static_cast<FLACtoMP3*>(client_data);
|
||||
if (frame->header.channels != 2) {
|
||||
self->log(fatal, "ERROR: This frame contains " + std::to_string(frame->header.channels) + " channels (should be 2)");
|
||||
self->logger.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) {
|
||||
self->log(fatal, "ERROR: buffer [0] is NULL");
|
||||
self->logger.fatal("ERROR: buffer [0] is NULL");
|
||||
return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT;
|
||||
}
|
||||
if (buffer[1] == NULL) {
|
||||
self->log(fatal, "ERROR: buffer [1] is NULL");
|
||||
self->logger.fatal("ERROR: buffer [1] is NULL");
|
||||
return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT;
|
||||
}
|
||||
|
||||
@ -383,7 +407,7 @@ void FLACtoMP3::error(const FLAC__StreamDecoder* decoder, FLAC__StreamDecoderErr
|
||||
(void)decoder;
|
||||
FLACtoMP3* self = static_cast<FLACtoMP3*>(client_data);
|
||||
std::string errText(FLAC__StreamDecoderErrorStatusString[status]);
|
||||
self->log(Loggable::error, "Got error callback: " + errText);
|
||||
self->logger.error("Got error callback: " + errText);
|
||||
}
|
||||
|
||||
void FLACtoMP3::attachPictureFrame(const FLAC__StreamMetadata_Picture& picture, const TagLib::ByteVector& bytes) {
|
||||
@ -395,91 +419,91 @@ void FLACtoMP3::attachPictureFrame(const FLAC__StreamMetadata_Picture& picture,
|
||||
switch (picture.type) {
|
||||
case FLAC__STREAM_METADATA_PICTURE_TYPE_OTHER:
|
||||
frame->setType(TagLib::ID3v2::AttachedPictureFrame::Other);
|
||||
log(info, "attached picture is described as \"other\"");
|
||||
logger.info("attached picture is described as \"other\"");
|
||||
break;
|
||||
case FLAC__STREAM_METADATA_PICTURE_TYPE_FILE_ICON_STANDARD:
|
||||
frame->setType(TagLib::ID3v2::AttachedPictureFrame::FileIcon);
|
||||
log(info, "attached picture is a standard file icon");
|
||||
logger.info("attached picture is a standard file icon");
|
||||
break;
|
||||
case FLAC__STREAM_METADATA_PICTURE_TYPE_FILE_ICON:
|
||||
frame->setType(TagLib::ID3v2::AttachedPictureFrame::OtherFileIcon);
|
||||
log(info, "attached picture is apparently not so standard file icon...");
|
||||
logger.info("attached picture is apparently not so standard file icon...");
|
||||
break;
|
||||
case FLAC__STREAM_METADATA_PICTURE_TYPE_FRONT_COVER:
|
||||
frame->setType(TagLib::ID3v2::AttachedPictureFrame::FrontCover);
|
||||
log(info, "attached picture is a front album cover");
|
||||
logger.info("attached picture is a front album cover");
|
||||
break;
|
||||
case FLAC__STREAM_METADATA_PICTURE_TYPE_BACK_COVER:
|
||||
frame->setType(TagLib::ID3v2::AttachedPictureFrame::BackCover);
|
||||
log(info, "attached picture is an back album cover");
|
||||
logger.info("attached picture is an back album cover");
|
||||
break;
|
||||
case FLAC__STREAM_METADATA_PICTURE_TYPE_LEAFLET_PAGE:
|
||||
frame->setType(TagLib::ID3v2::AttachedPictureFrame::LeafletPage);
|
||||
log(info, "attached picture is a leflet from the album");
|
||||
logger.info("attached picture is a leflet from the album");
|
||||
break;
|
||||
case FLAC__STREAM_METADATA_PICTURE_TYPE_MEDIA:
|
||||
frame->setType(TagLib::ID3v2::AttachedPictureFrame::Media);
|
||||
log(info, "attached picture is probably an imadge of an album CD");
|
||||
logger.info("attached picture is probably an imadge of an album CD");
|
||||
break;
|
||||
case FLAC__STREAM_METADATA_PICTURE_TYPE_LEAD_ARTIST:
|
||||
frame->setType(TagLib::ID3v2::AttachedPictureFrame::LeadArtist);
|
||||
log(info, "attached picture is an image of the lead artist");
|
||||
logger.info("attached picture is an image of the lead artist");
|
||||
break;
|
||||
case FLAC__STREAM_METADATA_PICTURE_TYPE_ARTIST:
|
||||
frame->setType(TagLib::ID3v2::AttachedPictureFrame::Artist);
|
||||
log(info, "attached picture is an image the artist");
|
||||
logger.info("attached picture is an image the artist");
|
||||
break;
|
||||
case FLAC__STREAM_METADATA_PICTURE_TYPE_CONDUCTOR:
|
||||
frame->setType(TagLib::ID3v2::AttachedPictureFrame::Conductor);
|
||||
log(info, "attached picture is an image the conductor");
|
||||
logger.info("attached picture is an image the conductor");
|
||||
break;
|
||||
case FLAC__STREAM_METADATA_PICTURE_TYPE_BAND:
|
||||
frame->setType(TagLib::ID3v2::AttachedPictureFrame::Band);
|
||||
log(info, "attached picture is an image of the band");
|
||||
logger.info("attached picture is an image of the band");
|
||||
break;
|
||||
case FLAC__STREAM_METADATA_PICTURE_TYPE_COMPOSER:
|
||||
frame->setType(TagLib::ID3v2::AttachedPictureFrame::Composer);
|
||||
log(info, "attached picture is an image of composer");
|
||||
logger.info("attached picture is an image of composer");
|
||||
break;
|
||||
case FLAC__STREAM_METADATA_PICTURE_TYPE_LYRICIST:
|
||||
frame->setType(TagLib::ID3v2::AttachedPictureFrame::Lyricist);
|
||||
log(info, "attached picture is an image of the lyricist");
|
||||
logger.info("attached picture is an image of the lyricist");
|
||||
break;
|
||||
case FLAC__STREAM_METADATA_PICTURE_TYPE_RECORDING_LOCATION:
|
||||
frame->setType(TagLib::ID3v2::AttachedPictureFrame::RecordingLocation);
|
||||
log(info, "attached picture is an image recording location");
|
||||
logger.info("attached picture is an image recording location");
|
||||
break;
|
||||
case FLAC__STREAM_METADATA_PICTURE_TYPE_DURING_RECORDING:
|
||||
frame->setType(TagLib::ID3v2::AttachedPictureFrame::DuringRecording);
|
||||
log(info, "attached picture is an image of the process of the recording");
|
||||
logger.info("attached picture is an image of the process of the recording");
|
||||
break;
|
||||
case FLAC__STREAM_METADATA_PICTURE_TYPE_DURING_PERFORMANCE:
|
||||
frame->setType(TagLib::ID3v2::AttachedPictureFrame::DuringPerformance);
|
||||
log(info, "attached picture is an image of process of the performance");
|
||||
logger.info("attached picture is an image of process of the performance");
|
||||
break;
|
||||
case FLAC__STREAM_METADATA_PICTURE_TYPE_VIDEO_SCREEN_CAPTURE:
|
||||
frame->setType(TagLib::ID3v2::AttachedPictureFrame::MovieScreenCapture);
|
||||
log(info, "attached picture is an frame from a movie");
|
||||
logger.info("attached picture is an frame from a movie");
|
||||
break;
|
||||
case FLAC__STREAM_METADATA_PICTURE_TYPE_FISH:
|
||||
frame->setType(TagLib::ID3v2::AttachedPictureFrame::ColouredFish);
|
||||
log(info, "attached picture is ... a bright large colo(u?)red fish...? o_O");
|
||||
logger.info("attached picture is ... a bright large colo(u?)red fish...? o_O");
|
||||
break;
|
||||
case FLAC__STREAM_METADATA_PICTURE_TYPE_ILLUSTRATION:
|
||||
frame->setType(TagLib::ID3v2::AttachedPictureFrame::Illustration);
|
||||
log(info, "attached picture is an track related illustration");
|
||||
logger.info("attached picture is an track related illustration");
|
||||
break;
|
||||
case FLAC__STREAM_METADATA_PICTURE_TYPE_BAND_LOGOTYPE:
|
||||
frame->setType(TagLib::ID3v2::AttachedPictureFrame::BandLogo);
|
||||
log(info, "attached picture is a band logo");
|
||||
logger.info("attached picture is a band logo");
|
||||
break;
|
||||
case FLAC__STREAM_METADATA_PICTURE_TYPE_PUBLISHER_LOGOTYPE:
|
||||
frame->setType(TagLib::ID3v2::AttachedPictureFrame::PublisherLogo);
|
||||
log(info, "attached picture is a publisher logo");
|
||||
logger.info("attached picture is a publisher logo");
|
||||
break;
|
||||
case FLAC__STREAM_METADATA_PICTURE_TYPE_UNDEFINED:
|
||||
frame->setType(TagLib::ID3v2::AttachedPictureFrame::Other);
|
||||
log(info, "attached picture is something unknown, so, I would assume it's \"other\"");
|
||||
logger.info("attached picture is something unknown, so, I would assume it's \"other\"");
|
||||
break;
|
||||
}
|
||||
|
||||
@ -487,11 +511,14 @@ void FLACtoMP3::attachPictureFrame(const FLAC__StreamMetadata_Picture& picture,
|
||||
float KBytes = (float)sizeBytes / 1024;
|
||||
std::string strKBytes = std::to_string(KBytes);
|
||||
strKBytes = strKBytes.substr(0, strKBytes.find(".") + 3) + " KiB";
|
||||
log(info, "attached picture size: " + strKBytes);
|
||||
logger.info("attached picture size: " + strKBytes);
|
||||
|
||||
std::string description = frame->description().to8Bit();
|
||||
if (description.size() > 0)
|
||||
log(info, "attached picture has a description (b'cuz where else would you ever read it?): " + description);
|
||||
logger.info("attached picture has a description (b'cuz where else would you ever read it?): " + description);
|
||||
id3v2tag.addFrame(frame);
|
||||
}
|
||||
|
||||
std::list<Logger::Message> FLACtoMP3::getHistory() const {
|
||||
return logger.getHistory();
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <FLAC/stream_decoder.h>
|
||||
#include <stream_decoder.h>
|
||||
#include <lame.h>
|
||||
#include <jpeglib.h>
|
||||
#include <id3v2tag.h>
|
||||
@ -8,19 +8,23 @@
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <map>
|
||||
#include <array>
|
||||
#include <stdio.h>
|
||||
|
||||
#include "loggable.h"
|
||||
#include "logger/accumulator.h"
|
||||
|
||||
class FLACtoMP3 : public Loggable {
|
||||
class FLACtoMP3 {
|
||||
public:
|
||||
FLACtoMP3(Severity severity = info, uint8_t size = 4);
|
||||
FLACtoMP3(Logger::Severity severity = Logger::Severity::info, uint8_t size = 4);
|
||||
~FLACtoMP3();
|
||||
|
||||
void setInputFile(const std::string& path);
|
||||
void setOutputFile(const std::string& path);
|
||||
void setParameters(unsigned char encodingQuality, unsigned char outputQuality, bool vbr);
|
||||
bool run();
|
||||
|
||||
std::list<Logger::Message> getHistory() const;
|
||||
|
||||
private:
|
||||
void processTags(const FLAC__StreamMetadata_VorbisComment& tags);
|
||||
void processInfo(const FLAC__StreamMetadata_StreamInfo& info);
|
||||
@ -41,6 +45,7 @@ private:
|
||||
);
|
||||
|
||||
private:
|
||||
Accumulator logger;
|
||||
std::string inPath;
|
||||
std::string outPath;
|
||||
|
42
src/help
Normal file
42
src/help
Normal file
@ -0,0 +1,42 @@
|
||||
Usage:
|
||||
mlc [action] [arguments] [flags]
|
||||
|
||||
Actions:
|
||||
convert - converts music
|
||||
config - prints default config
|
||||
help - prints this page
|
||||
|
||||
Default action is `convert`, so it can be omitted
|
||||
|
||||
Arguments work only for `convert` action
|
||||
first - collection source
|
||||
second - collection destination
|
||||
|
||||
Flags:
|
||||
-c (--config) <path>
|
||||
- sets custom config instead of default one
|
||||
|
||||
-h (--help)
|
||||
- sets action to help, and prints this page
|
||||
|
||||
Examples:
|
||||
`mlc ~/Music compile/latest`
|
||||
- reads config file from `~/.config/mlc.conf`
|
||||
- if there was not - creates default that changes nothing
|
||||
- creates directory `compile` in the CURRENT directory if it was not created before
|
||||
- create `latest` directory in `compile` directory if it was not created before
|
||||
- searches for music in `~/Music` directory
|
||||
- converts all music to `compile/latest` directory
|
||||
- copies all other files found in `~/Music` to `compile/latest`
|
||||
- any file name overlap will be overridden
|
||||
|
||||
`mlc config > myConfig.conf`
|
||||
- prints default config to standard output
|
||||
- unix operator `>` redirects output to a file `myConfig.conf`
|
||||
|
||||
`mlc convert -c myConfig.conf`
|
||||
- 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 `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
|
||||
- the rest is the same from the first example apart from default config
|
11
src/help.cpp
Normal file
11
src/help.cpp
Normal file
@ -0,0 +1,11 @@
|
||||
#include "help.h"
|
||||
|
||||
#include "iostream"
|
||||
|
||||
static const char* help =
|
||||
#include "generated/help"
|
||||
;
|
||||
|
||||
void printHelp() {
|
||||
std::cout << help << std::endl;
|
||||
}
|
13
src/logger/CMakeLists.txt
Normal file
13
src/logger/CMakeLists.txt
Normal file
@ -0,0 +1,13 @@
|
||||
set(SOURCES
|
||||
logger.cpp
|
||||
printer.cpp
|
||||
accumulator.cpp
|
||||
)
|
||||
|
||||
set(HEADERS
|
||||
logger.h
|
||||
printer.h
|
||||
accumulator.h
|
||||
)
|
||||
|
||||
target_sources(${PROJECT_NAME} PRIVATE ${SOURCES})
|
33
src/logger/accumulator.cpp
Normal file
33
src/logger/accumulator.cpp
Normal file
@ -0,0 +1,33 @@
|
||||
#include "accumulator.h"
|
||||
|
||||
Accumulator::Accumulator(Severity severity):
|
||||
severity(severity),
|
||||
history()
|
||||
{}
|
||||
|
||||
Logger::Severity Accumulator::getSeverity() const {
|
||||
return severity;
|
||||
}
|
||||
|
||||
void Accumulator::setSeverity(Severity severity) {
|
||||
Accumulator::severity = severity;
|
||||
}
|
||||
|
||||
void Accumulator::log(const std::list<Message>& comments, bool colored) const {
|
||||
(void)(colored);
|
||||
for (const Message& comment : comments)
|
||||
if (comment.first >= Accumulator::severity)
|
||||
history.emplace_back(comment);
|
||||
}
|
||||
|
||||
void Accumulator::log(Severity severity, const std::string& comment, bool colored) const {
|
||||
(void)(colored);
|
||||
if (severity < Accumulator::severity)
|
||||
return;
|
||||
|
||||
history.emplace_back(severity, comment);
|
||||
}
|
||||
|
||||
std::list<Logger::Message> Accumulator::getHistory() const {
|
||||
return history;
|
||||
}
|
20
src/logger/accumulator.h
Normal file
20
src/logger/accumulator.h
Normal file
@ -0,0 +1,20 @@
|
||||
#pragma once
|
||||
|
||||
#include "logger.h"
|
||||
|
||||
class Accumulator : public Logger {
|
||||
public:
|
||||
Accumulator(Severity severity = Severity::info);
|
||||
|
||||
virtual void log(const std::list<Message>& comments, bool colored = true) const override;
|
||||
virtual void log(Severity severity, const std::string& comment, bool colored = true) const override;
|
||||
|
||||
virtual Severity getSeverity() const override;
|
||||
virtual void setSeverity(Severity severity) override;
|
||||
|
||||
std::list<Message> getHistory() const;
|
||||
|
||||
private:
|
||||
Severity severity;
|
||||
mutable std::list<Message> history;
|
||||
};
|
53
src/logger/logger.cpp
Normal file
53
src/logger/logger.cpp
Normal file
@ -0,0 +1,53 @@
|
||||
#include "logger.h"
|
||||
|
||||
#include <array>
|
||||
#include <string_view>
|
||||
#include <algorithm>
|
||||
|
||||
constexpr std::array<std::string_view, static_cast<int>(Logger::Severity::_severitySize)> levels({
|
||||
"debug",
|
||||
"info",
|
||||
"minor",
|
||||
"major",
|
||||
"warning",
|
||||
"error",
|
||||
"fatal"
|
||||
});
|
||||
|
||||
Logger::~Logger() {}
|
||||
|
||||
void Logger::debug(const std::string& comment, bool colored) const {
|
||||
log(Severity::debug, comment, colored);
|
||||
}
|
||||
|
||||
void Logger::info(const std::string& comment, bool colored) const {
|
||||
log(Severity::info, comment, colored);
|
||||
}
|
||||
|
||||
void Logger::minor(const std::string& comment, bool colored) const {
|
||||
log(Severity::minor, comment, colored);
|
||||
}
|
||||
|
||||
void Logger::major(const std::string& comment, bool colored) const {
|
||||
log(Severity::major, comment, colored);
|
||||
}
|
||||
|
||||
void Logger::warn(const std::string& comment, bool colored) const {
|
||||
log(Severity::warning, comment, colored);
|
||||
}
|
||||
|
||||
void Logger::error(const std::string& comment, bool colored) const {
|
||||
log(Severity::error, comment, colored);
|
||||
}
|
||||
|
||||
void Logger::fatal(const std::string& comment, bool colored) const {
|
||||
log(Severity::fatal, comment, colored);
|
||||
}
|
||||
|
||||
Logger::Severity Logger::stringToSeverity(const std::string& line) {
|
||||
unsigned char dist = std::distance(levels.begin(), std::find(levels.begin(), levels.end(), line));
|
||||
if (dist < static_cast<unsigned char>(Severity::_severitySize))
|
||||
return static_cast<Severity>(dist);
|
||||
|
||||
return Severity::_severitySize;
|
||||
}
|
36
src/logger/logger.h
Normal file
36
src/logger/logger.h
Normal file
@ -0,0 +1,36 @@
|
||||
#pragma once
|
||||
|
||||
#include <list>
|
||||
#include <string>
|
||||
|
||||
class Logger {
|
||||
public:
|
||||
enum class Severity {
|
||||
debug,
|
||||
info,
|
||||
minor,
|
||||
major,
|
||||
warning,
|
||||
error,
|
||||
fatal,
|
||||
_severitySize
|
||||
};
|
||||
using Message = std::pair<Severity, std::string>;
|
||||
|
||||
void debug(const std::string& comment, bool colored = true) const;
|
||||
void info(const std::string& comment, bool colored = true) const;
|
||||
void minor(const std::string& comment, bool colored = true) const;
|
||||
void major(const std::string& comment, bool colored = true) const;
|
||||
void warn(const std::string& comment, bool colored = true) const;
|
||||
void error(const std::string& comment, bool colored = true) const;
|
||||
void fatal(const std::string& comment, bool colored = true) const;
|
||||
|
||||
virtual ~Logger();
|
||||
virtual void log(const std::list<Message>& comments, bool colored = true) const = 0;
|
||||
virtual void log(Severity severity, const std::string& comment, bool colored = true) const = 0;
|
||||
|
||||
virtual void setSeverity(Severity severity) = 0;
|
||||
virtual Severity getSeverity() const = 0;
|
||||
|
||||
static Severity stringToSeverity(const std::string& line);
|
||||
};
|
135
src/logger/printer.cpp
Normal file
135
src/logger/printer.cpp
Normal file
@ -0,0 +1,135 @@
|
||||
#include "printer.h"
|
||||
|
||||
#include <array>
|
||||
#include <string_view>
|
||||
#include <iostream>
|
||||
|
||||
constexpr const std::array<std::string_view, static_cast<int>(Logger::Severity::_severitySize)> 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<std::string_view, static_cast<int>(Logger::Severity::_severitySize)> logHeaders({
|
||||
/*debug*/ "DEBUG: ",
|
||||
/*info*/ "INFO: ",
|
||||
/*minor*/ "MINOR: ",
|
||||
/*major*/ "MAJOR: ",
|
||||
/*warning*/ "WARNING: ",
|
||||
/*error*/ "ERROR: ",
|
||||
/*fatal*/ "FATAL: "
|
||||
});
|
||||
|
||||
constexpr const std::string_view bold("\e[1m");
|
||||
constexpr const std::string_view regular("\e[22m");
|
||||
constexpr const std::string_view clearStyle("\e[0m");
|
||||
constexpr const std::string_view clearLine("\e[2K\r");
|
||||
|
||||
Printer::Printer(Severity severity):
|
||||
severity(severity),
|
||||
mutex(),
|
||||
status(std::nullopt)
|
||||
{}
|
||||
|
||||
Logger::Severity Printer::getSeverity() const {
|
||||
return severity;
|
||||
}
|
||||
|
||||
void Printer::setSeverity(Severity severity) {
|
||||
Printer::severity = severity;
|
||||
}
|
||||
|
||||
void Printer::setStatusMessage(const std::string& message) {
|
||||
if (status.has_value())
|
||||
std::cout << clearLine;
|
||||
|
||||
status = message;
|
||||
std::cout << message << std::flush;
|
||||
}
|
||||
|
||||
void Printer::clearStatusMessage() {
|
||||
if (status.has_value()) {
|
||||
std::cout << clearLine << std::flush;
|
||||
status = std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
void Printer::printNested(
|
||||
const std::string& header,
|
||||
const std::vector<std::string>& lines,
|
||||
const std::list<Message>& comments,
|
||||
const std::optional<std::string>& status
|
||||
) {
|
||||
std::lock_guard lock(mutex);
|
||||
if (comments.size() > 0) {
|
||||
if (status.has_value())
|
||||
std::cout << clearLine;
|
||||
|
||||
std::cout << bold << header << clearStyle << "\n";
|
||||
for (const std::string& line : lines)
|
||||
std::cout << line << "\n";
|
||||
|
||||
for (const Message& msg : comments) {
|
||||
if (msg.first >= severity) {
|
||||
std::cout << '\t';
|
||||
printMessage(msg, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (status.has_value())
|
||||
Printer::status = status;
|
||||
|
||||
finishPrint(true);
|
||||
}
|
||||
|
||||
void Printer::log(const std::list<Message>& comments, bool colored) const {
|
||||
std::lock_guard lock(mutex);
|
||||
|
||||
bool changed = false;
|
||||
for (const Message& msg : comments) {
|
||||
if (msg.first >= severity) {
|
||||
if (!changed && status.has_value())
|
||||
std::cout << clearLine;
|
||||
|
||||
printMessage(msg, colored);
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
finishPrint(colored);
|
||||
}
|
||||
|
||||
void Printer::log(Severity severity, const std::string& comment, bool colored) const {
|
||||
if (severity < Printer::severity)
|
||||
return;
|
||||
|
||||
std::lock_guard lock(mutex);
|
||||
if (status.has_value())
|
||||
std::cout << clearLine;
|
||||
|
||||
printMessage({severity, comment}, colored);
|
||||
finishPrint(colored);
|
||||
}
|
||||
|
||||
void Printer::finishPrint(bool colored) const {
|
||||
if (colored)
|
||||
std::cout << clearStyle;
|
||||
|
||||
if (status.has_value())
|
||||
std::cout << '\r' << status.value();
|
||||
|
||||
std::cout << std::flush;
|
||||
}
|
||||
|
||||
void Printer::printMessage(const Message& message, bool colored) const {
|
||||
if (colored) {
|
||||
int severity = static_cast<int>(message.first);
|
||||
std::cout << logSettings[severity] << bold << logHeaders[severity] << regular;
|
||||
}
|
||||
std::cout << message.second << '\n';
|
||||
}
|
38
src/logger/printer.h
Normal file
38
src/logger/printer.h
Normal file
@ -0,0 +1,38 @@
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
|
||||
#include "logger.h"
|
||||
|
||||
class Printer : public Logger {
|
||||
public:
|
||||
Printer(Severity severity = Severity::info);
|
||||
|
||||
virtual void log(const std::list<Message>& comments, bool colored = true) const override;
|
||||
virtual void log(Severity severity, const std::string& comment, bool colored = true) const override;
|
||||
|
||||
virtual void setSeverity(Severity severity) override;
|
||||
virtual Severity getSeverity() const override;
|
||||
|
||||
void setStatusMessage(const std::string& message);
|
||||
void clearStatusMessage();
|
||||
void printNested(
|
||||
const std::string& header,
|
||||
const std::vector<std::string>& lines = {},
|
||||
const std::list<Message>& comments = {},
|
||||
const std::optional<std::string>& status = std::nullopt
|
||||
);
|
||||
|
||||
private:
|
||||
void printMessage(const Message& message, bool colored = false) const;
|
||||
void finishPrint(bool colored = false) const;
|
||||
|
||||
private:
|
||||
std::atomic<Severity> severity;
|
||||
mutable std::mutex mutex;
|
||||
std::optional<std::string> status;
|
||||
};
|
76
src/main.cpp
Normal file
76
src/main.cpp
Normal file
@ -0,0 +1,76 @@
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
#include <chrono>
|
||||
#include <memory>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "FLAC/stream_decoder.h"
|
||||
#include <lame/lame.h>
|
||||
|
||||
#include "help.h"
|
||||
#include "collection.h"
|
||||
#include "taskmanager.h"
|
||||
#include "settings.h"
|
||||
#include "logger/logger.h"
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
std::shared_ptr<Printer> logger = std::make_shared<Printer>();
|
||||
std::shared_ptr<Settings> settings = std::make_shared<Settings>(argc, argv);
|
||||
|
||||
switch (settings->getAction()) {
|
||||
case Settings::help:
|
||||
printHelp();
|
||||
return 0;
|
||||
case Settings::config:
|
||||
std::cout << settings->defaultConfig() << std::endl;
|
||||
return 0;
|
||||
case Settings::convert:
|
||||
std::cout << "Converting..." << std::endl;
|
||||
break;
|
||||
default:
|
||||
std::cout << "Error in action" << std::endl;
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!settings->readConfigFile() && settings->isConfigDefault()) {
|
||||
std::string defaultConfigPath = settings->getConfigPath();
|
||||
std::ofstream file(defaultConfigPath, std::ios::out | std::ios::trunc);
|
||||
if (file.is_open()) {
|
||||
std::cout << "Writing default config to " << defaultConfigPath << std::endl;
|
||||
file << settings->defaultConfig();
|
||||
} else {
|
||||
std::cout << "Couldn't open " << defaultConfigPath << " to write default config" << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
std::string input = settings->getInput();
|
||||
if (input.empty()) {
|
||||
std::cout << "Input folder is not specified, quitting" << std::endl;
|
||||
return -2;
|
||||
}
|
||||
|
||||
std::string output = settings->getOutput();
|
||||
if (output.empty()) {
|
||||
std::cout << "Output folder is not specified, quitting" << std::endl;
|
||||
return -3;
|
||||
}
|
||||
|
||||
logger->setSeverity(settings->getLogLevel());
|
||||
TaskManager taskManager(settings, logger);
|
||||
taskManager.start();
|
||||
|
||||
std::chrono::time_point start = std::chrono::system_clock::now();
|
||||
Collection collection(input, &taskManager);
|
||||
collection.convert(output);
|
||||
|
||||
taskManager.wait();
|
||||
std::cout << std::endl;
|
||||
taskManager.stop();
|
||||
|
||||
std::chrono::time_point end = std::chrono::system_clock::now();
|
||||
std::chrono::duration<double> seconds = end - start;
|
||||
std::cout << "Encoding is done, it took " << seconds.count() << " seconds in total, enjoy!" << std::endl;
|
||||
|
||||
return 0;
|
||||
}
|
373
src/settings.cpp
Normal file
373
src/settings.cpp
Normal file
@ -0,0 +1,373 @@
|
||||
#include "settings.h"
|
||||
|
||||
static const char* defaultConfig =
|
||||
#include "generated/default.conf"
|
||||
;
|
||||
|
||||
enum class Flag {
|
||||
config,
|
||||
help,
|
||||
none
|
||||
};
|
||||
|
||||
enum class Option {
|
||||
level,
|
||||
type,
|
||||
source,
|
||||
destination,
|
||||
parallel,
|
||||
filesToCopy,
|
||||
encodingQuality,
|
||||
outputQuality,
|
||||
vbr,
|
||||
_optionsSize
|
||||
};
|
||||
|
||||
using Literals = std::array<std::string_view, 2>;
|
||||
constexpr std::array<Literals, static_cast<int>(Flag::none)> flags({{
|
||||
{"-c", "--config"},
|
||||
{"-h", "--help"}
|
||||
}});
|
||||
|
||||
constexpr std::array<std::string_view, Settings::_actionsSize> actions({
|
||||
"convert",
|
||||
"help",
|
||||
"config"
|
||||
});
|
||||
|
||||
constexpr std::array<std::string_view, static_cast<int>(Option::_optionsSize)> options({
|
||||
"level",
|
||||
"type",
|
||||
"source",
|
||||
"destination",
|
||||
"parallel",
|
||||
"filesToCopy",
|
||||
"encodingQuality",
|
||||
"outputQuality",
|
||||
"vbr"
|
||||
});
|
||||
|
||||
constexpr std::array<std::string_view, Settings::_typesSize> types({
|
||||
"mp3"
|
||||
});
|
||||
|
||||
constexpr unsigned int maxQuality = 9;
|
||||
constexpr unsigned int minQuality = 0;
|
||||
|
||||
bool is_space(char ch){
|
||||
return std::isspace(static_cast<unsigned char>(ch));
|
||||
}
|
||||
|
||||
Flag getFlag(const std::string_view arg) {
|
||||
for (unsigned char i = 0; i < flags.size(); ++i) {
|
||||
const Literals& lit = flags[i];
|
||||
unsigned char dist = std::distance(lit.begin(), std::find(lit.begin(), lit.end(), arg));
|
||||
if (dist < lit.size())
|
||||
return static_cast<Flag>(i);
|
||||
}
|
||||
|
||||
return Flag::none;
|
||||
}
|
||||
|
||||
Option stringToOption(const std::string& source) {
|
||||
unsigned char dist = std::distance(options.begin(), std::find(options.begin(), options.end(), source));
|
||||
if (dist < static_cast<unsigned char>(Option::_optionsSize))
|
||||
return static_cast<Option>(dist);
|
||||
|
||||
return Option::_optionsSize;
|
||||
}
|
||||
|
||||
Settings::Settings(int argc, char ** argv):
|
||||
arguments(),
|
||||
action(std::nullopt),
|
||||
outputType(std::nullopt),
|
||||
input(std::nullopt),
|
||||
output(std::nullopt),
|
||||
logLevel(std::nullopt),
|
||||
configPath(std::nullopt),
|
||||
threads(std::nullopt),
|
||||
nonMusic(std::nullopt),
|
||||
encodingQuality(std::nullopt),
|
||||
outputQuality(std::nullopt),
|
||||
vbr(std::nullopt)
|
||||
{
|
||||
for (int i = 1; i < argc; ++i)
|
||||
arguments.push_back(argv[i]);
|
||||
|
||||
parseArguments();
|
||||
}
|
||||
|
||||
void Settings::parseArguments() {
|
||||
Flag flag = Flag::none;
|
||||
for (unsigned int i = 0; i < arguments.size(); ++i) {
|
||||
const std::string_view& arg = arguments[i];
|
||||
if (i == 0) {
|
||||
Action act = stringToAction(arg);
|
||||
if (act < _actionsSize) {
|
||||
action = act;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
switch (flag) {
|
||||
case Flag::config:
|
||||
configPath = arg;
|
||||
flag = Flag::none;
|
||||
continue;
|
||||
case Flag::none:
|
||||
flag = getFlag(arg);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
switch (flag) {
|
||||
case Flag::none:
|
||||
break;
|
||||
case Flag::help:
|
||||
action = help;
|
||||
flag = Flag::none;
|
||||
continue;
|
||||
default:
|
||||
continue;
|
||||
}
|
||||
|
||||
if (getAction() == convert) {
|
||||
if (!input.has_value()) {
|
||||
input = arg;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!output.has_value()) {
|
||||
output = arg;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Settings::Action Settings::getAction() const {
|
||||
if (action.has_value())
|
||||
return action.value();
|
||||
else
|
||||
return convert;
|
||||
}
|
||||
|
||||
Settings::Type Settings::getType() const {
|
||||
if (outputType.has_value())
|
||||
return outputType.value();
|
||||
else
|
||||
return mp3;
|
||||
}
|
||||
|
||||
std::string Settings::getInput() const {
|
||||
if (input.has_value())
|
||||
return resolvePath(input.value());
|
||||
else
|
||||
return "";
|
||||
}
|
||||
|
||||
std::string Settings::getOutput() const {
|
||||
if (output.has_value())
|
||||
return resolvePath(output.value());
|
||||
else
|
||||
return "";
|
||||
}
|
||||
|
||||
std::string Settings::getConfigPath() const {
|
||||
if (configPath.has_value())
|
||||
return resolvePath(configPath.value());
|
||||
else
|
||||
return resolvePath("~/.config/mlc.conf");
|
||||
}
|
||||
|
||||
bool Settings::isConfigDefault() const {
|
||||
return !configPath.has_value();
|
||||
}
|
||||
|
||||
Logger::Severity Settings::getLogLevel() const {
|
||||
if (logLevel.has_value())
|
||||
return logLevel.value();
|
||||
else
|
||||
return Logger::Severity::info;
|
||||
}
|
||||
|
||||
unsigned int Settings::getThreads() const {
|
||||
if (threads.has_value())
|
||||
return threads.value();
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
unsigned char Settings::getOutputQuality() const {
|
||||
if (outputQuality.has_value())
|
||||
return outputQuality.value();
|
||||
else
|
||||
return minQuality; //it means max possible quality, min is for min enum value
|
||||
}
|
||||
|
||||
unsigned char Settings::getEncodingQuality() const {
|
||||
if (encodingQuality.has_value())
|
||||
return encodingQuality.value();
|
||||
else
|
||||
return minQuality; //it means max possible quality, min is for min enum value
|
||||
}
|
||||
|
||||
bool Settings::getVBR() const {
|
||||
if (vbr.has_value())
|
||||
return vbr.value();
|
||||
else
|
||||
return true;
|
||||
}
|
||||
|
||||
void Settings::strip(std::string& line) {
|
||||
line.erase(line.begin(), std::find_if(line.begin(), line.end(), std::not_fn(is_space)));
|
||||
line.erase(std::find_if(line.rbegin(), line.rend(), std::not_fn(is_space)).base(), line.end());
|
||||
}
|
||||
|
||||
void Settings::stripComment(std::string& line) {
|
||||
std::string::size_type index = line.find('#');
|
||||
if (index != std::string::npos)
|
||||
line.erase(index);
|
||||
|
||||
strip(line);
|
||||
}
|
||||
|
||||
std::string Settings::defaultConfig() const {
|
||||
return ::defaultConfig + 1;
|
||||
}
|
||||
|
||||
bool Settings::readConfigFile() {
|
||||
std::ifstream file;
|
||||
file.open(getConfigPath(), std::ios::in);
|
||||
if (file.is_open()){
|
||||
std::string tp;
|
||||
while(getline(file, tp)) {
|
||||
stripComment(tp);
|
||||
if (!tp.empty())
|
||||
readConfigLine(tp);
|
||||
}
|
||||
file.close();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void Settings::readConfigLine(const std::string& line) {
|
||||
std::istringstream stream(line);
|
||||
std::string key;
|
||||
if (!(stream >> key))
|
||||
return;
|
||||
|
||||
Option option = stringToOption(key);
|
||||
if (option == Option::_optionsSize)
|
||||
return;
|
||||
|
||||
switch (option) {
|
||||
case Option::level: {
|
||||
std::string 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 lv;
|
||||
if (!outputType.has_value() && stream >> lv) {
|
||||
Type type = stringToType(lv);
|
||||
if (type < _typesSize)
|
||||
outputType = type;
|
||||
}
|
||||
} break;
|
||||
case Option::source: {
|
||||
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: {
|
||||
std::string path;
|
||||
if (!output.has_value() && stream >> path)
|
||||
output = path;
|
||||
} break;
|
||||
case Option::parallel: {
|
||||
unsigned int count;
|
||||
if (!threads.has_value() && stream >> count)
|
||||
threads = count;
|
||||
} break;
|
||||
case Option::filesToCopy: {
|
||||
std::string regex;
|
||||
if (!nonMusic.has_value() && stream >> regex) {
|
||||
if (regex == "all")
|
||||
regex = "";
|
||||
else if (regex == "none")
|
||||
regex = "a^";
|
||||
|
||||
nonMusic = regex;
|
||||
}
|
||||
} break;
|
||||
case Option::outputQuality: {
|
||||
unsigned int value;
|
||||
if (!outputQuality.has_value() && stream >> value)
|
||||
outputQuality = std::clamp(value, minQuality, maxQuality);
|
||||
} break;
|
||||
case Option::encodingQuality: {
|
||||
unsigned int value;
|
||||
if (!encodingQuality.has_value() && stream >> value)
|
||||
encodingQuality = std::clamp(value, minQuality, maxQuality);
|
||||
} break;
|
||||
case Option::vbr: {
|
||||
bool value;
|
||||
if (!vbr.has_value() && stream >> std::boolalpha >> value)
|
||||
vbr = value;
|
||||
} break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Settings::Action Settings::stringToAction(const std::string& source) {
|
||||
unsigned char dist = std::distance(actions.begin(), std::find(actions.begin(), actions.end(), source));
|
||||
if (dist < _actionsSize)
|
||||
return static_cast<Action>(dist);
|
||||
|
||||
return _actionsSize;
|
||||
}
|
||||
|
||||
Settings::Action Settings::stringToAction(const std::string_view& source) {
|
||||
unsigned char dist = std::distance(actions.begin(), std::find(actions.begin(), actions.end(), source));
|
||||
if (dist < _actionsSize)
|
||||
return static_cast<Action>(dist);
|
||||
|
||||
return _actionsSize;
|
||||
}
|
||||
|
||||
Settings::Type Settings::stringToType(const std::string& source) {
|
||||
unsigned char dist = std::distance(types.begin(), std::find(types.begin(), types.end(), source));
|
||||
if (dist < _typesSize)
|
||||
return static_cast<Type>(dist);
|
||||
|
||||
return _typesSize;
|
||||
}
|
||||
|
||||
std::string Settings::resolvePath(const std::string& line) {
|
||||
if (line.size() > 0 && line[0] == '~')
|
||||
return getenv("HOME") + line.substr(1);
|
||||
else
|
||||
return line;
|
||||
}
|
||||
|
||||
bool Settings::matchNonMusic(const std::string& fileName) const {
|
||||
if (nonMusic.has_value())
|
||||
return std::regex_search(fileName, nonMusic.value());
|
||||
else
|
||||
return true;
|
||||
}
|
||||
|
75
src/settings.h
Normal file
75
src/settings.h
Normal file
@ -0,0 +1,75 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
#include <array>
|
||||
#include <algorithm>
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <functional>
|
||||
#include <cctype>
|
||||
#include <sstream>
|
||||
#include <regex>
|
||||
|
||||
#include "logger/logger.h"
|
||||
|
||||
class Settings {
|
||||
public:
|
||||
enum Action {
|
||||
convert,
|
||||
help,
|
||||
config,
|
||||
_actionsSize
|
||||
};
|
||||
|
||||
enum Type {
|
||||
mp3,
|
||||
_typesSize
|
||||
};
|
||||
|
||||
Settings(int argc, char **argv);
|
||||
|
||||
std::string getInput() const;
|
||||
std::string getOutput() const;
|
||||
std::string getConfigPath() const;
|
||||
bool isConfigDefault() const;
|
||||
Logger::Severity getLogLevel() const;
|
||||
Type getType() const;
|
||||
Action getAction() const;
|
||||
unsigned int getThreads() const;
|
||||
bool matchNonMusic(const std::string& fileName) const;
|
||||
unsigned char getEncodingQuality() const;
|
||||
unsigned char getOutputQuality() const;
|
||||
bool getVBR() const;
|
||||
|
||||
bool readConfigFile();
|
||||
void readConfigLine(const std::string& line);
|
||||
std::string defaultConfig() const;
|
||||
|
||||
static Action stringToAction(const std::string& source);
|
||||
static Action stringToAction(const std::string_view& source);
|
||||
static Type stringToType(const std::string& source);
|
||||
|
||||
private:
|
||||
void parseArguments();
|
||||
|
||||
static void strip(std::string& line);
|
||||
static void stripComment(std::string& line);
|
||||
static std::string resolvePath(const std::string& line);
|
||||
|
||||
private:
|
||||
std::vector<std::string_view> arguments;
|
||||
std::optional<Action> action;
|
||||
std::optional<Type> outputType;
|
||||
std::optional<std::string> input;
|
||||
std::optional<std::string> output;
|
||||
std::optional<Logger::Severity> logLevel;
|
||||
std::optional<std::string> configPath;
|
||||
std::optional<unsigned int> threads;
|
||||
std::optional<std::regex> nonMusic;
|
||||
std::optional<unsigned char> encodingQuality;
|
||||
std::optional<unsigned char> outputQuality;
|
||||
std::optional<bool> vbr;
|
||||
};
|
190
src/taskmanager.cpp
Normal file
190
src/taskmanager.cpp
Normal file
@ -0,0 +1,190 @@
|
||||
#include "taskmanager.h"
|
||||
|
||||
#include "flactomp3.h"
|
||||
|
||||
TaskManager::TaskManager(const std::shared_ptr<Settings>& settings, const std::shared_ptr<Printer>& logger):
|
||||
settings(settings),
|
||||
logger(logger),
|
||||
busyThreads(0),
|
||||
maxTasks(0),
|
||||
completeTasks(0),
|
||||
terminate(false),
|
||||
running(false),
|
||||
queueMutex(),
|
||||
loopConditional(),
|
||||
waitConditional(),
|
||||
threads(),
|
||||
jobs()
|
||||
{
|
||||
}
|
||||
|
||||
TaskManager::~TaskManager() {
|
||||
}
|
||||
|
||||
void TaskManager::queueConvert(const std::filesystem::path& source, const std::filesystem::path& destination) {
|
||||
std::unique_lock<std::mutex> lock(queueMutex);
|
||||
jobs.emplace(Job::convert, source, destination);
|
||||
|
||||
++maxTasks;
|
||||
logger->setStatusMessage(std::to_string(completeTasks) + "/" + std::to_string(maxTasks));
|
||||
|
||||
lock.unlock();
|
||||
loopConditional.notify_one();
|
||||
}
|
||||
|
||||
void TaskManager::queueCopy(const std::filesystem::path& source, const std::filesystem::path& destination) {
|
||||
if (!settings->matchNonMusic(source.filename()))
|
||||
return;
|
||||
|
||||
std::unique_lock<std::mutex> lock(queueMutex);
|
||||
jobs.emplace(Job::copy, source, destination);
|
||||
++maxTasks;
|
||||
logger->setStatusMessage(std::to_string(completeTasks) + "/" + std::to_string(maxTasks));
|
||||
|
||||
lock.unlock();
|
||||
loopConditional.notify_one();
|
||||
}
|
||||
|
||||
bool TaskManager::busy() const {
|
||||
std::lock_guard lock(queueMutex);
|
||||
return !jobs.empty();
|
||||
}
|
||||
|
||||
void TaskManager::start() {
|
||||
std::lock_guard lock(queueMutex);
|
||||
if (running)
|
||||
return;
|
||||
|
||||
unsigned int amount = settings->getThreads();
|
||||
if (amount == 0)
|
||||
amount = std::thread::hardware_concurrency();
|
||||
|
||||
for (uint32_t i = 0; i < amount; ++i)
|
||||
threads.emplace_back(std::thread(&TaskManager::loop, this));
|
||||
|
||||
running = true;
|
||||
}
|
||||
|
||||
void TaskManager::loop() {
|
||||
while (true) {
|
||||
std::unique_lock<std::mutex> lock(queueMutex);
|
||||
while (!terminate && jobs.empty())
|
||||
loopConditional.wait(lock);
|
||||
|
||||
if (terminate)
|
||||
return;
|
||||
|
||||
Job job = jobs.front();
|
||||
++busyThreads;
|
||||
jobs.pop();
|
||||
lock.unlock();
|
||||
|
||||
JobResult result = execute(job);
|
||||
|
||||
lock.lock();
|
||||
++completeTasks;
|
||||
printResilt(job, result);
|
||||
--busyThreads;
|
||||
lock.unlock();
|
||||
waitConditional.notify_all();
|
||||
}
|
||||
}
|
||||
|
||||
void TaskManager::stop() {
|
||||
std::unique_lock lock(queueMutex);
|
||||
if (!running)
|
||||
return;
|
||||
|
||||
terminate = true;
|
||||
|
||||
lock.unlock();
|
||||
loopConditional.notify_all();
|
||||
for (std::thread& thread : threads)
|
||||
thread.join();
|
||||
|
||||
lock.lock();
|
||||
threads.clear();
|
||||
running = false;
|
||||
logger->clearStatusMessage();
|
||||
}
|
||||
|
||||
void TaskManager::wait() {
|
||||
std::unique_lock lock(queueMutex);
|
||||
while (busyThreads != 0 || !jobs.empty())
|
||||
waitConditional.wait(lock);
|
||||
}
|
||||
|
||||
unsigned int TaskManager::getCompleteTasks() const {
|
||||
std::lock_guard lock(queueMutex);
|
||||
return completeTasks;
|
||||
}
|
||||
|
||||
TaskManager::JobResult TaskManager::execute(Job& job) {
|
||||
switch (job.type) {
|
||||
case Job::copy:
|
||||
return copyJob(job, settings);
|
||||
case Job::convert:
|
||||
switch (settings->getType()) {
|
||||
case Settings::mp3:
|
||||
job.destination.replace_extension("mp3");
|
||||
return mp3Job(job, settings);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return {false, {
|
||||
{Logger::Severity::error, "Unknown job type: " + std::to_string(job.type)}
|
||||
}};
|
||||
}
|
||||
|
||||
void TaskManager::printResilt(const TaskManager::Job& job, const TaskManager::JobResult& result) {
|
||||
std::string msg;
|
||||
switch (job.type) {
|
||||
case Job::copy:
|
||||
if (result.first)
|
||||
msg = "File copy complete, but there are messages about it:";
|
||||
else
|
||||
msg = "File copy failed!";
|
||||
break;
|
||||
case Job::convert:
|
||||
if (result.first)
|
||||
msg = "Encoding complete but there are messages about it:";
|
||||
else
|
||||
msg = "Encoding failed!";
|
||||
break;
|
||||
}
|
||||
|
||||
logger->printNested(
|
||||
msg,
|
||||
{"Source: \t" + job.source.string(), "Destination: \t" + job.destination.string()},
|
||||
result.second,
|
||||
std::to_string(completeTasks) + "/" + std::to_string(maxTasks)
|
||||
);
|
||||
}
|
||||
|
||||
TaskManager::JobResult TaskManager::mp3Job(const TaskManager::Job& job, const std::shared_ptr<Settings>& settings) {
|
||||
FLACtoMP3 convertor(settings->getLogLevel());
|
||||
convertor.setInputFile(job.source);
|
||||
convertor.setOutputFile(job.destination);
|
||||
convertor.setParameters(settings->getEncodingQuality(), settings->getOutputQuality(), settings->getVBR());
|
||||
bool result = convertor.run();
|
||||
|
||||
return {result, convertor.getHistory()};
|
||||
}
|
||||
|
||||
TaskManager::JobResult TaskManager::copyJob(const TaskManager::Job& job, const std::shared_ptr<Settings>& settings) {
|
||||
(void)(settings);
|
||||
bool success = std::filesystem::copy_file(
|
||||
job.source,
|
||||
job.destination,
|
||||
std::filesystem::copy_options::overwrite_existing
|
||||
);
|
||||
|
||||
return {success, {}};
|
||||
}
|
||||
|
||||
TaskManager::Job::Job(Type type, const std::filesystem::path& source, std::filesystem::path destination):
|
||||
type(type),
|
||||
source(source),
|
||||
destination(destination) {}
|
68
src/taskmanager.h
Normal file
68
src/taskmanager.h
Normal file
@ -0,0 +1,68 @@
|
||||
#pragma once
|
||||
|
||||
#include <mutex>
|
||||
#include <condition_variable>
|
||||
#include <thread>
|
||||
#include <functional>
|
||||
#include <filesystem>
|
||||
#include <vector>
|
||||
#include <list>
|
||||
#include <queue>
|
||||
#include <string>
|
||||
#include <atomic>
|
||||
#include <iostream>
|
||||
#include <array>
|
||||
#include <memory>
|
||||
|
||||
#include "settings.h"
|
||||
#include "logger/printer.h"
|
||||
|
||||
class TaskManager {
|
||||
using JobResult = std::pair<bool, std::list<Logger::Message>>;
|
||||
struct Job;
|
||||
public:
|
||||
TaskManager(const std::shared_ptr<Settings>& settings, const std::shared_ptr<Printer>& logger);
|
||||
~TaskManager();
|
||||
|
||||
void start();
|
||||
void queueConvert(const std::filesystem::path& source, const std::filesystem::path& destination);
|
||||
void queueCopy(const std::filesystem::path& source, const std::filesystem::path& destination);
|
||||
void stop();
|
||||
bool busy() const;
|
||||
void wait();
|
||||
|
||||
unsigned int getCompleteTasks() const;
|
||||
|
||||
private:
|
||||
void loop();
|
||||
JobResult execute(Job& job);
|
||||
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);
|
||||
|
||||
private:
|
||||
std::shared_ptr<Settings> settings;
|
||||
std::shared_ptr<Printer> logger;
|
||||
unsigned int busyThreads;
|
||||
unsigned int maxTasks;
|
||||
unsigned int completeTasks;
|
||||
bool terminate;
|
||||
bool running;
|
||||
mutable std::mutex queueMutex;
|
||||
std::condition_variable loopConditional;
|
||||
std::condition_variable waitConditional;
|
||||
std::vector<std::thread> threads;
|
||||
std::queue<Job> jobs;
|
||||
|
||||
};
|
||||
|
||||
struct TaskManager::Job {
|
||||
enum Type {
|
||||
copy,
|
||||
convert
|
||||
};
|
||||
Job(Type type, const std::filesystem::path& source, std::filesystem::path destination);
|
||||
Type type;
|
||||
std::filesystem::path source;
|
||||
std::filesystem::path destination;
|
||||
};
|
154
taskmanager.cpp
154
taskmanager.cpp
@ -1,154 +0,0 @@
|
||||
#include "taskmanager.h"
|
||||
|
||||
#include "flactomp3.h"
|
||||
|
||||
constexpr const std::array<std::string_view, Loggable::fatal + 1> 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<std::string_view, Loggable::fatal + 1> 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(),
|
||||
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();
|
||||
}
|
||||
|
||||
JobResult result = job(pair.first, pair.second);
|
||||
++completeTasks;
|
||||
printProgress(result, pair.first, pair.second);
|
||||
--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);
|
||||
}
|
||||
|
||||
TaskManager::JobResult TaskManager::job(const std::string& source, const std::string& destination) {
|
||||
FLACtoMP3 convertor(Loggable::debug);
|
||||
convertor.setInputFile(source);
|
||||
convertor.setOutputFile(destination);
|
||||
bool result = convertor.run();
|
||||
return {result, convertor.getHistory()};
|
||||
}
|
||||
|
||||
void TaskManager::printProgress() const {
|
||||
std::unique_lock<std::mutex> 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<std::mutex> 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;
|
||||
}
|
@ -1,53 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <mutex>
|
||||
#include <condition_variable>
|
||||
#include <thread>
|
||||
#include <functional>
|
||||
#include <vector>
|
||||
#include <list>
|
||||
#include <queue>
|
||||
#include <string>
|
||||
#include <atomic>
|
||||
#include <iostream>
|
||||
#include <array>
|
||||
|
||||
#include "loggable.h"
|
||||
|
||||
class TaskManager {
|
||||
typedef std::pair<bool, std::list<Loggable::Message>> JobResult;
|
||||
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;
|
||||
void printProgress(const JobResult& result, const std::string& source, const std::string& destination) const;
|
||||
|
||||
private:
|
||||
void loop();
|
||||
bool loopCondition() const;
|
||||
bool waitCondition() const;
|
||||
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<uint32_t> busyThreads;
|
||||
std::atomic<uint32_t> maxTasks;
|
||||
std::atomic<uint32_t> completeTasks;
|
||||
bool terminate;
|
||||
mutable std::mutex printMutex;
|
||||
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