diff --git a/CMakeLists.txt b/CMakeLists.txt index a19f1a6..e253293 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -14,6 +14,7 @@ set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake") find_package(LAME REQUIRED) find_package(FLAC REQUIRED) +find_package(JPEG REQUIRED) add_executable(mlc main.cpp @@ -25,6 +26,7 @@ add_executable(mlc target_link_libraries(mlc ${LAME_LIBRARIES} FLAC + JPEG::JPEG ) install(TARGETS mlc RUNTIME DESTINATION bin) diff --git a/flactomp3.cpp b/flactomp3.cpp index 2a21613..a183a4b 100644 --- a/flactomp3.cpp +++ b/flactomp3.cpp @@ -1,6 +1,6 @@ #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 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 date ("DATE="); +static const std::string_view jpeg ("image/jpeg"); + static const std::map knownTags { {"ALBUMARTIST=", "TPE2="}, {"PUBLISHER=", "TPUB="}, @@ -26,19 +28,21 @@ FLACtoMP3::FLACtoMP3(uint8_t size) : encoder(lame_init()), statusFLAC(), output(nullptr), + bufferMultiplier(size), + flacMaxBlockSize(0), pcmCounter(0), - pcmSize(size * flacBlockMaxSize), - pcm(new int16_t[pcmSize * 2]), - outputBuffer(new uint8_t[pcmSize * 2]), - outputInitilized(false) + pcmSize(0), + pcm(nullptr), + outputBuffer(nullptr), + outputBufferSize(0), + outputInitilized(false), + downscaleAlbumArt(false) { } FLACtoMP3::~FLACtoMP3() { lame_close(encoder); FLAC__stream_decoder_delete(decoder); - delete[] pcm; - delete[] outputBuffer; } void FLACtoMP3::run() { @@ -49,11 +53,30 @@ void FLACtoMP3::run() { flush(); 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) - lame_mp3_tags_fid(encoder, output); + if (downscaleAlbumArt) { + 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 { std::cout << "FAILED"; } @@ -64,6 +87,15 @@ void FLACtoMP3::run() { if (outputInitilized) { fclose(output); 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_quality(encoder, 0); lame_set_quality(encoder, 0); + if (!downscaleAlbumArt) + lame_set_write_id3tag_automatic(encoder, 0); } bool FLACtoMP3::initializeOutput() { @@ -108,13 +142,34 @@ bool FLACtoMP3::initializeOutput() { 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; + 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; + //std::cout << "bits per sample: " << info.bits_per_sample << std::endl;; } 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) { for (const std::pair& pair : knownTags) { 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) { - pcm[2 * pcmCounter] = (int16_t)buffer[0][i]; - pcm[2 * pcmCounter + 1] = (int16_t)buffer[1][i]; - ++pcmCounter; - } + pcm[pcmCounter++] = (int16_t)buffer[0][i]; + pcm[pcmCounter++] = (int16_t)buffer[1][i]; - if (pcmCounter == pcmSize) - return flush(); + if (pcmCounter == pcmSize) + return flush(); + } return true; } @@ -194,13 +327,39 @@ bool FLACtoMP3::flush() { int nwrite = lame_encode_buffer_interleaved( encoder, pcm, - pcmCounter, + pcmCounter / 2, outputBuffer, - pcmSize * 2 + outputBufferSize ); - int actuallyWritten = fwrite((char*)outputBuffer, nwrite, 1, output); - pcmCounter = 0; - return actuallyWritten == 1; + 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; + 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) { @@ -214,6 +373,9 @@ void FLACtoMP3::metadata(const FLAC__StreamDecoder* decoder, const FLAC__StreamM 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; } @@ -226,10 +388,6 @@ FLAC__StreamDecoderWriteStatus FLACtoMP3::write( void* client_data ) { (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) { // std::cout << "ERROR: this example only supports 16bit stereo streams" << std::endl; // return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT; diff --git a/flactomp3.h b/flactomp3.h index 5fd481c..5971e2e 100644 --- a/flactomp3.h +++ b/flactomp3.h @@ -2,6 +2,7 @@ #include "FLAC/stream_decoder.h" #include +#include #include #include @@ -21,10 +22,12 @@ public: 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 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 metadata(const FLAC__StreamDecoder *decoder, const FLAC__StreamMetadata *metadata, void *client_data); @@ -44,9 +47,13 @@ private: 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; };