Compare commits

..

18 Commits

Author SHA1 Message Date
ceab08a26d
Typo fixes, readme updates 2024-07-06 13:53:26 -03:00
3971a5b662
pthreads for compatibility 2023-10-20 18:12:26 -03:00
2cce5f52f0
build scenario changes 2023-10-20 17:39:27 -03:00
5c3a4a592e
encoding settings 2023-10-13 16:10:08 -03:00
03e7f29d84
regex for additional files copying 2023-10-12 22:00:16 -03:00
eb85b71651
CI, try 12 2023-10-11 17:11:23 -03:00
a337cc7ec3
CI, try 11 2023-10-11 17:09:54 -03:00
97ffe45b24
CI, try 9 2023-10-11 17:07:18 -03:00
ca9f67c223
CI, try 9 2023-10-11 17:00:31 -03:00
45f3e1d6dd
CI, try 8 2023-10-11 16:57:54 -03:00
9b726e37fc
CI, try 7 2023-10-11 16:22:39 -03:00
7edbc32377
CI, try 6 2023-10-11 16:19:28 -03:00
ed4d7365ac
CI try 5 2023-10-10 21:28:06 -03:00
18489476af
CI try 4 2023-10-10 20:48:38 -03:00
7685dd95f6
CI, try 3 2023-10-10 20:38:44 -03:00
e6068b1837 Update .gitea/workflows/release.yml 2023-10-10 23:09:25 +00:00
633d08fd4b Update .gitea/workflows/release.yml 2023-10-10 23:00:59 +00:00
e9a1e8cb1d
CI, try 2 2023-10-10 13:37:11 -03:00
21 changed files with 457 additions and 128 deletions

View File

@ -9,33 +9,37 @@ jobs:
runs-on: archlinux runs-on: archlinux
steps: steps:
- name: Download the release tarball - name: Download the release tarball
uses: curl -sL "${{ gitea.server_url }}/${{ gitea.repository }}/archive/${{ gitea.event.release.tag_name }}.tar.gz" run: curl -sL ${{ gitea.server_url }}/${{ gitea.repository }}/archive/${{ gitea.event.release.tag_name }}.tar.gz --output tarball.tar.gz
- name: Set tarball filename variable
run: echo "tbName=$(echo ${{ gitea.event.repository.name }}-${{ gitea.event.release.tag_name }}.tar.gz)" >> $GITHUB_ENV
- name: Calculate SHA256 for the tarball - name: Calculate SHA256 for the tarball
run: echo "tbSum=$(echo sha256sum ${{ env.tbName }} | cut -d " " -f 1" >> $GITHUB_ENV run: echo "tbSum=$(sha256sum tarball.tar.gz | cut -d ' ' -f 1)" >> $GITHUB_ENV
- name: Unarchive tarball - name: Unarchive tarball
run: tar -xvzf ${{ env.tbName }} 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: Create a directory
run: mkdir aur
- name: Copy PKGBUILD to the directory - name: Copy PKGBUILD to the directory
run: cp mlc/packaging/Archlinux/PKGBUILD aur/ run: cp mlc/packaging/Archlinux/PKGBUILD aur/
- name: Put SHA256 sum to PKGBUILD file - name: Put SHA256 sum to PKGBUILD file, and generate .SRCINFO
working-directory: aur working-directory: aur
run: sed -i "/sha256sums=/c\sha256sums=('${{ env.tbSum }}')" PKGBUILD run: |
sed -i "/sha256sums=/c\sha256sums=('${{ env.tbSum }}')" PKGBUILD
sudo -u build makepkg --printsrcinfo > .SRCINFO
- name: create SRCINFO - name: Commit package to aur
working-directory: aur working-directory: aur
run: makepkg --printsrcinfo > .SRCINFO run: |
git add PKGBUILD .SRCINFO
- name: Debug print PKGBUILD git commit -m "${{ gitea.event.release.body }}"
run: cat aur/PKGBUILD GIT_SSH_COMMAND="ssh -i ../key -o 'IdentitiesOnly yes' -o 'StrictHostKeyChecking no'" git push
- name: Debug print .SRCINFO
run: cat aur/.SRCINFO

