From 34986e2a415c82c85f280ff854733595667755ae Mon Sep 17 00:00:00 2001 From: Wittawas Nakkasem Date: Sun, 10 Mar 2024 00:20:01 +0700 Subject: [PATCH] refactor(node-medley): extract function for getting audio properties out of the Metadata class --- packages/engine/src/Metadata.cpp | 146 ++++++++++++++++++++-------- packages/engine/src/Metadata.h | 29 ++++-- packages/node-medley/package.json | 2 +- packages/node-medley/src/core.cpp | 40 ++++++-- packages/node-medley/src/core.h | 2 + packages/node-medley/src/index.d.ts | 11 ++- packages/node-medley/src/version.h | 2 +- 7 files changed, 167 insertions(+), 65 deletions(-) diff --git a/packages/engine/src/Metadata.cpp b/packages/engine/src/Metadata.cpp index 8a62aa12..d94e59be 100644 --- a/packages/engine/src/Metadata.cpp +++ b/packages/engine/src/Metadata.cpp @@ -191,30 +191,6 @@ namespace { } } -void medley::Metadata::readMpegInfo(const File& f) -{ - FileInputStream stream(f); - - mp3dec_io_t io{}; - mp3dec_ex_t dec{}; - - io.read = &minimp3_read_cb; - io.seek = &minimp3_seek_cb; - io.read_data = io.seek_data = &stream; - - mp3dec_ex_open_cb(&dec, &io, MP3D_SEEK_TO_SAMPLE); - - auto lengthInSamples = dec.detected_samples / dec.info.channels; - - if (lengthInSamples <= 0) { - lengthInSamples = dec.samples / dec.info.channels; - } - - bitrate = dec.info.bitrate_kbps; - sampleRate = dec.info.hz; - duration = lengthInSamples / (float)sampleRate; -} - bool medley::Metadata::readID3V2(const juce::File& f) { #ifdef _WIN32 @@ -230,9 +206,6 @@ bool medley::Metadata::readID3V2(const juce::File& f) } try { - - readMpegInfo(f); - if (!file.hasID3v2Tag()) { return false; } @@ -341,22 +314,7 @@ bool medley::Metadata::readFLAC(const File& f) TagLib::FLAC::File file(&stream, TagLib::ID3v2::FrameFactory::instance(), true, TagLib::AudioProperties::Fast); if (!file.isValid()) { - throw std::runtime_error("Invalid FLAC file"); - } - - if (isFLACSupported(&stream)) { - try { - auto const props = file.audioProperties(); - - bitrate = props->bitrate(); - sampleRate = props->sampleRate(); - duration = props->lengthInMilliseconds() / 1000.0; - } - catch (...) { - bitrate = 0; - sampleRate = 0; - duration = 0; - } + throw std::runtime_error("Could not open FLAC file for reading"); } try { @@ -533,3 +491,105 @@ void medley::Metadata::CoverAndLyrics::read(const File& file, bool readCover, bo } } } + +medley::Metadata::AudioProperties::AudioProperties(const File& file) +{ + read(file); +} + +void medley::Metadata::AudioProperties::read(const File& file) +{ + auto filetype = utils::getFileTypeFromFileName(file); + + switch (filetype) { + case utils::FileType::MP3: { + readMpegInfo(file); + return; + } + + case utils::FileType::FLAC: { + readXiph(file); + return; + } + } +} + +void medley::Metadata::AudioProperties::readMpegInfo(const File& f) +{ +#ifdef _WIN32 + TagLib::FileName fileName((const wchar_t*)f.getFullPathName().toWideCharPointer()); +#else + TagLib::FileName fileName(f.getFullPathName().toRawUTF8()); +#endif + + TagLib::FileStream stream(fileName); + TagLib::MPEG::File file(&stream, TagLib::ID3v2::FrameFactory::instance(), true, TagLib::AudioProperties::Average); + + if (file.isSupported(&stream)) { + auto props = file.audioProperties(); + + bitrate = props->bitrate(); + sampleRate = props->sampleRate(); + duration = props->lengthInSeconds(); + + //if ((bitrate != 0) && (sampleRate != 0) && (duration != 0)) { + // return; + //} + } + + /*{ + FileInputStream stream(f); + + mp3dec_io_t io{}; + mp3dec_ex_t dec{}; + + io.read = &minimp3_read_cb; + io.seek = &minimp3_seek_cb; + io.read_data = io.seek_data = &stream; + + if (mp3dec_ex_open_cb(&dec, &io, MP3D_SEEK_TO_SAMPLE) != 0) { + return; + } + + auto lengthInSamples = dec.detected_samples / dec.info.channels; + + if (lengthInSamples <= 0) { + lengthInSamples = dec.samples / dec.info.channels; + } + + bitrate = dec.info.bitrate_kbps; + sampleRate = dec.info.hz; + duration = lengthInSamples / (float)sampleRate; + }*/ +} + +void medley::Metadata::AudioProperties::readXiph(const File& f) +{ +#ifdef _WIN32 + TagLib::FileName fileName((const wchar_t*)f.getFullPathName().toWideCharPointer()); +#else + TagLib::FileName fileName(f.getFullPathName().toRawUTF8()); +#endif + + TagLib::FileStream stream(fileName); + TagLib::FLAC::File file(&stream, TagLib::ID3v2::FrameFactory::instance(), true, TagLib::AudioProperties::Fast); + + if (!file.isValid()) { + throw std::runtime_error("Invalid FLAC file"); + } + + if (isFLACSupported(&stream)) { + try { + auto const props = file.audioProperties(); + + bitrate = props->bitrate(); + sampleRate = props->sampleRate(); + duration = props->lengthInMilliseconds() / 1000.0; + } + catch (...) { + bitrate = 0; + sampleRate = 0; + duration = 0; + } + } +} diff --git a/packages/engine/src/Metadata.h b/packages/engine/src/Metadata.h index 346e833b..99b876f5 100644 --- a/packages/engine/src/Metadata.h +++ b/packages/engine/src/Metadata.h @@ -64,6 +64,25 @@ class Metadata { juce::String lyrics; }; + class AudioProperties + { + public: + AudioProperties(const File& file); + + int getBitrate() const { return bitrate; } + int getSampleRate() const { return sampleRate; } + double getDuration() const { return duration; } + + private: + void read(const File& file); + void readMpegInfo(const File& f); + void readXiph(const File& f); + + int bitrate = 0; + int sampleRate = 0; + double duration = 0; + }; + Metadata(); bool readFromTrack(const ITrack::Ptr track); @@ -82,14 +101,10 @@ class Metadata { double getCueIn() const { return cueIn; } double getCueOut() const { return cueOut; } double getLastAudible() const { return lastAudible; } - int getBitrate() const { return bitrate; } - int getSampleRate() const { return sampleRate; } - double getDuration() const { return duration; } std::vector>& getComments() { return comments; } -private: - void readMpegInfo(const File& f); +private: bool readID3V2(const File& f); bool readFLAC(const File& f); void readTag(const TagLib::Tag& tag); @@ -108,10 +123,6 @@ class Metadata { double lastAudible = -1.0; std::vector> comments; - - int bitrate = 0; - int sampleRate = 0; - double duration = 0; }; } diff --git a/packages/node-medley/package.json b/packages/node-medley/package.json index 795ddcc1..33711e74 100644 --- a/packages/node-medley/package.json +++ b/packages/node-medley/package.json @@ -1,7 +1,7 @@ { "name": "@seamless-medley/medley", "description": "Audio engine for Node.js, with built-in \"radio like\" gapless/seamless playback", - "version": "0.4.1-fix-doubled-track-loading.0", + "version": "0.4.1-audio-property.0", "author": { "name": "Wittawas Nakkasem", "email": "vittee@hotmail.com", diff --git a/packages/node-medley/src/core.cpp b/packages/node-medley/src/core.cpp index 57f934c5..c6a884e0 100644 --- a/packages/node-medley/src/core.cpp +++ b/packages/node-medley/src/core.cpp @@ -17,14 +17,6 @@ namespace { result.Set("albumArtist", safeString(env, metadata.getAlbumArtist())); result.Set("originalArtist", safeString(env, metadata.getOriginalArtist())); - auto bitrate = metadata.getBitrate(); - auto sampleRate = metadata.getSampleRate(); - auto duration = metadata.getDuration(); - - result.Set("bitrate", bitrate != 0.0f ? Napi::Number::New(env, bitrate) : env.Undefined()); - result.Set("sampleRate", sampleRate != 0.0f ? Napi::Number::New(env, sampleRate) : env.Undefined()); - result.Set("duration", duration != 0.0f ? Napi::Number::New(env, duration) : env.Undefined()); - auto trackGain = metadata.getTrackGain(); auto bpm = metadata.getBeatsPerMinute(); @@ -49,6 +41,21 @@ namespace { return result; } + + void readAudioProperties(juce::String trackFile, Object& result) { + medley::Metadata::AudioProperties audProps(trackFile); + + auto bitrate = audProps.getBitrate(); + auto sampleRate = audProps.getSampleRate(); + auto duration = audProps.getDuration(); + + auto env = result.Env(); + + result.Set("bitrate", bitrate != 0.0f ? Napi::Number::New(env, bitrate) : env.Undefined()); + result.Set("sampleRate", sampleRate != 0.0f ? Napi::Number::New(env, sampleRate) : env.Undefined()); + result.Set("duration", duration != 0.0f ? Napi::Number::New(env, duration) : env.Undefined()); + } + } void Medley::Initialize(Object& exports) { @@ -86,6 +93,7 @@ void Medley::Initialize(Object& exports) { InstanceAccessor<&Medley::getReplayGainBoost, &Medley::setReplayGainBoost>("replayGainBoost"), // StaticMethod<&Medley::static_getMetadata>("getMetadata"), + StaticMethod<&Medley::static_getAudioProperties>("getAudioProperties"), StaticMethod<&Medley::static_getCoverAndLyrics>("getCoverAndLyrics"), StaticMethod<&Medley::static_isTrackLoadable>("isTrackLoadable"), StaticMethod<&Medley::static_getInfo>("$getInfo"), @@ -967,6 +975,22 @@ Napi::Value Medley::static_getMetadata(const CallbackInfo& info) { return createJSMetadata(env, metadata); } +Napi::Value Medley::static_getAudioProperties(const Napi::CallbackInfo& info) { + auto env = info.Env(); + + if (info.Length() < 1) { + TypeError::New(env, "Insufficient parameter").ThrowAsJavaScriptException(); + return env.Undefined(); + } + + juce::String trackFile = info[0].ToString().Utf8Value(); + auto result = Object::New(env); + + readAudioProperties(trackFile, result); + + return result; +} + Napi::Value Medley::static_getCoverAndLyrics(const Napi::CallbackInfo& info) { auto env = info.Env(); diff --git a/packages/node-medley/src/core.h b/packages/node-medley/src/core.h index cb8780a0..915e4cfe 100644 --- a/packages/node-medley/src/core.h +++ b/packages/node-medley/src/core.h @@ -121,6 +121,8 @@ class Medley : public ObjectWrap, public Engine::Callback, public Engine static Napi::Value static_getMetadata(const Napi::CallbackInfo& info); + static Napi::Value static_getAudioProperties(const Napi::CallbackInfo& info); + static Napi::Value static_getCoverAndLyrics(const Napi::CallbackInfo& info); static Napi::Value static_isTrackLoadable(const Napi::CallbackInfo& info); diff --git a/packages/node-medley/src/index.d.ts b/packages/node-medley/src/index.d.ts index d6b87a81..85c39394 100644 --- a/packages/node-medley/src/index.d.ts +++ b/packages/node-medley/src/index.d.ts @@ -250,6 +250,8 @@ export declare class Medley { static getMetadata(path: string): Metadata | undefined; + static getAudioProperties(path: string): AudioProperties; + static getCoverAndLyrics(path: string): CoverAndLyrics; static isTrackLoadable(track: TrackDescriptor): boolean; @@ -360,14 +362,17 @@ export type Metadata = { isrc?: string; albumArtist?: string; originalArtist?: string; - bitrate?: number; - sampleRate?: number; - duration?: number; trackGain?: number; bpm?: number; comments: [string, string][]; } +export type AudioProperties = { + bitrate?: number; + sampleRate?: number; + duration?: number; +} + export type MetadataFields = keyof Metadata; export type CoverAndLyrics = { diff --git a/packages/node-medley/src/version.h b/packages/node-medley/src/version.h index db2dca77..8b83a7ae 100644 --- a/packages/node-medley/src/version.h +++ b/packages/node-medley/src/version.h @@ -1,4 +1,4 @@ #define MEDLEY_VERSION_MAJOR 0 #define MEDLEY_VERSION_MINOR 4 #define MEDLEY_VERSION_PATCH 1 -#define MEDLEY_VERSION_PRE_RELEASE "fix-doubled-track-loading.0" +#define MEDLEY_VERSION_PRE_RELEASE "audio-property.0"