reorganized the project, some idea of how to handle settings
This commit is contained in:
parent
e4cc5e8d0e
commit
870842f63d
17 changed files with 167 additions and 26 deletions
22
src/CMakeLists.txt
Normal file
22
src/CMakeLists.txt
Normal file
|
@ -0,0 +1,22 @@
|
|||
set(SOURCES
|
||||
main.cpp
|
||||
help.cpp
|
||||
decoded.cpp
|
||||
flactomp3.cpp
|
||||
collection.cpp
|
||||
taskmanager.cpp
|
||||
loggable.cpp
|
||||
settings.cpp
|
||||
)
|
||||
|
||||
set(HEADERS
|
||||
help.h
|
||||
decoded.h
|
||||
flactomp3.h
|
||||
collection.h
|
||||
taskmanager.h
|
||||
loggable.h
|
||||
settings.h
|
||||
)
|
||||
|
||||
target_sources(${PROJECT_NAME} PRIVATE ${SOURCES})
|
99
src/collection.cpp
Normal file
99
src/collection.cpp
Normal file
|
@ -0,0 +1,99 @@
|
|||
#include "collection.h"
|
||||
|
||||
#include "taskmanager.h"
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
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):
|
||||
path(fs::canonical(path)),
|
||||
countMusical(0),
|
||||
counted(false),
|
||||
taskManager(tm)
|
||||
{}
|
||||
|
||||
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) {
|
||||
if (taskManager == nullptr)
|
||||
throw 6;
|
||||
|
||||
fs::path out = fs::absolute(outPath);
|
||||
|
||||
fs::create_directories(out);
|
||||
out = fs::canonical(outPath);
|
||||
for (const fs::directory_entry& entry : fs::directory_iterator(path)) {
|
||||
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;
|
||||
} break;
|
||||
case fs::file_type::directory: {
|
||||
fs::path sourcePath = entry.path();
|
||||
Collection collection(sourcePath, taskManager);
|
||||
fs::path::iterator itr = sourcePath.end();
|
||||
--itr;
|
||||
collection.convert(std::string(out / *itr));
|
||||
} 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
|
||||
}
|
||||
|
30
src/collection.h
Normal file
30
src/collection.h
Normal file
|
@ -0,0 +1,30 @@
|
|||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
#include <filesystem>
|
||||
|
||||
#include "flactomp3.h"
|
||||
|
||||
class TaskManager;
|
||||
|
||||
class Collection {
|
||||
public:
|
||||
Collection(const std::string& path, TaskManager* tm = nullptr);
|
||||
Collection(const std::filesystem::path& path, TaskManager* tm = nullptr);
|
||||
~Collection();
|
||||
|
||||
void list() const;
|
||||
uint32_t countMusicFiles() const;
|
||||
void convert(const std::string& outPath);
|
||||
|
||||
private:
|
||||
static bool isMusic(const std::filesystem::path& path);
|
||||
|
||||
private:
|
||||
std::filesystem::path path;
|
||||
mutable uint32_t countMusical;
|
||||
mutable bool counted;
|
||||
TaskManager* taskManager;
|
||||
};
|
||||
|
202
src/decoded.cpp
Normal file
202
src/decoded.cpp
Normal file
|
@ -0,0 +1,202 @@
|
|||
#include "decoded.h"
|
||||
|
||||
#include <iostream>
|
||||
#include <ios>
|
||||
#include <fstream>
|
||||
|
||||
constexpr uint8_t waveDeclarationSize = 8;
|
||||
constexpr uint8_t waveDescriptionSize = 36;
|
||||
constexpr uint8_t waveHeaderSize = waveDeclarationSize + waveDescriptionSize;
|
||||
|
||||
Decoded::Decoded():
|
||||
totalSamples(0),
|
||||
sampleRate(0),
|
||||
channels(0),
|
||||
depth(0),
|
||||
streamPosition(0),
|
||||
stream(nullptr),
|
||||
streamSize(0)
|
||||
{}
|
||||
|
||||
Decoded::~Decoded() {
|
||||
if (streamInitialized())
|
||||
delete[] stream;
|
||||
}
|
||||
|
||||
bool Decoded::streamInitialized() const {
|
||||
return sampleRate != 0;
|
||||
}
|
||||
|
||||
void Decoded::initializeStream(uint64_t total, uint32_t rate, uint16_t channels, uint16_t depth) {
|
||||
if (streamInitialized())
|
||||
throw 1;
|
||||
|
||||
totalSamples = total;
|
||||
sampleRate = rate;
|
||||
Decoded::channels = channels;
|
||||
Decoded::depth = depth;
|
||||
streamPosition = 0;
|
||||
|
||||
streamSize = pcmSize() + waveHeaderSize;
|
||||
stream = new uint8_t[streamSize];
|
||||
}
|
||||
|
||||
void Decoded::printInfo() const {
|
||||
std::cout << "sample rate : " << sampleRate << " Hz" << std::endl;
|
||||
std::cout << "channels : " << channels << std::endl;
|
||||
std::cout << "bits per sample : " << depth << std::endl;
|
||||
std::cout << "total samples : " << totalSamples << std::endl;
|
||||
}
|
||||
|
||||
void Decoded::saveWaveToFile(const std::string& path) {
|
||||
std::ofstream fp;
|
||||
fp.open(path, std::ios::out | std::ios::binary);
|
||||
fp.write((char*)stream, streamSize);
|
||||
fp.close();
|
||||
}
|
||||
|
||||
uint64_t Decoded::waveStremSize() const {
|
||||
return streamSize;
|
||||
}
|
||||
|
||||
|
||||
void Decoded::write(uint16_t data) {
|
||||
uint16_t* stream16 = (uint16_t*)stream;
|
||||
stream16[streamPosition / 2] = data;
|
||||
streamPosition += 2;
|
||||
}
|
||||
|
||||
void Decoded::write(std::string_view data) {
|
||||
for (const char& letter : data) {
|
||||
stream[streamPosition] = (uint8_t)letter;
|
||||
++streamPosition;
|
||||
}
|
||||
}
|
||||
|
||||
void Decoded::write(uint32_t data) {
|
||||
uint32_t* stream32 = (uint32_t*)stream;
|
||||
stream32[streamPosition / 4] = data;
|
||||
streamPosition += 4;
|
||||
}
|
||||
|
||||
void Decoded::writeWaveHeader() {
|
||||
uint32_t pcm = pcmSize();
|
||||
uint8_t bytesPerSample = depth / 8;
|
||||
|
||||
write("RIFF"); //4
|
||||
write(pcm + waveDescriptionSize); //8
|
||||
write("WAVEfmt "); //8 //16
|
||||
write((uint32_t)16); //12 //20
|
||||
write((uint16_t)1); //14 //22
|
||||
write(channels); //16 //24
|
||||
write(sampleRate); //20 //28
|
||||
write((uint32_t)(sampleRate * channels * bytesPerSample)); //24 //32
|
||||
write((uint16_t)(channels * bytesPerSample));//block align //26 //34
|
||||
write(depth); //28 //36
|
||||
write("data"); //32 //40
|
||||
write(pcm); //36 //44
|
||||
}
|
||||
|
||||
uint64_t Decoded::pcmSize() const {
|
||||
return totalSamples * channels * (depth / 8);
|
||||
}
|
||||
|
||||
void Decoded::FLACerror(const FLAC__StreamDecoder* decoder, FLAC__StreamDecoderErrorStatus status, void* client_data) {
|
||||
(void)decoder, (void)client_data;
|
||||
std::cout << "Got error callback: " << FLAC__StreamDecoderErrorStatusString[status] << std::endl;
|
||||
}
|
||||
|
||||
void Decoded::FLACmetadata(const FLAC__StreamDecoder* decoder, const FLAC__StreamMetadata* metadata, void* client_data) {
|
||||
(void)(decoder);
|
||||
Decoded* decoded = static_cast<Decoded*>(client_data);
|
||||
|
||||
/* print some stats */
|
||||
if(metadata->type == FLAC__METADATA_TYPE_STREAMINFO) {
|
||||
/* save for later */
|
||||
decoded->initializeStream(
|
||||
metadata->data.stream_info.total_samples,
|
||||
metadata->data.stream_info.sample_rate,
|
||||
metadata->data.stream_info.channels,
|
||||
metadata->data.stream_info.bits_per_sample
|
||||
);
|
||||
|
||||
decoded->printInfo();
|
||||
}
|
||||
}
|
||||
|
||||
FLAC__StreamDecoderWriteStatus Decoded::FLACwrite(
|
||||
const FLAC__StreamDecoder* decoder,
|
||||
const FLAC__Frame* frame,
|
||||
const FLAC__int32 * const buffer[],
|
||||
void* client_data
|
||||
) {
|
||||
Decoded* decoded = static_cast<Decoded*>(client_data);
|
||||
|
||||
if (decoded->totalSamples == 0) {
|
||||
std::cout << "ERROR: this example only works for FLAC files that have a total_samples count in STREAMINFO" << std::endl;
|
||||
return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT;
|
||||
}
|
||||
if (decoded->channels != 2 || decoded->depth != 16) {
|
||||
std::cout << "ERROR: this example only supports 16bit stereo streams" << std::endl;
|
||||
return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT;
|
||||
}
|
||||
if (frame->header.channels != 2) {
|
||||
std::cout << "ERROR: This frame contains " << frame->header.channels << " channels (should be 2)" << std::endl;
|
||||
return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT;
|
||||
}
|
||||
if (buffer[0] == NULL) {
|
||||
std::cout << "ERROR: buffer [0] is NULL" << std::endl;
|
||||
return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT;
|
||||
}
|
||||
if (buffer[1] == NULL) {
|
||||
std::cout << "ERROR: buffer [1] is NULL" << std::endl;
|
||||
return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT;
|
||||
}
|
||||
|
||||
/* write WAVE header before we write the first frame */
|
||||
if (frame->header.number.sample_number == 0)
|
||||
decoded->writeWaveHeader();
|
||||
|
||||
/* write decoded PCM samples */
|
||||
for (size_t i = 0; i < frame->header.blocksize; i++) {
|
||||
decoded->write((uint16_t)buffer[0][i]);
|
||||
decoded->write((uint16_t)buffer[1][i]);
|
||||
}
|
||||
|
||||
return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE;
|
||||
}
|
||||
|
||||
Decoded* Decoded::fromFLACFile(const std::string& path) {
|
||||
FLAC__bool ok = true;
|
||||
FLAC__StreamDecoder *decoder = FLAC__stream_decoder_new();
|
||||
FLAC__StreamDecoderInitStatus init_status;
|
||||
|
||||
Decoded* decoded = new Decoded;
|
||||
|
||||
if (decoder == NULL) {
|
||||
std::cout << "error allocating flac decoder" << std::endl;
|
||||
throw 2;
|
||||
}
|
||||
FLAC__stream_decoder_set_md5_checking(decoder, true);
|
||||
init_status = FLAC__stream_decoder_init_file(decoder, path.c_str(), FLACwrite, FLACmetadata, FLACerror, decoded);
|
||||
|
||||
ok = init_status != FLAC__STREAM_DECODER_INIT_STATUS_OK;
|
||||
if (ok) {
|
||||
std::cout << "error initializing decoder: " << FLAC__StreamDecoderInitStatusString[init_status] << std::endl;
|
||||
} else {
|
||||
ok = FLAC__stream_decoder_process_until_end_of_stream(decoder);
|
||||
std::cout << "decoding: ";
|
||||
if (ok)
|
||||
std::cout << "succeeded";
|
||||
else
|
||||
std::cout << "FAILED";
|
||||
|
||||
std::cout << std::endl;
|
||||
std::cout << " state: " << FLAC__StreamDecoderStateString[FLAC__stream_decoder_get_state(decoder)] << std::endl;
|
||||
}
|
||||
|
||||
FLAC__stream_decoder_delete(decoder);
|
||||
|
||||
return decoded;
|
||||
}
|
||||
|
44
src/decoded.h
Normal file
44
src/decoded.h
Normal file
|
@ -0,0 +1,44 @@
|
|||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string_view>
|
||||
#include "FLAC/stream_decoder.h"
|
||||
|
||||
class Decoded {
|
||||
public:
|
||||
Decoded();
|
||||
~Decoded();
|
||||
|
||||
void initializeStream(uint64_t total, uint32_t rate, uint16_t channels, uint16_t depth);
|
||||
|
||||
bool streamInitialized() const;
|
||||
void printInfo() const;
|
||||
uint64_t pcmSize() const;
|
||||
uint64_t waveStremSize() const;
|
||||
void write(uint16_t data);
|
||||
void write(uint32_t data);
|
||||
void write(std::string_view data);
|
||||
void writeWaveHeader();
|
||||
void saveWaveToFile(const std::string& path);
|
||||
|
||||
static Decoded* fromFLACFile(const std::string& path);
|
||||
|
||||
static void FLACerror(const FLAC__StreamDecoder *decoder, FLAC__StreamDecoderErrorStatus status, void *client_data);
|
||||
static void FLACmetadata(const FLAC__StreamDecoder *decoder, const FLAC__StreamMetadata *metadata, void *client_data);
|
||||
static FLAC__StreamDecoderWriteStatus FLACwrite(
|
||||
const FLAC__StreamDecoder *decoder,
|
||||
const FLAC__Frame *frame,
|
||||
const FLAC__int32 * const buffer[],
|
||||
void *client_data
|
||||
);
|
||||
|
||||
private:
|
||||
uint64_t totalSamples;
|
||||
uint32_t sampleRate;
|
||||
uint16_t channels;
|
||||
uint16_t depth;
|
||||
uint64_t streamPosition;
|
||||
uint8_t* stream;
|
||||
uint64_t streamSize;
|
||||
};
|
||||
|
497
src/flactomp3.cpp
Normal file
497
src/flactomp3.cpp
Normal file
|
@ -0,0 +1,497 @@
|
|||
#include "flactomp3.h"
|
||||
|
||||
#include <cmath>
|
||||
|
||||
#include <tpropertymap.h>
|
||||
#include <attachedpictureframe.h>
|
||||
#include <textidentificationframe.h>
|
||||
|
||||
constexpr uint16_t flacDefaultMaxBlockSize = 4096;
|
||||
|
||||
constexpr std::string_view jpeg ("image/jpeg");
|
||||
const std::map<std::string, std::string> textIdentificationReplacements({
|
||||
{"PUBLISHER", "TPUB"}
|
||||
});
|
||||
|
||||
FLACtoMP3::FLACtoMP3(Severity severity, uint8_t size) :
|
||||
Loggable(severity),
|
||||
inPath(),
|
||||
outPath(),
|
||||
decoder(FLAC__stream_decoder_new()),
|
||||
encoder(lame_init()),
|
||||
statusFLAC(),
|
||||
output(nullptr),
|
||||
bufferMultiplier(size),
|
||||
flacMaxBlockSize(0),
|
||||
pcmCounter(0),
|
||||
pcmSize(0),
|
||||
pcm(nullptr),
|
||||
outputBuffer(nullptr),
|
||||
outputBufferSize(0),
|
||||
outputInitilized(false),
|
||||
downscaleAlbumArt(false),
|
||||
id3v2tag()
|
||||
{
|
||||
}
|
||||
|
||||
FLACtoMP3::~FLACtoMP3() {
|
||||
lame_close(encoder);
|
||||
FLAC__stream_decoder_delete(decoder);
|
||||
}
|
||||
|
||||
bool FLACtoMP3::run() {
|
||||
FLAC__bool ok = FLAC__stream_decoder_process_until_end_of_stream(decoder);
|
||||
uint32_t fileSize;
|
||||
if (ok) {
|
||||
if (pcmCounter > 0)
|
||||
flush();
|
||||
|
||||
int nwrite = lame_encode_flush(encoder, outputBuffer, pcmSize * 2);
|
||||
fwrite((char*)outputBuffer, nwrite, 1, output);
|
||||
|
||||
|
||||
fileSize = ftell(output);
|
||||
lame_mp3_tags_fid(encoder, output);
|
||||
}
|
||||
|
||||
// std::cout << " state: " << FLAC__StreamDecoderStateString[FLAC__stream_decoder_get_state(decoder)] << std::endl;
|
||||
|
||||
if (outputInitilized) {
|
||||
fclose(output);
|
||||
output = nullptr;
|
||||
|
||||
delete[] pcm;
|
||||
delete[] outputBuffer;
|
||||
|
||||
pcm = nullptr;
|
||||
outputBuffer = nullptr;
|
||||
pcmSize = 0;
|
||||
flacMaxBlockSize = 0;
|
||||
outputBufferSize = 0;
|
||||
if (ok) {
|
||||
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);
|
||||
}
|
||||
|
||||
return ok;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void FLACtoMP3::setInputFile(const std::string& path) {
|
||||
if (inPath.size() > 0)
|
||||
throw 1;
|
||||
|
||||
inPath = path;
|
||||
|
||||
FLAC__stream_decoder_set_md5_checking(decoder, true);
|
||||
FLAC__stream_decoder_set_metadata_respond_all(decoder);
|
||||
statusFLAC = FLAC__stream_decoder_init_file(decoder, path.c_str(), write, metadata, error, this);
|
||||
}
|
||||
|
||||
void FLACtoMP3::setOutputFile(const std::string& path) {
|
||||
if (outPath.size() > 0)
|
||||
throw 2;
|
||||
|
||||
outPath = path;
|
||||
|
||||
lame_set_VBR(encoder, vbr_default);
|
||||
lame_set_VBR_quality(encoder, 0);
|
||||
lame_set_quality(encoder, 0);
|
||||
}
|
||||
|
||||
bool FLACtoMP3::initializeOutput() {
|
||||
if (outputInitilized)
|
||||
throw 5;
|
||||
|
||||
output = fopen(outPath.c_str(), "w+b");
|
||||
if (output == 0) {
|
||||
output = nullptr;
|
||||
log(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));
|
||||
fclose(output);
|
||||
output = nullptr;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (flacMaxBlockSize == 0)
|
||||
flacMaxBlockSize = flacDefaultMaxBlockSize;
|
||||
|
||||
pcmSize = lame_get_num_channels(encoder) * flacMaxBlockSize * bufferMultiplier;
|
||||
outputBufferSize = pcmSize / 2;
|
||||
|
||||
TagLib::ByteVector vector = id3v2tag.render();
|
||||
fwrite((const char*)vector.data(), vector.size(), 1, output);
|
||||
|
||||
pcm = new int16_t[pcmSize];
|
||||
outputBuffer = new uint8_t[outputBufferSize];
|
||||
|
||||
outputInitilized = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
void FLACtoMP3::processTags(const FLAC__StreamMetadata_VorbisComment& tags) {
|
||||
TagLib::PropertyMap props;
|
||||
std::list<TagLib::ID3v2::Frame*> customFrames;
|
||||
for (FLAC__uint32 i = 0; i < tags.num_comments; ++i) {
|
||||
const FLAC__StreamMetadata_VorbisComment_Entry& entry = tags.comments[i];
|
||||
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");
|
||||
continue;
|
||||
}
|
||||
std::string key(comm.substr(0, ePos));
|
||||
std::string value(comm.substr(ePos + 1));
|
||||
|
||||
if (key == "BPM") { //somehow TagLib lets BPM be fractured
|
||||
std::string::size_type dotPos = value.find("."); //but IDv2.3.0 spec requires it to be integer
|
||||
if (dotPos != std::string::npos) //I don't know better than just to floor it
|
||||
value = value.substr(0, dotPos);
|
||||
}
|
||||
|
||||
bool success = true;
|
||||
std::map<std::string, std::string>::const_iterator itr = textIdentificationReplacements.find(key);
|
||||
if (itr != textIdentificationReplacements.end()) {
|
||||
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 + "\"");
|
||||
} else {
|
||||
success = props.insert(key, TagLib::String(value, TagLib::String::UTF8));
|
||||
}
|
||||
|
||||
if (!success)
|
||||
log(warning, "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");
|
||||
|
||||
id3v2tag.setProperties(props);
|
||||
|
||||
for (TagLib::ID3v2::Frame* frame : customFrames)
|
||||
id3v2tag.addFrame(frame);
|
||||
|
||||
}
|
||||
|
||||
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));
|
||||
if (picture.mime_type == jpeg) {
|
||||
if (scaleJPEG(picture))
|
||||
log(debug, "successfully rescaled album art");
|
||||
else
|
||||
log(warning, "failed to rescale album art");
|
||||
}
|
||||
} else {
|
||||
//auch, sorry for copying so much, but I haven't found a way around it yet
|
||||
TagLib::ByteVector bytes((const char*)picture.data, picture.data_length);
|
||||
attachPictureFrame(picture, bytes);
|
||||
}
|
||||
}
|
||||
|
||||
bool FLACtoMP3::scaleJPEG(const FLAC__StreamMetadata_Picture& picture) {
|
||||
struct jpeg_decompress_struct dinfo;
|
||||
struct jpeg_error_mgr derr;
|
||||
|
||||
dinfo.err = jpeg_std_error(&derr);
|
||||
jpeg_create_decompress(&dinfo);
|
||||
jpeg_mem_src(&dinfo, picture.data, picture.data_length);
|
||||
int rc = jpeg_read_header(&dinfo, TRUE);
|
||||
|
||||
if (rc != 1) {
|
||||
log(Loggable::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
|
||||
uint64_t mem_size = 0;
|
||||
uint8_t* data = (uint8_t*)vector.data();
|
||||
|
||||
dinfo.scale_num = 2; //need to tune it, feels like 500 by 500 is a good size
|
||||
dinfo.scale_denom = 3;
|
||||
jpeg_start_decompress(&dinfo);
|
||||
|
||||
struct jpeg_compress_struct cinfo;
|
||||
struct jpeg_error_mgr cerr;
|
||||
cinfo.err = jpeg_std_error(&cerr);
|
||||
jpeg_create_compress(&cinfo);
|
||||
jpeg_mem_dest(&cinfo, &data, &mem_size);
|
||||
|
||||
cinfo.image_width = dinfo.output_width;
|
||||
cinfo.image_height = dinfo.output_height;
|
||||
cinfo.input_components = dinfo.output_components;
|
||||
cinfo.in_color_space = dinfo.out_color_space;
|
||||
jpeg_set_defaults(&cinfo);
|
||||
|
||||
jpeg_start_compress(&cinfo, TRUE);
|
||||
|
||||
uint32_t rowSize = dinfo.image_width * dinfo.output_components;
|
||||
uint8_t* row = new uint8_t[rowSize];
|
||||
|
||||
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");
|
||||
}
|
||||
jpeg_read_scanlines(&dinfo, &row, 1);
|
||||
jpeg_write_scanlines(&cinfo, &row, 1);
|
||||
}
|
||||
jpeg_finish_decompress(&dinfo);
|
||||
jpeg_destroy_decompress(&dinfo);
|
||||
|
||||
jpeg_finish_compress(&cinfo);
|
||||
jpeg_destroy_compress(&cinfo);
|
||||
|
||||
delete[] row;
|
||||
|
||||
vector.resize(mem_size); //to shrink if down to the actual size
|
||||
attachPictureFrame(picture, vector);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FLACtoMP3::decodeFrame(const int32_t * const buffer[], uint32_t size) {
|
||||
if (!outputInitilized) {
|
||||
bool success = initializeOutput();
|
||||
if (!success)
|
||||
return false;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < size; ++i) {
|
||||
pcm[pcmCounter++] = (int16_t)buffer[0][i];
|
||||
pcm[pcmCounter++] = (int16_t)buffer[1][i];
|
||||
|
||||
if (pcmCounter == pcmSize)
|
||||
return flush();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FLACtoMP3::flush() {
|
||||
int nwrite = lame_encode_buffer_interleaved(
|
||||
encoder,
|
||||
pcm,
|
||||
pcmCounter / 2,
|
||||
outputBuffer,
|
||||
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");;
|
||||
outputBufferSize = outputBufferSize * 2;
|
||||
delete[] outputBuffer;
|
||||
outputBuffer = new uint8_t[outputBufferSize];
|
||||
log(major, "allocating " + std::to_string(outputBufferSize) + " bytes");
|
||||
|
||||
nwrite = lame_encode_buffer_interleaved(
|
||||
encoder,
|
||||
pcm,
|
||||
pcmCounter,
|
||||
outputBuffer,
|
||||
outputBufferSize
|
||||
);
|
||||
}
|
||||
|
||||
if (nwrite > 0) {
|
||||
int actuallyWritten = fwrite((char*)outputBuffer, nwrite, 1, output);
|
||||
pcmCounter = 0;
|
||||
return actuallyWritten == 1;
|
||||
} else {
|
||||
if (nwrite == 0) {
|
||||
log(minor, "encoding flush encoded 0 bytes, skipping write");
|
||||
return true;
|
||||
} else {
|
||||
log(fatal, "encoding flush failed. Code = : " + std::to_string(nwrite));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FLACtoMP3::metadata(const FLAC__StreamDecoder* decoder, const FLAC__StreamMetadata* metadata, void* client_data) {
|
||||
(void)(decoder);
|
||||
FLACtoMP3* self = static_cast<FLACtoMP3*>(client_data);
|
||||
|
||||
switch (metadata->type) {
|
||||
case FLAC__METADATA_TYPE_STREAMINFO:
|
||||
self->processInfo(metadata->data.stream_info);
|
||||
break;
|
||||
case FLAC__METADATA_TYPE_VORBIS_COMMENT:
|
||||
self->processTags(metadata->data.vorbis_comment);
|
||||
break;
|
||||
case FLAC__METADATA_TYPE_PICTURE:
|
||||
self->processPicture(metadata->data.picture);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
FLAC__StreamDecoderWriteStatus FLACtoMP3::write(
|
||||
const FLAC__StreamDecoder* decoder,
|
||||
const FLAC__Frame* frame,
|
||||
const FLAC__int32 * const buffer[],
|
||||
void* client_data
|
||||
) {
|
||||
(void)(decoder);
|
||||
// if (decoded->channels != 2 || decoded->depth != 16) {
|
||||
// std::cout << "ERROR: this example only supports 16bit stereo streams" << std::endl;
|
||||
// return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT;
|
||||
// }
|
||||
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)");
|
||||
return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT;
|
||||
}
|
||||
if (buffer[0] == NULL) {
|
||||
self->log(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");
|
||||
return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT;
|
||||
}
|
||||
|
||||
bool result = self->decodeFrame(buffer, frame->header.blocksize);
|
||||
|
||||
if (result)
|
||||
return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE;
|
||||
else
|
||||
return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT;
|
||||
}
|
||||
|
||||
void FLACtoMP3::error(const FLAC__StreamDecoder* decoder, FLAC__StreamDecoderErrorStatus status, void* client_data) {
|
||||
(void)decoder;
|
||||
FLACtoMP3* self = static_cast<FLACtoMP3*>(client_data);
|
||||
std::string errText(FLAC__StreamDecoderErrorStatusString[status]);
|
||||
self->log(Loggable::error, "Got error callback: " + errText);
|
||||
}
|
||||
|
||||
void FLACtoMP3::attachPictureFrame(const FLAC__StreamMetadata_Picture& picture, const TagLib::ByteVector& bytes) {
|
||||
TagLib::ID3v2::AttachedPictureFrame* frame = new TagLib::ID3v2::AttachedPictureFrame();
|
||||
frame->setPicture(bytes);
|
||||
frame->setType(TagLib::ID3v2::AttachedPictureFrame::Media);
|
||||
frame->setMimeType(picture.mime_type);
|
||||
frame->setDescription(TagLib::String((const char*)picture.description, TagLib::String::UTF8));
|
||||
switch (picture.type) {
|
||||
case FLAC__STREAM_METADATA_PICTURE_TYPE_OTHER:
|
||||
frame->setType(TagLib::ID3v2::AttachedPictureFrame::Other);
|
||||
log(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");
|
||||
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...");
|
||||
break;
|
||||
case FLAC__STREAM_METADATA_PICTURE_TYPE_FRONT_COVER:
|
||||
frame->setType(TagLib::ID3v2::AttachedPictureFrame::FrontCover);
|
||||
log(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");
|
||||
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");
|
||||
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");
|
||||
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");
|
||||
break;
|
||||
case FLAC__STREAM_METADATA_PICTURE_TYPE_ARTIST:
|
||||
frame->setType(TagLib::ID3v2::AttachedPictureFrame::Artist);
|
||||
log(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");
|
||||
break;
|
||||
case FLAC__STREAM_METADATA_PICTURE_TYPE_BAND:
|
||||
frame->setType(TagLib::ID3v2::AttachedPictureFrame::Band);
|
||||
log(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");
|
||||
break;
|
||||
case FLAC__STREAM_METADATA_PICTURE_TYPE_LYRICIST:
|
||||
frame->setType(TagLib::ID3v2::AttachedPictureFrame::Lyricist);
|
||||
log(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");
|
||||
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");
|
||||
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");
|
||||
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");
|
||||
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");
|
||||
break;
|
||||
case FLAC__STREAM_METADATA_PICTURE_TYPE_ILLUSTRATION:
|
||||
frame->setType(TagLib::ID3v2::AttachedPictureFrame::Illustration);
|
||||
log(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");
|
||||
break;
|
||||
case FLAC__STREAM_METADATA_PICTURE_TYPE_PUBLISHER_LOGOTYPE:
|
||||
frame->setType(TagLib::ID3v2::AttachedPictureFrame::PublisherLogo);
|
||||
log(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\"");
|
||||
break;
|
||||
}
|
||||
|
||||
uint32_t sizeBytes = frame->picture().size();
|
||||
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);
|
||||
|
||||
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);
|
||||
id3v2tag.addFrame(frame);
|
||||
}
|
||||
|
62
src/flactomp3.h
Normal file
62
src/flactomp3.h
Normal file
|
@ -0,0 +1,62 @@
|
|||
#pragma once
|
||||
|
||||
#include <FLAC/stream_decoder.h>
|
||||
#include <lame.h>
|
||||
#include <jpeglib.h>
|
||||
#include <id3v2tag.h>
|
||||
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <map>
|
||||
#include <stdio.h>
|
||||
|
||||
#include "loggable.h"
|
||||
|
||||
class FLACtoMP3 : public Loggable {
|
||||
public:
|
||||
FLACtoMP3(Severity severity = info, uint8_t size = 4);
|
||||
~FLACtoMP3();
|
||||
|
||||
void setInputFile(const std::string& path);
|
||||
void setOutputFile(const std::string& path);
|
||||
bool run();
|
||||
|
||||
private:
|
||||
void processTags(const FLAC__StreamMetadata_VorbisComment& tags);
|
||||
void processInfo(const FLAC__StreamMetadata_StreamInfo& info);
|
||||
void processPicture(const FLAC__StreamMetadata_Picture& picture);
|
||||
bool decodeFrame(const int32_t * const buffer[], uint32_t size);
|
||||
bool flush();
|
||||
bool initializeOutput();
|
||||
bool scaleJPEG(const FLAC__StreamMetadata_Picture& picture);
|
||||
void attachPictureFrame(const FLAC__StreamMetadata_Picture& picture, const TagLib::ByteVector& bytes);
|
||||
|
||||
static void error(const FLAC__StreamDecoder *decoder, FLAC__StreamDecoderErrorStatus status, void *client_data);
|
||||
static void metadata(const FLAC__StreamDecoder *decoder, const FLAC__StreamMetadata *metadata, void *client_data);
|
||||
static FLAC__StreamDecoderWriteStatus write(
|
||||
const FLAC__StreamDecoder *decoder,
|
||||
const FLAC__Frame *frame,
|
||||
const FLAC__int32 * const buffer[],
|
||||
void *client_data
|
||||
);
|
||||
|
||||
private:
|
||||
std::string inPath;
|
||||
std::string outPath;
|
||||
|
||||
FLAC__StreamDecoder *decoder;
|
||||
lame_t encoder;
|
||||
FLAC__StreamDecoderInitStatus statusFLAC;
|
||||
|
||||
FILE* output;
|
||||
uint8_t bufferMultiplier;
|
||||
uint32_t flacMaxBlockSize;
|
||||
uint32_t pcmCounter;
|
||||
uint32_t pcmSize;
|
||||
int16_t* pcm;
|
||||
uint8_t* outputBuffer;
|
||||
uint32_t outputBufferSize;
|
||||
bool outputInitilized;
|
||||
bool downscaleAlbumArt;
|
||||
TagLib::ID3v2::Tag id3v2tag;
|
||||
};
|
7
src/help.cpp
Normal file
7
src/help.cpp
Normal file
|
@ -0,0 +1,7 @@
|
|||
#include "help.h"
|
||||
|
||||
#include "iostream"
|
||||
|
||||
void printHelp() {
|
||||
std::cout << "Sorry, there is no help yet. It will arrive one day... I hope" << std::endl;
|
||||
}
|
3
src/help.h
Normal file
3
src/help.h
Normal file
|
@ -0,0 +1,3 @@
|
|||
#pragma once
|
||||
|
||||
void printHelp ();
|
18
src/loggable.cpp
Normal file
18
src/loggable.cpp
Normal file
|
@ -0,0 +1,18 @@
|
|||
#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
src/loggable.h
Normal file
28
src/loggable.h
Normal file
|
@ -0,0 +1,28 @@
|
|||
#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;
|
||||
};
|
48
src/main.cpp
Normal file
48
src/main.cpp
Normal file
|
@ -0,0 +1,48 @@
|
|||
#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"
|
||||
#include "settings.h"
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
Settings settings(argc, argv);
|
||||
|
||||
switch (settings.getAction()) {
|
||||
case Settings::help:
|
||||
printHelp();
|
||||
return 0;
|
||||
case Settings::printConfig:
|
||||
//printHelp();
|
||||
return 0;
|
||||
case Settings::convert:
|
||||
std::cout << "Converting..." << std::endl;
|
||||
break;
|
||||
default:
|
||||
std::cout << "Error in action" << std::endl;
|
||||
return -1;
|
||||
}
|
||||
|
||||
TaskManager taskManager;
|
||||
taskManager.start();
|
||||
|
||||
std::chrono::time_point start = std::chrono::system_clock::now();
|
||||
Collection collection(settings.getInput(), &taskManager);
|
||||
collection.convert(settings.getOutput());
|
||||
|
||||
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;
|
||||
}
|
84
src/settings.cpp
Normal file
84
src/settings.cpp
Normal file
|
@ -0,0 +1,84 @@
|
|||
#include "settings.h"
|
||||
|
||||
constexpr std::array<std::string_view, Settings::_actionsSize> actions({
|
||||
"convert",
|
||||
"help",
|
||||
"printConfig"
|
||||
});
|
||||
|
||||
Settings::Settings(int argc, char ** argv):
|
||||
arguments(),
|
||||
action(std::nullopt),
|
||||
input(std::nullopt),
|
||||
output(std::nullopt),
|
||||
logLevel(std::nullopt)
|
||||
{
|
||||
for (int i = 1; i < argc; ++i)
|
||||
arguments.push_back(argv[i]);
|
||||
|
||||
parseArguments();
|
||||
}
|
||||
|
||||
void Settings::parseArguments() {
|
||||
for (int i = 0; i < arguments.size(); ++i) {
|
||||
const std::string_view& arg = arguments[i];
|
||||
if (i == 0) {
|
||||
std::string_view act = stripFlags(arg);
|
||||
int dist = std::distance(actions.begin(), std::find(actions.begin(), actions.end(), act));
|
||||
if (dist < _actionsSize) {
|
||||
action = static_cast<Settings::Action>(dist);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (getAction() == convert) {
|
||||
if (!input.has_value()) {
|
||||
input = arg;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!output.has_value()) {
|
||||
input = arg;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Settings::Action Settings::getAction() const {
|
||||
if (action.has_value())
|
||||
return action.value();
|
||||
else
|
||||
return convert;
|
||||
}
|
||||
|
||||
std::string Settings::getInput() const {
|
||||
if (input.has_value())
|
||||
return input.value();
|
||||
else
|
||||
return "";
|
||||
}
|
||||
|
||||
std::string Settings::getOutput() const {
|
||||
if (output.has_value())
|
||||
return output.value();
|
||||
else
|
||||
return "";
|
||||
}
|
||||
|
||||
Loggable::Severity Settings::getLogLevel() const {
|
||||
if (logLevel.has_value())
|
||||
return logLevel.value();
|
||||
else
|
||||
return Loggable::info;
|
||||
}
|
||||
|
||||
std::string_view Settings::stripFlags(const std::string_view& option) {
|
||||
if (option[0] == '-') {
|
||||
if (option[1] == '-')
|
||||
return option.substr(2);
|
||||
|
||||
return option.substr(1);
|
||||
}
|
||||
return option;
|
||||
}
|
39
src/settings.h
Normal file
39
src/settings.h
Normal file
|
@ -0,0 +1,39 @@
|
|||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
#include <array>
|
||||
#include <algorithm>
|
||||
|
||||
#include "loggable.h"
|
||||
|
||||
class Settings {
|
||||
public:
|
||||
enum Action {
|
||||
convert,
|
||||
help,
|
||||
printConfig,
|
||||
_actionsSize
|
||||
};
|
||||
|
||||
Settings(int argc, char **argv);
|
||||
|
||||
std::string getInput() const;
|
||||
std::string getOutput() const;
|
||||
Loggable::Severity getLogLevel() const;
|
||||
Action getAction() const;
|
||||
|
||||
private:
|
||||
void parseArguments();
|
||||
|
||||
static std::string_view stripFlags(const std::string_view& option);
|
||||
|
||||
private:
|
||||
std::vector<std::string_view> arguments;
|
||||
std::optional<Action> action;
|
||||
std::optional<std::string> input;
|
||||
std::optional<std::string> output;
|
||||
std::optional<Loggable::Severity> logLevel;
|
||||
};
|
154
src/taskmanager.cpp
Normal file
154
src/taskmanager.cpp
Normal file
|
@ -0,0 +1,154 @@
|
|||
#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;
|
||||
}
|
53
src/taskmanager.h
Normal file
53
src/taskmanager.h
Normal file
|
@ -0,0 +1,53 @@
|
|||
#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…
Add table
Add a link
Reference in a new issue