new way of logging, some tags fixes
This commit is contained in:
parent
c5eafe7d98
commit
2c913d5f54
13
CHANGELOG.md
13
CHANGELOG.md
@ -1,8 +1,15 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## MLC 1.1.0 (July 23, 2023)
|
||||||
|
- New logging system
|
||||||
|
- Artist, Album artist, Album and Title are now utf16 encoded, should fix broten titles
|
||||||
|
- BPM tag is now rounded, as it supposed to be by spec
|
||||||
|
- 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
|
||||||
|
|
||||||
## MLC 1.0.1 (July 21, 2023)
|
## MLC 1.0.1 (July 21, 2023)
|
||||||
### Added multithreaded encoding
|
- Added multithreaded encoding
|
||||||
### Only the first artist vorbis tag is left in the id3 tags
|
- Only the first artist vorbis tag is left in the id3 tags
|
||||||
|
|
||||||
## MLC 1.0.0 (July 19, 2023)
|
## MLC 1.0.0 (July 19, 2023)
|
||||||
### Initial release, only core functionality
|
- Initial release, only core functionality
|
||||||
|
@ -23,6 +23,7 @@ add_executable(mlc
|
|||||||
flactomp3.cpp
|
flactomp3.cpp
|
||||||
collection.cpp
|
collection.cpp
|
||||||
taskmanager.cpp
|
taskmanager.cpp
|
||||||
|
loggable.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
target_link_libraries(mlc
|
target_link_libraries(mlc
|
||||||
|
173
flactomp3.cpp
173
flactomp3.cpp
@ -1,16 +1,21 @@
|
|||||||
#include "flactomp3.h"
|
#include "flactomp3.h"
|
||||||
|
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
constexpr uint16_t flacDefaultMaxBlockSize = 4096;
|
constexpr uint16_t flacDefaultMaxBlockSize = 4096;
|
||||||
|
constexpr uint16_t id3v1TagSize = 128;
|
||||||
|
// constexpr std::wstring_view BOM ({(0xFEFF}); //one day...
|
||||||
|
|
||||||
static const std::string_view title ("TITLE=");
|
constexpr std::string_view title ("TITLE=");
|
||||||
static const std::string_view artist ("ARTIST=");
|
constexpr std::string_view artist ("ARTIST=");
|
||||||
static const std::string_view album ("ALBUM=");
|
constexpr std::string_view album ("ALBUM=");
|
||||||
static const std::string_view comment ("COMMENT=");
|
constexpr std::string_view comment ("COMMENT=");
|
||||||
static const std::string_view genre ("GENRE=");
|
constexpr std::string_view genre ("GENRE=");
|
||||||
static const std::string_view track ("TRACKNUMBER=");
|
constexpr std::string_view track ("TRACKNUMBER=");
|
||||||
static const std::string_view date ("DATE=");
|
constexpr std::string_view date ("DATE=");
|
||||||
|
|
||||||
|
constexpr std::string_view jpeg ("image/jpeg");
|
||||||
|
|
||||||
static const std::string_view jpeg ("image/jpeg");
|
|
||||||
|
|
||||||
static const std::map<std::string, const std::string> knownTags {
|
static const std::map<std::string, const std::string> knownTags {
|
||||||
{"ALBUMARTIST=", "TPE2="},
|
{"ALBUMARTIST=", "TPE2="},
|
||||||
@ -18,10 +23,12 @@ static const std::map<std::string, const std::string> knownTags {
|
|||||||
{"LENGTH=", "TLEN="},
|
{"LENGTH=", "TLEN="},
|
||||||
{"ISRC=", "TSRC="},
|
{"ISRC=", "TSRC="},
|
||||||
{"DISCNUMBER=", "TPOS="},
|
{"DISCNUMBER=", "TPOS="},
|
||||||
{"BPM=", "TBPM="}
|
{"BPM=", "TBPM="},
|
||||||
|
{"LYRICS=", "USLT="} //but it's not supported in LAME
|
||||||
};
|
};
|
||||||
|
|
||||||
FLACtoMP3::FLACtoMP3(uint8_t size) :
|
FLACtoMP3::FLACtoMP3(Severity severity, uint8_t size) :
|
||||||
|
Loggable(severity),
|
||||||
inPath(),
|
inPath(),
|
||||||
outPath(),
|
outPath(),
|
||||||
decoder(FLAC__stream_decoder_new()),
|
decoder(FLAC__stream_decoder_new()),
|
||||||
@ -36,7 +43,8 @@ FLACtoMP3::FLACtoMP3(uint8_t size) :
|
|||||||
outputBuffer(nullptr),
|
outputBuffer(nullptr),
|
||||||
outputBufferSize(0),
|
outputBufferSize(0),
|
||||||
outputInitilized(false),
|
outputInitilized(false),
|
||||||
downscaleAlbumArt(false)
|
downscaleAlbumArt(false),
|
||||||
|
usc2convertor()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -47,6 +55,7 @@ FLACtoMP3::~FLACtoMP3() {
|
|||||||
|
|
||||||
bool FLACtoMP3::run() {
|
bool FLACtoMP3::run() {
|
||||||
FLAC__bool ok = FLAC__stream_decoder_process_until_end_of_stream(decoder);
|
FLAC__bool ok = FLAC__stream_decoder_process_until_end_of_stream(decoder);
|
||||||
|
uint32_t fileSize;
|
||||||
if (ok) {
|
if (ok) {
|
||||||
if (pcmCounter > 0)
|
if (pcmCounter > 0)
|
||||||
flush();
|
flush();
|
||||||
@ -55,24 +64,26 @@ bool FLACtoMP3::run() {
|
|||||||
fwrite((char*)outputBuffer, nwrite, 1, output);
|
fwrite((char*)outputBuffer, nwrite, 1, output);
|
||||||
|
|
||||||
if (downscaleAlbumArt) {
|
if (downscaleAlbumArt) {
|
||||||
|
fileSize = ftell(output);
|
||||||
lame_mp3_tags_fid(encoder, output);
|
lame_mp3_tags_fid(encoder, output);
|
||||||
} else {
|
} else {
|
||||||
int tag1Size = lame_get_id3v1_tag(encoder, outputBuffer, 128);
|
int tag1Size = lame_get_id3v1_tag(encoder, outputBuffer, id3v1TagSize);
|
||||||
if (tag1Size > 128)
|
if (tag1Size > id3v1TagSize)
|
||||||
std::cout << std::endl << "couldn't write id3v1 tag";
|
log(warning, "couldn't write id3v1 tag");
|
||||||
else
|
else
|
||||||
fwrite((char*)outputBuffer, tag1Size, 1, output);
|
fwrite((char*)outputBuffer, tag1Size, 1, output);
|
||||||
|
|
||||||
|
fileSize = ftell(output);
|
||||||
fseek(output, 0, SEEK_SET);
|
fseek(output, 0, SEEK_SET);
|
||||||
int tag2Size = lame_get_id3v2_tag(encoder, outputBuffer, outputBufferSize);
|
int tag2Size = lame_get_id3v2_tag(encoder, outputBuffer, outputBufferSize);
|
||||||
if (tag2Size > outputBufferSize) {
|
if (tag2Size > outputBufferSize) {
|
||||||
std::cout << std::endl << "couldn't write id3v1 tag";
|
log(Loggable::error, "couldn't write id3v2 tag");
|
||||||
} else
|
} else
|
||||||
fwrite((char*)outputBuffer, tag2Size, 1, output);
|
fwrite((char*)outputBuffer, tag2Size, 1, output);
|
||||||
|
|
||||||
int vbrTagSize = lame_get_lametag_frame(encoder, outputBuffer, outputBufferSize);
|
int vbrTagSize = lame_get_lametag_frame(encoder, outputBuffer, outputBufferSize);
|
||||||
if (vbrTagSize > outputBufferSize)
|
if (vbrTagSize > outputBufferSize)
|
||||||
std::cout << std::endl << "couldn't write vbr tag";
|
log(Loggable::error, "couldn't write vbr tag");
|
||||||
|
|
||||||
fwrite((char*)outputBuffer, vbrTagSize, 1, output);
|
fwrite((char*)outputBuffer, vbrTagSize, 1, output);
|
||||||
}
|
}
|
||||||
@ -92,6 +103,12 @@ bool FLACtoMP3::run() {
|
|||||||
pcmSize = 0;
|
pcmSize = 0;
|
||||||
flacMaxBlockSize = 0;
|
flacMaxBlockSize = 0;
|
||||||
outputBufferSize = 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 ok;
|
||||||
}
|
}
|
||||||
@ -130,13 +147,13 @@ bool FLACtoMP3::initializeOutput() {
|
|||||||
output = fopen(outPath.c_str(), "w+b");
|
output = fopen(outPath.c_str(), "w+b");
|
||||||
if (output == 0) {
|
if (output == 0) {
|
||||||
output = nullptr;
|
output = nullptr;
|
||||||
std::cout << "Error opening file " << outPath << std::endl;
|
log(fatal, "Error opening file " + outPath);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
int ret = lame_init_params(encoder);
|
int ret = lame_init_params(encoder);
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
std::cout << "Error occurred during parameters initializing. Code = " << ret << std::endl;
|
log(fatal, "Error initializing LAME parameters. Code = " + std::to_string(ret));
|
||||||
fclose(output);
|
fclose(output);
|
||||||
output = nullptr;
|
output = nullptr;
|
||||||
return false;
|
return false;
|
||||||
@ -169,7 +186,9 @@ void FLACtoMP3::processInfo(const FLAC__StreamMetadata_StreamInfo& info) {
|
|||||||
lame_set_in_samplerate(encoder, info.sample_rate);
|
lame_set_in_samplerate(encoder, info.sample_rate);
|
||||||
lame_set_num_channels(encoder, info.channels);
|
lame_set_num_channels(encoder, info.channels);
|
||||||
flacMaxBlockSize = info.max_blocksize;
|
flacMaxBlockSize = info.max_blocksize;
|
||||||
//std::cout << "bits per sample: " << info.bits_per_sample << std::endl;;
|
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) {
|
void FLACtoMP3::processTags(const FLAC__StreamMetadata_VorbisComment& tags) {
|
||||||
@ -178,14 +197,16 @@ void FLACtoMP3::processTags(const FLAC__StreamMetadata_VorbisComment& tags) {
|
|||||||
const FLAC__StreamMetadata_VorbisComment_Entry& entry = tags.comments[i]; //gonna keep only the first one for now
|
const FLAC__StreamMetadata_VorbisComment_Entry& entry = tags.comments[i]; //gonna keep only the first one for now
|
||||||
std::string_view comm((const char*)entry.entry);
|
std::string_view comm((const char*)entry.entry);
|
||||||
if (comm.find(title) == 0) {
|
if (comm.find(title) == 0) {
|
||||||
id3tag_set_title(encoder, comm.substr(title.size()).data());
|
setTagTitle(comm.substr(title.size()));
|
||||||
} else if (comm.find(artist) == 0) {
|
} else if (comm.find(artist) == 0) {
|
||||||
if (!artistSet) {
|
if (!artistSet) {
|
||||||
id3tag_set_artist(encoder, comm.substr(artist.size()).data());
|
setTagArtist(comm.substr(artist.size()));
|
||||||
artistSet = true;
|
artistSet = true;
|
||||||
|
} else {
|
||||||
|
log(minor, "more than one artist tag, ignoring " + std::string(comm.substr(artist.size())));
|
||||||
}
|
}
|
||||||
} else if (comm.find(album) == 0) {
|
} else if (comm.find(album) == 0) {
|
||||||
id3tag_set_album(encoder, comm.substr(album.size()).data());
|
setTagAlbum(comm.substr(album.size()));
|
||||||
} else if (comm.find(comment) == 0) {
|
} else if (comm.find(comment) == 0) {
|
||||||
id3tag_set_comment(encoder, comm.substr(comment.size()).data());
|
id3tag_set_comment(encoder, comm.substr(comment.size()).data());
|
||||||
} else if (comm.find(genre) == 0) {
|
} else if (comm.find(genre) == 0) {
|
||||||
@ -194,13 +215,13 @@ void FLACtoMP3::processTags(const FLAC__StreamMetadata_VorbisComment& tags) {
|
|||||||
id3tag_set_track(encoder, comm.substr(track.size()).data());
|
id3tag_set_track(encoder, comm.substr(track.size()).data());
|
||||||
} else if (comm.find(date) == 0) {
|
} else if (comm.find(date) == 0) {
|
||||||
std::string_view fullDate = comm.substr(date.size());
|
std::string_view fullDate = comm.substr(date.size());
|
||||||
if (fullDate.size() == 10) {
|
if (fullDate.size() == 10) { //1999-11-11 kind of string
|
||||||
std::string_view month = fullDate.substr(5, std::size_t(2));
|
std::string_view month = fullDate.substr(5, std::size_t(2));
|
||||||
std::string_view day = fullDate.substr(8);
|
std::string_view day = fullDate.substr(8);
|
||||||
std::string md = "TDAT=" + std::string(month) + std::string(day);
|
std::string md = "TDAT=" + std::string(day) + std::string(month);
|
||||||
int res = id3tag_set_fieldvalue(encoder, md.c_str());
|
int res = id3tag_set_fieldvalue(encoder, md.c_str());
|
||||||
if (res != 0)
|
if (res != 0)
|
||||||
std::cout << "wasn't able to set the date tag (" << md << ")" << std::endl;
|
log(warning, "wasn't able to set the date tag (" + md + ")");
|
||||||
|
|
||||||
fullDate = fullDate.substr(0, 4); //year;
|
fullDate = fullDate.substr(0, 4); //year;
|
||||||
}
|
}
|
||||||
@ -209,27 +230,25 @@ void FLACtoMP3::processTags(const FLAC__StreamMetadata_VorbisComment& tags) {
|
|||||||
std::string tag = "TXXX=" + std::string(comm);
|
std::string tag = "TXXX=" + std::string(comm);
|
||||||
int res = id3tag_set_fieldvalue(encoder, tag.c_str());
|
int res = id3tag_set_fieldvalue(encoder, tag.c_str());
|
||||||
if (res != 0)
|
if (res != 0)
|
||||||
std::cout << "wasn't able to set user tag (" << comm << ")" << std::endl;
|
log(warning, "wasn't able to set user tag (" + tag + ")");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void FLACtoMP3::processPicture(const FLAC__StreamMetadata_Picture& picture) {
|
void FLACtoMP3::processPicture(const FLAC__StreamMetadata_Picture& picture) {
|
||||||
if (downscaleAlbumArt && picture.data_length > LAME_MAXALBUMART) {
|
if (downscaleAlbumArt && picture.data_length > LAME_MAXALBUMART) {
|
||||||
std::cout << "embeded picture is too big (" << picture.data_length << " bytes) " << std::endl;
|
log(info, "embeded album art is too big (" + std::to_string(picture.data_length) + " bytes), rescaling");
|
||||||
std::cout << "mime type is " << picture.mime_type << std::endl;
|
log(debug, "mime type is " + std::string(picture.mime_type));
|
||||||
if (picture.mime_type == jpeg) {
|
if (picture.mime_type == jpeg) {
|
||||||
if (scaleJPEG(picture)) {
|
if (scaleJPEG(picture))
|
||||||
std::cout << "successfully scaled album art" << std::endl;
|
log(debug, "successfully rescaled album art");
|
||||||
} else {
|
else
|
||||||
std::cout << "failed to album art" << std::endl;
|
log(warning, "failed to rescale album art");
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
int result = id3tag_set_albumart(encoder, (const char*)picture.data, picture.data_length);
|
int result = id3tag_set_albumart(encoder, (const char*)picture.data, picture.data_length);
|
||||||
if (result != 0) {
|
if (result != 0)
|
||||||
std::cout << "couldn't set album art tag, errcode: " << result << std::endl;
|
log(warning, "couldn't set album art tag, errcode: " + std::to_string(result));
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -243,11 +262,11 @@ bool FLACtoMP3::scaleJPEG(const FLAC__StreamMetadata_Picture& picture) {
|
|||||||
int rc = jpeg_read_header(&dinfo, TRUE);
|
int rc = jpeg_read_header(&dinfo, TRUE);
|
||||||
|
|
||||||
if (rc != 1) {
|
if (rc != 1) {
|
||||||
std::cout << "error reading jpeg header" << std::endl;
|
log(Loggable::error, "error reading jpeg header");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
uint64_t mem_size = LAME_MAXALBUMART;
|
uint64_t mem_size = 0;
|
||||||
uint8_t *mem = new uint8_t[mem_size];
|
uint8_t *mem = new uint8_t[LAME_MAXALBUMART + 1024 * 4]; //I allocate a little bit more not to corrupt someone else's the memory
|
||||||
|
|
||||||
dinfo.scale_num = 2; //need to tune it, feels like 500 by 500 is a good size
|
dinfo.scale_num = 2; //need to tune it, feels like 500 by 500 is a good size
|
||||||
dinfo.scale_denom = 3;
|
dinfo.scale_denom = 3;
|
||||||
@ -273,6 +292,12 @@ bool FLACtoMP3::scaleJPEG(const FLAC__StreamMetadata_Picture& picture) {
|
|||||||
while (dinfo.output_scanline < dinfo.output_height) {
|
while (dinfo.output_scanline < dinfo.output_height) {
|
||||||
jpeg_read_scanlines(&dinfo, &row, 1);
|
jpeg_read_scanlines(&dinfo, &row, 1);
|
||||||
jpeg_write_scanlines(&cinfo, &row, 1);
|
jpeg_write_scanlines(&cinfo, &row, 1);
|
||||||
|
|
||||||
|
if (mem_size > LAME_MAXALBUMART) {
|
||||||
|
delete[] mem;
|
||||||
|
log(Loggable::error, "resized album art exceeded reserved buffer size, stopping before memory corrution");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
jpeg_finish_decompress(&dinfo);
|
jpeg_finish_decompress(&dinfo);
|
||||||
jpeg_destroy_decompress(&dinfo);
|
jpeg_destroy_decompress(&dinfo);
|
||||||
@ -291,10 +316,24 @@ bool FLACtoMP3::scaleJPEG(const FLAC__StreamMetadata_Picture& picture) {
|
|||||||
bool FLACtoMP3::tryKnownTag(std::string_view source) {
|
bool FLACtoMP3::tryKnownTag(std::string_view source) {
|
||||||
for (const std::pair<const std::string, const std::string>& pair : knownTags) {
|
for (const std::pair<const std::string, const std::string>& pair : knownTags) {
|
||||||
if (source.find(pair.first) == 0) {
|
if (source.find(pair.first) == 0) {
|
||||||
std::string tag = pair.second + std::string(source.substr(pair.first.size()));
|
if (pair.first == "LYRICS=") {
|
||||||
int res = id3tag_set_fieldvalue(encoder, tag.c_str());
|
log(warning, "lyrics tag was not transfered, it is not supported currently");
|
||||||
if (res != 0)
|
} else if (pair.first == "BPM=") {
|
||||||
std::cout << "wasn't able to set tag (" << source << ")" << std::endl;
|
float bpm = std::atof(source.substr(pair.first.size()).data());
|
||||||
|
bpm = std::round(bpm); //id3 bpm must be integer
|
||||||
|
int result = id3tag_set_fieldvalue(encoder, std::string(pair.second + std::to_string((int)bpm)).c_str());
|
||||||
|
if (result != 0)
|
||||||
|
log(warning, "wasn't able to set BPM tag. Code = " + std::to_string(result));
|
||||||
|
} else if (pair.first == "ALBUMARTIST=") {
|
||||||
|
int res = setTagUSC2("TPE2", source.substr(pair.first.size()));
|
||||||
|
if (res != 0)
|
||||||
|
log(warning, "wasn't able to album artist tag. Code = " + std::to_string(res));
|
||||||
|
} else {
|
||||||
|
std::string tag = pair.second + std::string(source.substr(pair.first.size()));
|
||||||
|
int res = id3tag_set_fieldvalue(encoder, tag.c_str());
|
||||||
|
if (res != 0)
|
||||||
|
log(warning, "wasn't able to set tag (" + std::string(source) + "). Code = " + std::to_string(res));
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -331,11 +370,11 @@ bool FLACtoMP3::flush() {
|
|||||||
outputBufferSize
|
outputBufferSize
|
||||||
);
|
);
|
||||||
while (nwrite == -1) { //-1 is returned when there was not enough space in the given buffer
|
while (nwrite == -1) { //-1 is returned when there was not enough space in the given buffer
|
||||||
std::cout << outputBufferSize << " bytes in the output buffer wasn't enough" << std::endl;
|
log(major, std::to_string(outputBufferSize) + " bytes in the output buffer wasn't enough");;
|
||||||
outputBufferSize = outputBufferSize * 2;
|
outputBufferSize = outputBufferSize * 2;
|
||||||
delete[] outputBuffer;
|
delete[] outputBuffer;
|
||||||
outputBuffer = new uint8_t[outputBufferSize];
|
outputBuffer = new uint8_t[outputBufferSize];
|
||||||
std::cout << "allocating " << outputBufferSize << " bytes" << std::endl;
|
log(major, "allocating " + std::to_string(outputBufferSize) + " bytes");
|
||||||
|
|
||||||
nwrite = lame_encode_buffer_interleaved(
|
nwrite = lame_encode_buffer_interleaved(
|
||||||
encoder,
|
encoder,
|
||||||
@ -352,10 +391,10 @@ bool FLACtoMP3::flush() {
|
|||||||
return actuallyWritten == 1;
|
return actuallyWritten == 1;
|
||||||
} else {
|
} else {
|
||||||
if (nwrite == 0) {
|
if (nwrite == 0) {
|
||||||
//std::cout << "encoding flush encoded 0 bytes, skipping write" << std::endl;
|
log(minor, "encoding flush encoded 0 bytes, skipping write");
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
std::cout << "encoding flush failed, error: " << nwrite << std::endl;
|
log(fatal, "encoding flush failed. Code = : " + std::to_string(nwrite));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -391,20 +430,20 @@ FLAC__StreamDecoderWriteStatus FLACtoMP3::write(
|
|||||||
// std::cout << "ERROR: this example only supports 16bit stereo streams" << std::endl;
|
// std::cout << "ERROR: this example only supports 16bit stereo streams" << std::endl;
|
||||||
// return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT;
|
// return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT;
|
||||||
// }
|
// }
|
||||||
|
FLACtoMP3* self = static_cast<FLACtoMP3*>(client_data);
|
||||||
if (frame->header.channels != 2) {
|
if (frame->header.channels != 2) {
|
||||||
std::cout << "ERROR: This frame contains " << frame->header.channels << " channels (should be 2)" << std::endl;
|
self->log(fatal, "ERROR: This frame contains " + std::to_string(frame->header.channels) + " channels (should be 2)");
|
||||||
return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT;
|
return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT;
|
||||||
}
|
}
|
||||||
if (buffer[0] == NULL) {
|
if (buffer[0] == NULL) {
|
||||||
std::cout << "ERROR: buffer [0] is NULL" << std::endl;
|
self->log(fatal, "ERROR: buffer [0] is NULL");
|
||||||
return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT;
|
return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT;
|
||||||
}
|
}
|
||||||
if (buffer[1] == NULL) {
|
if (buffer[1] == NULL) {
|
||||||
std::cout << "ERROR: buffer [1] is NULL" << std::endl;
|
self->log(fatal, "ERROR: buffer [1] is NULL");
|
||||||
return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT;
|
return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT;
|
||||||
}
|
}
|
||||||
|
|
||||||
FLACtoMP3* self = static_cast<FLACtoMP3*>(client_data);
|
|
||||||
bool result = self->decodeFrame(buffer, frame->header.blocksize);
|
bool result = self->decodeFrame(buffer, frame->header.blocksize);
|
||||||
|
|
||||||
if (result)
|
if (result)
|
||||||
@ -414,6 +453,34 @@ FLAC__StreamDecoderWriteStatus FLACtoMP3::write(
|
|||||||
}
|
}
|
||||||
|
|
||||||
void FLACtoMP3::error(const FLAC__StreamDecoder* decoder, FLAC__StreamDecoderErrorStatus status, void* client_data) {
|
void FLACtoMP3::error(const FLAC__StreamDecoder* decoder, FLAC__StreamDecoderErrorStatus status, void* client_data) {
|
||||||
(void)decoder, (void)client_data;
|
(void)decoder;
|
||||||
std::cout << "Got error callback: " << FLAC__StreamDecoderErrorStatusString[status] << std::endl;
|
FLACtoMP3* self = static_cast<FLACtoMP3*>(client_data);
|
||||||
|
std::string errText(FLAC__StreamDecoderErrorStatusString[status]);
|
||||||
|
self->log(Loggable::error, "Got error callback: " + errText);
|
||||||
|
}
|
||||||
|
|
||||||
|
void FLACtoMP3::setTagTitle(const std::string_view& title) {
|
||||||
|
id3tag_set_title(encoder, title.data()); //to set at least some possibly encoding broken id3v1 tag
|
||||||
|
int res = setTagUSC2("TIT2", title);
|
||||||
|
if (res != 0)
|
||||||
|
log(warning, "Couldn't set Title tag. Code = " + std::to_string(res));
|
||||||
|
}
|
||||||
|
|
||||||
|
void FLACtoMP3::setTagAlbum(const std::string_view& album) {
|
||||||
|
id3tag_set_album(encoder, album.data()); //to set at least some possibly encoding broken id3v1 tag
|
||||||
|
int res = setTagUSC2("TALB", album);
|
||||||
|
if (res != 0)
|
||||||
|
log(warning, "Couldn't set Album tag. Code = " + std::to_string(res));
|
||||||
|
}
|
||||||
|
|
||||||
|
void FLACtoMP3::setTagArtist(const std::string_view& artist) {
|
||||||
|
id3tag_set_artist(encoder, artist.data()); //to set at least some possibly encoding broken id3v1 tag
|
||||||
|
int res = setTagUSC2("TPE1", artist);
|
||||||
|
if (res != 0)
|
||||||
|
log(warning, "Couldn't set Artist tag. Code = " + std::to_string(res));
|
||||||
|
}
|
||||||
|
|
||||||
|
int FLACtoMP3::setTagUSC2(const std::string_view& field, const std::string_view& value) {
|
||||||
|
std::u16string utf16 = std::u16string({0xFEFF}) + usc2convertor.from_bytes(value.data());
|
||||||
|
return id3tag_set_textinfo_utf16(encoder, field.data(), (unsigned short*)utf16.c_str());
|
||||||
}
|
}
|
||||||
|
16
flactomp3.h
16
flactomp3.h
@ -6,13 +6,16 @@
|
|||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
#include <iostream>
|
#include <codecvt>
|
||||||
|
#include <locale>
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
|
||||||
class FLACtoMP3 {
|
#include "loggable.h"
|
||||||
|
|
||||||
|
class FLACtoMP3 : public Loggable {
|
||||||
public:
|
public:
|
||||||
FLACtoMP3(uint8_t size = 4);
|
FLACtoMP3(Severity severity = info, uint8_t size = 4);
|
||||||
~FLACtoMP3();
|
~FLACtoMP3();
|
||||||
|
|
||||||
void setInputFile(const std::string& path);
|
void setInputFile(const std::string& path);
|
||||||
@ -29,6 +32,12 @@ private:
|
|||||||
bool tryKnownTag(std::string_view source);
|
bool tryKnownTag(std::string_view source);
|
||||||
bool scaleJPEG(const FLAC__StreamMetadata_Picture& picture);
|
bool scaleJPEG(const FLAC__StreamMetadata_Picture& picture);
|
||||||
|
|
||||||
|
void setTagTitle(const std::string_view& title);
|
||||||
|
void setTagAlbum(const std::string_view& album);
|
||||||
|
void setTagArtist(const std::string_view& artist);
|
||||||
|
|
||||||
|
int setTagUSC2(const std::string_view& field, const std::string_view& value);
|
||||||
|
|
||||||
static void error(const FLAC__StreamDecoder *decoder, FLAC__StreamDecoderErrorStatus status, void *client_data);
|
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 void metadata(const FLAC__StreamDecoder *decoder, const FLAC__StreamMetadata *metadata, void *client_data);
|
||||||
static FLAC__StreamDecoderWriteStatus write(
|
static FLAC__StreamDecoderWriteStatus write(
|
||||||
@ -56,4 +65,5 @@ private:
|
|||||||
uint32_t outputBufferSize;
|
uint32_t outputBufferSize;
|
||||||
bool outputInitilized;
|
bool outputInitilized;
|
||||||
bool downscaleAlbumArt;
|
bool downscaleAlbumArt;
|
||||||
|
std::wstring_convert<std::codecvt_utf8<char16_t>, char16_t> usc2convertor;
|
||||||
};
|
};
|
||||||
|
18
loggable.cpp
Normal file
18
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
loggable.h
Normal file
28
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;
|
||||||
|
};
|
6
main.cpp
6
main.cpp
@ -17,13 +17,13 @@ int main(int argc, char **argv) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const std::string firstArgument(argv[1]);
|
const std::string firstArgument(argv[1]);
|
||||||
const std::string secondArgument(argv[2]);
|
|
||||||
|
|
||||||
if (firstArgument == "--help") {
|
if (firstArgument == "--help") {
|
||||||
printHelp();
|
printHelp();
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const std::string secondArgument(argv[2]);
|
||||||
|
|
||||||
TaskManager taskManager;
|
TaskManager taskManager;
|
||||||
taskManager.start();
|
taskManager.start();
|
||||||
|
|
||||||
@ -35,7 +35,7 @@ int main(int argc, char **argv) {
|
|||||||
taskManager.wait();
|
taskManager.wait();
|
||||||
std::chrono::time_point end = std::chrono::system_clock::now();
|
std::chrono::time_point end = std::chrono::system_clock::now();
|
||||||
std::chrono::duration<double> seconds = end - start;
|
std::chrono::duration<double> seconds = end - start;
|
||||||
std::cout << std::endl << "took " << seconds.count() << std::endl;
|
std::cout << std::endl << "Encoding is done, it took " << seconds.count() << " seconds in total, enjoy!" << std::endl;
|
||||||
|
|
||||||
taskManager.stop();
|
taskManager.stop();
|
||||||
|
|
||||||
|
@ -2,11 +2,32 @@
|
|||||||
|
|
||||||
#include "flactomp3.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():
|
TaskManager::TaskManager():
|
||||||
busyThreads(0),
|
busyThreads(0),
|
||||||
maxTasks(0),
|
maxTasks(0),
|
||||||
completeTasks(0),
|
completeTasks(0),
|
||||||
terminate(false),
|
terminate(false),
|
||||||
|
printMutex(),
|
||||||
queueMutex(),
|
queueMutex(),
|
||||||
busyMutex(),
|
busyMutex(),
|
||||||
loopConditional(),
|
loopConditional(),
|
||||||
@ -62,9 +83,9 @@ void TaskManager::loop() {
|
|||||||
jobs.pop();
|
jobs.pop();
|
||||||
}
|
}
|
||||||
|
|
||||||
job(pair.first, pair.second);
|
JobResult result = job(pair.first, pair.second);
|
||||||
++completeTasks;
|
++completeTasks;
|
||||||
printProgress();
|
printProgress(result, pair.first, pair.second);
|
||||||
--busyThreads;
|
--busyThreads;
|
||||||
waitConditional.notify_all();
|
waitConditional.notify_all();
|
||||||
}
|
}
|
||||||
@ -96,13 +117,38 @@ void TaskManager::wait() {
|
|||||||
waitConditional.wait(lock, boundWaitCondition);
|
waitConditional.wait(lock, boundWaitCondition);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool TaskManager::job(const std::string& source, const std::string& destination) {
|
TaskManager::JobResult TaskManager::job(const std::string& source, const std::string& destination) {
|
||||||
FLACtoMP3 convertor;
|
FLACtoMP3 convertor;
|
||||||
convertor.setInputFile(source);
|
convertor.setInputFile(source);
|
||||||
convertor.setOutputFile(destination);
|
convertor.setOutputFile(destination);
|
||||||
return convertor.run();
|
bool result = convertor.run();
|
||||||
|
return {result, convertor.getHistory()};
|
||||||
}
|
}
|
||||||
|
|
||||||
void TaskManager::printProgress() const {
|
void TaskManager::printProgress() const {
|
||||||
|
std::unique_lock<std::mutex> lock(printMutex);
|
||||||
std::cout << "\r" << completeTasks << "/" << maxTasks << std::flush;
|
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;
|
||||||
|
}
|
||||||
|
@ -5,11 +5,17 @@
|
|||||||
#include <thread>
|
#include <thread>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
#include <list>
|
||||||
#include <queue>
|
#include <queue>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
|
#include <iostream>
|
||||||
|
#include <array>
|
||||||
|
|
||||||
|
#include "loggable.h"
|
||||||
|
|
||||||
class TaskManager {
|
class TaskManager {
|
||||||
|
typedef std::pair<bool, std::list<Loggable::Message>> JobResult;
|
||||||
public:
|
public:
|
||||||
TaskManager();
|
TaskManager();
|
||||||
~TaskManager();
|
~TaskManager();
|
||||||
@ -20,18 +26,20 @@ public:
|
|||||||
bool busy() const;
|
bool busy() const;
|
||||||
void wait();
|
void wait();
|
||||||
void printProgress() const;
|
void printProgress() const;
|
||||||
|
void printProgress(const JobResult& result, const std::string& source, const std::string& destination) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void loop();
|
void loop();
|
||||||
bool loopCondition() const;
|
bool loopCondition() const;
|
||||||
bool waitCondition() const;
|
bool waitCondition() const;
|
||||||
static bool job(const std::string& source, const std::string& destination);
|
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:
|
private:
|
||||||
std::atomic<uint32_t> busyThreads;
|
std::atomic<uint32_t> busyThreads;
|
||||||
std::atomic<uint32_t> maxTasks;
|
std::atomic<uint32_t> maxTasks;
|
||||||
std::atomic<uint32_t> completeTasks;
|
std::atomic<uint32_t> completeTasks;
|
||||||
bool terminate;
|
bool terminate;
|
||||||
|
mutable std::mutex printMutex;
|
||||||
mutable std::mutex queueMutex;
|
mutable std::mutex queueMutex;
|
||||||
std::mutex busyMutex;
|
std::mutex busyMutex;
|
||||||
std::condition_variable loopConditional;
|
std::condition_variable loopConditional;
|
||||||
|
Loading…
Reference in New Issue
Block a user