View File

@ -1,7 +1,17 @@
# Changelog # 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) ## MLC 1.3.1 (October 10, 2023)
- Release build with optimisations - Release build with optimizations
- Removed unused files from build - Removed unused files from build
- Suppressed warnings - Suppressed warnings
- CI to release to AUR - CI to release to AUR
@ -16,7 +26,7 @@
## 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 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 - 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 - 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

View File

@ -1,8 +1,8 @@
cmake_minimum_required(VERSION 3.5) cmake_minimum_required(VERSION 3.5)
project( project(
mlc mlc
VERSION 1.3.1 VERSION 1.3.4
DESCRIPTION "Media Library Compiler: rips your media library to a lossy compilation" DESCRIPTION "Media Library Compiler: converts your media library to a lossy compilation"
LANGUAGES CXX LANGUAGES CXX
) )
cmake_policy(SET CMP0076 NEW) cmake_policy(SET CMP0076 NEW)
@ -25,24 +25,25 @@ 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)
pkg_check_modules(LAME REQUIRED IMPORTED_TARGET lame) add_executable(${PROJECT_NAME})
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(mlc target_link_libraries(${PROJECT_NAME}
FLAC::FLAC FLAC::FLAC
PkgConfig::LAME LAME::LAME
JPEG::JPEG JPEG::JPEG
PkgConfig::TAGLIB TAGLIB::TAGLIB
Threads::Threads
) )
install(TARGETS mlc RUNTIME DESTINATION bin) install(TARGETS ${PROJECT_NAME} RUNTIME DESTINATION bin)

View File

@ -1,6 +1,8 @@
# MLC - Music Library Compiler # 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 ### Prerequisites
@ -11,7 +13,7 @@ This is a program for compilation of your loseless music library to lossy format
### Building ### Building
``` ```sh
$ git clone https://git.macaw.me/blue/mlc $ git clone https://git.macaw.me/blue/mlc
$ cd mlc $ cd mlc
$ mkdir build $ mkdir build
@ -22,12 +24,48 @@ $ 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 ./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
View 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()

View File

@ -1,20 +1,26 @@
#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 NAMES mp3lame) find_library(LAME_LIBRARIES lame 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)
IF (NOT Lame_FIND_QUIETLY) add_library(LAME::LAME SHARED IMPORTED)
MESSAGE(STATUS "Found lame includes: ${LAME_INCLUDE_DIR}/lame/lame.h") set_target_properties(LAME::LAME PROPERTIES
MESSAGE(STATUS "Found lame library: ${LAME_LIBRARIES}") IMPORTED_LOCATION "${LAME_LIBRARIES}"
ENDIF (NOT Lame_FIND_QUIETLY) INTERFACE_INCLUDE_DIRECTORIES "${LAME_INCLUDE_DIR}/lame"
ELSE(LAME_FOUND) INTERFACE_LINK_LIBRARIES "${LAME_LIBRARIES}"
IF (Lame_FIND_REQUIRED) )
MESSAGE(FATAL_ERROR "Could NOT find lame development files") if (NOT Lame_FIND_QUIETLY)
ENDIF (Lame_FIND_REQUIRED) message(STATUS "Found lame includes: ${LAME_INCLUDE_DIR}/lame")
ENDIF(LAME_FOUND) 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
View 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()

View File

@ -1,6 +1,6 @@
# Maintainer: Yury Gubich <blue@macaw.me> # Maintainer: Yury Gubich <blue@macaw.me>
pkgname=mlc pkgname=mlc
pkgver=1.3.1 pkgver=1.3.4
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')

View File

