album art scaling and now scaling coping
This commit is contained in:
parent
e34c7f4fba
commit
f16e4b4873
@ -14,6 +14,7 @@ set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake")
|
|||||||
|
|
||||||
find_package(LAME REQUIRED)
|
find_package(LAME REQUIRED)
|
||||||
find_package(FLAC REQUIRED)
|
find_package(FLAC REQUIRED)
|
||||||
|
find_package(JPEG REQUIRED)
|
||||||
|
|
||||||
add_executable(mlc
|
add_executable(mlc
|
||||||
main.cpp
|
main.cpp
|
||||||
@ -25,6 +26,7 @@ add_executable(mlc
|
|||||||
target_link_libraries(mlc
|
target_link_libraries(mlc
|
||||||
${LAME_LIBRARIES}
|
${LAME_LIBRARIES}
|
||||||
FLAC
|
FLAC
|
||||||
|
JPEG::JPEG
|
||||||
)
|
)
|
||||||
|
|
||||||
install(TARGETS mlc RUNTIME DESTINATION bin)
|
install(TARGETS mlc RUNTIME DESTINATION bin)
|
||||||
|
208
flactomp3.cpp
208
flactomp3.cpp
@ -1,6 +1,6 @@
|
|||||||
#include "flactomp3.h"
|
#include "flactomp3.h"
|
||||||
|
|
||||||
constexpr uint16_t flacBlockMaxSize = 4096;
|
constexpr uint16_t flacDefaultMaxBlockSize = 4096;
|
||||||
|
|
||||||
static const std::string_view title ("TITLE=");
|
static const std::string_view title ("TITLE=");
|
||||||
static const std::string_view artist ("ARTIST=");
|
static const std::string_view artist ("ARTIST=");
|
||||||
@ -10,6 +10,8 @@ static const std::string_view genre ("GENRE=");
|
|||||||
static const std::string_view track ("TRACKNUMBER=");
|
static const std::string_view track ("TRACKNUMBER=");
|
||||||
static const std::string_view date ("DATE=");
|
static const std::string_view date ("DATE=");
|
||||||
|
|
||||||
|
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="},
|
||||||
{"PUBLISHER=", "TPUB="},
|
{"PUBLISHER=", "TPUB="},
|
||||||
@ -26,19 +28,21 @@ FLACtoMP3::FLACtoMP3(uint8_t size) :
|
|||||||
encoder(lame_init()),
|
encoder(lame_init()),
|
||||||
statusFLAC(),
|
statusFLAC(),
|
||||||
output(nullptr),
|
output(nullptr),
|
||||||
|
bufferMultiplier(size),
|
||||||
|
flacMaxBlockSize(0),
|
||||||
pcmCounter(0),
|
pcmCounter(0),
|
||||||
pcmSize(size * flacBlockMaxSize),
|
pcmSize(0),
|
||||||
pcm(new int16_t[pcmSize * 2]),
|
pcm(nullptr),
|
||||||
outputBuffer(new uint8_t[pcmSize * 2]),
|
outputBuffer(nullptr),
|
||||||
outputInitilized(false)
|
outputBufferSize(0),
|
||||||
|
outputInitilized(false),
|
||||||
|
downscaleAlbumArt(false)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
FLACtoMP3::~FLACtoMP3() {
|
FLACtoMP3::~FLACtoMP3() {
|
||||||
lame_close(encoder);
|
lame_close(encoder);
|
||||||
FLAC__stream_decoder_delete(decoder);
|
FLAC__stream_decoder_delete(decoder);
|
||||||
delete[] pcm;
|
|
||||||
delete[] outputBuffer;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void FLACtoMP3::run() {
|
void FLACtoMP3::run() {
|
||||||
@ -49,11 +53,30 @@ void FLACtoMP3::run() {
|
|||||||
|
|
||||||
flush();
|
flush();
|
||||||
int nwrite = lame_encode_flush(encoder, outputBuffer, pcmSize * 2);
|
int nwrite = lame_encode_flush(encoder, outputBuffer, pcmSize * 2);
|
||||||
fwrite((char*)outputBuffer ,nwrite, 1, output);
|
fwrite((char*)outputBuffer, nwrite, 1, output);
|
||||||
|
|
||||||
// // 7. Write INFO tag (OPTIONAL)
|
if (downscaleAlbumArt) {
|
||||||
lame_mp3_tags_fid(encoder, output);
|
lame_mp3_tags_fid(encoder, output);
|
||||||
|
} else {
|
||||||
|
int tag1Size = lame_get_id3v1_tag(encoder, outputBuffer, 128);
|
||||||
|
if (tag1Size > 128)
|
||||||
|
std::cout << std::endl << "couldn't write id3v1 tag";
|
||||||
|
else
|
||||||
|
fwrite((char*)outputBuffer, tag1Size, 1, output);
|
||||||
|
|
||||||
|
fseek(output, 0, SEEK_SET);
|
||||||
|
int tag2Size = lame_get_id3v2_tag(encoder, outputBuffer, outputBufferSize);
|
||||||
|
if (tag2Size > outputBufferSize) {
|
||||||
|
std::cout << std::endl << "couldn't write id3v1 tag";
|
||||||
|
} else
|
||||||
|
fwrite((char*)outputBuffer, tag2Size, 1, output);
|
||||||
|
|
||||||
|
int vbrTagSize = lame_get_lametag_frame(encoder, outputBuffer, outputBufferSize);
|
||||||
|
if (vbrTagSize > outputBufferSize)
|
||||||
|
std::cout << std::endl << "couldn't write vbr tag";
|
||||||
|
|
||||||
|
fwrite((char*)outputBuffer, vbrTagSize, 1, output);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
std::cout << "FAILED";
|
std::cout << "FAILED";
|
||||||
}
|
}
|
||||||
@ -64,6 +87,15 @@ void FLACtoMP3::run() {
|
|||||||
if (outputInitilized) {
|
if (outputInitilized) {
|
||||||
fclose(output);
|
fclose(output);
|
||||||
output = nullptr;
|
output = nullptr;
|
||||||
|
|
||||||
|
delete[] pcm;
|
||||||
|
delete[] outputBuffer;
|
||||||
|
|
||||||
|
pcm = nullptr;
|
||||||
|
outputBuffer = nullptr;
|
||||||
|
pcmSize = 0;
|
||||||
|
flacMaxBlockSize = 0;
|
||||||
|
outputBufferSize = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -87,6 +119,8 @@ void FLACtoMP3::setOutputFile(const std::string& path) {
|
|||||||
lame_set_VBR(encoder, vbr_default);
|
lame_set_VBR(encoder, vbr_default);
|
||||||
lame_set_VBR_quality(encoder, 0);
|
lame_set_VBR_quality(encoder, 0);
|
||||||
lame_set_quality(encoder, 0);
|
lame_set_quality(encoder, 0);
|
||||||
|
if (!downscaleAlbumArt)
|
||||||
|
lame_set_write_id3tag_automatic(encoder, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool FLACtoMP3::initializeOutput() {
|
bool FLACtoMP3::initializeOutput() {
|
||||||
@ -108,13 +142,34 @@ bool FLACtoMP3::initializeOutput() {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (flacMaxBlockSize == 0)
|
||||||
|
flacMaxBlockSize = flacDefaultMaxBlockSize;
|
||||||
|
|
||||||
|
pcmSize = lame_get_num_channels(encoder) * flacMaxBlockSize * bufferMultiplier;
|
||||||
|
outputBufferSize = pcmSize / 2;
|
||||||
|
|
||||||
|
if (!downscaleAlbumArt) {
|
||||||
|
int tag2Size = lame_get_id3v2_tag(encoder, nullptr, 0);
|
||||||
|
int vbrTagSize = lame_get_lametag_frame(encoder, nullptr, 0);
|
||||||
|
|
||||||
|
fseek(output, tag2Size + vbrTagSize, SEEK_SET); //reserve place for tags
|
||||||
|
if (tag2Size > outputBufferSize)
|
||||||
|
outputBufferSize = tag2Size;
|
||||||
|
}
|
||||||
|
|
||||||
|
pcm = new int16_t[pcmSize];
|
||||||
|
outputBuffer = new uint8_t[outputBufferSize];
|
||||||
|
|
||||||
outputInitilized = true;
|
outputInitilized = true;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void FLACtoMP3::processInfo(const FLAC__StreamMetadata_StreamInfo& info) {
|
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;
|
||||||
|
//std::cout << "bits per sample: " << info.bits_per_sample << std::endl;;
|
||||||
}
|
}
|
||||||
|
|
||||||
void FLACtoMP3::processTags(const FLAC__StreamMetadata_VorbisComment& tags) {
|
void FLACtoMP3::processTags(const FLAC__StreamMetadata_VorbisComment& tags) {
|
||||||
@ -155,6 +210,85 @@ void FLACtoMP3::processTags(const FLAC__StreamMetadata_VorbisComment& tags) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void FLACtoMP3::processPicture(const FLAC__StreamMetadata_Picture& picture) {
|
||||||
|
if (downscaleAlbumArt && picture.data_length > LAME_MAXALBUMART) {
|
||||||
|
std::cout << "embeded picture is too big (" << picture.data_length << " bytes) " << std::endl;
|
||||||
|
std::cout << "mime type is " << picture.mime_type << std::endl;
|
||||||
|
if (picture.mime_type == jpeg) {
|
||||||
|
if (scaleJPEG(picture)) {
|
||||||
|
std::cout << "successfully scaled album art" << std::endl;
|
||||||
|
} else {
|
||||||
|
std::cout << "failed to album art" << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
int result = id3tag_set_albumart(encoder, (const char*)picture.data, picture.data_length);
|
||||||
|
if (result != 0) {
|
||||||
|
std::cout << "couldn't set album art tag, errcode: " << result << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FLACtoMP3::scaleJPEG(const FLAC__StreamMetadata_Picture& picture) {
|
||||||
|
// Variables for the decompressor itself
|
||||||
|
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) {
|
||||||
|
std::cout << "error reading jpeg header" << std::endl;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
uint64_t mem_size = LAME_MAXALBUMART - 1024 * 16;
|
||||||
|
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
|
||||||
|
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, &mem, &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) {
|
||||||
|
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);
|
||||||
|
|
||||||
|
// And free the input buffer
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
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) {
|
||||||
@ -179,13 +313,12 @@ bool FLACtoMP3::decodeFrame(const int32_t * const buffer[], uint32_t size) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (size_t i = 0; i < size; ++i) {
|
for (size_t i = 0; i < size; ++i) {
|
||||||
pcm[2 * pcmCounter] = (int16_t)buffer[0][i];
|
pcm[pcmCounter++] = (int16_t)buffer[0][i];
|
||||||
pcm[2 * pcmCounter + 1] = (int16_t)buffer[1][i];
|
pcm[pcmCounter++] = (int16_t)buffer[1][i];
|
||||||
++pcmCounter;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pcmCounter == pcmSize)
|
if (pcmCounter == pcmSize)
|
||||||
return flush();
|
return flush();
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -194,13 +327,39 @@ bool FLACtoMP3::flush() {
|
|||||||
int nwrite = lame_encode_buffer_interleaved(
|
int nwrite = lame_encode_buffer_interleaved(
|
||||||
encoder,
|
encoder,
|
||||||
pcm,
|
pcm,
|
||||||
pcmCounter,
|
pcmCounter / 2,
|
||||||
outputBuffer,
|
outputBuffer,
|
||||||
pcmSize * 2
|
outputBufferSize
|
||||||
);
|
);
|
||||||
int actuallyWritten = fwrite((char*)outputBuffer, nwrite, 1, output);
|
while (nwrite == -1) { //-1 is returned when there was not enough space in the given buffer
|
||||||
pcmCounter = 0;
|
std::cout << outputBufferSize << " bytes in the output buffer wasn't enough" << std::endl;
|
||||||
return actuallyWritten == 1;
|
outputBufferSize = outputBufferSize * 2;
|
||||||
|
delete[] outputBuffer;
|
||||||
|
outputBuffer = new uint8_t[outputBufferSize];
|
||||||
|
std::cout << "allocating " << outputBufferSize << " bytes" << std::endl;
|
||||||
|
|
||||||
|
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) {
|
||||||
|
std::cout << "encoding flush encoded 0 bytes, skipping write" << std::endl;
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
std::cout << "encoding flush failed, error: " << nwrite << std::endl;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void FLACtoMP3::metadata(const FLAC__StreamDecoder* decoder, const FLAC__StreamMetadata* metadata, void* client_data) {
|
void FLACtoMP3::metadata(const FLAC__StreamDecoder* decoder, const FLAC__StreamMetadata* metadata, void* client_data) {
|
||||||
@ -214,6 +373,9 @@ void FLACtoMP3::metadata(const FLAC__StreamDecoder* decoder, const FLAC__StreamM
|
|||||||
case FLAC__METADATA_TYPE_VORBIS_COMMENT:
|
case FLAC__METADATA_TYPE_VORBIS_COMMENT:
|
||||||
self->processTags(metadata->data.vorbis_comment);
|
self->processTags(metadata->data.vorbis_comment);
|
||||||
break;
|
break;
|
||||||
|
case FLAC__METADATA_TYPE_PICTURE:
|
||||||
|
self->processPicture(metadata->data.picture);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -226,10 +388,6 @@ FLAC__StreamDecoderWriteStatus FLACtoMP3::write(
|
|||||||
void* client_data
|
void* client_data
|
||||||
) {
|
) {
|
||||||
(void)(decoder);
|
(void)(decoder);
|
||||||
// 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) {
|
// if (decoded->channels != 2 || decoded->depth != 16) {
|
||||||
// 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;
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
#include "FLAC/stream_decoder.h"
|
#include "FLAC/stream_decoder.h"
|
||||||
#include <lame/lame.h>
|
#include <lame/lame.h>
|
||||||
|
#include <jpeglib.h>
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
@ -21,10 +22,12 @@ public:
|
|||||||
private:
|
private:
|
||||||
void processTags(const FLAC__StreamMetadata_VorbisComment& tags);
|
void processTags(const FLAC__StreamMetadata_VorbisComment& tags);
|
||||||
void processInfo(const FLAC__StreamMetadata_StreamInfo& info);
|
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 decodeFrame(const int32_t * const buffer[], uint32_t size);
|
||||||
bool flush();
|
bool flush();
|
||||||
bool initializeOutput();
|
bool initializeOutput();
|
||||||
bool tryKnownTag(std::string_view source);
|
bool tryKnownTag(std::string_view source);
|
||||||
|
bool scaleJPEG(const FLAC__StreamMetadata_Picture& picture);
|
||||||
|
|
||||||
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);
|
||||||
@ -44,9 +47,13 @@ private:
|
|||||||
FLAC__StreamDecoderInitStatus statusFLAC;
|
FLAC__StreamDecoderInitStatus statusFLAC;
|
||||||
|
|
||||||
FILE* output;
|
FILE* output;
|
||||||
|
uint8_t bufferMultiplier;
|
||||||
|
uint32_t flacMaxBlockSize;
|
||||||
uint32_t pcmCounter;
|
uint32_t pcmCounter;
|
||||||
uint32_t pcmSize;
|
uint32_t pcmSize;
|
||||||
int16_t* pcm;
|
int16_t* pcm;
|
||||||
uint8_t* outputBuffer;
|
uint8_t* outputBuffer;
|
||||||
|
uint32_t outputBufferSize;
|
||||||
bool outputInitilized;
|
bool outputInitilized;
|
||||||
|
bool downscaleAlbumArt;
|
||||||
};
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user