diff --git a/CMakeLists.txt b/CMakeLists.txt index e253293..5949bb0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -21,6 +21,7 @@ add_executable(mlc help.cpp decoded.cpp flactomp3.cpp + collection.cpp ) target_link_libraries(mlc diff --git a/README.md b/README.md new file mode 100644 index 0000000..d356f93 --- /dev/null +++ b/README.md @@ -0,0 +1,32 @@ +# MLC - Music Library Compiler + +This is a program for compilation of your loseless music library to lossy formats + +### Prerequisites + +- flac +- lame +- jpeg + +### Building + +``` +$ git clone https://git.macaw.me/blue/mlc +$ cd mlc +$ mkdir build +$ cd build +$ cmake .. +$ cmake --build . +``` + +### Usage + +``` +./mlc path/to/loseless/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. + +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. + +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. diff --git a/collection.cpp b/collection.cpp new file mode 100644 index 0000000..c319a00 --- /dev/null +++ b/collection.cpp @@ -0,0 +1,109 @@ +#include "collection.h" + +namespace fs = std::filesystem; + +static const std::string flac(".flac"); + +Collection::Collection(const std::string& path) : + path(fs::canonical(path)), + countMusical(0), + counted(false), + convertedSoFar(0) +{ +} + +Collection::Collection(const std::filesystem::path& path): + path(fs::canonical(path)), + countMusical(0), + counted(false), + convertedSoFar(0) +{ +} + + +Collection::~Collection() { +} + +void Collection::list() const { + if (fs::is_regular_file(path)) + return; + + for (const fs::directory_entry & entry : fs::directory_iterator(path)) + std::cout << entry.path() << std::endl; +} + +uint32_t Collection::countMusicFiles() const { + if (counted) + return countMusical; + + if (fs::is_regular_file(path)) { + if (isMusic(path)) + ++countMusical; + } else if (fs::is_directory(path)) { + for (const fs::directory_entry& entry : fs::directory_iterator(path)) { + switch (entry.status().type()) { + case fs::file_type::regular: + if (isMusic(entry.path())) + ++countMusical; + break; + case fs::file_type::directory: { + Collection collection(entry.path()); + countMusical += collection.countMusicFiles(); + } break; + default: + break; + } + } + } + + counted = true; + return countMusical; +} + +void Collection::convert(const std::string& outPath, std::function progress) { + convertedSoFar = 0; + fs::path out = fs::absolute(outPath); + if (progress == nullptr) { + progress = std::bind(&Collection::prg, this); + } + + fs::create_directories(out); + out = fs::canonical(outPath); + for (const fs::directory_entry& entry : fs::directory_iterator(path)) { + 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"); + FLACtoMP3 convertor; + convertor.setInputFile(sourcePath); + convertor.setOutputFile(dstPath); + convertor.run(); + progress(); + } else { + fs::copy_file(sourcePath, dstPath); + } + //std::cout << sourcePath << " => " << dstPath << std::endl; + } break; + case fs::file_type::directory: { + fs::path sourcePath = entry.path(); + Collection collection(sourcePath); + fs::path::iterator itr = sourcePath.end(); + --itr; + collection.convert(std::string(out / *itr), progress); + } break; + default: + break; + } + } +} + +bool Collection::isMusic(const std::filesystem::path& path) { + return path.extension() == flac; //I know, it's primitive yet, but it's the fastest +} + +void Collection::prg() const { + std::cout << "\r" << ++convertedSoFar << "/" << countMusicFiles() << std::flush; +} + diff --git a/collection.h b/collection.h new file mode 100644 index 0000000..cda8d68 --- /dev/null +++ b/collection.h @@ -0,0 +1,30 @@ +#pragma once + +#include +#include +#include +#include + +#include "flactomp3.h" + +class Collection { +public: + Collection(const std::string& path); + Collection(const std::filesystem::path& path); + ~Collection(); + + void list() const; + uint32_t countMusicFiles() const; + void convert(const std::string& outPath, std::function progress = nullptr); + +private: + static bool isMusic(const std::filesystem::path& path); + void prg() const; + +private: + std::filesystem::path path; + mutable uint32_t countMusical; + mutable bool counted; + mutable uint32_t convertedSoFar; +}; + diff --git a/flactomp3.cpp b/flactomp3.cpp index a183a4b..9f206d0 100644 --- a/flactomp3.cpp +++ b/flactomp3.cpp @@ -45,13 +45,12 @@ FLACtoMP3::~FLACtoMP3() { FLAC__stream_decoder_delete(decoder); } -void FLACtoMP3::run() { +bool FLACtoMP3::run() { FLAC__bool ok = FLAC__stream_decoder_process_until_end_of_stream(decoder); - std::cout << "decoding: "; if (ok) { - std::cout << "succeeded"; + if (pcmCounter > 0) + flush(); - flush(); int nwrite = lame_encode_flush(encoder, outputBuffer, pcmSize * 2); fwrite((char*)outputBuffer, nwrite, 1, output); @@ -77,12 +76,9 @@ void FLACtoMP3::run() { fwrite((char*)outputBuffer, vbrTagSize, 1, output); } - } else { - std::cout << "FAILED"; } - std::cout << std::endl; - std::cout << " state: " << FLAC__StreamDecoderStateString[FLAC__stream_decoder_get_state(decoder)] << std::endl; + // std::cout << " state: " << FLAC__StreamDecoderStateString[FLAC__stream_decoder_get_state(decoder)] << std::endl; if (outputInitilized) { fclose(output); @@ -96,7 +92,11 @@ void FLACtoMP3::run() { pcmSize = 0; flacMaxBlockSize = 0; outputBufferSize = 0; + + return ok; } + + return false; } void FLACtoMP3::setInputFile(const std::string& path) { @@ -230,7 +230,6 @@ void FLACtoMP3::processPicture(const FLAC__StreamMetadata_Picture& picture) { } bool FLACtoMP3::scaleJPEG(const FLAC__StreamMetadata_Picture& picture) { - // Variables for the decompressor itself struct jpeg_decompress_struct dinfo; struct jpeg_error_mgr derr; @@ -243,7 +242,7 @@ bool FLACtoMP3::scaleJPEG(const FLAC__StreamMetadata_Picture& picture) { std::cout << "error reading jpeg header" << std::endl; return false; } - uint64_t mem_size = LAME_MAXALBUMART - 1024 * 16; + uint64_t mem_size = LAME_MAXALBUMART; uint8_t *mem = new uint8_t[mem_size]; dinfo.scale_num = 2; //need to tune it, feels like 500 by 500 is a good size @@ -267,23 +266,19 @@ bool FLACtoMP3::scaleJPEG(const FLAC__StreamMetadata_Picture& picture) { uint32_t rowSize = dinfo.image_width * dinfo.output_components; uint8_t* row = new uint8_t[rowSize]; - while (dinfo.output_scanline < dinfo.output_height) { - jpeg_read_scanlines(&dinfo, &row, 1); + while (dinfo.output_scanline < dinfo.output_height) { + jpeg_read_scanlines(&dinfo, &row, 1); jpeg_write_scanlines(&cinfo, &row, 1); - } - jpeg_finish_decompress(&dinfo); - jpeg_destroy_decompress(&dinfo); + } + jpeg_finish_decompress(&dinfo); + jpeg_destroy_decompress(&dinfo); jpeg_finish_compress(&cinfo); jpeg_destroy_compress(&cinfo); - // And free the input buffer - delete[] row; + delete[] row; - std::cout << "writing " << mem_size << std::endl; int result = id3tag_set_albumart(encoder, (const char*)mem, mem_size); - - std::cout << "deallocating" << std::endl; delete[] mem; return result == 0; @@ -353,7 +348,7 @@ bool FLACtoMP3::flush() { return actuallyWritten == 1; } else { if (nwrite == 0) { - std::cout << "encoding flush encoded 0 bytes, skipping write" << std::endl; + //std::cout << "encoding flush encoded 0 bytes, skipping write" << std::endl; return true; } else { std::cout << "encoding flush failed, error: " << nwrite << std::endl; diff --git a/flactomp3.h b/flactomp3.h index 5971e2e..f59f9da 100644 --- a/flactomp3.h +++ b/flactomp3.h @@ -17,7 +17,7 @@ public: void setInputFile(const std::string& path); void setOutputFile(const std::string& path); - void run(); + bool run(); private: void processTags(const FLAC__StreamMetadata_VorbisComment& tags);