@ -6,22 +6,15 @@ namespace fs = std::filesystem;
static const std::string flac(".flac"); static const std::string flac(".flac");
Collection::Collection(const std::string& path, TaskManager* tm) :
path(fs::canonical(path)),
countMusical(0),
counted(false),
taskManager(tm)
{}
Collection::Collection(const std::filesystem::path& path, TaskManager* tm) : Collection::Collection(const std::filesystem::path& path, TaskManager* tm) :
path(fs::canonical(path)), path(path),
countMusical(0), countMusical(0),
counted(false), counted(false),
taskManager(tm) taskManager(tm)
{} {}
Collection::~Collection() { Collection::~Collection()
} {}
void Collection::list() const { void Collection::list() const {
if (fs::is_regular_file(path)) if (fs::is_regular_file(path))
@ -71,13 +64,10 @@ void Collection::convert(const std::string& outPath) {
switch (entry.status().type()) { switch (entry.status().type()) {
case fs::file_type::regular: { case fs::file_type::regular: {
fs::path sourcePath = entry.path(); fs::path sourcePath = entry.path();
fs::path dstPath = out / sourcePath.stem();
if (isMusic(sourcePath)) if (isMusic(sourcePath))
taskManager->queueJob(sourcePath, dstPath); taskManager->queueConvert(sourcePath, out / sourcePath.stem());
else else
fs::copy_file(sourcePath, dstPath, fs::copy_options::overwrite_existing); taskManager->queueCopy(sourcePath, out / sourcePath.filename());
//std::cout << sourcePath << " => " << dstPath << std::endl;
} break; } break;
case fs::file_type::directory: { case fs::file_type::directory: {
fs::path sourcePath = entry.path(); fs::path sourcePath = entry.path();

View File

@ -10,7 +10,6 @@ class TaskManager;
class Collection { class Collection {
public: public:
Collection(const std::string& path, TaskManager* tm = nullptr);
Collection(const std::filesystem::path& path, TaskManager* tm = nullptr); Collection(const std::filesystem::path& path, TaskManager* tm = nullptr);
~Collection(); ~Collection();

View File

@ -5,12 +5,12 @@
# #
# Use # sign for comments # Use # sign for comments
# #
# All printed comented out values are default values # All printed commented 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 behaviour. # so, edit it if you wish to change default behavior.
# #
# 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,27 +20,79 @@
#level info #level info
# Output type # Output type
# Allowed values are: [mp3] (more comming soon) # Allowed values are: [mp3] (more coming 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 usefull to set it when you always encode the same collection # 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 # 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
# Destingation path # Destination path
# This is a default path for your encoding destination # This is a default path for your encoding destination
# It's usefull to set it when you often encode your collection # It's useful 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 parralel # Defines how many threads are going to be started in parallel
# 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
#parallel 0 #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

View File

@ -12,6 +12,18 @@ constexpr std::string_view jpeg ("image/jpeg");
const std::map<std::string, std::string> textIdentificationReplacements({ const std::map<std::string, std::string> textIdentificationReplacements({
{"PUBLISHER", "TPUB"} {"PUBLISHER", "TPUB"}
}); });
constexpr std::array<int, 10> bitrates({
320,
288,
256,
224,
192,
160,
128,
96,
64,
32
});
FLACtoMP3::FLACtoMP3(Logger::Severity severity, uint8_t size) : FLACtoMP3::FLACtoMP3(Logger::Severity severity, uint8_t size) :
logger(severity), logger(severity),
@ -46,7 +58,7 @@ bool FLACtoMP3::run() {
if (pcmCounter > 0) if (pcmCounter > 0)
flush(); flush();
int nwrite = lame_encode_flush(encoder, outputBuffer, pcmSize * 2); int nwrite = lame_encode_flush(encoder, outputBuffer, outputBufferSize);
fwrite((char*)outputBuffer, nwrite, 1, output); fwrite((char*)outputBuffer, nwrite, 1, output);
@ -98,9 +110,21 @@ void FLACtoMP3::setOutputFile(const std::string& path) {
outPath = 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(encoder, vbr_default);
lame_set_VBR_quality(encoder, 0); lame_set_VBR_quality(encoder, outputQuality);
lame_set_quality(encoder, 0); } 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() { bool FLACtoMP3::initializeOutput() {
@ -306,7 +330,7 @@ bool FLACtoMP3::flush() {
nwrite = lame_encode_buffer_interleaved( nwrite = lame_encode_buffer_interleaved(
encoder, encoder,
pcm, pcm,
pcmCounter, pcmCounter / 2,
outputBuffer, outputBuffer,
outputBufferSize outputBufferSize
); );

View File

@ -1,6 +1,6 @@
#pragma once #pragma once
#include <FLAC/stream_decoder.h> #include <stream_decoder.h>
#include <lame.h> #include <lame.h>
#include <jpeglib.h> #include <jpeglib.h>
#include <id3v2tag.h> #include <id3v2tag.h>
@ -8,6 +8,7 @@
#include <string> #include <string>
#include <string_view> #include <string_view>
#include <map> #include <map>
#include <array>
#include <stdio.h> #include <stdio.h>
#include "logger/accumulator.h" #include "logger/accumulator.h"
@ -19,6 +20,7 @@ public:
void setInputFile(const std::string& path); void setInputFile(const std::string& path);
void setOutputFile(const std::string& path); void setOutputFile(const std::string& path);
void setParameters(unsigned char encodingQuality, unsigned char outputQuality, bool vbr);
bool run(); bool run();
std::list<Logger::Message> getHistory() const; std::list<Logger::Message> getHistory() const;

View File

@ -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 comile/latest` `mlc ~/Music compile/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 `conpile` in the CURRENT directory if it was not created before - creates directory `compile` in the CURRENT directory if it was not created before
- create `latest` directory in `compile` dirrectory if it was not created before - create `latest` directory in `compile` directory if it was not created before
- searches for music in `~/Music` directory - searches for music in `~/Music` directory
- converts all music to `comile/latest` directory - converts all music to `compile/latest` directory
- copies all other files found in `~/Music` to `comile/latest` - copies all other files found in `~/Music` to `compile/latest`
- any file name overlap will be overriden - any file name overlap will be overridden
`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 rip -c myConfig.conf` `mlc convert -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 behaviour - 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 - the rest is the same from the first example apart from default config

View File

@ -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::_sevetirySize)> levels({ constexpr std::array<std::string_view, static_cast<int>(Logger::Severity::_severitySize)> 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::_sevetirySize)) if (dist < static_cast<unsigned char>(Severity::_severitySize))
return static_cast<Severity>(dist); return static_cast<Severity>(dist);
return Severity::_sevetirySize; return Severity::_severitySize;
} }

View File

@ -13,7 +13,7 @@ public:
warning, warning,
error, error,
fatal, fatal,
_sevetirySize _severitySize
}; };
using Message = std::pair<Severity, std::string>; using Message = std::pair<Severity, std::string>;

View File

@ -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::_sevetirySize)> logSettings({ constexpr const std::array<std::string_view, static_cast<int>(Logger::Severity::_severitySize)> 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::_sevetirySize)> logHeaders({ constexpr const std::array<std::string_view, static_cast<int>(Logger::Severity::_severitySize)> logHeaders({
/*debug*/ "DEBUG: ", /*debug*/ "DEBUG: ",
/*info*/ "INFO: ", /*info*/ "INFO: ",
/*minor*/ "MINOR: ", /*minor*/ "MINOR: ",

View File

@ -16,6 +16,10 @@ enum class Option {
source, source,
destination, destination,
parallel, parallel,
filesToCopy,
encodingQuality,
outputQuality,
vbr,
_optionsSize _optionsSize
}; };
@ -36,13 +40,20 @@ constexpr std::array<std::string_view, static_cast<int>(Option::_optionsSize)> o
"type", "type",
"source", "source",
"destination", "destination",
"parallel" "parallel",
"filesToCopy",
"encodingQuality",
"outputQuality",
"vbr"
}); });
constexpr std::array<std::string_view, Settings::_typesSize> types({ constexpr std::array<std::string_view, Settings::_typesSize> types({
"mp3" "mp3"
}); });
constexpr unsigned int maxQuality = 9;
constexpr unsigned int minQuality = 0;
bool is_space(char ch){ bool is_space(char ch){
return std::isspace(static_cast<unsigned char>(ch)); return std::isspace(static_cast<unsigned char>(ch));
} }
@ -72,7 +83,13 @@ Settings::Settings(int argc, char ** argv):
outputType(std::nullopt), outputType(std::nullopt),
input(std::nullopt), input(std::nullopt),
output(std::nullopt), output(std::nullopt),
logLevel(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) for (int i = 1; i < argc; ++i)
arguments.push_back(argv[i]); arguments.push_back(argv[i]);
@ -182,6 +199,27 @@ unsigned int Settings::getThreads() const {
return 0; 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) { void Settings::strip(std::string& line) {
line.erase(line.begin(), std::find_if(line.begin(), line.end(), std::not_fn(is_space))); 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()); line.erase(std::find_if(line.rbegin(), line.rend(), std::not_fn(is_space)).base(), line.end());
@ -231,7 +269,7 @@ void Settings::readConfigLine(const std::string& line) {
std::string lv; std::string lv;
if (!logLevel.has_value() && stream >> lv) { if (!logLevel.has_value() && stream >> lv) {
Logger::Severity level = Logger::stringToSeverity(lv); Logger::Severity level = Logger::stringToSeverity(lv);
if (level < Logger::Severity::_sevetirySize) if (level < Logger::Severity::_severitySize)
logLevel = level; logLevel = level;
} }
} break; } break;
@ -264,6 +302,32 @@ void Settings::readConfigLine(const std::string& line) {
if (!threads.has_value() && stream >> count) if (!threads.has_value() && stream >> count)
threads = count; threads = count;
} break; } 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: default:
break; break;
} }
@ -299,3 +363,11 @@ std::string Settings::resolvePath(const std::string& line) {
else else
return line; return line;
} }
bool Settings::matchNonMusic(const std::string& fileName) const {
if (nonMusic.has_value())
return std::regex_search(fileName, nonMusic.value());
else
return true;
}

View File

@ -11,6 +11,7 @@
#include <functional> #include <functional>
#include <cctype> #include <cctype>
#include <sstream> #include <sstream>
#include <regex>
#include "logger/logger.h" #include "logger/logger.h"
@ -38,6 +39,10 @@ public:
Type getType() const; Type getType() const;
Action getAction() const; Action getAction() const;
unsigned int getThreads() 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(); bool readConfigFile();
void readConfigLine(const std::string& line); void readConfigLine(const std::string& line);
@ -63,4 +68,8 @@ private:
std::optional<Logger::Severity> logLevel; std::optional<Logger::Severity> logLevel;
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<unsigned char> encodingQuality;
std::optional<unsigned char> outputQuality;
std::optional<bool> vbr;
}; };

View File

@ -21,9 +21,9 @@ TaskManager::TaskManager(const std::shared_ptr<Settings>& settings, const std::s
TaskManager::~TaskManager() { TaskManager::~TaskManager() {
} }
void TaskManager::queueJob(const std::filesystem::path& source, const std::filesystem::path& destination) { void TaskManager::queueConvert(const std::filesystem::path& source, const std::filesystem::path& destination) {
std::unique_lock<std::mutex> lock(queueMutex); std::unique_lock<std::mutex> lock(queueMutex);
jobs.emplace(source, destination); jobs.emplace(Job::convert, source, destination);
++maxTasks; ++maxTasks;
logger->setStatusMessage(std::to_string(completeTasks) + "/" + std::to_string(maxTasks)); logger->setStatusMessage(std::to_string(completeTasks) + "/" + std::to_string(maxTasks));
@ -32,6 +32,19 @@ void TaskManager::queueJob(const std::filesystem::path& source, const std::files
loopConditional.notify_one(); 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 { bool TaskManager::busy() const {
std::lock_guard lock(queueMutex); std::lock_guard lock(queueMutex);
return !jobs.empty(); return !jobs.empty();
@ -61,28 +74,16 @@ void TaskManager::loop() {
if (terminate) if (terminate)
return; return;
std::pair<std::string, std::string> pair = jobs.front(); Job job = jobs.front();
++busyThreads; ++busyThreads;
jobs.pop(); jobs.pop();
lock.unlock(); lock.unlock();
JobResult result; JobResult result = execute(job);
switch (settings->getType()) {
case Settings::mp3:
result = mp3Job(pair.first, pair.second + ".mp3", settings->getLogLevel());
break;
default:
break;
}
lock.lock(); lock.lock();
++completeTasks; ++completeTasks;
logger->printNested( printResilt(job, result);
result.first ? "Encoding complete but there are messages about it" : "Encoding failed!",
{"Source: \t" + pair.first, "Destination: \t" + pair.second},
result.second,
std::to_string(completeTasks) + "/" + std::to_string(maxTasks)
);
--busyThreads; --busyThreads;
lock.unlock(); lock.unlock();
waitConditional.notify_all(); waitConditional.notify_all();
@ -118,14 +119,72 @@ unsigned int TaskManager::getCompleteTasks() const {
return completeTasks; return completeTasks;
} }
TaskManager::JobResult TaskManager::mp3Job( TaskManager::JobResult TaskManager::execute(Job& job) {
const std::filesystem::path& source, switch (job.type) {
const std::filesystem::path& destination, case Job::copy:
Logger::Severity logLevel) return copyJob(job, settings);
{ case Job::convert:
FLACtoMP3 convertor(logLevel); switch (settings->getType()) {
convertor.setInputFile(source); case Settings::mp3:
convertor.setOutputFile(destination); 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(); bool result = convertor.run();
return {result, convertor.getHistory()}; 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) {}

View File

@ -18,13 +18,15 @@
#include "logger/printer.h" #include "logger/printer.h"
class TaskManager { class TaskManager {
typedef std::pair<bool, std::list<Logger::Message>> JobResult; using JobResult = std::pair<bool, std::list<Logger::Message>>;
struct Job;
public: public:
TaskManager(const std::shared_ptr<Settings>& settings, const std::shared_ptr<Printer>& logger); TaskManager(const std::shared_ptr<Settings>& settings, const std::shared_ptr<Printer>& logger);
~TaskManager(); ~TaskManager();
void start(); void start();
void queueJob(const std::filesystem::path& source, const std::filesystem::path& destination); 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(); void stop();
bool busy() const; bool busy() const;
void wait(); void wait();
@ -33,7 +35,11 @@ public:
private: private:
void loop(); void loop();
static JobResult mp3Job(const std::filesystem::path& source, const std::filesystem::path& destination, Logger::Severity logLevel); 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: private:
std::shared_ptr<Settings> settings; std::shared_ptr<Settings> settings;
std::shared_ptr<Printer> logger; std::shared_ptr<Printer> logger;
@ -46,7 +52,17 @@ private:
std::condition_variable loopConditional; std::condition_variable loopConditional;
std::condition_variable waitConditional; std::condition_variable waitConditional;
std::vector<std::thread> threads; std::vector<std::thread> threads;
std::queue<std::pair<std::filesystem::path, std::filesystem::path>> jobs; 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;
};