diff options
Diffstat (limited to 'Src/Plugins/Input/in_mp3')
93 files changed, 17365 insertions, 0 deletions
diff --git a/Src/Plugins/Input/in_mp3/AACFrame.cpp b/Src/Plugins/Input/in_mp3/AACFrame.cpp new file mode 100644 index 00000000..b2e8384d --- /dev/null +++ b/Src/Plugins/Input/in_mp3/AACFrame.cpp @@ -0,0 +1,94 @@ +#include "AACFrame.h" +#include "api__in_mp3.h" +#include "resource.h" +#include "in2.h" +extern In_Module mod; + +void AACFrame::ReadBuffer( unsigned __int8 *buffer ) +{ + syncword = ( buffer[ 0 ] << 4 ) | ( buffer[ 1 ] >> 4 ); + id = ( buffer[ 1 ] >> 3 ) & 1; + layer = ( buffer[ 1 ] >> 1 ) & 3; + protection = ( buffer[ 1 ] ) & 1; + profile = ( buffer[ 2 ] >> 6 ) & 3; + sampleRateIndex = ( buffer[ 2 ] >> 2 ) & 0xF; + privateBit = ( buffer[ 2 ] >> 1 ) & 1; + channelConfiguration = ( ( buffer[ 2 ] & 1 ) << 2 ) | ( ( buffer[ 3 ] >> 6 ) & 3 ); + original = ( buffer[ 3 ] >> 5 ) & 1; + home = ( buffer[ 3 ] >> 4 ) & 1; + + //copyright_identification_bit = (buffer[3] >> 3) & 1; + //copyright_identification_start = (buffer[3] >> 2) & 1; + frameLength = ( ( buffer[ 3 ] & 3 ) << 11 ) | ( buffer[ 4 ] << 3 ) | ( ( buffer[ 5 ] >> 5 ) & 7 ); + bufferFullness = ( ( buffer[ 5 ] & 0xF8 ) << 5 ) | ( ( buffer[ 6 ] >> 2 ) & 0x3F ); + numDataBlocks = buffer[ 6 ] & 3; +} + +bool AACFrame::OK() +{ + if (syncword == SYNC + && layer == 0 + && sampleRateIndex < 13 + //&& profile != LTP // TODO: can coding technologies decoder do LTP? + ) + return true; + else + return false; +} + + +static const unsigned int aac_sratetab[] = + { + 96000, + 88200, + 64000, + 48000, + 44100, + 32000, + 24000, + 22050, + 16000, + 12000, + 11025, + 8000, + 7350, + }; + +int AACFrame::GetSampleRate() +{ + return aac_sratetab[sampleRateIndex]; +} + +static const wchar_t *aac_profiletab[] = {L"Main", L"LC", L"SSR", L"LTP"}; + +const wchar_t *AACFrame::GetProfileName() +{ + return aac_profiletab[profile]; +} + +//static const char *aac_channels[] = {"Custom", "Mono", "Stereo", "3 channel", "4 channel", "surround", "5.1", "7.1"}; +static wchar_t aac_channels_str[64]; +static int aac_channels_id[] = {IDS_CUSTOM, IDS_MONO, IDS_STEREO, IDS_3_CHANNEL, IDS_4_CHANNEL, IDS_SURROUND, IDS_5_1, IDS_7_1}; +const wchar_t *AACFrame::GetChannelConfigurationName() +{ + return WASABI_API_LNGSTRINGW_BUF(aac_channels_id[channelConfiguration],aac_channels_str,64); +} + +int AACFrame::GetNumChannels() +{ + switch(channelConfiguration) + { + case 7: + return 8; + default: + return channelConfiguration; + } +} + +int AACFrame::GetMPEGVersion() +{ + if (id == 0) + return 2; + else + return 4; +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mp3/AACFrame.h b/Src/Plugins/Input/in_mp3/AACFrame.h new file mode 100644 index 00000000..c53bd18c --- /dev/null +++ b/Src/Plugins/Input/in_mp3/AACFrame.h @@ -0,0 +1,43 @@ +#ifndef NULLSOFT_AACFRAME_H +#define NULLSOFT_AACFRAME_H + +class AACFrame +{ +public: + void ReadBuffer(unsigned __int8 *buffer); + bool OK(); + + enum + { + NOT_PROTECTED=1, + PROTECTED=0, + SYNC = 0xFFF, + MAIN = 0x00, + LC = 0x01, + SSR = 0x10, + LTP = 0x11, + }; + int GetNumChannels(); + int GetSampleRate(); + const wchar_t *GetProfileName(); + const wchar_t *GetChannelConfigurationName(); + int GetMPEGVersion(); // returns 2 or 4 +public: + int syncword; + int layer; + int id; + int protection; + int profile; + int sampleRateIndex; + int privateBit; + int channelConfiguration; + int original; + int home; + int frameLength; + int bufferFullness; + int numDataBlocks; + + +}; + +#endif
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mp3/AlbumArt.cpp b/Src/Plugins/Input/in_mp3/AlbumArt.cpp new file mode 100644 index 00000000..b0bd78a2 --- /dev/null +++ b/Src/Plugins/Input/in_mp3/AlbumArt.cpp @@ -0,0 +1,283 @@ +#include "main.h" +#include "Metadata.h" +#include "api__in_mp3.h" +#include "../nu/AutoWide.h" +#include "AlbumArt.h" +#include "Stopper.h" +#include <shlwapi.h> +#include <strsafe.h> + +bool IsMyExtension(const wchar_t *filename) +{ + // check if it's the current stream and is playing and is SHOUTcast2 + if (PathIsURLW(filename)) + { + if (g_playing_file) + { + EnterCriticalSection(&streamInfoLock); + if (g_playing_file && + (g_playing_file->uvox_artwork.uvox_stream_artwork || g_playing_file->uvox_artwork.uvox_playing_artwork) && + !lstrcmpW(lastfn, filename)) // check again now that we've acquired the lock + { + LeaveCriticalSection(&streamInfoLock); + return true; + } + LeaveCriticalSection(&streamInfoLock); + } + } + // otherwise handle as normal embedded + else + { + const wchar_t *extension = PathFindExtension(filename); + if (extension && *extension) + { + AutoWide wideList(config_extlist); // TODO: build a copy of this at config load time so we don't have to run this every time + extension++; + wchar_t *b = wideList; + + wchar_t *c = 0; + do + { + wchar_t d[20] = {0}; + StringCchCopyW(d, 15, b); + if ((c = wcschr(b, L';'))) + { + if ((c-b)<15) + d[c - b] = 0; + } + + if (!lstrcmpiW(extension, d)) + return true; + + b = c + 1; + } + while (c); + } + } + return false; +} + +bool ID3v2_AlbumArtProvider::IsMine(const wchar_t *filename) +{ + return IsMyExtension(filename); +} + +int ID3v2_AlbumArtProvider::ProviderType() +{ + return ALBUMARTPROVIDER_TYPE_EMBEDDED; +} + +int ID3v2_AlbumArtProvider::GetAlbumArtData(const wchar_t *filename, const wchar_t *type, void **bits, size_t *len, wchar_t **mimeType) +{ + if (g_playing_file) + { + EnterCriticalSection(&streamInfoLock); + if (g_playing_file && !lstrcmpW(lastfn, filename)) // check again now that we've acquired the lock + { + wchar_t* mimeType[] = { + L"image/jpeg", + L"image/png", + L"image/bmp", + L"image/gif" + }; + if (!g_playing_file->uvox_artwork.uvox_stream_artwork) + { + int ret = g_playing_file->info.GetAlbumArt(type, bits, len, mimeType); + LeaveCriticalSection(&streamInfoLock); + return ret; + } + else + { + // will handle "playing" and "cover" - cover is the stream branding + // with "playing" used to provide song specific stream artwork + if (!_wcsicmp(type, L"playing")) + { + if (g_playing_file->uvox_artwork.uvox_playing_artwork_len > 0) + { + *len = g_playing_file->uvox_artwork.uvox_playing_artwork_len; + int type = g_playing_file->uvox_artwork.uvox_playing_artwork_type; + if(type >= 0 && type <= 3) + { + *mimeType = (wchar_t *)WASABI_API_MEMMGR->sysMalloc(12 * sizeof(wchar_t)); + wcsncpy(*mimeType, mimeType[type], 12); + } + else + { + *mimeType = 0; + } + + *bits = WASABI_API_MEMMGR->sysMalloc(*len); + memcpy(*bits, g_playing_file->uvox_artwork.uvox_playing_artwork, *len); + + LeaveCriticalSection(&streamInfoLock); + return ALBUMARTPROVIDER_SUCCESS; + } + else + { + LeaveCriticalSection(&streamInfoLock); + return ALBUMARTPROVIDER_FAILURE; + } + } + else + { + if (g_playing_file->uvox_artwork.uvox_stream_artwork_len > 0) + { + *len = g_playing_file->uvox_artwork.uvox_stream_artwork_len; + + int type = g_playing_file->uvox_artwork.uvox_stream_artwork_type; + if(type >= 0 && type <= 3) + { + *mimeType = (wchar_t *)WASABI_API_MEMMGR->sysMalloc(12 * sizeof(wchar_t)); + wcsncpy(*mimeType, mimeType[type], 12); + } + else + { + *mimeType = 0; + } + + *bits = WASABI_API_MEMMGR->sysMalloc(*len); + memcpy(*bits, g_playing_file->uvox_artwork.uvox_stream_artwork, *len); + + LeaveCriticalSection(&streamInfoLock); + return ALBUMARTPROVIDER_SUCCESS; + } + else + { + LeaveCriticalSection(&streamInfoLock); + return ALBUMARTPROVIDER_FAILURE; + } + } + } + } + LeaveCriticalSection(&streamInfoLock); + } + + Metadata metadata; + if (metadata.Open(filename) == METADATA_SUCCESS) + { + return metadata.id3v2.GetAlbumArt(type, bits, len, mimeType); + } + + return ALBUMARTPROVIDER_FAILURE; +} + +extern Metadata *m_ext_get_mp3info; + +int ID3v2_AlbumArtProvider::SetAlbumArtData(const wchar_t *filename, const wchar_t *type, void *bits, size_t len, const wchar_t *mimeType) +{ + Metadata metadata; + if (metadata.Open(filename) == METADATA_SUCCESS) + { + int ret = metadata.id3v2.SetAlbumArt(type, bits, len, mimeType); + if (ret == METADATA_SUCCESS) + { + // flush our read cache too :) + if (m_ext_get_mp3info) m_ext_get_mp3info->Release(); + m_ext_get_mp3info = NULL; + + Stopper stopper; + if (metadata.IsMe(lastfn)) + stopper.Stop(); + metadata.Save(); + stopper.Play(); + } + return ret; + } + return ALBUMARTPROVIDER_FAILURE; +} + +int ID3v2_AlbumArtProvider::DeleteAlbumArt(const wchar_t *filename, const wchar_t *type) +{ + Metadata metadata; + if (metadata.Open(filename) == METADATA_SUCCESS) + { + int ret = metadata.id3v2.DeleteAlbumArt(type); + if (ret == METADATA_SUCCESS) + { + // flush our read cache too :) + if (m_ext_get_mp3info) m_ext_get_mp3info->Release(); + m_ext_get_mp3info = NULL; + + Stopper stopper; + if (metadata.IsMe(lastfn)) + stopper.Stop(); + metadata.Save(); + stopper.Play(); + } + return ret; + } + return ALBUMARTPROVIDER_FAILURE; +} + +#define CBCLASS ID3v2_AlbumArtProvider +START_DISPATCH; +CB(SVC_ALBUMARTPROVIDER_PROVIDERTYPE, ProviderType); +CB(SVC_ALBUMARTPROVIDER_GETALBUMARTDATA, GetAlbumArtData); +CB(SVC_ALBUMARTPROVIDER_ISMINE, IsMine); +CB(SVC_ALBUMARTPROVIDER_SETALBUMARTDATA, SetAlbumArtData); +CB(SVC_ALBUMARTPROVIDER_DELETEALBUMART, DeleteAlbumArt); +END_DISPATCH; +#undef CBCLASS + +static ID3v2_AlbumArtProvider albumArtProvider; + +// {C8222317-8F0D-4e79-9222-447381C46E07} +static const GUID id3v2_albumartproviderGUID = + { 0xc8222317, 0x8f0d, 0x4e79, { 0x92, 0x22, 0x44, 0x73, 0x81, 0xc4, 0x6e, 0x7 } }; + +FOURCC AlbumArtFactory::GetServiceType() +{ + return svc_albumArtProvider::SERVICETYPE; +} + +const char *AlbumArtFactory::GetServiceName() +{ + return "ID3v2 Album Art Provider"; +} + +GUID AlbumArtFactory::GetGUID() +{ + return id3v2_albumartproviderGUID; +} + +void *AlbumArtFactory::GetInterface(int global_lock) +{ + return &albumArtProvider; +} + +int AlbumArtFactory::SupportNonLockingInterface() +{ + return 1; +} + +int AlbumArtFactory::ReleaseInterface(void *ifc) +{ + //plugin.service->service_unlock(ifc); + return 1; +} + +const char *AlbumArtFactory::GetTestString() +{ + return 0; +} + +int AlbumArtFactory::ServiceNotify(int msg, int param1, int param2) +{ + return 1; +} + +#ifdef CBCLASS +#undef CBCLASS +#endif + +#define CBCLASS AlbumArtFactory +START_DISPATCH; +CB(WASERVICEFACTORY_GETSERVICETYPE, GetServiceType) +CB(WASERVICEFACTORY_GETSERVICENAME, GetServiceName) +CB(WASERVICEFACTORY_GETGUID, GetGUID) +CB(WASERVICEFACTORY_GETINTERFACE, GetInterface) +CB(WASERVICEFACTORY_SUPPORTNONLOCKINGGETINTERFACE, SupportNonLockingInterface) +CB(WASERVICEFACTORY_RELEASEINTERFACE, ReleaseInterface) +CB(WASERVICEFACTORY_GETTESTSTRING, GetTestString) +CB(WASERVICEFACTORY_SERVICENOTIFY, ServiceNotify) +END_DISPATCH;
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mp3/AlbumArt.h b/Src/Plugins/Input/in_mp3/AlbumArt.h new file mode 100644 index 00000000..a01c826d --- /dev/null +++ b/Src/Plugins/Input/in_mp3/AlbumArt.h @@ -0,0 +1,39 @@ +#ifndef NULLSOFT_IN_MP3_ALBUMART_H +#define NULLSOFT_IN_MP3_ALBUMART_H + +#include "../Agave/AlbumArt/svc_albumArtProvider.h" + +class ID3v2_AlbumArtProvider : public svc_albumArtProvider +{ +public: + bool IsMine(const wchar_t *filename); + int ProviderType(); + // implementation note: use WASABI_API_MEMMGR to alloc bits and mimetype, so that the recipient can free through that + int GetAlbumArtData(const wchar_t *filename, const wchar_t *type, void **bits, size_t *len, wchar_t **mimeType); + int SetAlbumArtData(const wchar_t *filename, const wchar_t *type, void *bits, size_t len, const wchar_t *mimeType); + int DeleteAlbumArt(const wchar_t *filename, const wchar_t *type); +protected: + RECVS_DISPATCH; +}; + +#include <api/service/waservicefactory.h> +#include <api/service/services.h> + +class AlbumArtFactory : public waServiceFactory +{ +public: + FOURCC GetServiceType(); + const char *GetServiceName(); + GUID GetGUID(); + void *GetInterface(int global_lock); + int SupportNonLockingInterface(); + int ReleaseInterface(void *ifc); + const char *GetTestString(); + int ServiceNotify(int msg, int param1, int param2); + +protected: + RECVS_DISPATCH; +}; + + +#endif
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mp3/CVbriHeader.cpp b/Src/Plugins/Input/in_mp3/CVbriHeader.cpp new file mode 100644 index 00000000..d8eebd84 --- /dev/null +++ b/Src/Plugins/Input/in_mp3/CVbriHeader.cpp @@ -0,0 +1,327 @@ +//---------------------------------------------------------------------------\ +// +// (C) copyright Fraunhofer - IIS (2000) +// All Rights Reserved +// +// filename: CVbriHeader.cpp +// MPEG Layer-3 Audio Decoder +// author : Martin Weishart martin.weishart@iis.fhg.de +// date : 2000-02-11 +// contents/description: provides functions to read a VBRI header +// of a MPEG Layer 3 bitstream encoded +// with variable bitrate using Fraunhofer +// variable bitrate format +// +//--------------------------------------------------------------------------/ + +#include <windows.h> + +#include "CVbriHeader.h" +#include "LAMEInfo.h" +#include <malloc.h> + +//---------------------------------------------------------------------------\ +// +// Constructor: set position in buffer to parse and create a +// VbriHeaderTable +// +//---------------------------------------------------------------------------/ + +CVbriHeader::CVbriHeader(){ + position = 0; + VbriTable=0; + VbriStreamFrames=0; + encoderDelay=0; + h_id=0; + SampleRate=0; + VbriTableSize=0; + VbriEntryFrames=0; + VbriStreamBytes=0; +} + + + +//---------------------------------------------------------------------------\ +// +// Destructor: delete a VbriHeaderTable and a VbriHeader +// +//---------------------------------------------------------------------------/ + +CVbriHeader::~CVbriHeader(){ + free(VbriTable); +} + + + +//---------------------------------------------------------------------------\ +// +// Method: checkheader +// Reads the header to a struct that has to be stored and is +// used in other functions to determine file offsets +// Input: buffer containing the first frame +// Output: fills struct VbriHeader +// Return: 0 on success; 1 on error +// +//---------------------------------------------------------------------------/ + +int CVbriHeader::readVbriHeader(unsigned char *Hbuffer) +{ + position=0; + // MPEG header + MPEGFrame frame; + frame.ReadBuffer(Hbuffer); + if (!frame.IsSync()) + return 0; + + SampleRate = frame.GetSampleRate(); + h_id = frame.mpegVersion & 1; + + position += DWORD ; + + // data indicating silence + position += (8*DWORD) ; + + // if a VBRI Header exists read it + + if ( *(Hbuffer+position ) == 'V' && + *(Hbuffer+position+1) == 'B' && + *(Hbuffer+position+2) == 'R' && + *(Hbuffer+position+3) == 'I'){ + + position += DWORD; + + //position += WORD; + /*unsigned int vbriVersion = */readFromBuffer(Hbuffer, WORD); // version + + encoderDelay = readFromBuffer(Hbuffer, WORD); // delay + + position += WORD; + //readFromBuffer(Hbuffer, WORD); // quality + + VbriStreamBytes = readFromBuffer(Hbuffer, DWORD); + VbriStreamFrames = readFromBuffer(Hbuffer, DWORD); + VbriTableSize = readFromBuffer(Hbuffer, WORD); + unsigned int VbriTableScale = readFromBuffer(Hbuffer, WORD); + unsigned int VbriEntryBytes = readFromBuffer(Hbuffer, WORD); + VbriEntryFrames = readFromBuffer(Hbuffer, WORD); + + if (VbriTableSize > 32768) return 1; + + VbriTable = (int *)calloc((VbriTableSize + 1), sizeof(int)); + + for (unsigned int i = 0 ; i <= VbriTableSize ; i++){ + VbriTable[i] = readFromBuffer(Hbuffer, VbriEntryBytes*BYTE) + * VbriTableScale ; + } + } + else + { + return 0; + } + return frame.FrameSize(); +} + + + +//---------------------------------------------------------------------------\ +// +// Method: seekPointByTime +// Returns a point in the file to decode in bytes that is nearest +// to a given time in seconds +// Input: time in seconds +// Output: None +// Returns: point belonging to the given time value in bytes +// +//---------------------------------------------------------------------------/ + +int CVbriHeader::seekPointByTime(float EntryTimeInMilliSeconds){ + + unsigned int SamplesPerFrame, i=0, SeekPoint = 0 , fraction = 0; + + float TotalDuration ; + float DurationPerVbriFrames ; + float AccumulatedTime = 0.0f ; + + (SampleRate >= 32000) ? (SamplesPerFrame = 1152) : (SamplesPerFrame = 576) ; + + TotalDuration = ((float)VbriStreamFrames * (float)SamplesPerFrame) + / (float)SampleRate * 1000.0f ; + DurationPerVbriFrames = (float)TotalDuration / (float)(VbriTableSize+1) ; + + if ( EntryTimeInMilliSeconds > TotalDuration ) EntryTimeInMilliSeconds = TotalDuration; + + while ( AccumulatedTime <= EntryTimeInMilliSeconds ){ + + SeekPoint += VbriTable[i] ; + AccumulatedTime += DurationPerVbriFrames; + i++; + + } + + // Searched too far; correct result + fraction = ( (int)(((( AccumulatedTime - EntryTimeInMilliSeconds ) / DurationPerVbriFrames ) + + (1.0f/(2.0f*(float)VbriEntryFrames))) * (float)VbriEntryFrames)); + + + SeekPoint -= (int)((float)VbriTable[i-1] * (float)(fraction) + / (float)VbriEntryFrames) ; + + return SeekPoint ; + +} + +int CVbriHeader::getNumMS() + { + if (!VbriStreamFrames || !SampleRate) return 0; + + int nf=VbriStreamFrames; + int sr=SampleRate; + if (sr >= 32000) sr/=2; + //576 + return MulDiv(nf,576*1000,sr); + } + +#if 0 +//---------------------------------------------------------------------------\ +// +// Method: seekTimeByPoint +// Returns a time in the file to decode in seconds that is +// nearest to a given point in bytes +// Input: time in seconds +// Output: None +// Returns: point belonging to the given time value in bytes +// +//---------------------------------------------------------------------------/ + +float CVbriHeader::seekTimeByPoint(unsigned int EntryPointInBytes){ + + unsigned int SamplesPerFrame, i=0, AccumulatedBytes = 0, fraction = 0; + + float SeekTime = 0.0f; + float TotalDuration ; + float DurationPerVbriFrames ; + + (SampleRate >= 32000) ? (SamplesPerFrame = 1152) : (SamplesPerFrame = 576) ; + + TotalDuration = ((float)VbriStreamFrames * (float)SamplesPerFrame) + / (float)SampleRate; + DurationPerVbriFrames = (float)TotalDuration / (float)(VbriTableSize+1) ; + + while (AccumulatedBytes <= EntryPointInBytes){ + + AccumulatedBytes += VbriTable[i] ; + SeekTime += DurationPerVbriFrames; + i++; + + } + + // Searched too far; correct result + fraction = (int)(((( AccumulatedBytes - EntryPointInBytes ) / (float)VbriTable[i-1]) + + (1/(2*(float)VbriEntryFrames))) * (float)VbriEntryFrames); + + SeekTime -= (DurationPerVbriFrames * (float) ((float)(fraction) / (float)VbriEntryFrames)) ; + + return SeekTime ; + +} + + + +//---------------------------------------------------------------------------\ +// +// Method: seekPointByPercent +// Returns a point in the file to decode in bytes that is +// nearest to a given percentage of the time of the stream +// Input: percent of time +// Output: None +// Returns: point belonging to the given time percentage value in bytes +// +//---------------------------------------------------------------------------/ + +int CVbriHeader::seekPointByPercent(float percent){ + + int SamplesPerFrame; + + float TotalDuration ; + + if (percent >= 100.0f) percent = 100.0f; + if (percent <= 0.0f) percent = 0.0f; + + (SampleRate >= 32000) ? (SamplesPerFrame = 1152) : (SamplesPerFrame = 576) ; + + TotalDuration = ((float)VbriStreamFrames * (float)SamplesPerFrame) + / (float)SampleRate; + + return seekPointByTime( (percent/100.0f) * TotalDuration * 1000.0f ); + +} + +#endif + + +//---------------------------------------------------------------------------\ +// +// Method: GetSampleRate +// Returns the sampling rate of the file to decode +// Input: Buffer containing the part of the first frame after the +// syncword +// Output: None +// Return: sampling rate of the file to decode +// +//---------------------------------------------------------------------------/ + +/*int CVbriHeader::getSampleRate(unsigned char * buffer){ + + unsigned char id, idx, mpeg ; + + id = (0xC0 & (buffer[1] << 3)) >> 4; + idx = (0xC0 & (buffer[2] << 4)) >> 6; + + mpeg = id | idx; + + switch ((int)mpeg){ + + case 0 : return 11025; + case 1 : return 12000; + case 2 : return 8000; + case 8 : return 22050; + case 9 : return 24000; + case 10: return 16000; + case 12: return 44100; + case 13: return 48000; + case 14: return 32000; + default: return 0; + + } +}*/ + + + +//---------------------------------------------------------------------------\ +// +// Method: readFromBuffer +// reads from a buffer a segment to an int value +// Input: Buffer containig the first frame +// Output: none +// Return: number containing int value of buffer segmenet +// length +// +//---------------------------------------------------------------------------/ + +int CVbriHeader::readFromBuffer ( unsigned char * HBuffer, int length ){ + + if (HBuffer) + { + int number = 0; + for(int i = 0; i < length ; i++ ) + { + int b = length-1-i ; + number = number | (unsigned int)( HBuffer[position+i] & 0xff ) << ( 8*b ); + } + position += length ; + return number; + } + else{ + return 0; + } +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mp3/CVbriHeader.h b/Src/Plugins/Input/in_mp3/CVbriHeader.h new file mode 100644 index 00000000..df9aaa70 --- /dev/null +++ b/Src/Plugins/Input/in_mp3/CVbriHeader.h @@ -0,0 +1,49 @@ +#ifndef _VBRIHEADER_H_ +#define _VBRIHEADER_H_ + +class CVbriHeader{ + +public: + + CVbriHeader(); + ~CVbriHeader(); + + int readVbriHeader(unsigned char *Hbuffer); + + int seekPointByTime(float EntryTimeInSeconds); +#if 0 + float seekTimeByPoint(unsigned int EntryPointInBytes); + int seekPointByPercent(float percent); +#endif + + int getNumFrames() { return VbriStreamFrames; } + int getNumMS(); + int getEncoderDelay() { return encoderDelay; } + int getBytes() { return VbriStreamBytes; } +int h_id; +private: + + int getSampleRate(unsigned char * buffer); + int readFromBuffer ( unsigned char * HBuffer, int length ); + + int SampleRate; + unsigned int VbriStreamBytes; + unsigned int VbriStreamFrames; + unsigned int VbriTableSize; + unsigned int VbriEntryFrames; + int * VbriTable; + int encoderDelay; + + int position ; + + enum offset{ + + BYTE = 1, + WORD = 2, + DWORD = 4 + + }; + +}; + +#endif//_VBRIHEADER_H_
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mp3/CreateFile.cpp b/Src/Plugins/Input/in_mp3/CreateFile.cpp new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/Src/Plugins/Input/in_mp3/CreateFile.cpp diff --git a/Src/Plugins/Input/in_mp3/CreateFile.h b/Src/Plugins/Input/in_mp3/CreateFile.h new file mode 100644 index 00000000..25be7078 --- /dev/null +++ b/Src/Plugins/Input/in_mp3/CreateFile.h @@ -0,0 +1,6 @@ +#ifndef NULLSOFT_CREATEFILEH +#define NULLSOFT_CREATEFILEH + + + +#endif
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mp3/DXHEAD.C b/Src/Plugins/Input/in_mp3/DXHEAD.C new file mode 100644 index 00000000..f739240a --- /dev/null +++ b/Src/Plugins/Input/in_mp3/DXHEAD.C @@ -0,0 +1,54 @@ + +/*---- DXhead.c -------------------------------------------- + + +decoder MPEG Layer III + +handle Xing header + +mod 12/7/98 add vbr scale + +Copyright 1998 Xing Technology Corp. +-----------------------------------------------------------*/ +#include <windows.h> +#include <stdlib.h> +#include <stdio.h> +#include <float.h> +#include <math.h> +#include "dxhead.h" + + +/*-------------------------------------------------------------*/ +int SeekPoint(unsigned char TOC[100], int file_bytes, float percent) +{ + // interpolate in TOC to get file seek point in bytes + int a, seekpoint; + float fa, fb, fx; + + + if (percent < 0.0f) + percent = 0.0f; + if (percent > 100.0f) + percent = 100.0f; + + a = (int)percent; + if (a > 99) a = 99; + fa = TOC[a]; + if (a < 99) + { + fb = TOC[a + 1]; + } + else + { + fb = 256.0f; + } + + + fx = fa + (fb - fa) * (percent - a); + + seekpoint = (int) ((1.0f / 256.0f) * fx * file_bytes); + + + return seekpoint; +} +/*-------------------------------------------------------------*/ diff --git a/Src/Plugins/Input/in_mp3/DXHEAD.H b/Src/Plugins/Input/in_mp3/DXHEAD.H new file mode 100644 index 00000000..5b59e043 --- /dev/null +++ b/Src/Plugins/Input/in_mp3/DXHEAD.H @@ -0,0 +1,43 @@ +/*---- DXhead.h -------------------------------------------- + + +decoder MPEG Layer III + +handle Xing header + + +Copyright 1998 Xing Technology Corp. +-----------------------------------------------------------*/ +// A Xing header may be present in the ancillary +// data field of the first frame of an mp3 bitstream +// The Xing header (optionally) contains +// frames total number of audio frames in the bitstream +// bytes total number of bytes in the bitstream +// toc table of contents + +// toc (table of contents) gives seek points +// for random access +// the ith entry determines the seek point for +// i-percent duration +// seek point in bytes = (toc[i]/256.0) * total_bitstream_bytes +// e.g. half duration seek point = (toc[50]/256.0) * total_bitstream_bytes + + +#define FRAMES_FLAG 0x0001 +#define BYTES_FLAG 0x0002 +#define TOC_FLAG 0x0004 +#define VBR_SCALE_FLAG 0x0008 + +#define FRAMES_AND_BYTES (FRAMES_FLAG | BYTES_FLAG) + + +int SeekPoint(unsigned char TOC[100], int file_bytes, float percent); +// return seekpoint in bytes (may be at eof if percent=100.0) +// TOC = table of contents from Xing header +// file_bytes = number of bytes in mp3 file +// percent = play time percentage of total playtime. May be +// fractional (e.g. 87.245) + + + + diff --git a/Src/Plugins/Input/in_mp3/DecodeThread.cpp b/Src/Plugins/Input/in_mp3/DecodeThread.cpp new file mode 100644 index 00000000..99654069 --- /dev/null +++ b/Src/Plugins/Input/in_mp3/DecodeThread.cpp @@ -0,0 +1,810 @@ +#include "DecodeThread.h" +#include "giofile.h" +#include "main.h" +#include "pdtimer.h" +#include "mpegutil.h" +#include "../Winamp/wa_ipc.h" +#include "config.h" +#include <shlwapi.h> +#include "adts.h" +#include "adts_vlb.h" +#include <foundation/error.h> + +// {19450308-90D7-4E45-8A9D-DC71E67123E2} +static const GUID adts_aac_guid = +{ 0x19450308, 0x90d7, 0x4e45, { 0x8a, 0x9d, 0xdc, 0x71, 0xe6, 0x71, 0x23, 0xe2 } }; + +// {4192FE3F-E843-445c-8D62-51BE5EE5E68C} +static const GUID adts_mp2_guid = +{ 0x4192fe3f, 0xe843, 0x445c, { 0x8d, 0x62, 0x51, 0xbe, 0x5e, 0xe5, 0xe6, 0x8c } }; + +extern int m_is_stream; +extern bool m_is_stream_seekable; + +// post this to the main window at end of file (after playback as stopped) +#define WM_WA_MPEG_EOF WM_USER+2 + +/* public data */ +int last_decode_pos_ms; +int decode_pos_ms; // current decoding position, in milliseconds. +volatile int seek_needed; // if != -1, it is the point that the decode +// thread should seek to, in ms. +int g_ds; + +size_t g_bits; +int g_sndopened; +int g_bufferstat; +int g_length = -1000; +int g_vis_enabled; +volatile int g_closeaudio = 0; + +CGioFile *g_playing_file=0; +/* private data */ +static size_t g_samplebuf_used; +static int need_prebuffer; +static int g_srate, g_nch, g_br_add, g_br_div, g_avg_vbr_br; +int g_br; + +class EndCutter +{ +public: + EndCutter() : buffer(0), cutSize(0), filledSize(0), preCutSize(0), preCut(0), decoderDelay(0) + {} + ~EndCutter() + { + free(buffer); + } + void SetEndSize(int postSize) + { + postSize -= decoderDelay; + if (postSize < 0) + postSize = 0; + else if (postSize) + { + free(buffer); + buffer = (char *)calloc(postSize, sizeof(char)); + cutSize = postSize; + } + } + + void SetSize(int decoderDelaySize, int preSize, int postSize) + { + decoderDelay = decoderDelaySize; + SetEndSize(postSize); + + preCutSize = preSize; + preCut = preCutSize + decoderDelay; + } + + void Flush(int time_in_ms) + { + if (time_in_ms == 0) // TODO: calculate actual delay if we seek within the encoder delay area + preCut = preCutSize; // reset precut size if we seek to the start + + filledSize = 0; + mod.outMod->Flush(time_in_ms); + } + + void Write(char *out, int outSize) + { + if (!out && (!outSize)) + { + mod.outMod->Write(0, 0); + return ; + } + + // cut pre samples, if necessary + int pre = min(preCut, outSize); + out += pre; + outSize -= pre; + preCut -= pre; + + if (!outSize) + return ; + + int remainingFill = cutSize - filledSize; + int fillWrite = min(outSize - remainingFill, filledSize); // only write fill buffer if we've got enough left to fill it up + + if (fillWrite > 0) + { + mod.outMod->Write((char *)buffer, fillWrite); + if (cutSize - fillWrite) + memmove(buffer, buffer + fillWrite, cutSize - fillWrite); + filledSize -= fillWrite; + + } + remainingFill = cutSize - filledSize; + int outWrite = max(0, outSize - remainingFill); + if (outWrite) + mod.outMod->Write((char *)out, outWrite); + out += outWrite; + outSize -= outWrite; + + if (outSize) + { + memcpy(buffer + filledSize, out, outSize); + filledSize += outSize; + } + + + } + char *buffer; + int cutSize; + int filledSize; + int preCut, preCutSize, decoderDelay; +}; + +class DecodeLoop +{ +public: + DecodeLoop() : decoder(0) + { + isAac = 0; + isEAAC = 0; + + last_bpos = -1; + need_synclight = true; + done = 0; + br = 0; + + g_framesize = 0; + maxlatency = 0; + sampleFrameSize = 0; + memset(&g_samplebuf, 0, sizeof(g_samplebuf)); + } + + ~DecodeLoop() + { + if (decoder) + { + decoder->Close(); + decoder->Release(); + } + decoder=0; + + } + + DWORD Loop(); + DWORD OpenDecoder(); + void Seek(int seekPosition); + void PreBuffer(); + void Decode(); + void Viz(); + void CalculateCodecDelay(); + DWORD OpenOutput(int numChannels, int sampleRate, int bitsPerSample); + void SetupStream(); + + BYTE g_samplebuf[6*3*2*2*1152]; + + int g_framesize; + int isAac; + int isEAAC; + + CGioFile file; + + int maxlatency; + int last_bpos; + bool need_synclight; + int done; // set to TRUE if decoding has finished, 2 if all has been written + size_t br; + + EndCutter endCutter; + int sampleFrameSize; + adts *decoder; +}; + +static int CalcPreBuffer(int buffer_setting, int bitrate) +{ + if (bitrate < 8) + bitrate = 8; + else if (bitrate > 320) + bitrate = 320; + int prebuffer = (buffer_setting * bitrate) / 128; + if (prebuffer > 100) + prebuffer=100; + return prebuffer; +} + +void DecodeLoop::SetupStream() +{ + char buf[1024] = {0}; + int len; + + m_is_stream = file.IsStream(); + + //Wait until we have data... + while (!killDecodeThread && file.Peek(buf, 1024, &len) == NErr_Success && !len) + Sleep(50); + + m_is_stream_seekable = file.IsStreamSeekable(); + char *content_type = file.m_content_type; + if (content_type) + { + if (!_strnicmp(content_type, "misc/ultravox", 13)) + { + switch (file.uvox_last_message) + { + case 0x8001: + case 0x8003: + isEAAC = 1; + isAac = 1; + break; + + case 0x8000: + isAac = 1; + break; + } + } + else if (!_strnicmp(content_type, "audio/aac", 9)) + { + isEAAC = 1; + isAac = 1; + } + else if (!_strnicmp(content_type, "audio/aacp", 10)) + { + isEAAC = 1; + isAac = 1; + } + else if (!_strnicmp(content_type, "audio/apl", 10)) + { + isEAAC = 1; + isAac = 1; + } + } + + // todo: poll until connected to see if we get aac uvox frames or a content-type:aac header +} + +DWORD DecodeLoop::OpenOutput(int numChannels, int sampleRate, int bitsPerSample) +{ + maxlatency = mod.outMod->Open(sampleRate, numChannels, bitsPerSample, -1, -1); + + // maxlatency is the maxium latency between a outMod->Write() call and + // when you hear those samples. In ms. Used primarily by the visualization + // system. + + if (maxlatency < 0) // error opening device + { + PostMessage(mod.hMainWindow, WM_COMMAND, 40047, 0); + return 0; + } + g_sndopened = 1; + if (maxlatency == 0 && file.IsStream() == 2) // can't use with disk writer + { + if (!killDecodeThread) + { + EnterCriticalSection(&g_lfnscs); + WASABI_API_LNGSTRING_BUF(IDS_CANNOT_WRITE_STREAMS_TO_DISK,lastfn_status,256); + LeaveCriticalSection(&g_lfnscs); + PostMessage(mod.hMainWindow, WM_USER, 0, IPC_UPDTITLE); + } + if (!killDecodeThread) Sleep(200); + if (!killDecodeThread) PostMessage(mod.hMainWindow, WM_WA_MPEG_EOF, 0, 0); + g_bufferstat = 0; + g_closeaudio = 1; + + return 0; + } + + if (paused) mod.outMod->Pause(1); + + // set the output plug-ins default volume. + // volume is 0-255, -666 is a token for + // current volume. + + mod.outMod->SetVolume(-666); + return 1; +} + +void DecodeLoop::CalculateCodecDelay() +{ + int decoderDelaySamples = (int)decoder->GetDecoderDelay(); + + endCutter.SetSize(decoderDelaySamples*sampleFrameSize, + file.prepad*sampleFrameSize, + file.postpad*sampleFrameSize); +} + +void DecodeLoop::Viz() +{ + if (!config_fastvis || (decoder->GetLayer() != 3 || g_ds)) + { + int vis_waveNch; + int vis_specNch; + int csa = mod.SAGetMode(); + int is_vis_running = mod.VSAGetMode(&vis_specNch, &vis_waveNch); + if (csa || is_vis_running) + { + int l = 576 * sampleFrameSize; + int ti = decode_pos_ms; + { + if (g_ds == 2) + { + memcpy(g_samplebuf + g_samplebuf_used, g_samplebuf, g_samplebuf_used); + } + size_t pos = 0; + while (pos < g_samplebuf_used) + { + int a, b; + if (mod.SAGetMode()) mod.SAAddPCMData((char *)g_samplebuf + pos, g_nch, (int)g_bits, ti); + if (mod.VSAGetMode(&a, &b)) mod.VSAAddPCMData((char *)g_samplebuf + pos, g_nch, (int)g_bits, ti); + ti += ((l / sampleFrameSize * 1000) / g_srate); + pos += l >> g_ds; + } + } + } + } + else + { + int l = (576 * (int)g_bits * g_nch); + int ti = decode_pos_ms; + size_t pos = 0; + int x = 0; + while (pos < g_samplebuf_used) + { + do_layer3_vis((short*)(g_samplebuf + pos), &g_vis_table[x++][0][0][0], g_nch, ti); + ti += (l / g_nch / 2 * 1000) / g_srate; + pos += l; + } + } +} + +void DecodeLoop::Decode() +{ + while (g_samplebuf_used < (size_t)g_framesize && !killDecodeThread && seek_needed == -1) + { + size_t newl = 0; + size_t br=0; + size_t endCut=0; + int res = decoder->Decode(&file, g_samplebuf + g_samplebuf_used, sizeof(g_samplebuf) / 2 - g_samplebuf_used, &newl, &br, &endCut); + + if (config_gapless && endCut) + endCutter.SetEndSize((int)endCut* sampleFrameSize); + + // we're not using switch here because we sometimes need to break out of the while loop + if (res == adts::SUCCESS) + { + if (!file.m_vbr_frames) + { + if (br) { + bool do_real_br=false; + if (!(config_miscopts&2) && br != decoder->GetCurrentBitrate()) + { + do_real_br=true; + } + + int r = (int)br; + g_br_add += r; + g_br_div++; + r = (g_br_add + g_br_div / 2) / g_br_div; + if (g_br != r) + { + need_synclight = false; + g_br = r; + if (!file.m_vbr_frames && file.IsSeekable()) g_length = MulDiv(file.GetContentLength(), 8, g_br); + if (!do_real_br) + mod.SetInfo(g_br, -1, -1, 1); + } + if (do_real_br) + mod.SetInfo((int)br, -1, -1, 1); + } + } + else + { + if (br) { + int r; + if (!(config_miscopts&2) || !g_avg_vbr_br) + r = (int)br; + else r = g_avg_vbr_br; + if (g_br != r) + { + need_synclight = false; + g_br = r; + mod.SetInfo(g_br, -1, -1, 1); + } + } + } + if (need_synclight) + { + need_synclight = false; + mod.SetInfo(-1, -1, -1, 1); + } + g_samplebuf_used += newl; + } + else if (res == adts::ENDOFFILE) + { + done = 1; + break; + } + else if (res == adts::NEEDMOREDATA) + { + if (file.IsStream() && !need_synclight) + { + need_synclight = true; mod.SetInfo(-1, -1, -1, 0); + } + if (file.IsStream() && !mod.outMod->IsPlaying()) + { + need_prebuffer = CalcPreBuffer(config_http_prebuffer_underrun, (int)br); + } + break; + } + else + { + if (!need_synclight) mod.SetInfo(-1, -1, -1, 0); + need_synclight = true; + break; + } + } +} + +void DecodeLoop::PreBuffer() +{ + int p = file.RunStream(); + int pa = file.PercentAvailable(); + if (pa >= need_prebuffer || p == 2) + { + EnterCriticalSection(&g_lfnscs); + lastfn_status[0] = 0; + LeaveCriticalSection(&g_lfnscs); + PostMessage(mod.hMainWindow, WM_USER, 0, IPC_UPDTITLE); + need_prebuffer = 0; + g_bufferstat = 0; + last_bpos = -1; + } + else + { + int bpos = pa * 100 / need_prebuffer; + if (!g_bufferstat) g_bufferstat = decode_pos_ms; + if (bpos != last_bpos) + { + last_bpos = bpos; + EnterCriticalSection(&g_lfnscs); + if (stricmp(lastfn_status, "stream temporarily interrupted")) + { + char langbuf[512] = {0}; + wsprintfA(lastfn_status, WASABI_API_LNGSTRING_BUF(IDS_BUFFER_X,langbuf,512), bpos); + } + LeaveCriticalSection(&g_lfnscs); + + int csa = mod.SAGetMode(); + char tempdata[75*2] = {0, }; + int x; + if (csa&1) + { + for (x = 0; x < bpos*75 / 100; x ++) + { + tempdata[x] = x * 16 / 75; + } + } + if (csa&2) + { + int offs = (csa & 1) ? 75 : 0; + x = 0; + while (x < bpos*75 / 100) + { + tempdata[offs + x++] = -6 + x * 14 / 75; + } + while (x < 75) + { + tempdata[offs + x++] = 0; + } + } + if (csa == 4) + { + tempdata[0] = tempdata[1] = (bpos * 127 / 100); + } + + if (csa) mod.SAAdd(tempdata, ++g_bufferstat, (csa == 3) ? 0x80000003 : csa); + PostMessage(mod.hMainWindow, WM_USER, 0, IPC_UPDTITLE); + } + } +} + +void DecodeLoop::Seek(int seekPosition) +{ + if (done == 3) + return; + done=0; + int br = (int)decoder->GetCurrentBitrate(); + + need_prebuffer = CalcPreBuffer(config_http_prebuffer_underrun, br); + if (need_prebuffer < 1) need_prebuffer = 5; + + last_decode_pos_ms = decode_pos_ms = seekPosition; + + seek_needed = -1; + endCutter.Flush(decode_pos_ms); + decoder->Flush(&file); + done = 0; + g_samplebuf_used = 0; + + int r = g_br; + if (g_br_div) r = (g_br_add + g_br_div / 2) / g_br_div; + file.Seek(decode_pos_ms, r); + // need_prebuffer=config_http_prebuffer/8; + // g_br_add=g_br_div=0; + +} + +DWORD DecodeLoop::OpenDecoder() +{ + mod.UsesOutputPlug &= ~8; + if (isAac) + { + if (isEAAC) + { + waServiceFactory *factory = mod.service->service_getServiceByGuid(adts_aac_guid); + if (factory) + decoder = (adts *)factory->getInterface(); + + mod.UsesOutputPlug|=8; + } + if (!decoder) + { + decoder = new ADTS_VLB; + mod.UsesOutputPlug &= ~8; + } + } + else + { + waServiceFactory *factory = mod.service->service_getServiceByGuid(adts_mp2_guid); + if (factory) + decoder = (adts *)factory->getInterface(); + + mod.UsesOutputPlug|=8; + } + + if (decoder) { + decoder->SetDecoderHooks(mp3GiveVisData, mp2Equalize, mp3Equalize); + } + + if (decoder + && decoder->Initialize(AGAVE_API_CONFIG->GetBool(playbackConfigGroupGUID, L"mono", false), + config_downmix == 2, + AGAVE_API_CONFIG->GetBool(playbackConfigGroupGUID, L"surround", true), + (int)AGAVE_API_CONFIG->GetUnsigned(playbackConfigGroupGUID, L"bits", 16), true, false, + (config_miscopts&1)/*crc*/) == adts::SUCCESS + && decoder->Open(&file)) + { + // sync to stream + while (1) + { + switch (decoder->Sync(&file, g_samplebuf, sizeof(g_samplebuf), &g_samplebuf_used, &br)) + { + case adts::SUCCESS: + return 1; + case adts::FAILURE: + case adts::ENDOFFILE: + if (!killDecodeThread) + { + if (!lastfn_status_err) + { + EnterCriticalSection(&g_lfnscs); + WASABI_API_LNGSTRING_BUF(IDS_ERROR_SYNCING_TO_STREAM,lastfn_status,256); + LeaveCriticalSection(&g_lfnscs); + PostMessage(mod.hMainWindow, WM_USER, 0, IPC_UPDTITLE); + } + } + if (!killDecodeThread) Sleep(200); + if (!killDecodeThread) PostMessage(mod.hMainWindow, WM_WA_MPEG_EOF, 0, 0); + return 0; + case adts::NEEDMOREDATA: + if (!killDecodeThread && file.IsStream()) Sleep(25); + if (killDecodeThread) return 0; + } + } + } + + return 0; +} + +DWORD DecodeLoop::Loop() +{ + last_decode_pos_ms = 0; + + if (file.Open(lastfn, config_max_bufsize_k) != NErr_Success) + { + if (!killDecodeThread) Sleep(200); + if (!killDecodeThread) PostMessage(mod.hMainWindow, WM_WA_MPEG_EOF, 0, 0); + return 0; + } + + if (file.IsSeekable()) mod.is_seekable = 1; + + wchar_t *ext = PathFindExtension(lastfn); + if (!_wcsicmp(ext, L".aac") + || !_wcsicmp(ext, L".vlb") + || !_wcsicmp(ext, L".apl")) + { + if (file.IsStream()) + SetupStream(); + else + { + isAac = 1; + if (!_wcsicmp(ext, L".aac") || !_wcsicmp(ext, L".apl")) isEAAC = 1; + } + } + else if (file.IsStream()) + SetupStream(); + + if (OpenDecoder() == 0) + return 0; + + EnterCriticalSection(&streamInfoLock); + g_playing_file = &file; + if (file.uvox_3901) + { + PostMessage(mod.hMainWindow, WM_WA_IPC, (WPARAM) "0x3901", IPC_METADATA_CHANGED); + PostMessage(mod.hMainWindow, WM_WA_IPC, 0, IPC_UPDTITLE); + } + LeaveCriticalSection(&streamInfoLock); + + + EnterCriticalSection(&g_lfnscs); + lastfn_status[0] = 0; + LeaveCriticalSection(&g_lfnscs); + + lastfn_data_ready = 1; + +// TODO? if (decoder != &aacp) // hack because aac+ bitrate isn't accurate at this point + br = decoder->GetCurrentBitrate(); + + need_prebuffer = CalcPreBuffer(config_http_prebuffer, (int)br); + + if (((!(config_eqmode&4) && decoder->GetLayer() == 3) || + ((config_eqmode&8) && decoder->GetLayer() < 3))) + { + mod.UsesOutputPlug |= 2; + } + else + mod.UsesOutputPlug &= ~2; + + decoder->CalculateFrameSize(&g_framesize); + decoder->GetOutputParameters(&g_bits, &g_nch, &g_srate); + + if (!killDecodeThread && file.IsStream() == 1) + { + DWORD_PTR dw; + if (!killDecodeThread) SendMessageTimeout(mod.hMainWindow, WM_USER, 0, IPC_UPDTITLE, SMTO_BLOCK, 100, &dw); + if (!killDecodeThread) SendMessageTimeout(mod.hMainWindow, WM_TIMER, 38, 0, SMTO_BLOCK, 100, &dw); + } + + sampleFrameSize = g_nch * ((int)g_bits/8); + + if (config_gapless) + CalculateCodecDelay(); + + if (OpenOutput(g_nch, g_srate, (int)g_bits) == 0) + return 0; + + /* ----- send info to winamp and vis: bitrate, etc ----- */ + g_br = (int)decoder->GetCurrentBitrate(); + + g_br_add = g_br; + g_br_div = 1; + g_avg_vbr_br = file.GetAvgVBRBitrate(); + mod.SetInfo(g_br, g_srate / 1000, g_nch, 0); + + // initialize visualization stuff + mod.SAVSAInit((maxlatency << g_ds), g_srate); + mod.VSASetInfo(g_srate, g_nch); + /* ----- end send info to winamp and vis ----- */ + + if (file.IsSeekable() && g_br) + { + mod.is_seekable = 1; + if (!file.m_vbr_frames) g_length = MulDiv(file.GetContentLength(), 8, g_br); + else g_length = file.m_vbr_ms; + } + + if (file.IsStream()) + { + if (need_prebuffer < config_http_prebuffer / 2) + need_prebuffer = config_http_prebuffer / 2; + } + + while (!killDecodeThread) + { + if (seek_needed != -1) + Seek(seek_needed); + + if (need_prebuffer && file.IsStream() && maxlatency && !file.EndOf()) + PreBuffer(); + + int needsleep = 1; + + if (done == 2) // done was set to TRUE during decoding, signaling eof + { + mod.outMod->CanWrite(); // some output drivers need CanWrite + // to be called on a regular basis. + + if (!mod.outMod->IsPlaying()) + { + // we're done playing, so tell Winamp and quit the thread. + if (!killDecodeThread) PostMessage(mod.hMainWindow, WM_WA_MPEG_EOF, 0, 0); + done=3; + break; + } + } + else + { + int fs = (g_framesize * ((mod.dsp_isactive() == 1) ? 2 : 1)); + // TODO: we should really support partial writes, there's no gaurantee that CanWrite() will EVER get big enough + if (mod.outMod->CanWrite() >= fs && (!need_prebuffer || !file.IsStream() || !maxlatency)) + // CanWrite() returns the number of bytes you can write, so we check that + // to the block size. the reason we multiply the block size by two if + // mod.dsp_isactive() is that DSP plug-ins can change it by up to a + // factor of two (for tempo adjustment). + { + int p = mod.SAGetMode(); + g_vis_enabled = ((p & 1) || p == 4); + if (!g_vis_enabled) + { + int s, a; + mod.VSAGetMode(&s, &a); + if (s) g_vis_enabled = 1; + } + + Decode(); + + if ((g_samplebuf_used >= (size_t)g_framesize || (done && g_samplebuf_used > 0)) && seek_needed == -1) + { + // adjust decode position variable + if (file.isSeekReset()) + last_decode_pos_ms = decode_pos_ms = 0; + else + decode_pos_ms += ((int)g_samplebuf_used / sampleFrameSize * 1000) / g_srate; + + // if we have a DSP plug-in, then call it on our samples + if (mod.dsp_isactive()) + { + g_samplebuf_used = mod.dsp_dosamples((short *)g_samplebuf, (int)g_samplebuf_used / sampleFrameSize, (int)g_bits, g_nch, g_srate) * sampleFrameSize; + } + Viz(); + endCutter.Write((char *)g_samplebuf, (int)g_samplebuf_used); + g_samplebuf_used = 0; + needsleep = 0; + //memcpy(g_samplebuf,g_samplebuf+r,g_samplebuf_used); + } + if (done) + { + endCutter.Write(0, 0); + done = 2; + } + } + } + if (decode_pos_ms > last_decode_pos_ms + 1000) + { + last_decode_pos_ms = decode_pos_ms; + } + + if (needsleep) Sleep(10); + // if we can't write data, wait a little bit. Otherwise, continue + // through the loop writing more data (without sleeping) + } + + /* ---- change some globals to let everyone know we're done */ + EnterCriticalSection(&g_lfnscs); + lastfn_status[0] = 0; + LeaveCriticalSection(&g_lfnscs); + g_bufferstat = 0; + g_closeaudio = 1; + /* ---- */ + + return 0; +} + +DWORD WINAPI DecodeThread(LPVOID b) +{ + DecodeLoop loop; + + + + DWORD ret = loop.Loop(); + + EnterCriticalSection(&streamInfoLock); + g_playing_file = 0; + LeaveCriticalSection(&streamInfoLock); + return ret; +} + diff --git a/Src/Plugins/Input/in_mp3/DecodeThread.h b/Src/Plugins/Input/in_mp3/DecodeThread.h new file mode 100644 index 00000000..b3fc6735 --- /dev/null +++ b/Src/Plugins/Input/in_mp3/DecodeThread.h @@ -0,0 +1,19 @@ +#ifndef NULLSOFT_DECODETHREADH +#define NULLSOFT_DECODETHREADH + +#include <windows.h> + +DWORD WINAPI DecodeThread(LPVOID b); + +extern volatile int seek_needed; +extern CRITICAL_SECTION g_lfnscs; +extern int g_ds; +extern int g_sndopened; +extern int g_bufferstat; +extern int g_length; +extern volatile int g_closeaudio; +extern int decode_pos_ms; // current decoding position, in milliseconds. +extern int g_vis_enabled; + + +#endif
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mp3/ExtendedInfo.cpp b/Src/Plugins/Input/in_mp3/ExtendedInfo.cpp new file mode 100644 index 00000000..a3affdeb --- /dev/null +++ b/Src/Plugins/Input/in_mp3/ExtendedInfo.cpp @@ -0,0 +1,315 @@ +#include "main.h" +#include "Metadata.h" +#include "../Winamp/wa_ipc.h" +#include "../nu/ns_wc.h" +#include "uvox_3901.h" +#include "uvox_3902.h" +#include "Stopper.h" +#include <shlwapi.h> +#include "../Agave/Language/api_language.h" +#include <strsafe.h> + +extern CGioFile *g_playing_file; +static FILETIME ftLastWriteTime; + +// is used to determine if the last write time of the file has changed when +// asked to get the metadata for the same cached file so we can update things +BOOL HasFileTimeChanged(const wchar_t *fn) +{ + WIN32_FILE_ATTRIBUTE_DATA fileData = {0}; + if (GetFileAttributesExW(fn, GetFileExInfoStandard, &fileData) == TRUE) + { + if(CompareFileTime(&ftLastWriteTime, &fileData.ftLastWriteTime)) + { + ftLastWriteTime = fileData.ftLastWriteTime; + return TRUE; + } + } + return FALSE; +} + +void UpdateFileTimeChanged(const wchar_t *fn) +{ + WIN32_FILE_ATTRIBUTE_DATA fileData; + if (GetFileAttributesExW(fn, GetFileExInfoStandard, &fileData) == TRUE) + { + ftLastWriteTime = fileData.ftLastWriteTime; + } +} + +Metadata *m_ext_set_mp3info = NULL; +Metadata *m_ext_get_mp3info = NULL; +extern "C" __declspec(dllexport) +int winampGetExtendedFileInfoW(const wchar_t *fn, const char *data, wchar_t *dest, size_t destlen) +{ + if (!_stricmp(data, "type")) + { + dest[0] = '0'; + dest[1] = 0; + return 1; + } + if (!_stricmp(data, "rateable")) + { + dest[0] = '1'; + dest[1] = 0; + return 1; + } + else if (!_stricmp(data, "family")) + { + if (!fn || !fn[0]) return 0; + int len = lstrlenW(fn); + if (len < 4 || L'.' != fn[len - 4]) return 0; + const wchar_t *p = &fn[len - 3]; + if (!_wcsicmp(p, L"MP3")) { WASABI_API_LNGSTRINGW_BUF(IDS_FAMILY_STRING_MP3, dest, destlen); return 1; } + if (!_wcsicmp(p, L"MP2")) { WASABI_API_LNGSTRINGW_BUF(IDS_FAMILY_STRING_MP2, dest, destlen); return 1; } + if (!_wcsicmp(p, L"MP1")) { WASABI_API_LNGSTRINGW_BUF(IDS_FAMILY_STRING_MP1, dest, destlen); return 1; } + if (!_wcsicmp(p, L"AAC")) { WASABI_API_LNGSTRINGW_BUF(IDS_FAMILY_STRING_MPEG2_AAC, dest, destlen); return 1; } + if (!_wcsicmp(p, L"VLB")) { WASABI_API_LNGSTRINGW_BUF(IDS_FAMILY_STRING_DOLBY, dest, destlen); return 1; } + return 0; + } + else if (!_stricmp(data, "mime")) + { + if (!fn || !fn[0]) return 0; + int len = lstrlenW(fn); + if (len < 4 || L'.' != fn[len - 4]) return 0; + const wchar_t *p = &fn[len - 3]; + if (!_wcsicmp(p, L"MP3")) { StringCchCopyW(dest, destlen, L"audio/mpeg"); return 1; } + if (!_wcsicmp(p, L"MP2")) { StringCchCopyW(dest, destlen, L"audio/mpeg"); return 1; } + if (!_wcsicmp(p, L"MP1")) { StringCchCopyW(dest, destlen, L"audio/mpeg"); return 1; } + if (!_wcsicmp(p, L"AAC")) { StringCchCopyW(dest, destlen, L"audio/aac"); return 1; } + if (!_wcsicmp(p, L"VLB")) { StringCchCopyW(dest, destlen, L"audio/vlb"); return 1; } + return 0; + } + else if (!_strnicmp(data, "uvox/", 5)) + { + EnterCriticalSection(&streamInfoLock); + if (g_playing_file) + { + if (g_playing_file->uvox_3901) // check again now that we've acquired the lock + { + Ultravox3901 uvox_metadata; + if (uvox_metadata.Parse(g_playing_file->uvox_3901) != API_XML_FAILURE) + { + LeaveCriticalSection(&streamInfoLock); + return uvox_metadata.GetExtendedData(data, dest, (int)destlen); + } + } + else if (g_playing_file->uvox_3902) + { + Ultravox3902 uvox_metadata; + if (uvox_metadata.Parse(g_playing_file->uvox_3902) != API_XML_FAILURE) + { + LeaveCriticalSection(&streamInfoLock); + return uvox_metadata.GetExtendedData(data, dest, (int)destlen); + } + } + } + LeaveCriticalSection(&streamInfoLock); + return 0; + } + else if (_stricmp(data, "0x3901") == 0) + { + EnterCriticalSection(&streamInfoLock); + if (g_playing_file && g_playing_file->uvox_3901) // check again now that we've acquired the lock + { + if (dest == NULL) // It's empty, he's looking for the size of the 0x3901 + { + int size = MultiByteToWideChar(CP_UTF8, 0, g_playing_file->uvox_3901, -1, 0, 0); + LeaveCriticalSection(&streamInfoLock); + return size; + } + else + { + MultiByteToWideCharSZ(CP_UTF8, 0, g_playing_file->uvox_3901, -1, dest, (int)destlen); + LeaveCriticalSection(&streamInfoLock); + return 1; + } + } + LeaveCriticalSection(&streamInfoLock); + return 0; + } + else if (!_stricmp(data, "streamtype")) + { + if (lstrcmpW(lastfn, fn)) + return 0; + + if (g_playing_file) + { + EnterCriticalSection(&streamInfoLock); + if (g_playing_file) // check again now that we've acquired the lock + { + StringCchPrintfW(dest, destlen, L"%d", g_playing_file->IsStream()); + LeaveCriticalSection(&streamInfoLock); + return 1; + } + LeaveCriticalSection(&streamInfoLock); + } + + return 0; + } + else if (!_stricmp(data, "streammetadata")) + { + if (lstrcmpW(lastfn, fn)) + return 0; + + if (g_playing_file) + { + uint32_t len=0; + EnterCriticalSection(&streamInfoLock); + if (g_playing_file && g_playing_file->GetID3v2(&len) && len > 0) // check again now that we've acquired the lock + { + lstrcpynW(dest, L"1", (int)destlen); + LeaveCriticalSection(&streamInfoLock); + return 1; // always return 1 to ensure we can do title lookups + } + LeaveCriticalSection(&streamInfoLock); + } + return 0; + } + else if (!_stricmp(data, "streamtitle")) + { + EnterCriticalSection(&streamInfoLock); + if (g_playing_file) // check again now that we've acquired the lock + ConvertTryUTF8(g_playing_file->stream_current_title, dest, destlen); + else + dest[0]=0; + LeaveCriticalSection(&streamInfoLock); + return 1; + } + else if (!_stricmp(data, "streamname")) + { + EnterCriticalSection(&streamInfoLock); + if (g_playing_file) // check again now that we've acquired the lock + ConvertTryUTF8(g_playing_file->stream_name, dest, destlen); + else + dest[0]=0; + LeaveCriticalSection(&streamInfoLock); + return 1; + } + else if (!_stricmp(data, "streamurl")) + { + EnterCriticalSection(&streamInfoLock); + if (g_playing_file) // check again now that we've acquired the lock + ConvertTryUTF8(g_playing_file->stream_url, dest, destlen); + else + dest[0]=0; + LeaveCriticalSection(&streamInfoLock); + return 1; + } + else if (!_stricmp(data, "streamgenre")) + { + EnterCriticalSection(&streamInfoLock); + if (g_playing_file) // check again now that we've acquired the lock + ConvertTryUTF8(g_playing_file->stream_genre, dest, destlen); + else + dest[0]=0; + LeaveCriticalSection(&streamInfoLock); + return 1; + } + else if (!_stricmp(data, "streaminformation")) + { + EnterCriticalSection(&streamInfoLock); + if (g_playing_file) + g_playing_file->GetStreamInfo(dest, destlen); + else + dest[0]=0; + LeaveCriticalSection(&streamInfoLock); + return 1; + } + + if (!fn || !fn[0]) return 0; + + if (!_wcsnicmp(fn, L"uvox://", 7)) + return 0; + + if (g_playing_file && PathIsURL(fn) && !lstrcmpW(lastfn, fn)) + { + EnterCriticalSection(&streamInfoLock); + uint32_t len = 0; + if (g_playing_file && g_playing_file->GetID3v2(&len) && len > 0) // check again now that we've acquired the lock + { + Metadata meta(g_playing_file, fn); + int ret = meta.GetExtendedData(data, dest, (int)destlen); + LeaveCriticalSection(&streamInfoLock); + return ret; + } + LeaveCriticalSection(&streamInfoLock); + } + + if (PathIsURL(fn)) + return 0; + + if (m_ext_get_mp3info && (!m_ext_get_mp3info->IsMe(fn) || HasFileTimeChanged(fn))) + { + m_ext_get_mp3info->Release(); + m_ext_get_mp3info=0; + } + + if (!m_ext_get_mp3info) + { + m_ext_get_mp3info = new Metadata; + m_ext_get_mp3info->Open(fn); + } + + return m_ext_get_mp3info->GetExtendedData(data, dest, (int)destlen); +} + +extern "C" + __declspec(dllexport) int winampSetExtendedFileInfoW(const wchar_t *fn, const char *data, const wchar_t *val) +{ + if (!m_ext_set_mp3info || (m_ext_set_mp3info && !m_ext_set_mp3info->IsMe(fn))) + { + if(m_ext_set_mp3info) m_ext_set_mp3info->Release(); + m_ext_set_mp3info = new Metadata; + m_ext_set_mp3info->Open(fn); + } + return m_ext_set_mp3info->SetExtendedData(data, val); +} + +extern "C" + __declspec(dllexport) int winampWriteExtendedFileInfo() +{ + // flush our read cache too :) + if(m_ext_get_mp3info) m_ext_get_mp3info->Release(); + m_ext_get_mp3info = NULL; + + if (!m_ext_set_mp3info) return 0; + + Stopper stopper; + if (m_ext_set_mp3info->IsMe(lastfn)) + stopper.Stop(); + + // just in-case something changed + if (!m_ext_set_mp3info) return 0; + + int ret = m_ext_set_mp3info->Save(); + stopper.Play(); + m_ext_set_mp3info->Release(); + m_ext_set_mp3info = NULL; + + // update last modified so we're not re-queried on our own updates + UpdateFileTimeChanged(lastfn); + + return !ret; +} + +extern "C" __declspec(dllexport) + int winampClearExtendedFileInfoW(const wchar_t *fn) +{ + Metadata meta; + if (meta.Open(fn)==METADATA_SUCCESS) + { + meta.id3v2.Clear(); + Stopper stopper; + if (meta.IsMe(lastfn)) + stopper.Stop(); + meta.Save(); + stopper.Play(); + + // update last modified so we're not re-queried on our own updates + UpdateFileTimeChanged(fn); + + return 1; + } + return 0; +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mp3/ExtendedRead.cpp b/Src/Plugins/Input/in_mp3/ExtendedRead.cpp new file mode 100644 index 00000000..7196aad9 --- /dev/null +++ b/Src/Plugins/Input/in_mp3/ExtendedRead.cpp @@ -0,0 +1,323 @@ +#include "main.h" +#include "adts.h" +#include <memory.h> +#include <malloc.h> +#include <xutility> +#include <assert.h> +#include <shlwapi.h> +#include <foundation/error.h> +#include "../nu/RingBuffer.h" +#include <api/service/waservicefactory.h> + +// {19450308-90D7-4E45-8A9D-DC71E67123E2} +static const GUID adts_aac_guid = +{ 0x19450308, 0x90d7, 0x4e45, { 0x8a, 0x9d, 0xdc, 0x71, 0xe6, 0x71, 0x23, 0xe2 } }; + +// {4192FE3F-E843-445c-8D62-51BE5EE5E68C} +static const GUID adts_mp2_guid = +{ 0x4192fe3f, 0xe843, 0x445c, { 0x8d, 0x62, 0x51, 0xbe, 0x5e, 0xe5, 0xe6, 0x8c } }; + +class GapCutter +{ +public: + GapCutter() {} + + void SetEndSize( int postSize ); + void SetSize( int preSize, int postSize ); + void Flush( int time_in_ms ); + int Write( void *dest, void *input, size_t inputBytes ); + +private: + RingBuffer ringBuffer; + + int preCut = 0; + int preCutSize = 0; +}; + +void GapCutter::SetEndSize(int postSize) +{ + if (postSize < 0) + postSize = 0; + + if (postSize) + { + ringBuffer.Reset(); + ringBuffer.reserve(postSize); + } +} + +void GapCutter::SetSize( int preSize, int postSize ) +{ + if ( preSize < 0 ) + preSize = 0; + + if ( postSize < 0 ) + postSize = 0; + + SetEndSize( postSize ); + + preCutSize = preSize; + preCut = preSize; +} + +void GapCutter::Flush( int time_in_ms ) +{ + // if (time_in_ms == 0) // TODO: calculate actual delay if we seek within the encoder delay area + preCut = preCutSize; // reset precut size if we seek to the start + + ringBuffer.clear(); +} + +int GapCutter::Write( void *dest, void *input, size_t inputBytes ) // returns # of bytes written +{ + int bytesWritten = 0; + unsigned __int8 *in = (unsigned __int8 *)input; + unsigned __int8 *out = (unsigned __int8 *)dest; + // cut pre samples, if necessary + + intptr_t pre = min( preCut, (intptr_t)inputBytes ); + in += pre; + inputBytes -= pre; + preCut -= (int)pre; + + if ( !inputBytes ) + return bytesWritten; + + size_t remainingFill = ringBuffer.avail(); + intptr_t fillWrite = min( (intptr_t)( inputBytes - remainingFill ), (intptr_t)ringBuffer.size() ); // only write fill buffer if we've got enough left to fill it up + + if ( fillWrite > 0 ) + { + size_t written = ringBuffer.read( out, fillWrite ); + + bytesWritten += (int)written; + out += written; + } + + remainingFill = ringBuffer.avail(); + + int outWrite = (int)max( 0, (intptr_t)( inputBytes - remainingFill ) ); + if ( outWrite ) + memcpy( out, in, outWrite ); + + bytesWritten += outWrite; + in += outWrite; + inputBytes -= outWrite; + + if ( inputBytes ) + ringBuffer.write( in, inputBytes ); + + return bytesWritten; +} + + +struct ExtendedRead +{ + ExtendedRead() { memset(&data, 0, sizeof(data)); } + ~ExtendedRead() + { + file.Close(); + if ( decoder ) + { + decoder->Close(); + decoder->Release(); + } + } + + bool Open( const wchar_t *fn, int *size, int *bps, int *nch, int *srate, bool useFloat ); + + adts *decoder = NULL; + int bits = 0; + size_t initialData = 0; + int frameSize = 0; + + GapCutter cutter; + CGioFile file; + +#define DATA_SIZE (6*4*2*2*1152) + unsigned char data[DATA_SIZE]; +}; + +bool ExtendedRead::Open(const wchar_t *fn, int *size, int *bps, int *nch, int *srate, bool useFloat) +{ + if (file.Open(fn, config_max_bufsize_k) != NErr_Success) + return false; + + int downmix = 0; + bool allowsurround = 1; + if (*nch == 1) + { + downmix = 1; + allowsurround = 0; + } + else if (*nch == 2) + { + allowsurround = 0; + } + + if (useFloat) + bits=32; + else if (*bps == 24) + bits = 24; + else + { + bits = 16; + *bps = 16; + } + + wchar_t *ext = PathFindExtensionW(fn); + if (!_wcsicmp(ext, L".vlb")) + { + return false; + } + else if (!_wcsicmp(ext, L".aac") || !_wcsicmp(ext, L".apl")) + { + waServiceFactory *factory = mod.service->service_getServiceByGuid(adts_aac_guid); + if (factory) + decoder = (adts *)factory->getInterface(); + } + else + { + waServiceFactory *factory = mod.service->service_getServiceByGuid(adts_mp2_guid); + if (factory) + decoder = (adts *)factory->getInterface(); + } + + if (!decoder) + return false; + + decoder->Initialize(!!downmix, 0, allowsurround, bits, false, useFloat); + decoder->Open(&file); + size_t bitrate; + bool done=false; + while (!done) + { + switch (decoder->Sync(&file, data, sizeof(data), &initialData, &bitrate)) + { + case adts::SUCCESS: + done=true; + break; + case adts::FAILURE: + case adts::ENDOFFILE: + return false; + case adts::NEEDMOREDATA: + break; + } + } + + size_t numBits = 0; + decoder->GetOutputParameters(&numBits, nch, srate); + *bps = bits = (int)numBits; + frameSize = bits / 8 * *nch; + if (config_gapless) + cutter.SetSize((file.prepad + (int)decoder->GetDecoderDelay())*frameSize, (file.postpad - (int)decoder->GetDecoderDelay())*frameSize); + + if (file.m_vbr_samples) // exact number of samples in the LAME header, how nice :) + *size = (int)file.m_vbr_samples*frameSize; + else if (file.m_vbr_ms) // if we know the milliseconds accurately + *size = MulDiv(*srate * frameSize, file.m_vbr_ms, 1000); // our size should be mostly accurate + else // no helpful info to go on + { + // just guess based on bitrate and content length + bitrate=decoder->GetCurrentBitrate(); + int len_ms = MulDiv(file.GetContentLength(), 8, (int)bitrate); + *size = MulDiv(*srate * frameSize, len_ms, 1000); + } + + return true; +} + +extern "C" +{ + //returns handle!=0 if successful, 0 if error + //size will return the final nb of bytes written to the output, -1 if unknown + __declspec(dllexport) intptr_t winampGetExtendedRead_openW(const wchar_t *fn, int *size, int *bps, int *nch, int *srate) + { + ExtendedRead *ext = new ExtendedRead; + if (ext) + { + if (ext->Open(fn, size, bps, nch, srate, false)) + return reinterpret_cast<intptr_t>(ext); + delete ext; + } + return 0; + } + + __declspec(dllexport) intptr_t winampGetExtendedRead_openW_float(const wchar_t *fn, int *size, int *bps, int *nch, int *srate) + { + ExtendedRead *ext = new ExtendedRead; + if (ext) + { + if (ext->Open(fn, size, bps, nch, srate, true)) + return reinterpret_cast<intptr_t>(ext); + delete ext; + } + return 0; + } + + //returns nb of bytes read. -1 if read error (like CD ejected). if (ret==0), EOF is assumed + __declspec(dllexport) size_t winampGetExtendedRead_getData(intptr_t handle, char *dest, size_t len, int *killswitch) + { + ExtendedRead *ext = (ExtendedRead *)handle; + int copied = 0; + if (ext) + { + len -= (len % ext->frameSize); // only do whole frames + while (len) + { + size_t toMove = min(len, ext->initialData); + int toCopy = ext->cutter.Write(dest, ext->data, toMove); + + if (ext->initialData != toMove) + memmove(ext->data, ext->data + toMove, ext->initialData - toMove); + + ext->initialData -= toMove; + len -= toCopy; + copied += toCopy; + dest += toCopy; + + if (!ext->initialData) + { + size_t written = 0, bitrate, endCut = 0; + int ret = ext->decoder->Decode(&ext->file, ext->data, DATA_SIZE, &written, &bitrate, &endCut); + if (config_gapless && endCut) + ext->cutter.SetEndSize((int)(endCut - ext->decoder->GetDecoderDelay())*ext->frameSize); + ext->initialData = written; + if (/*ret != adts::SUCCESS && */!ext->initialData && (copied || ret == adts::ENDOFFILE)) + return copied; + + if (ret == adts::FAILURE) + return -1; + } + } + } + return copied; + } + + // return nonzero on success, zero on failure. + __declspec(dllexport) int winampGetExtendedRead_setTime(intptr_t handle, int millisecs) + { + ExtendedRead *ext = (ExtendedRead *)handle; + if (ext) + { + if (!ext->file.IsSeekable()) return 0; // not seekable + + int br = ext->file.GetAvgVBRBitrate(); + if (!br) br = (int)ext->decoder->GetCurrentBitrate(); + if (!br) return 0; // can't find a valid bitrate + + ext->cutter.Flush(millisecs); // fucko? + ext->decoder->Flush(&ext->file); + + ext->file.Seek(millisecs,br); + return 1; + } + return 0; + } + + __declspec(dllexport) void winampGetExtendedRead_close(intptr_t handle) + { + ExtendedRead *ext = (ExtendedRead *)handle; + if (ext) delete ext; + } +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mp3/FactoryHelper.h b/Src/Plugins/Input/in_mp3/FactoryHelper.h new file mode 100644 index 00000000..afbda372 --- /dev/null +++ b/Src/Plugins/Input/in_mp3/FactoryHelper.h @@ -0,0 +1,25 @@ +#include "api__in_mp3.h" +#include <api/service/waservicefactory.h> + +template <class api_T> +void ServiceBuild(api_T *&api_t, GUID factoryGUID_t) +{ + if (mod.service) + { + waServiceFactory *factory = mod.service->service_getServiceByGuid(factoryGUID_t); + if (factory) + api_t = reinterpret_cast<api_T *>( factory->getInterface() ); + } +} + +template <class api_T> +void ServiceRelease(api_T *api_t, GUID factoryGUID_t) +{ + if (mod.service && api_t) + { + waServiceFactory *factory = mod.service->service_getServiceByGuid(factoryGUID_t); + if (factory) + factory->releaseInterface(api_t); + } + api_t = NULL; +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mp3/ID3Info.cpp b/Src/Plugins/Input/in_mp3/ID3Info.cpp new file mode 100644 index 00000000..99933dea --- /dev/null +++ b/Src/Plugins/Input/in_mp3/ID3Info.cpp @@ -0,0 +1,10 @@ +#include "main.h" +#include "MP3Info.h" +#include "../nu/AutoWide.h" +#include "id3.h" +#include "api.h" +#include <math.h> +#include "config.h" +#include "LAMEinfo.h" +#include <strsafe.h> + diff --git a/Src/Plugins/Input/in_mp3/ID3v1.cpp b/Src/Plugins/Input/in_mp3/ID3v1.cpp new file mode 100644 index 00000000..5fe25106 --- /dev/null +++ b/Src/Plugins/Input/in_mp3/ID3v1.cpp @@ -0,0 +1,211 @@ +#include "ID3v1.h" +#include "../nu/ns_wc.h" +#include <windows.h> +#include "config.h" +#include <strsafe.h> + +const wchar_t *id3v1_genres[] = +{ + L"Blues", L"Classic Rock", L"Country", L"Dance", L"Disco", L"Funk", L"Grunge", + L"Hip-Hop", L"Jazz", L"Metal", L"New Age", L"Oldies", L"Other", L"Pop", L"R&B", + L"Rap", L"Reggae", L"Rock", L"Techno", L"Industrial", L"Alternative", L"Ska", + L"Death Metal", L"Pranks", L"Soundtrack", L"Euro-Techno", L"Ambient", L"Trip-Hop", + L"Vocal", L"Jazz+Funk", L"Fusion", L"Trance", L"Classical", L"Instrumental", + L"Acid", L"House", L"Game", L"Sound Clip", L"Gospel", L"Noise", L"Alt Rock", + L"Bass", L"Soul", L"Punk", L"Space", L"Meditative", L"Instrumental Pop", + L"Instrumental Rock", L"Ethnic", L"Gothic", L"Darkwave", L"Techno-Industrial", + L"Electronic", L"Pop-Folk", L"Eurodance", L"Dream", L"Southern Rock", L"Comedy", + L"Cult", L"Gangsta Rap", L"Top 40", L"Christian Rap", L"Pop/Funk", L"Jungle", + L"Native American", L"Cabaret", L"New Wave", L"Psychedelic", L"Rave", L"Showtunes", + L"Trailer", L"Lo-Fi", L"Tribal", L"Acid Punk", L"Acid Jazz", L"Polka", L"Retro", + L"Musical", L"Rock & Roll", L"Hard Rock", L"Folk", L"Folk-Rock", L"National Folk", + L"Swing", L"Fast-Fusion", L"Bebop", L"Latin", L"Revival", L"Celtic", L"Bluegrass", + L"Avantgarde", L"Gothic Rock", L"Progressive Rock", L"Psychedelic Rock", + L"Symphonic Rock", L"Slow Rock", L"Big Band", L"Chorus", L"Easy Listening", + L"Acoustic", L"Humour", L"Speech", L"Chanson", L"Opera", L"Chamber Music", L"Sonata", + L"Symphony", L"Booty Bass", L"Primus", L"Porn Groove", L"Satire", L"Slow Jam", + L"Club", L"Tango", L"Samba", L"Folklore", L"Ballad", L"Power Ballad", L"Rhythmic Soul", + L"Freestyle", L"Duet", L"Punk Rock", L"Drum Solo", L"A Cappella", L"Euro-House", + L"Dance Hall", L"Goa", L"Drum & Bass", L"Club-House", L"Hardcore", L"Terror", L"Indie", + L"BritPop", L"Afro-Punk", L"Polsk Punk", L"Beat", L"Christian Gangsta Rap", + L"Heavy Metal", L"Black Metal", L"Crossover", L"Contemporary Christian", + L"Christian Rock", L"Merengue", L"Salsa", L"Thrash Metal", L"Anime", L"JPop", + L"Synthpop", L"Abstract", L"Art Rock", L"Baroque", L"Bhangra", L"Big Beat", + L"Breakbeat", L"Chillout", L"Downtempo", L"Dub", L"EBM", L"Eclectic", L"Electro", + L"Electroclash", L"Emo", L"Experimental", L"Garage", L"Global", L"IDM", L"Illbient", + L"Industro-Goth", L"Jam Band", L"Krautrock", L"Leftfield", L"Lounge", L"Math Rock", + L"New Romantic", L"Nu-Breakz", L"Post-Punk", L"Post-Rock", L"Psytrance", L"Shoegaze", + L"Space Rock", L"Trop Rock", L"World Music", L"Neoclassical", L"Audiobook", + L"Audio Theatre", L"Neue Deutsche Welle", L"Podcast", L"Indie Rock", L"G-Funk", + L"Dubstep", L"Garage Rock", L"Psybient", L"Glam Rock", L"Dream Pop", L"Merseybeat", + L"K-Pop", L"Chiptune", L"Grime", L"Grindcore", L"Indietronic", L"Indietronica", + L"Jazz Rock", L"Jazz Fusion", L"Post-Punk Revival", L"Electronica", L"Psychill", + L"Ethnotronic", L"Americana", L"Ambient Dub", L"Digital Dub", L"Chillwave", L"Stoner Rock", + L"Slowcore", L"Softcore", L"Flamenco", L"Hi-NRG", L"Ethereal", L"Drone", L"Doom Metal", + L"Doom Jazz", L"Mainstream", L"Glitch", L"Balearic", L"Modern Classical", L"Mod", + L"Contemporary Classical", L"Psybreaks", L"Psystep", L"Psydub", L"Chillstep", L"Berlin School", + L"Future Jazz", L"Djent", L"Musique Concrète", L"Electroacoustic", L"Folktronica", L"Texas Country", L"Red Dirt", + L"Arabic", L"Asian", L"Bachata", L"Bollywood", L"Cajun", L"Calypso", L"Creole", L"Darkstep", L"Jewish", L"Reggaeton", L"Smooth Jazz", + L"Soca", L"Spiritual", L"Turntablism", L"Zouk", L"Neofolk", L"Nu Jazz", +}; + +size_t numGenres = sizeof(id3v1_genres)/sizeof(*id3v1_genres); + +ID3v1::ID3v1() +{ + title[0]=0; + artist[0]=0; + album[0]=0; + comment[0]=0; + year[0]=0; + genre=255; + track=0; + hasData=false; + dirty=false; + title[30]=0; + artist[30]=0; + album[30]=0; + comment[30]=0; + year[4]=0; +} + +int ID3v1::Decode(const void *data) +{ + const char *fbuf = (const char *)data; + ptrdiff_t x; + + hasData = false; + title[0] = artist[0] = album[0] = year[0] = comment[0] = 0; + genre = 255; track = 0; + + if (memcmp(fbuf, "TAG", 3)) + { + return 1; + } + memcpy(title, fbuf + 3, 30); x = 29; while (x >= 0 && title[x] == ' ') x--; title[x + 1] = 0; + memcpy(artist, fbuf + 33, 30); x = 29; while (x >= 0 && artist[x] == ' ') x--; artist[x + 1] = 0; + memcpy(album, fbuf + 63, 30); x = 29; while (x >= 0 && album[x] == ' ') x--; album[x + 1] = 0; + memcpy(year, fbuf + 93, 4); x = 3; while (x >= 0 && year[x] == ' ') x--; year[x + 1] = 0; + memcpy(comment, fbuf + 97, 30); x = 29; while (x >= 0 && comment[x] == ' ') x--; comment[x + 1] = 0; + if (fbuf[97 + 28] == 0 && fbuf[97 + 28 + 1] != 0) track = fbuf[97 + 28 + 1]; + genre = ((unsigned char *)fbuf)[127]; + hasData = 1; + return 0; +} + +int ID3v1::Encode(void *data) +{ + if (!hasData) + return 1; + char *fbuf = (char *)data; + size_t x; + fbuf[0] = 'T';fbuf[1] = 'A';fbuf[2] = 'G'; + if (title) strncpy(fbuf + 3, title, 30); for (x = 3 + strlen(title); x < 33; x ++) fbuf[x] = 0; + if (artist) strncpy(fbuf + 33, artist, 30); for (x = 33 + strlen(artist); x < 63; x ++) fbuf[x] = 0; + if (album) strncpy(fbuf + 63, album, 30); for (x = 63 + strlen(album); x < 93; x ++) fbuf[x] = 0; + if (year) strncpy(fbuf + 93, year, 4); for (x = 93 + strlen(year); x < 97; x ++) fbuf[x] = 0; + if (comment) strncpy(fbuf + 97, comment, 30); for (x = 97 + strlen(comment); x < 127; x ++) fbuf[x] = 0; + if (track) + { + fbuf[97 + 28] = 0; + fbuf[97 + 28 + 1] = track; + } + ((unsigned char *)fbuf)[127] = genre; + return 0; +} + +#define ID3V1_CODEPAGE ((config_read_mode==READ_LOCAL)?CP_ACP:28591) +int ID3v1::GetString(const char *tag, wchar_t *data, int dataLen) +{ + if (!hasData) + return 0; + + if (!_stricmp(tag, "title")) + { + MultiByteToWideCharSZ(ID3V1_CODEPAGE, 0, title, -1, data, dataLen); + return 1; + } + else if (!_stricmp(tag, "artist")) + { + MultiByteToWideCharSZ(ID3V1_CODEPAGE, 0, artist, -1, data, dataLen); + return 1; + } + else if (!_stricmp(tag, "album")) + { + MultiByteToWideCharSZ(ID3V1_CODEPAGE, 0, album, -1, data, dataLen); + return 1; + } + else if (!_stricmp(tag, "comment")) + { + MultiByteToWideCharSZ(ID3V1_CODEPAGE, 0, comment, -1, data, dataLen); + return 1; + } + else if (!_stricmp(tag, "year")) + { + MultiByteToWideCharSZ(ID3V1_CODEPAGE, 0, year, -1, data, dataLen); + return 1; + } + else if (!_stricmp(tag, "genre")) + { + if (genre >= numGenres) return -1; + StringCchCopyW(data, dataLen, id3v1_genres[genre]); + return 1; + } + else if (!_stricmp(tag, "track")) + { + if (track == 0) return -1; + StringCchPrintfW(data, dataLen, L"%u", track); + return 1; + } + else + return 0; +} + +int ID3v1::SetString(const char *tag, const wchar_t *data) +{ + if (!_stricmp(tag, "title")) + WideCharToMultiByteSZ(ID3V1_CODEPAGE, 0, data, -1, title, 31, 0 ,0); + else if (!_stricmp(tag, "artist")) + WideCharToMultiByteSZ(ID3V1_CODEPAGE, 0, data, -1, artist, 31, 0 ,0); + else if (!_stricmp(tag, "album")) + WideCharToMultiByteSZ(ID3V1_CODEPAGE, 0, data, -1, album, 31, 0 ,0); + else if (!_stricmp(tag, "comment")) + WideCharToMultiByteSZ(ID3V1_CODEPAGE, 0, data, -1, comment, 31, 0 ,0); + else if (!_stricmp(tag, "year")) + WideCharToMultiByteSZ(ID3V1_CODEPAGE, 0, data, -1, year, 5, 0 ,0); + else if (!_stricmp(tag, "genre")) + { + genre=255; + if (data) + { + for (size_t i=0;i<numGenres;i++) + { + if (!_wcsicmp(id3v1_genres[i], data)) + { + genre= (unsigned char)i; + } + } + } + } + else if (!_stricmp(tag, "track")) + { + int t = _wtoi(data); + if(t > 255) track = 0; + else track = t; + } + else + return 0; + + dirty=true; + hasData = 1; + return 1; +} + +void ID3v1::Clear() +{ + hasData=false; + dirty=true; + //clear data + title[0]=artist[0]=album[0]=comment[0]=year[0]=0; + genre = track = 0; +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mp3/ID3v1.h b/Src/Plugins/Input/in_mp3/ID3v1.h new file mode 100644 index 00000000..8763cd01 --- /dev/null +++ b/Src/Plugins/Input/in_mp3/ID3v1.h @@ -0,0 +1,29 @@ +#ifndef NULLSOFT_IN_MP3_ID3V1_H +#define NULLSOFT_IN_MP3_ID3V1_H + +#include <bfc/platform/types.h> +class ID3v1 +{ +public: + ID3v1(); + int Decode(const void *data); + // return -1 for empty, 1 for OK, 0 for "don't understand tag name" + int GetString(const char *tag, wchar_t *data, int dataLen); + // returns 1 for OK, 0 for "don't understand tag name" + int SetString(const char *tag, const wchar_t *data); + int Encode(void *data); + bool IsDirty() { return dirty; } + bool HasData() { return hasData; } + void Clear(); + +private: + char title[31],artist[31],album[31],comment[31]; + char year[5]; + unsigned char genre; + unsigned char track; + + bool hasData; + bool dirty; +}; + +#endif
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mp3/ID3v2.cpp b/Src/Plugins/Input/in_mp3/ID3v2.cpp new file mode 100644 index 00000000..04600232 --- /dev/null +++ b/Src/Plugins/Input/in_mp3/ID3v2.cpp @@ -0,0 +1,654 @@ +#include "ID3v2.h" +#include "id3.h" +#include "config.h" +#include "api__in_mp3.h" +#include "../Agave/AlbumArt/svc_albumArtProvider.h" +#include "../nu/AutoChar.h" +#include "../nu/AutoWide.h" +#include <strsafe.h> + +static inline const wchar_t *IncSafe(const wchar_t *val, int x) +{ + while (x--) + { + if (val && *val) + val++; + } + return val; +} + +extern const wchar_t *id3v1_genres[]; +extern size_t numGenres; + +ID3v2::ID3v2() +{ + hasData=false; + dirty=false; +} + +int ID3v2::Decode(const void *data, size_t len) +{ + if (!config_parse_id3v2 || !data) + { + hasData=false; + return 0; + } + + id3v2.Parse((uchar *)data, (uchar *)data+ID3_TAGHEADERSIZE); + if (id3v2.NumFrames() > 0) + { + hasData=true; + return 0; + } + else + return 1; +} + +// return -1 for empty, 1 for OK, 0 for "don't understand tag name" +int ID3v2::GetString(const char *tag, wchar_t *data, int dataLen) +{ + if (!_stricmp(tag, "title")) + return ID3_GetTagText(&id3v2, ID3FID_TITLE, data, dataLen)?1:-1; + else if (!_stricmp(tag, "album")) + return ID3_GetTagText(&id3v2, ID3FID_ALBUM, data, dataLen)?1:-1; + else if (!_stricmp(tag, "artist")) + return ID3_GetTagText(&id3v2, ID3FID_LEADARTIST, data, dataLen)?1:-1; + else if (!_stricmp(tag, "albumartist")) + { + if (!ID3_GetTagText(&id3v2, ID3FID_BAND, data, dataLen) && !ID3_GetUserText(&id3v2, L"ALBUM ARTIST", data, dataLen) && !ID3_GetUserText(&id3v2, L"ALBUMARTIST", data, dataLen)) + return ID3_GetUserText(&id3v2, L"Band", data, dataLen)?1:-1; + else + return 1; + } + else if (!_stricmp(tag, "comment")) + return ID3_GetComment(&id3v2, data, dataLen)?1:-1; + else if (!_stricmp(tag, "year")) + { + if (!ID3_GetTagText(&id3v2, ID3FID_RECORDINGTIME, data, dataLen)) + return ID3_GetTagText(&id3v2, ID3FID_YEAR, data, dataLen)?1:-1; + else + return 1; + } + else if (!_stricmp(tag, "composer")) + return ID3_GetTagText(&id3v2, ID3FID_COMPOSER, data, dataLen)?1:-1; + else if (!_stricmp(tag, "replaygain_track_gain")) + return ID3_GetUserText(&id3v2, L"replaygain_track_gain", data, dataLen)?1:-1; + else if (!_stricmp(tag, "replaygain_album_gain")) + return ID3_GetUserText(&id3v2, L"replaygain_album_gain", data, dataLen)?1:-1; + else if (!_stricmp(tag, "replaygain_track_peak")) + return ID3_GetUserText(&id3v2, L"replaygain_track_peak", data, dataLen)?1:-1; + else if (!_stricmp(tag, "replaygain_album_peak")) + return ID3_GetUserText(&id3v2, L"replaygain_album_peak", data, dataLen)?1:-1; + else if (!_stricmp(tag, "genre")) + { + data[0] = 0; + if (ID3_GetTagText(&id3v2, ID3FID_CONTENTTYPE, data, dataLen)) + { + wchar_t *tmp = data; + while (tmp && *tmp == ' ') tmp++; + if (tmp && (*tmp == '(' || (*tmp >= '0' && *tmp <= '9'))) // both (%d) and %d forms + { + int noparam = 0; + if (*tmp == '(') tmp++; + else noparam = 1; + size_t genre_index = _wtoi(tmp); + int cnt = 0; + while (tmp && *tmp >= '0' && *tmp <= '9') cnt++, tmp++; + while (tmp && *tmp == ' ') tmp++; + + if (tmp && (((!*tmp && noparam) || (!noparam && *tmp == ')')) && cnt > 0)) + { + if (genre_index < numGenres) + StringCchCopyW(data, dataLen, id3v1_genres[genre_index]); + } + } + return 1; + } + return -1; + } + else if (!_stricmp(tag, "track")) + return ID3_GetTagText(&id3v2, ID3FID_TRACKNUM, data, dataLen)?1:-1; + else if (!_stricmp(tag, "disc")) + return ID3_GetTagText(&id3v2, ID3FID_PARTINSET, data, dataLen)?1:-1; + else if (!_stricmp(tag, "bpm")) + return ID3_GetTagText(&id3v2, ID3FID_BPM, data, dataLen)?1:-1; + else if (!_stricmp(tag, "rating")) + return ID3_GetRating(&id3v2, data, dataLen)?1:-1; + else if (!_stricmp(tag, "conductor")) + return ID3_GetTagText(&id3v2, ID3FID_CONDUCTOR, data, dataLen)?1:-1; + else if (!_stricmp(tag, "key")) + return ID3_GetTagText(&id3v2, ID3FID_KEY, data, dataLen)?1:-1; + else if (!_stricmp(tag, "mood")) + return ID3_GetTagText(&id3v2, ID3FID_MOOD, data, dataLen)?1:-1; + else if (!_stricmp(tag, "subtitle")) + return ID3_GetTagText(&id3v2, ID3FID_SUBTITLE, data, dataLen)?1:-1; + else if (!_stricmp(tag, "lyricist")) + return ID3_GetTagText(&id3v2, ID3FID_LYRICIST, data, dataLen)?1:-1; + else if (!_stricmp(tag, "ISRC")) + return ID3_GetTagText(&id3v2, ID3FID_ISRC, data, dataLen)?1:-1; + else if (!_stricmp(tag, "media")) + return ID3_GetTagText(&id3v2, ID3FID_MEDIATYPE, data, dataLen)?1:-1; + else if (!_stricmp(tag, "remixing")) + return ID3_GetTagText(&id3v2, ID3FID_MIXARTIST, data, dataLen)?1:-1; + else if (!_stricmp(tag, "originalartist")) + return ID3_GetTagText(&id3v2, ID3FID_ORIGARTIST, data, dataLen)?1:-1; + else if (!_stricmp(tag, "encoder")) + return ID3_GetTagText(&id3v2, ID3FID_ENCODERSETTINGS, data, dataLen)?1:-1; + else if (!_stricmp(tag, "publisher")) + return ID3_GetTagText(&id3v2, ID3FID_PUBLISHER, data, dataLen)?1:-1; + else if (!_stricmp(tag, "copyright")) + return ID3_GetTagText(&id3v2, ID3FID_COPYRIGHT, data, dataLen)?1:-1; + else if (!_stricmp(tag, "compilation")) + return ID3_GetTagText(&id3v2, ID3FID_COMPILATION, data, dataLen)?1:-1; + else if (!_stricmp(tag, "url")) + return ID3_GetTagUrl(&id3v2, ID3FID_WWWUSER, data, dataLen)?1:-1; + else if (!_stricmp(tag, "GracenoteFileID")) + return ID3_GetGracenoteTagID(&id3v2, data, dataLen)?1:-1; + else if (!_stricmp(tag, "GracenoteExtData")) + { + if (!ID3_GetUserText(&id3v2, L"GN_ExtData", data, dataLen)) + return ID3_GetUserText(&id3v2, L"GN/ExtData", data, dataLen)?1:-1; + else + return 1; + } + else if (!_stricmp(tag, "tool")) + return ID3_GetTagText(&id3v2, ID3FID_ENCODEDBY, data, dataLen)?1:-1; + else if (!_stricmp(tag, "pregap")) + { + data[0] = 0; + // first, check for stupid iTunSMPB TXXX frame + wchar_t gaps[128] = L""; + const wchar_t *itr = ID3_GetComment(&id3v2, L"iTunSMPB", gaps, 128); + if (itr && *itr) + { + itr = IncSafe(itr, 9); + unsigned int prepad = wcstoul(itr, 0, 16); + StringCchPrintfW(data, dataLen, L"%u", prepad); + return 1; + } + return -1; + } + else if (!_stricmp(tag, "postgap")) + { + data[0] = 0; + // first, check for stupid iTunSMPB TXXX frame + wchar_t gaps[128] = L""; + const wchar_t *itr = ID3_GetComment(&id3v2, L"iTunSMPB", gaps, 128); + if (itr && *itr) + { + itr = IncSafe(itr, 18); + unsigned int postpad = wcstoul(itr, 0, 16); + StringCchPrintfW(data, dataLen, L"%u", postpad); + return 1; + } + return -1; + } + else if (!_stricmp(tag, "numsamples")) + { + data[0] = 0; + // first, check for stupid iTunSMPB TXXX frame + wchar_t gaps[128] = L""; + const wchar_t *itr = ID3_GetComment(&id3v2, L"iTunSMPB", gaps, 128); + if (itr && *itr) + { + itr = IncSafe(itr, 27); + unsigned __int64 samples = wcstoul(itr, 0, 16); + StringCchPrintfW(data, dataLen, L"%I64u", samples); + return 1; + } + return -1; + } + else if (!_stricmp(tag, "endoffset")) + { + data[0] = 0; + // first, check for stupid iTunSMPB TXXX frame + wchar_t gaps[128] = L""; + const wchar_t *itr = ID3_GetComment(&id3v2, L"iTunSMPB", gaps, 128); + if (itr && *itr) + { + itr = IncSafe(itr, 53); + unsigned __int32 endoffset = wcstoul(itr, 0, 16); + StringCchPrintfW(data, dataLen, L"%I32u", endoffset); + return 1; + } + return -1; + } + else if (!_stricmp(tag, "category")) + { + return ID3_GetTagText(&id3v2, ID3FID_CONTENTGROUP, data, dataLen)?1:-1; + } + // things generally added by Musicbrainz tagging (either specific or additional) + else if (!_stricmp(tag, "acoustid") || !_stricmp(tag, "acoustid_id")) + { + return ID3_GetUserText(&id3v2, L"Acoustid Id", data, dataLen)?1:-1; + } + else if (!_stricmp(tag, "acoustid_fingerprint")) + { + return ID3_GetUserText(&id3v2, L"Acoustid Fingerprint", data, dataLen)?1:-1; + } + else if (!_stricmp(tag, "asin")) + { + return ID3_GetUserText(&id3v2, L"ASIN", data, dataLen)?1:-1; + } + else if (!_stricmp(tag, "barcode")) + { + return ID3_GetUserText(&id3v2, L"BARCODE", data, dataLen)?1:-1; + } + else if (!_stricmp(tag, "catalognumber")) + { + return ID3_GetUserText(&id3v2, L"CATALOGNUMBER", data, dataLen)?1:-1; + } + else if (!_stricmp(tag, "script")) + { + return ID3_GetUserText(&id3v2, L"SCRIPT", data, dataLen)?1:-1; + } + else if (!_stricmp(tag, "musicbrainz_recordingid")) // (track id) + { + return ID3_GetMusicbrainzRecordingID(&id3v2, data, dataLen)?1:-1; + } + else if (!_stricmp(tag, "musicbrainz_trackid")) // TODO not working (album track id) + { + return ID3_GetUserText(&id3v2, L"MusicBrainz Release Track Id", data, dataLen)?1:-1; + } + else if (!_stricmp(tag, "musicbrainz_albumid")) + { + return ID3_GetUserText(&id3v2, L"MusicBrainz Album Id", data, dataLen)?1:-1; + } + else if (!_stricmp(tag, "musicbrainz_artistid")) + { + return ID3_GetUserText(&id3v2, L"MusicBrainz Artist Id", data, dataLen)?1:-1; + } + else if (!_stricmp(tag, "musicbrainz_albumartistid")) + { + return ID3_GetUserText(&id3v2, L"MusicBrainz Album Artist Id", data, dataLen)?1:-1; + } + else if (!_stricmp(tag, "musicbrainz_releasestatus") || !_stricmp(tag, "musicbrainz_albumstatus")) + { + return ID3_GetUserText(&id3v2, L"MusicBrainz Album Status", data, dataLen)?1:-1; + } + else if (!_stricmp(tag, "musicbrainz_releasetype") || !_stricmp(tag, "musicbrainz_albumtype")) + { + return ID3_GetUserText(&id3v2, L"MusicBrainz Album Type", data, dataLen)?1:-1; + } + else if (!_stricmp(tag, "musicbrainz_releasecountry") || !_stricmp(tag, "musicbrainz_albumcountry")) + { + return ID3_GetUserText(&id3v2, L"MusicBrainz Album Release Country", data, dataLen)?1:-1; + } + else if (!_stricmp(tag, "musicbrainz_releasegroupid") || !_stricmp(tag, "musicbrainz_albumgroupid")) + { + return ID3_GetUserText(&id3v2, L"MusicBrainz Release Group Id", data, dataLen)?1:-1; + } + else + { + return 0; + } +} + +void ID3v2::add_set_latin_id3v2_frame(ID3_FrameID id, const wchar_t *c) +{ + ID3_Frame *f = id3v2.Find(id); + if (!c) + { + if (f) + id3v2.RemoveFrame(f); + } + else + { + if (f) + { + SetFrameEncoding(f, ENCODING_FORCE_ASCII); + AutoChar temp(c); //AutoChar temp(c, 28591); // todo: benski> changed back to local to keep old winamp tagged files working + f->Field(ID3FN_URL).SetLocal(temp); //f->Field(ID3FN_TEXT).SetLatin(temp);// todo: benski> changed back to local to keep old winamp tagged files working + } + else + { + f = new ID3_Frame(id); + SetFrameEncoding(f, ENCODING_FORCE_ASCII); + AutoChar temp(c); //AutoChar temp(c, 28591); // todo: benski> changed back to local to keep old winamp tagged files working + f->Field(ID3FN_URL).SetLocal(temp); //f->Field(ID3FN_TEXT).SetLatin(temp);// todo: benski> changed back to local to keep old winamp tagged files working + id3v2.AddFrame(f, TRUE); + } + } +} + +int ID3v2::SetString(const char *tag, const wchar_t *data) +{ + if (!_stricmp(tag, "artist")) + add_set_id3v2_frame(ID3FID_LEADARTIST, data); + else if (!_stricmp(tag, "album")) + add_set_id3v2_frame(ID3FID_ALBUM, data); + else if (!_stricmp(tag, "albumartist")) + { + add_set_id3v2_frame(ID3FID_BAND, data); + if (!data || !*data) // if we're deleting the field + { + ID3_AddUserText(&id3v2, L"ALBUM ARTIST", data); // delete this alternate field also, or it's gonna look like it didn't "take" with a fb2k file + ID3_AddUserText(&id3v2, L"ALBUMARTIST", data); // delete this alternate field also, or it's gonna look like it didn't "take" with an mp3tag file + ID3_AddUserText(&id3v2, L"Band", data); // delete this alternate field also, or it's gonna look like it didn't "take" with an audacity file + } + } + else if (!_stricmp(tag, "comment")) + ID3_AddSetComment(&id3v2, data); + else if (!_stricmp(tag, "title")) + add_set_id3v2_frame(ID3FID_TITLE, data); + else if (!_stricmp(tag, "year")) + { + add_set_id3v2_frame(ID3FID_YEAR, data); + if (id3v2.version >= 4) // work around the fact that our id3 code doesn't handle versioning like this too well + add_set_id3v2_frame(ID3FID_RECORDINGTIME, data); + else + add_set_id3v2_frame(ID3FID_RECORDINGTIME, (wchar_t *)0); + } + else if (!_stricmp(tag, "genre")) + add_set_id3v2_frame(ID3FID_CONTENTTYPE, data); + else if (!_stricmp(tag, "track")) + add_set_id3v2_frame(ID3FID_TRACKNUM, data); + else if (!_stricmp(tag, "disc")) + add_set_id3v2_frame(ID3FID_PARTINSET, data); + else if (!_stricmp(tag, "bpm")) + add_set_id3v2_frame(ID3FID_BPM, data); + else if (!_stricmp(tag, "rating")) + ID3_AddSetRating(&id3v2, data); + else if (!_stricmp(tag, "tool")) + add_set_id3v2_frame(ID3FID_ENCODEDBY, data); + else if (!_stricmp(tag, "composer")) + add_set_id3v2_frame(ID3FID_COMPOSER, data); + else if (!_stricmp(tag, "replaygain_track_gain")) + ID3_AddUserText(&id3v2, L"replaygain_track_gain", data, ENCODING_FORCE_ASCII); + else if (!_stricmp(tag, "replaygain_track_peak")) + ID3_AddUserText(&id3v2, L"replaygain_track_peak", data, ENCODING_FORCE_ASCII); + else if (!_stricmp(tag, "replaygain_album_gain")) + ID3_AddUserText(&id3v2, L"replaygain_album_gain", data, ENCODING_FORCE_ASCII); + else if (!_stricmp(tag, "replaygain_album_peak")) + ID3_AddUserText(&id3v2, L"replaygain_album_peak", data, ENCODING_FORCE_ASCII); + else if (!_stricmp(tag, "originalartist")) + add_set_id3v2_frame(ID3FID_ORIGARTIST, data); + else if (!_stricmp(tag, "encoder")) + add_set_id3v2_frame(ID3FID_ENCODERSETTINGS, data); + else if (!_stricmp(tag, "publisher")) + add_set_id3v2_frame(ID3FID_PUBLISHER, data); + else if (!_stricmp(tag, "copyright")) + add_set_id3v2_frame(ID3FID_COPYRIGHT, data); + else if (!_stricmp(tag, "compilation")) + add_set_id3v2_frame(ID3FID_COMPILATION, data); + else if (!_stricmp(tag, "remixing")) + add_set_id3v2_frame(ID3FID_MIXARTIST, data); + else if (!_stricmp(tag, "ISRC")) + add_set_id3v2_frame(ID3FID_ISRC, data); + else if (!_stricmp(tag, "url")) + add_set_latin_id3v2_frame(ID3FID_WWWUSER, data); // TODO: we should %## escape invalid characters + //add_set_id3v2_frame(ID3FID_WWWUSER, data); + else if (!_stricmp(tag, "GracenoteFileID")) + ID3_AddSetGracenoteTagID(&id3v2, data); + else if (!_stricmp(tag, "GracenoteExtData")) + { + ID3_AddUserText(&id3v2, L"GN_ExtData", data, ENCODING_FORCE_ASCII); + ID3_AddUserText(&id3v2, L"GN_ExtData",0); // delete this alternate field also + } + else if (!_stricmp(tag, "category")) + add_set_id3v2_frame(ID3FID_CONTENTGROUP, data); + else + return 0; + hasData=true; + dirty=true; + return 1; +} + +void ID3v2::add_set_id3v2_frame(ID3_FrameID id, const wchar_t *c) +{ + ID3_Frame *f = id3v2.Find(id); + if (!c || !*c) + { + if (f) + id3v2.RemoveFrame(f); + } + else + { + if (f) + { + SetFrameEncoding(f); + f->Field(ID3FN_TEXT).SetUnicode(c); + } + else + { + f = new ID3_Frame(id); + SetFrameEncoding(f); + f->Field(ID3FN_TEXT).SetUnicode(c); + id3v2.AddFrame(f, TRUE); + } + } +} + +uint32_t ID3v2::EncodeSize() +{ + if (!hasData) + return 0; // simple :) + + return (uint32_t)id3v2.Size(); +} + +int ID3v2::Encode(const void *data, size_t len) +{ + id3v2.Render((uchar *)data); + return 0; +} + +static bool NameToAPICType(const wchar_t *name, int &num) +{ + if (!name || !*name) // default to cover + num=0x3; + else if (!_wcsicmp(name, L"fileicon")) // 32x32 pixels 'file icon' (PNG only) + num=0x1; + else if (!_wcsicmp(name, L"icon")) // Other file icon + num=0x2; + else if (!_wcsicmp(name, L"cover")) // Cover (front) + num=0x3; + else if (!_wcsicmp(name, L"back")) // Cover (back) + num=0x4; + else if (!_wcsicmp(name, L"leaflet")) // Leaflet page + num=0x5; + else if (!_wcsicmp(name, L"media")) // Media (e.g. lable side of CD) + num=0x6; + else if (!_wcsicmp(name, L"leadartist")) //Lead artist/lead performer/soloist + num=0x7; + else if (!_wcsicmp(name, L"artist")) // Artist/performer + num=0x8; + else if (!_wcsicmp(name, L"conductor")) // Conductor + num=0x9; + else if (!_wcsicmp(name, L"band")) // Band/Orchestra + num=0xA; + else if (!_wcsicmp(name, L"composer")) // Composer + num=0xB; + else if (!_wcsicmp(name, L"lyricist")) // Lyricist/text writer + num=0xC; + else if (!_wcsicmp(name, L"location")) // Recording Location + num=0xD; + else if (!_wcsicmp(name, L"recording")) // During recording + num=0xE; + else if (!_wcsicmp(name, L"performance")) // During performance + num=0xF; + else if (!_wcsicmp(name, L"preview")) // Movie/video screen capture + num=0x10; + else if (!_wcsicmp(name, L"fish")) // A bright coloured fish + num=0x11; + else if (!_wcsicmp(name, L"illustration")) // Illustration + num=0x12; + else if (!_wcsicmp(name, L"artistlogo")) // Band/artist logotype + num=0x13; + else if (!_wcsicmp(name, L"publisherlogo")) // Publisher/Studio logotype + num=0x14; + else + return false; + return true; +} + +int ID3v2::GetAlbumArt(const wchar_t *type, void **bits, size_t *len, wchar_t **mimeType) +{ + int pictype = 0; + if (NameToAPICType(type, pictype)) + { + // try to get our specific picture type + ID3_Frame *frame = id3v2.Find(ID3FID_PICTURE, ID3FN_PICTURETYPE, pictype); + + if (!frame && pictype == 3) // if not, just try a generic one + { + frame = id3v2.Find(ID3FID_PICTURE); + /*benski> CUT! + if (frame) + { + ID3_Field &field = frame->Field(ID3FN_PICTURETYPE); + if (field.Get()) + frame=0; + }*/ + } + + if (frame) + { + char *fulltype = ID3_GetString(frame, ID3FN_MIMETYPE); + char *type = 0; + if (fulltype && *fulltype) + { + type = strchr(fulltype, '/'); + } + + if (type && *type) + { + type++; + + char *type2 = strchr(type, '/'); + if (type2 && *type2) type2++; + else type2 = type; + + int typelen = MultiByteToWideChar(CP_ACP, 0, type2, -1, 0, 0); + *mimeType = (wchar_t *)WASABI_API_MEMMGR->sysMalloc(typelen * sizeof(wchar_t)); + MultiByteToWideChar(CP_ACP, 0, type2, -1, *mimeType, typelen); + free(fulltype); + } + else + { + // attempt to work out a mime type from known 'invalid' values + if (fulltype && *fulltype) + { + if (!strcmpi(fulltype, "png") || !strcmpi(fulltype, "bmp") || + !strcmpi(fulltype, "jpg") || !strcmpi(fulltype, "jpeg") || + !strcmpi(fulltype, "gif")) + { + int typelen = MultiByteToWideChar(CP_ACP, 0, fulltype, -1, 0, 0);// + 6; + *mimeType = (wchar_t*)WASABI_API_MEMMGR->sysMalloc(typelen * sizeof(wchar_t)); + MultiByteToWideChar(CP_ACP, 0, fulltype, -1, *mimeType, typelen); + CharLowerBuff(*mimeType, typelen); + free(fulltype); + fulltype = 0; + } + if (0 != fulltype) + { + free(fulltype); + fulltype = 0; + } + } + else + { + *mimeType = 0; // unknown! + } + } + + ID3_Field &field = frame->Field(ID3FN_DATA); + *len = field.Size(); + *bits = WASABI_API_MEMMGR->sysMalloc(*len); + field.Get((uchar *)*bits, *len); + return ALBUMARTPROVIDER_SUCCESS; + } + } + return ALBUMARTPROVIDER_FAILURE; +} + +int ID3v2::SetAlbumArt(const wchar_t *type, void *bits, size_t len, const wchar_t *mimeType) +{ + int pictype; + if (NameToAPICType(type, pictype)) + { + // try to get our specific picture type + ID3_Frame *frame = id3v2.Find(ID3FID_PICTURE, ID3FN_PICTURETYPE, pictype); + + if (!frame && pictype == 3) // if not, just try a generic one + { + frame = id3v2.Find(ID3FID_PICTURE); + /* benski> cut + if (frame) + { + ID3_Field &field = frame->Field(ID3FN_PICTURETYPE); + if (field.Get()) + frame=0; + }*/ + } + bool newFrame=false; + if (!frame) + { + frame = new ID3_Frame(ID3FID_PICTURE); + newFrame = true; + } + + if (frame) + { + wchar_t mt[32] = {L"image/jpeg"}; + if (mimeType) + { + if (wcsstr(mimeType, L"/") != 0) + { + StringCchCopyW(mt, 32, mimeType); + } + else + { + StringCchPrintfW(mt, 32, L"image/%s", mimeType); + } + } + + frame->Field(ID3FN_MIMETYPE).SetLatin(AutoChar(mt, 28591)); + frame->Field(ID3FN_PICTURETYPE).Set(pictype); + frame->Field(ID3FN_DESCRIPTION).Clear(); + frame->Field(ID3FN_DATA).Set((uchar *)bits, len); + if (newFrame) + id3v2.AddFrame(frame, TRUE); + dirty=1; + return ALBUMARTPROVIDER_SUCCESS; + } + } + return ALBUMARTPROVIDER_FAILURE; +} + +int ID3v2::DeleteAlbumArt(const wchar_t *type) +{ + int pictype; + if (NameToAPICType(type, pictype)) + { + // try to get our specific picture type + ID3_Frame *frame = id3v2.Find(ID3FID_PICTURE, ID3FN_PICTURETYPE, pictype); + + if (!frame && pictype == 3) // if not, just try a generic one + { + frame = id3v2.Find(ID3FID_PICTURE); + /* benski> cut + if (frame) + { + ID3_Field &field = frame->Field(ID3FN_PICTURETYPE); + if (field.Get()) + frame=0; + } + */ + } + if (frame) + { + id3v2.RemoveFrame(frame); + dirty=1; + return ALBUMARTPROVIDER_SUCCESS; + } + } + return ALBUMARTPROVIDER_FAILURE; +} + +void ID3v2::Clear() +{ + dirty=1; + hasData=false; + id3v2.Clear(); +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mp3/ID3v2.h b/Src/Plugins/Input/in_mp3/ID3v2.h new file mode 100644 index 00000000..f7f21f0e --- /dev/null +++ b/Src/Plugins/Input/in_mp3/ID3v2.h @@ -0,0 +1,33 @@ +#ifndef NULLSOFT_IN_MP3_ID3v2_H +#define NULLSOFT_IN_MP3_ID3v2_H + +#include "../id3v2/id3_tag.h" + +class ID3v2 +{ +public: + ID3v2(); + bool HasData() { return hasData; } + bool IsDirty() { return dirty; } + int Decode(const void *data, size_t len); + int Encode(const void *data, size_t len); + uint32_t EncodeSize(); + // return -1 for empty, 1 for OK, 0 for "don't understand tag name" + int GetString(const char *tag, wchar_t *data, int dataLen); + int SetString(const char *tag, const wchar_t *data); + + int GetAlbumArt(const wchar_t *type, void **bits, size_t *len, wchar_t **mimeType); + int SetAlbumArt(const wchar_t *type, void *bits, size_t len, const wchar_t *mimeType); + int DeleteAlbumArt(const wchar_t *type); + + void Clear(); +private: + void add_set_id3v2_frame(ID3_FrameID id, const wchar_t *c); + void add_set_latin_id3v2_frame(ID3_FrameID id, const wchar_t *c); + bool hasData; + bool dirty; +public: + ID3_Tag id3v2; +}; + +#endif
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mp3/IN2.H b/Src/Plugins/Input/in_mp3/IN2.H new file mode 100644 index 00000000..3c6f95e7 --- /dev/null +++ b/Src/Plugins/Input/in_mp3/IN2.H @@ -0,0 +1,4 @@ +#ifndef UNICODE_INPUT_PLUGIN +#define UNICODE_INPUT_PLUGIN +#endif +#include "../Winamp/in2.h"
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mp3/LAMEinfo.cpp b/Src/Plugins/Input/in_mp3/LAMEinfo.cpp new file mode 100644 index 00000000..febfec98 --- /dev/null +++ b/Src/Plugins/Input/in_mp3/LAMEinfo.cpp @@ -0,0 +1,398 @@ +#include "LAMEinfo.h" +#include <windows.h> +#include <memory.h> +#include <math.h> +#include "api__in_mp3.h" +#include "resource.h" +#include "in2.h" +#pragma intrinsic(memcmp) + +extern In_Module mod; + +// Xing header - +// 4 Xing +// 4 flags +// 4 frames +// 4 bytes +// 100 toc +// 4 bytes VBR quality + +// Lame tag +// 9 bytes - release name +// 11 + +// Lame extended info tag + +// http://gabriel.mp3-tech.org/mp3infotag.html + + +/*-------------------------------------------------------------*/ +static int32_t ExtractI4(unsigned char *buf) +{ + int x; + // big endian extract + + x = buf[0]; + x <<= 8; + x |= buf[1]; + x <<= 8; + x |= buf[2]; + x <<= 8; + x |= buf[3]; + + return x; +} + +static int16_t ExtractI2(unsigned char *buf) +{ + int x; + // big endian extract + + x = buf[0]; + x <<= 8; + x |= buf[1]; + + return x; +} + + +const static int bitrateV1L3[] = { 0, 32000, 40000, 48000, 56000, 64000, 80000, 96000, 112000, 128000, 160000, 192000, 224000, 256000, 320000, 0}; +const static int bitrateV1L1[] = { 0, 32000, 64000, 96000, 128000, 160000, 192000, 224000, 256000, 288000, 320000, 352000, 384000, 416000, 448000, 0}; +const static int bitrateV1L2[] = { 0, 32000, 48000, 56000, 64000, 80000, 96000, 112000, 128000, 160000, 192000, 224000, 256000, 320000, 384000, 0}; +const static int bitrateV2L1[] = { 0, 32000, 48000, 56000, 64000, 80000, 96000, 112000, 128000, 144000, 160000, 176000, 192000, 224000, 256000, 0}; +const static int bitrateV2L2L3[] = { 0, 8000, 16000, 24000, 32000, 40000, 48000, 56000, 64000, 80000, 96000, 112000, 128000, 144000, 160000, 0}; + +const static int sampleRateV1[] = {44100, 48000, 32000, 0}; +const static int sampleRateV2[] = {22050, 24000, 16000, 0}; +const static int sampleRateV2_5[] = {11025, 12000, 8000, 0}; + +// [mpeg_version][layer] +static const int samples_per_frame[4][4] = +{ + // Layer 3, Layer 2, Layer 1 + { 0, 576, 1152, 384}, // MPEG2.5 + { 0, }, + { 0, 576, 1152, 384}, // MPEG2 + { 0, 1152, 1152, 384}, // MPEG1 +}; + +void MPEGFrame::ReadBuffer(const unsigned char *buffer) + { + sync = ((unsigned short)buffer[0] << 3) | (buffer[1] >> 5); + mpegVersion = (buffer[1] >> 3) & 3; + layer = (buffer[1] >> 1) & 3; + protection = (buffer[1]) & 1; + bitrateIndex = (buffer[2] >> 4) & 0xF; + sampleRateIndex = (buffer[2] >> 2) & 3; + paddingBit = (buffer[2] >> 1) & 1; + privateBit = buffer[2] & 1; + channelMode = (buffer[3] >> 6) & 3; + modeExtension = (buffer[3] >> 4) & 3; + copyright = (buffer[3] >> 3) & 1; + original = (buffer[3] >> 2) & 1; + emphasis = (buffer[3]) & 3; + } + bool MPEGFrame::IsSync() + { + return sync == 0x07FF + && layer != LayerError + && mpegVersion != MPEG_Error + && bitrateIndex != 15 + && bitrateIndex != 0 + && sampleRateIndex != 3 + && !(mpegVersion == MPEG2 && layer != Layer3) + && !(mpegVersion == MPEG2_5 && layer != Layer3); + + } + int MPEGFrame::GetBitrate() + { + switch (mpegVersion) + { + case MPEG1: + switch (layer) + { + case Layer1: + return bitrateV1L1[bitrateIndex]; + case Layer2: + return bitrateV1L2[bitrateIndex]; + case Layer3: + return bitrateV1L3[bitrateIndex]; + } + break; + case MPEG2: + case MPEG2_5: + switch (layer) + { + case Layer1: + return bitrateV2L1[bitrateIndex]; + case Layer2: + case Layer3: + return bitrateV2L2L3[bitrateIndex]; + } + break; + } + + return 0; // shouldn't get here + } + int MPEGFrame::GetPadding() + { + if (paddingBit == NotPadded) + return 0; + + if (layer == Layer1) + return 4; + else + return 1; + } + int MPEGFrame::HeaderSize() + { + if (protection == CRC) + return 4 + 2; // 32bits frame header, 16bits CRC + else + return 4; // 32bits frame ehader + } + + int MPEGFrame::GetSampleRate() const + { + switch(mpegVersion) + { + case MPEG1: return sampleRateV1[sampleRateIndex]; + case MPEG2:return sampleRateV2[sampleRateIndex]; + case MPEG2_5:return sampleRateV2_5[sampleRateIndex]; + default: return 99999999; // return something that will hopefully cause the framesize to be 0 + } + + } + + int MPEGFrame::GetSamplesPerFrame() const + { + return samples_per_frame[mpegVersion][layer]; + } + + bool MPEGFrame::IsCopyright() + { + return copyright == 1; + } + bool MPEGFrame::IsCRC() + { + return protection == CRC; + } + + bool MPEGFrame::IsOriginal() + { + return original == 1; + } + + const char *MPEGFrame::GetEmphasisString() + { + static char tempGE[32]; + switch (emphasis) + { + case Emphasis_None: + return WASABI_API_LNGSTRING_BUF(IDS_NONE,tempGE,32); + case Emphasis_50_15_ms: + return WASABI_API_LNGSTRING_BUF(IDS_50_15_MICROSEC,tempGE,32); + case Emphasis_reserved: + return WASABI_API_LNGSTRING_BUF(IDS_INVALID,tempGE,32); + case Emphasis_CCIT_J_17: + return "CITT j.17"; + default: + return WASABI_API_LNGSTRING_BUF(IDS_ERROR,tempGE,32); + } + } + + int MPEGFrame::FrameSize() + { + if (layer == Layer1) + { + return (int)floor((48.0f*(float)GetBitrate())/GetSampleRate()) + GetPadding(); + } + else if (layer == Layer2 || layer == Layer3) + { + if (mpegVersion == MPEG1) + return (int)floor((144.0f*(float)GetBitrate())/GetSampleRate()) + GetPadding(); + else + return (int)floor((72.0f*(float)GetBitrate())/GetSampleRate()) + GetPadding(); + } + return 0; + } + + const char *MPEGFrame::GetMPEGVersionString() + { + switch(mpegVersion) + { + case MPEG1: + return "MPEG-1"; + case MPEG2: + return "MPEG-2"; + case MPEG2_5: + return "MPEG-2.5"; + default: + static char tempMF[16]; + return WASABI_API_LNGSTRING_BUF(IDS_ERROR,tempMF,16); + } + } + + const char *MPEGFrame::GetChannelModeString() + { + static char tempGC[32]; + switch(channelMode) + { + case Stereo: + return WASABI_API_LNGSTRING_BUF(IDS_STEREO,tempGC,32); + case JointStereo: + return WASABI_API_LNGSTRING_BUF(IDS_JOINT_STEREO,tempGC,32); + case DualChannel: + return WASABI_API_LNGSTRING_BUF(IDS_2_CHANNEL,tempGC,32); + case Mono: + return WASABI_API_LNGSTRING_BUF(IDS_MONO,tempGC,32); + default: + return WASABI_API_LNGSTRING_BUF(IDS_ERROR,tempGC,32); + } + } + + int MPEGFrame::GetLayer() + { + switch(layer) + { + case Layer1: + return 1; + case Layer2: + return 2; + case Layer3: + return 3; + default: + return 0; + } + } + + int MPEGFrame::GetNumChannels() + { + switch(channelMode) + { + case Stereo: + return 2; + case JointStereo: + return 2; + case DualChannel: + return 2; + case Mono: + return 1; + default: + return 0; + } + } + +int ReadLAMEinfo(unsigned char *buffer, LAMEinfo *lameInfo) +{ + int flags; + MPEGFrame frame; + frame.ReadBuffer(buffer); + + if (!frame.IsSync()) + return 0; + + lameInfo->h_id = frame.mpegVersion & 1; + lameInfo->samprate = frame.GetSampleRate(); + // determine offset of header + if (frame.mpegVersion == MPEGFrame::MPEG1) // MPEG 1 + { + if (frame.channelMode == MPEGFrame::Mono) + buffer += (17 + 4);//frame.HeaderSize()); + + else + buffer += (32 + 4);//frame.HeaderSize()); + } + else if (frame.mpegVersion == MPEGFrame::MPEG2) // MPEG 2 + { + if (frame.channelMode == MPEGFrame::Mono) + buffer += (9 + 4);//frame.HeaderSize()); + else + buffer += (17 + 4);//frame.HeaderSize()); + } + else if (frame.mpegVersion == MPEGFrame::MPEG2_5) // MPEG 2 + { + if (frame.channelMode == MPEGFrame::Mono) + buffer += (9 + 4);//frame.HeaderSize()); + else + buffer += (17 + 4);//frame.HeaderSize()); + } + + if (!memcmp(buffer, "Info", 4)) + lameInfo->cbr=1; + else if (memcmp(buffer, "Xing", 4) && memcmp(buffer, "Lame", 4)) + return 0; + + buffer += 4; // skip Xing tag + flags = lameInfo->flags = ExtractI4(buffer); + buffer += 4; // skip flags + + if (flags & FRAMES_FLAG) + { + lameInfo->frames = ExtractI4(buffer); + buffer += 4; // skip frames + } + if (flags & BYTES_FLAG) + { + lameInfo->bytes = ExtractI4(buffer); + buffer += 4; + } + if (flags & TOC_FLAG) + { + if (lameInfo->toc) + { + for (int i = 0;i < 100;i++) + lameInfo->toc[i] = buffer[i]; + } + buffer += 100; + } + + lameInfo->vbr_scale = -1; + if (flags & VBR_SCALE_FLAG) + { + lameInfo->vbr_scale = ExtractI4(buffer); + buffer += 4; + } + + if (!memcmp(buffer, "LAME", 4)) + { + for (int i=0;i<9;i++) + lameInfo->lameTag[i]=*buffer++; + lameInfo->lameTag[9]=0; // null terminate in case tag used all 20 characters + + lameInfo->encodingMethod = (*buffer++)&0xF; // we'll grab the VBR method + lameInfo->lowpass = (*buffer++)*100; // lowpass value + lameInfo->peak=*((float *)buffer); // read peak value + buffer+=4; // skip peak value + + // read track gain + int16_t gain_word = ExtractI2(buffer); + if ((gain_word & 0xFC00) == 0x2C00) + { + lameInfo->replaygain_track_gain = (float)(gain_word & 0x01FF); + lameInfo->replaygain_track_gain /= 10; + if (gain_word & 0x0200) + lameInfo->replaygain_track_gain = -lameInfo->replaygain_track_gain; + } + buffer+=2; + + // read album gain + gain_word = ExtractI2(buffer); + if ((gain_word & 0xFC00) == 0x4C00) + { + lameInfo->replaygain_album_gain = (float)(gain_word & 0x01FF); + lameInfo->replaygain_album_gain /= 10; + if (gain_word & 0x0200) + lameInfo->replaygain_album_gain = -lameInfo->replaygain_album_gain; + } + buffer+=2; + + buffer+=1; // skip encoding flags + ATH type + buffer+=1; // skip bitrate + + // get the encoder delay and padding, annoyingly as 12 bit values packed into 3 bytes + lameInfo->encoderDelay = ((unsigned short)buffer[0] << 4) | (buffer[1] >> 4); + lameInfo->padding = ((unsigned short)(buffer[1]&0x0F) << 8) | (buffer[2]); + } + return frame.FrameSize(); +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mp3/LAMEinfo.h b/Src/Plugins/Input/in_mp3/LAMEinfo.h new file mode 100644 index 00000000..d8e6db04 --- /dev/null +++ b/Src/Plugins/Input/in_mp3/LAMEinfo.h @@ -0,0 +1,118 @@ +#ifndef NULLSOFT_LAMEINFOH +#define NULLSOFT_LAMEINFOH + + +#define FRAMES_FLAG 0x0001 +#define BYTES_FLAG 0x0002 +#define TOC_FLAG 0x0004 +#define VBR_SCALE_FLAG 0x0008 + +#define FRAMES_AND_BYTES (FRAMES_FLAG | BYTES_FLAG) +#include <memory.h> +#pragma intrinsic(memset) +struct LAMEinfo +{ + LAMEinfo() + { + memset(this, 0, sizeof(LAMEinfo)); + } + + int cbr; // set to 1 if the file is actually just CBR + // Xing + int h_id; + int samprate; // determined from MPEG header + int flags; // from Xing header data + int frames; // total bit stream frames from Xing header data + int bytes; // total bit stream bytes from Xing header data + int vbr_scale; // encoded vbr scale from Xing header data + unsigned char *toc; // pointer to unsigned char toc_buffer[100] + // may be NULL if toc not desired + + // LAME + char lameTag[10]; // 9 characters, but we'll add an extra NULL just in case + float peak; + float replaygain_album_gain; + float replaygain_track_gain; + unsigned short lowpass; + unsigned short encoderDelay; + unsigned short padding; + int encodingMethod; +}; + +enum +{ + ENCODING_METHOD_LAME = 0, + ENCODING_METHOD_CBR = 1, + ENCODING_METHOD_ABR = 2, + ENCODING_METHOD_VBR1 = 3, + ENCODING_METHOD_VBR2 = 4, + ENCODING_METHOD_VBR3 = 5, + ENCODING_METHOD_VBR4 = 6, + ENCODING_METHOD_CBR_2PASS = 8, + ENCODING_METHOD_ABR_2PASS = 9, +}; + +int ReadLAMEinfo(unsigned char *buffer, LAMEinfo *lameInfo); + +class MPEGFrame +{ +public: + int GetNumChannels(); + + void ReadBuffer(const unsigned char *buffer); + bool IsSync(); + int GetBitrate(); + int GetPadding(); + int HeaderSize(); + int GetSampleRate() const; + int FrameSize(); + const char *GetMPEGVersionString(); + const char *GetChannelModeString(); + const char *GetEmphasisString(); + int GetLayer(); + bool IsCRC(); + bool IsCopyright(); + bool IsOriginal(); + int MPEGFrame::GetSamplesPerFrame() const; + + enum + { + NotPadded=0, + Padded=1, + CRC = 0, + NoProtection = 1, + Stereo = 0, + JointStereo = 1, + DualChannel = 2, + Mono = 3, + MPEG1 = 3, + MPEG2 = 2, + MPEG_Error = 1, + MPEG2_5 = 0, + Layer1 = 3, + Layer2 = 2, + Layer3 = 1, + LayerError = 0, + Emphasis_None = 0, + Emphasis_50_15_ms = 1, + Emphasis_reserved = 2, + Emphasis_CCIT_J_17 = 3, + }; + + unsigned int sync:11, +mpegVersion:2, +layer:2, +protection:1, +bitrateIndex:4, +paddingBit:1, +privateBit:1, +channelMode:2, +modeExtension:2, +sampleRateIndex:2, +copyright:1, +original:1, +emphasis:2; +}; + + +#endif
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mp3/Lyrics3.cpp b/Src/Plugins/Input/in_mp3/Lyrics3.cpp new file mode 100644 index 00000000..bdd8ece6 --- /dev/null +++ b/Src/Plugins/Input/in_mp3/Lyrics3.cpp @@ -0,0 +1,193 @@ +#include <windows.h> +#include "Lyrics3.h" +#include "config.h" +#include <strsafe.h> + +// http://www.id3.org/Lyrics3v2 + +Lyrics3::Lyrics3() +{ + artist=0; + title=0; + album=0; + hasData=false; + dirty=false; +} + +Lyrics3::~Lyrics3() +{ + free(artist); + free(title); + free(album); +} + +static wchar_t *CopyField(const uint8_t *buffer, uint32_t size) +{ + int converted = MultiByteToWideChar(28591, 0,(LPCSTR)buffer, size, 0, 0); + wchar_t *str = (wchar_t *)calloc((converted+1), sizeof(wchar_t)); + if (str) + { + converted = MultiByteToWideChar(28591, 0, (LPCSTR)buffer, size, str, converted); + str[converted]=0; + } + + return str; +} + +int Lyrics3::Decode(const void *data, size_t datalen) +{ + if (!config_parse_lyrics3) + return 1; + + if (memcmp(data, "LYRICSBEGIN", 11) == 0) + { + hasData = true; + + datalen-=11; + uint8_t *buffer = (uint8_t *)data+11; + + while (datalen > 8) + { + uint8_t fid[4] = {0}; + uint8_t sizeT[6] = {0}; + uint32_t size; + fid[3] = 0; + sizeT[5] = 0; + + memcpy(fid, buffer, 3); + buffer+=3; datalen-=3; + + memcpy(sizeT, buffer, 5); + buffer+=5; datalen-=5; + + size = strtoul((char *)sizeT, 0, 10); + + if (datalen >= size) + { + /*if ( memcmp(fid, "IND", 3) == 0) // the IND field + { + if ( buff2[ posn + 8 + 1 ] == '1') + stampsUsed = true; + } + else */ + if (memcmp(fid, "ETT", 3) == 0) // the TITLE field + { + title = CopyField(buffer, size); + } + else if (strcmp((char *) fid, "EAR") == 0) // the ARTIST field + { + artist = CopyField(buffer, size); + } + else if (strcmp((char *) fid, "EAL") == 0) // the ALBUM field + { + album = CopyField(buffer, size); + } + /*else if ( strcmp((char *) fid, "LYR") == 0) // the LYRICS field + { + char *text; + luint newSize; + + newSize = ID3_CRLFtoLF((char *) & buff2[ posn + 8 ], size); + + if ( stampsUsed) + newSize = ID3_StripTimeStamps((char *) & buff2[ posn + 8 ], newSize); + + if ( text = (char*)malloc(newSize + 1)) + { + text[ newSize ] = 0; + + memcpy( text, &buff2[ posn + 8 ], newSize); + + ID3_AddLyrics( this, text); + + free(text); + } + else + ID3_THROW( ID3E_NoMemory); + }*/ + + datalen-=size; + buffer+=size; + } + else + break; + } + return 0; + } + return 1; + +} + +int Lyrics3::GetString(const char *tag, wchar_t *data, int dataLen) +{ + if (!hasData) + return 0; + + if (!_stricmp(tag, "title")) + { + if (title && *title) + { + StringCchCopyW(data, dataLen, title); + return 1; + } + return -1; + } + else if (!_stricmp(tag, "artist")) + { + if (artist && *artist) + { + StringCchCopyW(data, dataLen, artist); + return 1; + } + return -1; + } + else if (!_stricmp(tag, "album")) + { + if (album && *album) + { + StringCchCopyW(data, dataLen, album); + return 1; + } + return -1; + } + + return 0; +} + +int Lyrics3::SetString(const char *tag, const wchar_t *data) +{ + int ret=0; + if (!_stricmp(tag, "title")) + { + if (title) free(title); + title = _wcsdup(data); + ret = 1; + } + else if (!_stricmp(tag, "artist")) + { + if ( artist ) free(artist); + artist = _wcsdup(data); + ret = 1; + } + else if (!_stricmp(tag, "album")) + { + if ( album ) free(album); + album = _wcsdup(data); + ret = 1; + } + + if(ret) + { + hasData=true; + } + return ret; +} + +void Lyrics3::Clear() +{ + free(artist); artist=0; + free(album); album=0; + free(title); title=0; + dirty=true; + hasData=false; +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mp3/Lyrics3.h b/Src/Plugins/Input/in_mp3/Lyrics3.h new file mode 100644 index 00000000..68a30f4d --- /dev/null +++ b/Src/Plugins/Input/in_mp3/Lyrics3.h @@ -0,0 +1,25 @@ +#ifndef NULLSOFT_IN_MP3_LYRICS3_H +#define NULLSOFT_IN_MP3_LYRICS3_H + +#include <bfc/platform/types.h> +class Lyrics3 +{ +public: + Lyrics3(); + ~Lyrics3(); + bool HasData() { return hasData; } + bool IsDirty() { return dirty; } + void Clear(); + void ResetDirty() { dirty=0; }; + int Decode(const void *data, size_t datalen); +// return -1 for empty, 1 for OK, 0 for "don't understand tag name" + int GetString(const char *tag, wchar_t *data, int dataLen); + int SetString(const char *tag, const wchar_t *data); + +private: + bool hasData; + bool dirty; + wchar_t *title, *album, *artist; +}; + +#endif
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mp3/MP3Info.cpp b/Src/Plugins/Input/in_mp3/MP3Info.cpp new file mode 100644 index 00000000..bc3d7158 --- /dev/null +++ b/Src/Plugins/Input/in_mp3/MP3Info.cpp @@ -0,0 +1,612 @@ +#include "main.h" +#include "LAMEInfo.h" +#include "AACFrame.h" +#include "config.h" +#include "../winamp/wa_ipc.h" +#include <Richedit.h> +#include "api__in_mp3.h" +#include "FactoryHelper.h" +#include <shlwapi.h> +#include <strsafe.h> +#include <foundation/error.h> + +int fixAACCBRbitrate(int br); + +#if 0 +/* + +*/ +/* +int MP3Info::remove_id3v1() +{ +char temp[3] = {0, 0, 0}; +DWORD x; +int err = 0; +HANDLE hFile = CreateFile(file, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, 0, + OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0); +if (hFile == INVALID_HANDLE_VALUE) +{ + return 0; //1; +} +SetFilePointer(hFile, -128, NULL, FILE_END); +ReadFile(hFile, temp, 3, &x, NULL); +if (!memcmp(temp, "TAG", 3)) +{ + SetFilePointer(hFile, -128, NULL, FILE_END); + if (!SetEndOfFile(hFile)) + { + err = 1; + } +} +CloseHandle(hFile); +if (!err) fbuf[0] = 0; +return err; + +return 0; +} +*/ + + + + +/* +da_tag.SetUnsync(false); +da_tag.SetExtendedHeader(true); +da_tag.SetCompression(false); +da_tag.SetPadding(true); +*/ + + +#endif + + +int FindAverageAACBitrate(unsigned __int8 *data, int sizeBytes) +{ + AACFrame aacFrame; + aacFrame.ReadBuffer(data); + + if (aacFrame.OK()) + { + int aac_frame_length = aacFrame.frameLength; + int no_rawdb = aacFrame.numDataBlocks; + + int fc_tot = aac_frame_length; + int fc_cnt = no_rawdb + 1; + + unsigned char *aa = data + aac_frame_length; + int tt = sizeBytes - aac_frame_length; + while (tt >= 8) + { + AACFrame nextFrame; + nextFrame.ReadBuffer(aa); + if (!nextFrame.OK()) break; // error + int fcaac_frame_length = nextFrame.frameLength; + int fcno_rawdb = nextFrame.numDataBlocks; + + fc_cnt += fcno_rawdb + 1; + fc_tot += fcaac_frame_length; + + aa += fcaac_frame_length; + tt -= fcaac_frame_length; + } + + + int avg_framesize = fc_tot / (fc_cnt ? fc_cnt : 1); + return fixAACCBRbitrate(MulDiv(avg_framesize * 8, aacFrame.GetSampleRate(), 1024 * 1000)); + } + return 0; +} + +static bool ScanForFrame(CGioFile *file, int *bytesRead) +{ + unsigned char buffer[512] = {0}; /* don't want to read too much, since most MP3's start at 0 */ + int buflen = 0; + + int checked=0; + if (file->Peek(buffer, sizeof(buffer), &buflen) != NErr_Success) + return false; + unsigned char *b = buffer; + while (buflen >= 4) + { + MPEGFrame frame1; + frame1.ReadBuffer(b); + if (frame1.IsSync()) + { + if (checked) + file->Read(buffer, checked, &buflen); + *bytesRead=checked; + return true; + } + + checked++; + buflen--; + b++; + } + if (checked) + file->Read(buffer, checked, &buflen); + *bytesRead=checked; + return false; +} + +static bool mp3sync(CGioFile *file) +{ + unsigned char buffer[1448 + 4] = {0}; /* large enough for one max-size frame and the header of the second */ + int buflen = 0; + + static const unsigned long gdwHeaderSyncMask = 0xfffe0c00L; + unsigned long ulHdr1=0; + unsigned long ulHdr2=0; + + int bytesChecked=0; + while (bytesChecked<32768) + { + int bytesRead=0; + if (ScanForFrame(file, &bytesRead)) + { + if (file->Peek(buffer, sizeof(buffer), &buflen) != NErr_Success) + return false; + + if (buflen >= 4) + { + MPEGFrame frame1; + frame1.ReadBuffer(buffer); + if (frame1.IsSync()) + { + ulHdr1 = (buffer[0] << 24) | (buffer[1] << 16) | (buffer[2] << 8) | buffer[3]; + int framelength= frame1.FrameSize(); + if (buflen >= (framelength+4)) + { + unsigned char *b = buffer + framelength; + buflen -= frame1.FrameSize(); + MPEGFrame frame2; + frame2.ReadBuffer(b); + ulHdr2 = (b[0] << 24) | (b[1] << 16) | (b[2] << 8) | b[3]; + if (!((ulHdr1 ^ ulHdr2) & gdwHeaderSyncMask) && frame2.IsSync()) + return true; + else + { + + } + } + } + file->Read(buffer, 1, &buflen); + bytesChecked++; + } + } + else if (file->EndOf()) + return 0; + + bytesChecked+=bytesRead; + } + return false; +} + +static const wchar_t *GetMPEGVersionString(int mpegVersion) +{ + switch (mpegVersion) + { + case MPEGFrame::MPEG1: + return L"MPEG-1"; + case MPEGFrame::MPEG2: + return L"MPEG-2"; + case MPEGFrame::MPEG2_5: + return L"MPEG-2.5"; + default: + static wchar_t temp[64]; + return WASABI_API_LNGSTRINGW_BUF(IDS_ERROR,temp,64); + } +} + +static const wchar_t *GetEmphasisString(int emphasis) +{ + static wchar_t tempE[32]; + switch (emphasis) + { + case 0: + return WASABI_API_LNGSTRINGW_BUF(IDS_NONE,tempE,32); + case 1: + return WASABI_API_LNGSTRINGW_BUF(IDS_50_15_MICROSEC,tempE,32); + case 2: + return WASABI_API_LNGSTRINGW_BUF(IDS_INVALID,tempE,32); + case 3: + return L"CITT j.17"; + default: + return WASABI_API_LNGSTRINGW_BUF(IDS_ERROR,tempE,32); + } +} + +static const wchar_t *GetChannelModeString(int channelMode) +{ + static wchar_t tempM[32]; + switch (channelMode) + { + case 0: + return WASABI_API_LNGSTRINGW_BUF(IDS_STEREO,tempM,32); + case 1: + return WASABI_API_LNGSTRINGW_BUF(IDS_JOINT_STEREO,tempM,32); + case 2: + return WASABI_API_LNGSTRINGW_BUF(IDS_2_CHANNEL,tempM,32); + case 3: + return WASABI_API_LNGSTRINGW_BUF(IDS_MONO,tempM,32); + default: + return WASABI_API_LNGSTRINGW_BUF(IDS_ERROR,tempM,32); + } +} +#define INFO_READ_SIZE 32768 +void GetFileDescription(const wchar_t *file, CGioFile &_file, wchar_t *data, size_t datalen) +{ + int hdroffs = 0; + size_t size = datalen; + wchar_t *mt = data; + wchar_t *ext = PathFindExtension(file); + + wchar_t langbuf[256] = {0}; + int flen = _file.GetContentLength(); + StringCchPrintfExW(mt, size, &mt, &size, 0, WASABI_API_LNGSTRINGW_BUF(IDS_PAYLOAD_SIZE, langbuf, 256), _file.GetContentLength()); + if (!_wcsicmp(ext, L".aac") || !_wcsicmp(ext, L".vlb")) + { + #if 0 // TODO! + if (_wcsicmp(ext, L".vlb")) // aacplus can't do VLB + { + if (aacPlus) + { + aacPlus->EasyOpen(AACPLUSDEC_OUTPUTFORMAT_INT16_HOSTENDIAN, 6); + AACPLUSDEC_EXPERTSETTINGS *pConf = aacPlus->GetDecoderSettingsHandle(); + pConf->bEnableOutputLimiter = 1; + pConf->bDoUpsampling = 1; + aacPlus->SetDecoderSettings(); + + StringCchCatEx(mt, size, WASABI_API_LNGSTRINGW_BUF(IDS_FORMAT_AAC, langbuf, 256), &mt, &size, 0); + + char buffer[INFO_READ_SIZE] = {0}; + int inputRead; + _file.Read(buffer, INFO_READ_SIZE, &inputRead); + AACPLUSDEC_BITSTREAMBUFFERINFO bitbufInfo = { inputRead, 0, 0}; + aacPlus->StreamFeed((unsigned char *)buffer, &bitbufInfo); + + unsigned char tempBuf[65536] = {0}; // grr, can't we find a better way to do this? + AACPLUSDEC_AUDIOBUFFERINFO audioBufInfo = {65536, 0, 0}; + aacPlus->StreamDecode(tempBuf, &audioBufInfo, 0, 0); + audioBufInfo.nBytesBufferSizeIn -= audioBufInfo.nBytesWrittenOut; + aacPlus->StreamDecode(tempBuf + audioBufInfo.nBytesWrittenOut, &audioBufInfo, 0, 0); + AACPLUSDEC_STREAMPROPERTIES *streamProperties = aacPlus->GetStreamPropertiesHandle(); + if (streamProperties->nDecodingState == AACPLUSDEC_DECODINGSTATE_STREAMVERIFIED) + { + AACPLUSDEC_PROGRAMPROPERTIES *currentProgram = &(streamProperties->programProperties[streamProperties->nCurrentProgram]); + + switch (currentProgram->nStreamType) + { + case AACPLUSDEC_MPEG2_PROFILE_AACMAIN: + StringCchCatEx(mt, size, L"\r\nMPEG-2 AAC", &mt, &size, 0); + break; + case AACPLUSDEC_MPEG2_PROFILE_AACLC: + if (currentProgram->bProgramSbrEnabled) + StringCchCatEx(mt, size, WASABI_API_LNGSTRINGW_BUF(IDS_MPEG2_HE_AAC_IS, langbuf, 256), &mt, &size, 0); + else + StringCchCatEx(mt, size, L"\r\nMPEG-2 AAC LC", &mt, &size, 0); + break; + case AACPLUSDEC_MPEG4_AOT_AACMAIN: + StringCchCatEx(mt, size, L"\r\nMPEG-4 AAC", &mt, &size, 0); + break; + case AACPLUSDEC_MPEG4_AOT_AACLC: + if (currentProgram->bProgramSbrEnabled) + StringCchCatEx(mt, size, WASABI_API_LNGSTRINGW_BUF(IDS_MPEG4_HE_AAC_IS, langbuf, 256), &mt, &size, 0); + else + StringCchCatEx(mt, size, L"\r\nMPEG-4 AAC LC", &mt, &size, 0); + break; + case AACPLUSDEC_MPEG4_AOT_SBR: + StringCchCatEx(mt, size, L"\r\nMPEG-4 HE-AAC", &mt, &size, 0); + break; + } + + if (currentProgram->nAacSamplingRate != currentProgram->nOutputSamplingRate) + StringCchPrintfEx(mt, size, &mt, &size, 0, + WASABI_API_LNGSTRINGW(IDS_SAMPLE_RATE_OUTPUT), + currentProgram->nAacSamplingRate, currentProgram->nOutputSamplingRate); + else + StringCchPrintfEx(mt, size, &mt, &size, 0, + WASABI_API_LNGSTRINGW(IDS_SAMPLE_RATE), + currentProgram->nAacSamplingRate); + int srate = currentProgram->nOutputSamplingRate; + + if (currentProgram->bProgramSbrEnabled) + StringCchCatEx(mt, size, WASABI_API_LNGSTRINGW(IDS_SBR_PRESENT), &mt, &size, 0); + else + StringCchCatEx(mt, size, WASABI_API_LNGSTRINGW(IDS_SBR_NOT_PRESENT), &mt, &size, 0); + + if (currentProgram->nAacChannels != currentProgram->nOutputChannels) + StringCchPrintfEx(mt, size, &mt, &size, 0, + WASABI_API_LNGSTRINGW(IDS_CHANNELS_OUTPUT), + currentProgram->nAacChannels, currentProgram->nOutputChannels); + else + StringCchPrintfEx(mt, size, &mt, &size, 0, + WASABI_API_LNGSTRINGW(IDS_CHANNELS), + currentProgram->nAacChannels); + switch (currentProgram->nChannelMode) + { + case AACPLUSDEC_CHANNELMODE_MONO: + StringCchCatEx(mt, size, WASABI_API_LNGSTRINGW(IDS_MODE_MONO), &mt, &size, 0); + break; + case AACPLUSDEC_CHANNELMODE_STEREO: + StringCchCatEx(mt, size, WASABI_API_LNGSTRINGW(IDS_MODE_STEREO), &mt, &size, 0); + break; + case AACPLUSDEC_CHANNELMODE_PARAMETRIC_STEREO: + StringCchCatEx(mt, size, WASABI_API_LNGSTRINGW(IDS_MODE_PARAMETRIC_STEREO), &mt, &size, 0); + break; + case AACPLUSDEC_CHANNELMODE_DUAL_CHANNEL: + StringCchCatEx(mt, size, WASABI_API_LNGSTRINGW(IDS_MODE_DUAL_CHANNEL), &mt, &size, 0); + break; + case AACPLUSDEC_CHANNELMODE_4_CHANNEL_2CPE: + StringCchCatEx(mt, size, WASABI_API_LNGSTRINGW(IDS_MODE_4_CHANNEL_2_CPE), &mt, &size, 0); + break; + case AACPLUSDEC_CHANNELMODE_4_CHANNEL_MPEG: + StringCchCatEx(mt, size, WASABI_API_LNGSTRINGW(IDS_MODE_4_CHANNEL_MPEG), &mt, &size, 0); + break; + case AACPLUSDEC_CHANNELMODE_5_CHANNEL: + StringCchCatEx(mt, size, WASABI_API_LNGSTRINGW(IDS_MODE_5_CHANNEL), &mt, &size, 0); + break; + case AACPLUSDEC_CHANNELMODE_5_1_CHANNEL: + StringCchCatEx(mt, size, WASABI_API_LNGSTRINGW(IDS_MODE_5_1), &mt, &size, 0); + break; + case AACPLUSDEC_CHANNELMODE_6_1_CHANNEL: + StringCchCatEx(mt, size, WASABI_API_LNGSTRINGW(IDS_MODE_6_1), &mt, &size, 0); + break; + case AACPLUSDEC_CHANNELMODE_7_1_CHANNEL: + StringCchCatEx(mt, size, WASABI_API_LNGSTRINGW(IDS_MODE_7_1), &mt, &size, 0); + break; + } + + if (streamProperties->nBitrate) + { + StringCchPrintfEx(mt, size, &mt, &size, 0, + WASABI_API_LNGSTRINGW(IDS_BITRATE), + streamProperties->nBitrate); + } + else + { + int avg_bitrate = FindAverageAACBitrate((unsigned char *)buffer, inputRead); + StringCchPrintfEx(mt, size, &mt, &size, 0, + WASABI_API_LNGSTRINGW(IDS_AVERAGE_BITRATE), + avg_bitrate); + } + } + } + } + + if (!aacPlus) + #endif + { + char buffer[INFO_READ_SIZE] = {0}; + int inputRead = 0; + _file.Read(buffer, INFO_READ_SIZE, &inputRead); + + StringCchCopyEx(mt, size, WASABI_API_LNGSTRINGW(IDS_FORMAT_AAC), &mt, &size, 0); + unsigned char *a = (unsigned char *)buffer; + while (inputRead-- >= 8) + { + AACFrame aacFrame; + aacFrame.ReadBuffer(a); + + if (aacFrame.OK()) + { + int aac_frame_length = aacFrame.frameLength; + int no_rawdb = aacFrame.numDataBlocks; + + /*size_t size = 1024; + char *mt = mpeg_description;*/ + StringCchPrintfEx(mt, size, &mt, &size, 0, WASABI_API_LNGSTRINGW(IDS_HEADER_FOUND_AT_X_BYTES), hdroffs); + StringCchPrintfEx(mt, size, &mt, &size, 0, L"\r\nMPEG-%d AAC", aacFrame.GetMPEGVersion()); + + int fc_tot = aac_frame_length; + int fc_cnt = no_rawdb + 1; + + unsigned char *aa = a + aac_frame_length; + int tt = inputRead - aac_frame_length; + while (tt >= 8) + { + AACFrame nextFrame; + nextFrame.ReadBuffer(aa); + if (!nextFrame.OK()) break; // error + int fcaac_frame_length = nextFrame.frameLength; + int fcno_rawdb = nextFrame.numDataBlocks; + + fc_cnt += fcno_rawdb + 1; + fc_tot += fcaac_frame_length; + + aa += fcaac_frame_length; + tt -= fcaac_frame_length; + } + + { + int avg_framesize = fc_tot / (fc_cnt ? fc_cnt : 1); + int srate = aacFrame.GetSampleRate(); + int avg_bitrate = fixAACCBRbitrate(MulDiv(avg_framesize * 8, srate, 1024 * 1000)); + + int len_s = MulDiv(flen, 1024, avg_framesize * srate); + + StringCchPrintfEx(mt, size, &mt, &size, 0, WASABI_API_LNGSTRINGW(IDS_LENGTH_X_SECONDS), len_s); + StringCchPrintfEx(mt, size, &mt, &size, 0, L"\r\nCBR %d kbps", avg_bitrate); + } + + StringCchPrintfEx(mt, size, &mt, &size, 0, WASABI_API_LNGSTRINGW(IDS_PROFILE), aacFrame.GetProfileName()); + StringCchPrintfEx(mt, size, &mt, &size, 0, L"\r\n%dHz %s", aacFrame.GetSampleRate(), aacFrame.GetChannelConfigurationName()); + StringCchPrintfEx(mt, size, &mt, &size, 0, L"\r\nCRC: %s", WASABI_API_LNGSTRINGW((aacFrame.protection == 0 ? IDS_YES : IDS_NO))); + break; + } + + a++; + hdroffs++; + } + } + } + else + { + unsigned char mp3syncbuf[INFO_READ_SIZE] = {0}; + int inputRead = 0; + + DWORD start = _file.GetCurrentPosition(); + // find position of first sync + if (!mp3sync(&_file)) + return; + int syncposition = _file.GetCurrentPosition()-start; + + // advance to first sync + _file.Peek(mp3syncbuf, INFO_READ_SIZE, &inputRead); + + unsigned int padding = 0; + unsigned int encoderDelay = 0; + + MPEGFrame frame; + frame.ReadBuffer(mp3syncbuf); + + + int framelen = frame.FrameSize(); + + //const CMp3StreamInfo *info = decoder.GetStreamInfo(); + //const CMpegHeader *header = decoder.m_Mbs.GetHdr(); + + StringCchPrintfEx(mt, size, &mt, &size, 0, WASABI_API_LNGSTRINGW_BUF(IDS_HEADER_FOUND_AT_X_BYTES, langbuf, 256), _file.GetHeaderOffset() + syncposition); + if (!padding) padding = _file.postpad; + if (!encoderDelay) encoderDelay = _file.prepad; + + if (padding || encoderDelay) + StringCchPrintfEx(mt, size, &mt, &size, 0, WASABI_API_LNGSTRINGW_BUF(IDS_ENC_DELAY_ZERO_PADDING, langbuf, 256), encoderDelay, padding); + + int is_vbr_lens = _file.m_vbr_ms; + int iLen = (is_vbr_lens ? is_vbr_lens/1000 : ((flen * 8) / frame.GetBitrate())); + if(iLen > 0) + StringCchPrintfEx(mt, size, &mt, &size, 0, WASABI_API_LNGSTRINGW_BUF(IDS_LENGTH_X_SECONDS, langbuf, 256), iLen); + else + { + float fLen = (is_vbr_lens ? is_vbr_lens/1000.0f : ((flen * 8.0f) / frame.GetBitrate())); + StringCchPrintfEx(mt, size, &mt, &size, 0, WASABI_API_LNGSTRINGW_BUF(IDS_LENGTH_X_PART_SECONDS, langbuf, 256), fLen); + } + + StringCchPrintfEx(mt, size, &mt, &size, 0, WASABI_API_LNGSTRINGW_BUF(IDS_S_LAYER_X, langbuf, 256), GetMPEGVersionString(frame.mpegVersion), frame.GetLayer()); + + int frames = _file.m_vbr_frames; + + int is_vbr = _file.m_vbr_flag || _file.m_vbr_hdr; + if (!is_vbr || _file.encodingMethod == ENCODING_METHOD_CBR) + { + if (frames) + { + StringCchPrintfEx(mt, size, &mt, &size, 0, WASABI_API_LNGSTRINGW_BUF(IDS_X_KBIT, langbuf, 256), frame.GetBitrate() / 1000, frames); + } + else + { + StringCchPrintfEx(mt, size, &mt, &size, 0, WASABI_API_LNGSTRINGW_BUF(IDS_X_KBIT_APPROX, langbuf, 256), frame.GetBitrate() / 1000, MulDiv(flen, 8, framelen)); + } + } + else if (is_vbr && _file.encodingMethod == ENCODING_METHOD_ABR) + StringCchPrintfEx(mt, size, &mt, &size, 0, WASABI_API_LNGSTRINGW_BUF(IDS_X_KBIT_ABR, langbuf, 256), _file.GetAvgVBRBitrate(), frames); + else + { + StringCchPrintfEx(mt, size, &mt, &size, 0, WASABI_API_LNGSTRINGW_BUF(IDS_X_KBIT_VBR, langbuf, 256), _file.GetAvgVBRBitrate(), _file.m_vbr_hdr?L"I":L"", frames); + } + + StringCchPrintfEx(mt, size, &mt, &size, 0, WASABI_API_LNGSTRINGW_BUF(IDS_X_HZ_S, langbuf, 256), frame.GetSampleRate(), GetChannelModeString(frame.channelMode)); + StringCchPrintfEx(mt, size, &mt, &size, 0, L"\r\nCRC: %s", WASABI_API_LNGSTRINGW_BUF((frame.CRC ? IDS_YES : IDS_NO), langbuf, 256)); + wchar_t tmp[16] = {0}; + StringCchPrintfEx(mt, size, &mt, &size, 0, WASABI_API_LNGSTRINGW_BUF(IDS_COPYRIGHTED, langbuf, 256), WASABI_API_LNGSTRINGW_BUF((frame.copyright ? IDS_YES : IDS_NO),tmp,16)); + StringCchPrintfEx(mt, size, &mt, &size, 0, WASABI_API_LNGSTRINGW_BUF(IDS_ORIGINAL, langbuf, 256), WASABI_API_LNGSTRINGW_BUF((frame.original ? IDS_YES : IDS_NO),tmp,16)); + StringCchPrintfEx(mt, size, &mt, &size, 0, WASABI_API_LNGSTRINGW_BUF(IDS_EMPHASIS, langbuf, 256), GetEmphasisString(frame.emphasis)); + + if (_file.m_vbr_frame_len && !_file.lengthVerified) + StringCchPrintfEx(mt, size, &mt, &size, 0, WASABI_API_LNGSTRINGW_BUF(IDS_MP3_HAS_BEEN_MODIFIED_NOT_ALL_MAY_BE_CORRECT, langbuf, 256)); + } +} + +void GetAudioInfo(const wchar_t *filename, CGioFile *file, int *len, int *channels, int *bitrate, int *vbr, int *sr) +{ + *bitrate=0; + *len=0; + *vbr=0; + *channels=0; + if (file) + { + wchar_t *ext = PathFindExtension(filename); + if (!_wcsicmp(ext, L".aac") || !_wcsicmp(ext, L".vlb")) + { + unsigned char t[INFO_READ_SIZE*2] = {0}; + unsigned char *a = t; + int n = 0; + + if (file->Read(t, sizeof(t), &n) != NErr_Success) + return; + + while (n-- >= 8) + { + AACFrame aacFrame; + aacFrame.ReadBuffer(a); + + if (aacFrame.OK()) + { + int aac_frame_length = aacFrame.frameLength; + + int fc_tot = aac_frame_length; + int fc_cnt = aacFrame.numDataBlocks + 1; + + unsigned char *aa = a + aac_frame_length; + int tt = n - aac_frame_length; + while (tt >= 8 && aac_frame_length) + { + AACFrame nextFrame; + nextFrame.ReadBuffer(aa); + if (!nextFrame.OK()) break; // error + int fcaac_frame_length = nextFrame.frameLength; + int fcno_rawdb = nextFrame.numDataBlocks; + + fc_cnt += fcno_rawdb + 1; + fc_tot += fcaac_frame_length; + + aa += fcaac_frame_length; + tt -= fcaac_frame_length; + } + + int avg_framesize = fc_tot / (fc_cnt ? fc_cnt : 1); + + int br = MulDiv(avg_framesize * 8, aacFrame.GetSampleRate(), 1024); + *len = MulDiv(file->GetContentLength(), 1024*8, br); + *bitrate = fixAACCBRbitrate(br/1000)*1000; + + *sr = aacFrame.GetSampleRate(); + *channels = aacFrame.GetNumChannels(); + + break; + } + a++; + } + } + else + { + if (*bitrate = file->GetAvgVBRBitrate()*1000) + { + *len = file->m_vbr_ms; + *vbr = file->m_vbr_flag || file->m_vbr_hdr; + } + + if (!mp3sync(file)) + return; + + unsigned char t[4] = {0}; + int n = 0; + + if (file->Peek(t, sizeof(t), &n) != NErr_Success) + return; + + MPEGFrame frame; + frame.ReadBuffer(t); + if (frame.IsSync()) + { + if (!*bitrate) + { + *bitrate = frame.GetBitrate(); + *len = MulDiv(file->GetContentLength(), 1000*8, *bitrate); + } + *channels = frame.GetNumChannels(); + *sr = frame.GetSampleRate(); + } + } + } +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mp3/MP3Info.h b/Src/Plugins/Input/in_mp3/MP3Info.h new file mode 100644 index 00000000..b161feeb --- /dev/null +++ b/Src/Plugins/Input/in_mp3/MP3Info.h @@ -0,0 +1,47 @@ +#ifndef NULLSOFT_IN_MP3_MP3_INFO_H +#define NULLSOFT_IN_MP3_MP3_INFO_H + +#include "Metadata.h" +#include <windows.h> + +class MP3Info +{ +public: + MP3Info(const wchar_t *fn); + + char mpeg_description[1024]; + + bool isOld(); + + void get_file_info(); + int write_id3v1(); + int remove_id3v1(); + void display_id3v1(HWND hwndDlg); + void get_id3v1_values(HWND hwndDlg); + void display_id3v2(HWND hwndDlg); + void get_id3v2_values(HWND hwndDlg); + void write_id3v2(HWND hwndDlg); + void do_enable_id3v1(HWND hwndDlg, int en); + void do_enable_id3v2(HWND hwndDlg, int en); + + BOOL CALLBACK id3Proc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam); + + int setExtendedFileInfoW(const char *data, wchar_t *val); + int writeExtendedFileInfo(); + + bool IsMe(const wchar_t *fn) + { + return !lstrcmpW(file, fn); + } +protected: + // Keep track of file timestamp for file system change notification handling + FILETIME last_write_time; + +private: + void SetField(const wchar_t *value, wchar_t *&v2, char *v1, size_t v1size); + Metadata metadata; + wchar_t file[MAX_PATH]; +}; + + +#endif
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mp3/Metadata.cpp b/Src/Plugins/Input/in_mp3/Metadata.cpp new file mode 100644 index 00000000..89e87697 --- /dev/null +++ b/Src/Plugins/Input/in_mp3/Metadata.cpp @@ -0,0 +1,616 @@ +#include "Metadata.h" +#include "main.h" +#include "api__in_mp3.h" +#include "LAMEInfo.h" +#include "AACFrame.h" +#include "config.h" +#include "LAMEInfo.h" +#include <shlwapi.h> +#include <assert.h> +#include <foundation/error.h> +#include <strsafe.h> + +#define INFO_READ_SIZE 32768 + +Metadata::Metadata( CGioFile *_file, const wchar_t *_filename ) +{ + if ( !PathIsURL( _filename ) ) + filename = _wcsdup( _filename ); + + ReadTags( _file ); + if ( bitrate = _file->GetAvgVBRBitrate() * 1000 ) + { + length_ms = _file->m_vbr_ms; + vbr = _file->m_vbr_flag || _file->m_vbr_hdr; + } +} + +void GetFileDescription(const wchar_t *file, CGioFile &_file, wchar_t *data, size_t datalen); +void GetAudioInfo(const wchar_t *filename, CGioFile *file, int *len, int *channels, int *bitrate, int *vbr, int *sr); + +int Metadata::Open(const wchar_t *_filename) +{ + if ( filename && *filename ) + free( filename ); + + filename = _wcsdup(_filename); + if (file.Open(filename, INFO_READ_SIZE/1024) != NErr_Success) + return 1; + + GetAudioInfo(filename, &file, &length_ms, &channels, &bitrate, &vbr, &sampleRate); + ReadTags(&file); + file.Close(); + return METADATA_SUCCESS; +} + +Metadata::~Metadata() +{ + if (filename) + { + free(filename); + filename=0; + } +} + +void Metadata::ReadTags(CGioFile *_file) +{ + // Process ID3v1 + if (config_parse_id3v1) + { + void *id3v1_data = _file->GetID3v1(); + if (id3v1_data) + id3v1.Decode(id3v1_data); + } + + if (config_parse_id3v2) + { + uint32_t len = 0; + void *id3v2_data = _file->GetID3v2(&len); + if (id3v2_data) + id3v2.Decode(id3v2_data, len); + } + + if (config_parse_lyrics3) + { + uint32_t len = 0; + void *lyrics3_data = _file->GetLyrics3(&len); + if (lyrics3_data) + lyrics3.Decode(lyrics3_data, len); + } + + if (config_parse_apev2) + { + uint32_t len = 0; + void *apev2_data = _file->GetAPEv2(&len); + if (apev2_data) + apev2.Decode(apev2_data, len); + } +} + +static int ID3Write(const wchar_t *filename, HANDLE infile, DWORD offset, void *data, DWORD len) +{ + wchar_t tempFile[MAX_PATH] = {0}; + StringCchCopyW(tempFile, MAX_PATH, filename); + PathRemoveExtension(tempFile); + StringCchCatW(tempFile, MAX_PATH, L".tmp"); + + // check to make sure the filename was actually different! + // benski> TODO: we should just try to mangle the filename more rather than totally bail out + if (!_wcsicmp(tempFile, filename)) + return SAVE_ERROR_CANT_OPEN_TEMPFILE; + + // TODO: overlapped I/O + HANDLE outfile = CreateFile(tempFile, GENERIC_WRITE|GENERIC_READ, FILE_SHARE_READ, 0, CREATE_ALWAYS, FILE_FLAG_SEQUENTIAL_SCAN, 0); + if (outfile != INVALID_HANDLE_VALUE) + { + DWORD written=0; + if (data && len) + WriteFile(outfile, data, len, &written, NULL); + SetFilePointer(infile, offset, 0, FILE_BEGIN); + + DWORD read=0; + do + { + char data[4096] = {0}; + written = read = 0; + ReadFile(infile, data, 4096, &read, NULL); + if (read) WriteFile(outfile, data, read, &written, NULL); + } + while (read != 0); + CloseHandle(outfile); + CloseHandle(infile); + if (!MoveFile(tempFile, filename)) + { + if (!CopyFile(tempFile, filename, FALSE)) + { + DeleteFile(tempFile); + return SAVE_ERROR_ERROR_OVERWRITING; + } + DeleteFile(tempFile); + } + return SAVE_SUCCESS; + } + return SAVE_ERROR_CANT_OPEN_TEMPFILE; + +} + +bool Metadata::IsDirty() +{ + return id3v1.IsDirty() || id3v2.IsDirty() || lyrics3.IsDirty() || apev2.IsDirty(); +} + +int Metadata::Save() +{ + if (!IsDirty()) + return SAVE_SUCCESS; + + int err=SAVE_SUCCESS; + if (GetFileAttributes(filename)&FILE_ATTRIBUTE_READONLY) + return SAVE_ERROR_READONLY; + + HANDLE metadataFile = CreateFile(filename, GENERIC_WRITE|GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, 0); + if (metadataFile == INVALID_HANDLE_VALUE) + return SAVE_ERROR_OPENING_FILE; + + if (file.Open(filename, INFO_READ_SIZE/1024) != NErr_Success) + { + CloseHandle(metadataFile); + return SAVE_ERROR_OPENING_FILE; + } + + bool strippedID3v1=false; // this flag will get set to true when we remove ID3v1 as a side effect of removing APEv2 or Lyrics3 (or ID3v2.4 end-tag if/when we implement) + bool strippedLyrics3=false; + + /* Strip APEv2 */ + if (config_parse_apev2 && config_write_apev2 && apev2.IsDirty()) + { + uint32_t len = 0; + void *apev2_data = file.GetAPEv2(&len); + if (apev2_data) + { + uint32_t lyrics3_len = 0; + void *lyrics3_data = file.GetLyrics3(&lyrics3_len); + if (lyrics3_data) + SetFilePointer(metadataFile, -(LONG)(len + 15 + lyrics3_len + (file.GetID3v1()?128:0)), NULL, FILE_END); + else + SetFilePointer(metadataFile, -(LONG)(len + (file.GetID3v1()?128:0)), NULL, FILE_END); + SetEndOfFile(metadataFile); + strippedLyrics3=true; + strippedID3v1=true; + } + } + + /* Strip Lyrics3 tag */ + if (!strippedLyrics3 && config_parse_lyrics3 && lyrics3.IsDirty()) + { + uint32_t len = 0; + void *lyrics3_data = file.GetLyrics3(&len); + if (lyrics3_data) + { + SetFilePointer(metadataFile, -(LONG)(len + 15 + (file.GetID3v1()?128:0)), NULL, FILE_END); + SetEndOfFile(metadataFile); + strippedID3v1=true; + } + } + + /* Strip ID3v1(.1) tag */ + if (!strippedID3v1 /* if we stripped lyrics3 tag, then we ended up stripping id3v1 also */ + && config_parse_id3v1 && config_write_id3v1 && id3v1.IsDirty()) + { + if (file.GetID3v1()) // see if we have ID3v1 + { + SetFilePointer(metadataFile, -128, NULL, FILE_END); + SetEndOfFile(metadataFile); + } + } + + /* Write APEv2 */ + if (config_parse_apev2 && config_write_apev2 && apev2.IsDirty() && apev2.HasData()) + { + switch(config_apev2_header) + { + case ADD_HEADER: + apev2.SetFlags(APEv2::FLAG_HEADER_HAS_HEADER, APEv2::FLAG_HEADER_HAS_HEADER); + break; + case REMOVE_HEADER: + apev2.SetFlags(0, APEv2::FLAG_HEADER_HAS_HEADER); + break; + } + + size_t apev2_len = apev2.EncodeSize(); + void *apev2_data = malloc(apev2_len); + if (apev2_data && apev2.Encode(apev2_data, apev2_len) == APEv2::APEV2_SUCCESS) + { + SetFilePointer(metadataFile, 0, NULL, FILE_END); + DWORD bytesWritten=0; + WriteFile(metadataFile, apev2_data, (DWORD)apev2_len, &bytesWritten, 0); + free(apev2_data); + apev2_data = 0; + if (bytesWritten != apev2_len) + { + err=SAVE_APEV2_WRITE_ERROR; + goto fail; + } + } + else + { + free(apev2_data); + apev2_data = 0; + err=SAVE_APEV2_WRITE_ERROR; + goto fail; + } + } + + /* Write Lyrics3 */ + if (strippedLyrics3) /* if we need to rewrite it because we stripped it (e.g. removing an APEv2 tag)*/ + { + /* since we don't modify lyrics3 (yet) we'll just rewrite the original binary data */ + uint32_t len = 0; + void *lyrics3_data = file.GetLyrics3(&len); + if (lyrics3_data) + { + SetFilePointer(metadataFile, 0, NULL, FILE_END); + DWORD bytesWritten=0; + WriteFile(metadataFile, lyrics3_data, len, &bytesWritten, NULL); + if (bytesWritten != len) + { + err=SAVE_LYRICS3_WRITE_ERROR; + goto fail; + } + char temp[7] = {0}; + StringCchPrintfA(temp, 7, "%06u", len); + bytesWritten = 0; + WriteFile(metadataFile, temp, 6, &bytesWritten, NULL); + if (bytesWritten != 6) + { + err=SAVE_LYRICS3_WRITE_ERROR; + goto fail; + } + bytesWritten = 0; + WriteFile(metadataFile, "LYRICS200", 9, &bytesWritten, NULL); + if (bytesWritten != 9) + { + err=SAVE_LYRICS3_WRITE_ERROR; + goto fail; + } + } + } + + /* Write ID3v1 */ + if (config_parse_id3v1 && config_write_id3v1 && id3v1.IsDirty()) + { + uint8_t id3v1_data[128] = {0}; + if (id3v1.Encode(id3v1_data) == METADATA_SUCCESS) + { + SetFilePointer(metadataFile, 0, NULL, FILE_END); + DWORD bytesWritten=0; + WriteFile(metadataFile, id3v1_data, 128, &bytesWritten, NULL); + if (bytesWritten != 128) + { + err=SAVE_ID3V1_WRITE_ERROR; + goto fail; + } + } + } + else if (strippedID3v1) + { + /** if we stripped lyrics3 or apev2 but didn't modify id3v1 (or are configured not to use it), + ** we need to rewrite it back to the original data + **/ + void *id3v1_data=file.GetID3v1(); + if (id3v1_data) + { + SetFilePointer(metadataFile, 0, NULL, FILE_END); + DWORD bytesWritten=0; + WriteFile(metadataFile, id3v1_data, 128, &bytesWritten, NULL); + if (bytesWritten != 128) + { + err=SAVE_ID3V1_WRITE_ERROR; + goto fail; + } + } + } + + /* Write ID3v2 */ + if (config_parse_id3v2 && config_write_id3v2 && id3v2.IsDirty()) + { + uint32_t oldlen=0; + void *old_id3v2_data = file.GetID3v2(&oldlen); + id3v2.id3v2.SetPadding(false); // turn off padding to see if we can get away with non re-writing the file + uint32_t newlen = id3v2.EncodeSize(); + if (old_id3v2_data && !newlen) // there's an old tag, but no new tag + { + err = ID3Write(filename, metadataFile, oldlen, 0, 0); + if (err == SAVE_SUCCESS) + metadataFile = INVALID_HANDLE_VALUE; // ID3Write returns true if it closed the handle + else + goto fail; + } + else if (!old_id3v2_data && !newlen) // no old tag, no new tag.. easy :) + { + } + else + { + id3v2.id3v2.SetPadding(true); + if (newlen <= oldlen) // if we can fit in the old tag + { + if (oldlen != newlen) + id3v2.id3v2.ForcePading(oldlen-newlen); // pad out the rest of the tag + else + id3v2.id3v2.SetPadding(false); + assert(id3v2.EncodeSize() == oldlen); + newlen = oldlen; + uint8_t *new_id3v2_data = (uint8_t *)calloc(newlen, sizeof(uint8_t)); + if (new_id3v2_data && id3v2.Encode(new_id3v2_data, newlen) == METADATA_SUCCESS) + { + // TODO: deal with files with multiple starting id3v2 tags + SetFilePointer(metadataFile, 0, NULL, FILE_BEGIN); + DWORD bytesWritten=0; + WriteFile(metadataFile, new_id3v2_data, newlen, &bytesWritten, NULL); + free(new_id3v2_data); + new_id3v2_data = 0; + if (bytesWritten != newlen) + { + err = SAVE_ID3V2_WRITE_ERROR; + goto fail; + } + } + else + { + free(new_id3v2_data); + new_id3v2_data = 0; + err = SAVE_ID3V2_WRITE_ERROR; + goto fail; + } + } + else // otherwise we have to pad out the start + { + newlen = id3v2.EncodeSize(); + uint8_t *new_id3v2_data = (uint8_t *)calloc(newlen, sizeof(uint8_t)); + if (new_id3v2_data && id3v2.Encode(new_id3v2_data, newlen) == METADATA_SUCCESS) + { + // TODO: deal with files with multiple starting id3v2 tags + SetFilePointer(metadataFile, 0, NULL, FILE_BEGIN); + DWORD bytesWritten=0; + err = ID3Write(filename, metadataFile, oldlen, new_id3v2_data, newlen); + free(new_id3v2_data); + new_id3v2_data = 0; + if (err == SAVE_SUCCESS) + metadataFile = INVALID_HANDLE_VALUE; // ID3Write returns true if it closed the handle + else + goto fail; + } + else + { + free(new_id3v2_data); + new_id3v2_data = 0; + err = SAVE_ID3V2_WRITE_ERROR; + goto fail; + } + } + } + } + +fail: + file.Close(); + if (metadataFile != INVALID_HANDLE_VALUE) + CloseHandle(metadataFile); + return err; +} + +int Metadata::GetExtendedData(const char *tag, wchar_t *data, int dataLen) +{ + int understood=0; + switch (id3v2.GetString(tag, data, dataLen)) + { + case -1: + data[0]=0; + understood=1; + break; + + case 1: + return 1; + } + + switch (apev2.GetString(tag, data, dataLen)) + { + case -1: + data[0]=0; + understood=1; + break; + + case 1: + return 1; + } + + switch (lyrics3.GetString(tag, data, dataLen)) + { + case -1: + data[0]=0; + understood=1; + break; + + case 1: + return 1; + } + + switch (id3v1.GetString(tag, data, dataLen)) + { + case -1: + data[0]=0; + understood=1; + break; + + case 1: + return 1; + } + + switch (GetString(tag, data, dataLen)) + { + case -1: + data[0]=0; + understood=1; + break; + + case 1: + return 1; + } + + return understood; +} + +int Metadata::SetExtendedData(const char *tag, const wchar_t *data) +{ + int understood=0; + if (config_create_id3v2 || id3v2.HasData()) + understood |= id3v2.SetString(tag, data); + if (config_create_apev2 || apev2.HasData()) + understood |= apev2.SetString(tag, data); + if (config_create_id3v1 || id3v1.HasData()) + understood |= id3v1.SetString(tag, data); + return understood; +} + +int Metadata::GetString(const char *tag, wchar_t *data, int dataLen) +{ + if (!_stricmp(tag, "formatinformation")) + { + data[0]=0; + if (filename) + { + if (file.Open(filename, INFO_READ_SIZE/1024) == NErr_Success) + GetFileDescription(filename, file, data, dataLen); + file.Close(); + } + } + else if (!_stricmp(tag, "length")) + { + StringCchPrintfW(data, dataLen, L"%d", length_ms); + } + else if (!_stricmp(tag, "stereo")) + { + StringCchPrintfW(data, dataLen, L"%d", channels==2); + } + else if (!_stricmp(tag, "vbr")) + { + StringCchPrintfW(data, dataLen, L"%d", vbr); + } + else if (!_stricmp(tag, "bitrate")) + { + StringCchPrintfW(data, dataLen, L"%d", bitrate/1000); + } + else if (!_stricmp(tag, "gain")) + { + StringCchPrintfW(data, dataLen, L"%-+.2f dB", file.GetGain()); + } + else if (!_stricmp(tag, "pregap")) + { + if (file.prepad) + { + StringCchPrintfW(data, dataLen, L"%u", file.prepad); + return 1; + } + return -1; + } + else if (!_stricmp(tag, "postgap")) + { + if (file.prepad) // yes, we check for this because postpad could legitimately be 0 + { + StringCchPrintfW(data, dataLen, L"%u", file.postpad); + return 1; + } + return -1; + } + else if (!_stricmp(tag, "numsamples")) + { + if (file.m_vbr_samples) + { + StringCchPrintfW(data, dataLen, L"%I64u", file.m_vbr_samples); + return 1; + } + return -1; + } + else if (!_stricmp(tag, "endoffset")) + { + if (file.m_vbr_frames) + { + int totalFrames = file.m_vbr_frames; + if (totalFrames > 8) + { + int seekPoint = 0; + // we're using m_vbr_bytes here instead of file.ContentLength(), because we're already trusting the other LAME header info + #define MAX_SIZE_8_FRAMES (1448 * 8) // mp3 frames won't be ever be any bigger than this (320kbps 32000Hz + padding) + if (file.m_vbr_bytes > MAX_SIZE_8_FRAMES) + seekPoint = (int)(file.m_vbr_bytes - MAX_SIZE_8_FRAMES); + else + seekPoint = 0; + + size_t offsets[8] = {0}; + size_t offsetsRead = 0; + size_t offsetPosition = 0; + + unsigned char header[6] = {0}; + MPEGFrame frame; + + if (file.Open(filename, INFO_READ_SIZE/1024) != NErr_Success) + return -1; + + // first we need to sync + while (1) + { + file.SetCurrentPosition(seekPoint, CGioFile::GIO_FILE_BEGIN); + int read = 0; + file.Read(header, 6, &read); + if (read != 6) + break; + frame.ReadBuffer(header); + if (frame.IsSync() && frame.GetLayer() == 3) + { + // make sure this isn't false sync - see if we can get another sync... + int nextPoint = seekPoint + frame.FrameSize(); + file.SetCurrentPosition(nextPoint, CGioFile::GIO_FILE_BEGIN); + file.Read(header, 6, &read); + if (read != 6) // must be EOF + break; + frame.ReadBuffer(header); + if (frame.IsSync() && frame.GetLayer() == 3) + break; + } + seekPoint++; + } + while (1) + { + file.SetCurrentPosition(seekPoint, CGioFile::GIO_FILE_BEGIN); + int read = 0; + file.Read(header, 6, &read); + if (read != 6) + break; + frame.ReadBuffer(header); + if (frame.IsSync() && frame.GetLayer() == 3) + { + offsets[offsetPosition] = seekPoint; + offsetPosition = (offsetPosition + 1) % 8; + offsetsRead++; + seekPoint += frame.FrameSize(); + } + else + break; + } + if (offsetsRead >= 8) + { + StringCchPrintfW(data, dataLen, L"%I32d", offsets[offsetPosition] + file.m_vbr_frame_len); + file.Close(); + return 1; + } + + file.Close(); + } + } + return -1; + } + else + return 0; + return 1; +} + +int fixAACCBRbitrate(int br);
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mp3/Metadata.h b/Src/Plugins/Input/in_mp3/Metadata.h new file mode 100644 index 00000000..f84ce34a --- /dev/null +++ b/Src/Plugins/Input/in_mp3/Metadata.h @@ -0,0 +1,64 @@ +#ifndef NULLSOFT_IN_MP3_METADATA +#define NULLSOFT_IN_MP3_METADATA + +#include "giofile.h" +#include "ID3v1.h" +#include "ID3v2.h" +#include "Lyrics3.h" +#include "apev2.h" + +enum +{ + METADATA_SUCCESS = 0, + SAVE_SUCCESS = 0, + SAVE_ERROR_OPENING_FILE = 1, + SAVE_ID3V1_WRITE_ERROR = 2, + SAVE_ID3V2_WRITE_ERROR = 3, + SAVE_ERROR_READONLY = 4, + SAVE_ERROR_CANT_OPEN_TEMPFILE = 5, + SAVE_ERROR_ERROR_OVERWRITING = 6, + SAVE_LYRICS3_WRITE_ERROR = 7, + SAVE_APEV2_WRITE_ERROR = 8, +}; + + +class Metadata +{ +public: + Metadata() {} + Metadata(CGioFile *_file, const wchar_t *_filename); + ~Metadata(); + + int Open(const wchar_t *filename); + int GetExtendedData(const char *tag, wchar_t *data, int dataLen); + int SetExtendedData(const char *tag, const wchar_t *data); + int Save(); + bool IsMe(const wchar_t *fn) { return filename && !_wcsicmp(filename, fn); } + + void AddRef() { InterlockedIncrement(&refs); } + void Release() { if(!InterlockedDecrement(&refs)) delete this; } + +private: + bool IsDirty(); + void ReadTags(CGioFile *_file); + int GetString(const char *tag, wchar_t *data, int dataLen); + + int sampleRate = 0; + int bitrate = 0; + int vbr = 0; + int channels = 0; + int length_ms = 0; + CGioFile file; + +public: + ID3v1 id3v1; + ID3v2 id3v2; + Lyrics3 lyrics3; + APE apev2; + + wchar_t *filename = 0; +protected: + volatile LONG refs = 1; +}; + +#endif
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mp3/MetadataFactory.cpp b/Src/Plugins/Input/in_mp3/MetadataFactory.cpp new file mode 100644 index 00000000..a95d0450 --- /dev/null +++ b/Src/Plugins/Input/in_mp3/MetadataFactory.cpp @@ -0,0 +1,62 @@ +#include "MetadataFactory.h" +#include "api__in_mp3.h" +#include "WasabiMetadata.h" + +static const char serviceName[] = "MP3 Stream Metadata Provider"; + +FOURCC MetadataFactory::GetServiceType() +{ + return MP3StreamMetadata::getServiceType(); +} + +const char *MetadataFactory::GetServiceName() +{ + return serviceName; +} + +GUID MetadataFactory::GetGUID() +{ + return MP3StreamMetadataGUID; +} + +void *MetadataFactory::GetInterface(int global_lock) +{ + return new MP3StreamMetadata; +} + +int MetadataFactory::SupportNonLockingInterface() +{ + return 1; +} + +int MetadataFactory::ReleaseInterface(void *ifc) +{ + //plugin.service->service_unlock(ifc); + svc_metaTag *metadata = static_cast<svc_metaTag *>(ifc); + MP3StreamMetadata *mp3metadata = static_cast<MP3StreamMetadata *>(metadata); + delete mp3metadata; + return 1; +} + +const char *MetadataFactory::GetTestString() +{ + return 0; +} + +int MetadataFactory::ServiceNotify(int msg, int param1, int param2) +{ + return 1; +} + +#define CBCLASS MetadataFactory +START_DISPATCH; +CB(WASERVICEFACTORY_GETSERVICETYPE, GetServiceType) +CB(WASERVICEFACTORY_GETSERVICENAME, GetServiceName) +CB(WASERVICEFACTORY_GETGUID, GetGUID) +CB(WASERVICEFACTORY_GETINTERFACE, GetInterface) +CB(WASERVICEFACTORY_SUPPORTNONLOCKINGGETINTERFACE, SupportNonLockingInterface) +CB(WASERVICEFACTORY_RELEASEINTERFACE, ReleaseInterface) +CB(WASERVICEFACTORY_GETTESTSTRING, GetTestString) +CB(WASERVICEFACTORY_SERVICENOTIFY, ServiceNotify) +END_DISPATCH; +#undef CBCLASS
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mp3/MetadataFactory.h b/Src/Plugins/Input/in_mp3/MetadataFactory.h new file mode 100644 index 00000000..e25c40e4 --- /dev/null +++ b/Src/Plugins/Input/in_mp3/MetadataFactory.h @@ -0,0 +1,24 @@ +#ifndef NULLSOFT_MP3_METADATAFACTORY_H +#define NULLSOFT_MP3_METADATAFACTORY_H + +#include <api/service/waservicefactory.h> +#include <api/service/services.h> + +class MetadataFactory : public waServiceFactory +{ +public: + FOURCC GetServiceType(); + const char *GetServiceName(); + GUID GetGUID(); + void *GetInterface(int global_lock); + int SupportNonLockingInterface(); + int ReleaseInterface(void *ifc); + const char *GetTestString(); + int ServiceNotify(int msg, int param1, int param2); + +protected: + RECVS_DISPATCH; +}; + + +#endif
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mp3/OFL.cpp b/Src/Plugins/Input/in_mp3/OFL.cpp new file mode 100644 index 00000000..230fa907 --- /dev/null +++ b/Src/Plugins/Input/in_mp3/OFL.cpp @@ -0,0 +1,165 @@ +#include "OFL.h" +#include "foundation/error.h" + +static void crcofl(unsigned short crcPoly, unsigned short crcMask, unsigned long *crc, unsigned char byte) +{ + int i; + for (i=0; i<8; i++) + { + unsigned short flag = (*crc) & crcMask ? 1:0; + flag ^= (byte & 0x80 ? 1 : 0); + (*crc)<<=1; + byte <<= 1; + if(flag) + (*crc) ^= crcPoly; + } +} + +int OFL::GetGaps(size_t *pregap, size_t *postgap) +{ + /* TODO: verify the postgap calculation */ + if (codec_delay >= 529) + { + *pregap = codec_delay; + size_t endcut; + endcut = samples_per_frame - ((total_length + codec_delay) % samples_per_frame); // how many 0 samples had to be added? + *postgap = endcut; + return NErr_Success; + } + return NErr_Empty; +} + + +double OFL::GetLengthSeconds() const +{ + return (double)GetSamples() / (double)sample_rate; +} + +uint64_t OFL::GetSamples() const +{ + return total_length; +} + +uint32_t OFL::GetFrames() const +{ + uint64_t real_samples = (total_length+codec_delay)*samples_per_frame; + return (uint32_t) (real_samples/samples_per_frame); +} + +int OFL::Read(const MPEGFrame &header, const uint8_t *buffer, size_t buffer_len) +{ + if (header.layer != MPEGFrame::Layer3) + return NErr_False; + + sample_rate = header.GetSampleRate(); + samples_per_frame = header.GetSamplesPerFrame(); + + if (header.channelMode == MPEGFrame::Mono) + { + if (header.mpegVersion == MPEGFrame::MPEG1) + { + // 0-9 : main_data_end + int16_t main_data_end = (buffer[0] << 1) | (buffer[1] >> 7); + + // read the 2 part2_3_lengths out so we know how big the main data section is + uint16_t part2_3_length = ((buffer[2] & 0x3F) << 6) | (buffer[3]>>2); // bits 18-30 + part2_3_length += ((buffer[9] & 0x7) << 9) | (buffer[10] << 1) | (buffer[11] >> 7) ; // bits 77-89 + + size_t offset = 17 + (part2_3_length+7)/8; + if (offset+9 < buffer_len && buffer[offset] == 0xb4) + { + unsigned long crc=255; + for (int i=0;i<9;i++) + crcofl(0x0045, 0x0080, &crc, buffer[offset+i]); + + if ((crc & 0xFF) == buffer[offset+9]) + { + total_length = (buffer[offset+3] << 24) | (buffer[offset+4] << 16) | (buffer[offset+5] << 8) | (buffer[offset+6]); + codec_delay = (buffer[offset+1] << 8) | (buffer[offset+2]); + additional_delay= (buffer[offset+7] << 8) | (buffer[offset+8]); + return NErr_Success; + } + } + } + else + { // MPEG2 and 2.5 + // 0-8 : main_data_end + uint16_t main_data_end = buffer[0]; + + // read the 2 part2_3_lengths out so we know how big the main data section is + uint16_t part2_3_length = ((buffer[1] & 0x7F) << 5) | (buffer[2]>>3); // bits 9-21 + + size_t offset = 9 + (part2_3_length+7)/8; + if (offset+9 < buffer_len && buffer[offset] == 0xb4) + { + unsigned long crc=255; + for (int i=0;i<9;i++) + crcofl(0x0045, 0x0080, &crc, buffer[offset+i]); + + if ((crc & 0xFF) == buffer[offset+9]) + { + total_length = (buffer[offset+3] << 24) | (buffer[offset+4] << 16) | (buffer[offset+5] << 8) | (buffer[offset+6]); + codec_delay = (buffer[offset+1] << 8) | (buffer[offset+2]); + additional_delay= (buffer[offset+7] << 8) | (buffer[offset+8]); + return NErr_Success; + } + } + } + } + else + { + if (header.mpegVersion == MPEGFrame::MPEG1) + { + // 0-9 : main_data_end + uint16_t main_data_end = (buffer[0] << 1) | (buffer[1] >> 7); + + // read the 4 part2_3_lengths out so we know how big the main data section is + uint16_t part2_3_length = ((buffer[2] & 0xF) << 8) | buffer[3]; // bits 20-32 + part2_3_length += ((buffer[9] & 0x1) << 11) | (buffer[10] << 3) | (buffer[11] >> 5) ; // bits 79-91 + part2_3_length += ((buffer[17] & 0x3F) << 6) | (buffer[18] >> 2); // bits 138-150 + part2_3_length += ((buffer[24] & 0x7) << 9) | (buffer[25] << 1) | (buffer[26] >> 7); // bits 197-209 + + size_t offset = 32 + (part2_3_length+7)/8; + if (offset+9 < buffer_len && buffer[offset] == 0xb4) + { + unsigned long crc=255; + for (int i=0;i<9;i++) + crcofl(0x0045, 0x0080, &crc, buffer[offset+i]); + + if ((crc & 0xFF) == buffer[offset+9]) + { + total_length = (buffer[offset+3] << 24) | (buffer[offset+4] << 16) | (buffer[offset+5] << 8) | (buffer[offset+6]); + codec_delay = (buffer[offset+1] << 8) | (buffer[offset+2]); + additional_delay= (buffer[offset+7] << 8) | (buffer[offset+8]); + return NErr_Success; + } + } + } + else + { // MPEG2 and 2.5 + // 0-8 : main_data_end + uint16_t main_data_end = buffer[0]; + + // read the 4 part2_3_lengths out so we know how big the main data section is + uint16_t part2_3_length = ((buffer[1] & 0x3F) << 6) | (buffer[2] >> 2); // bits 10-22 + part2_3_length += ((buffer[8] & 0x7) << 9) | (buffer[9] << 1) | (buffer[10] >> 7) ; // bits 69-81 + + size_t offset = 17 + (part2_3_length+7)/8; + if (offset+9 < buffer_len && buffer[offset] == 0xb4) + { + unsigned long crc=255; + for (int i=0;i<9;i++) + crcofl(0x0045, 0x0080, &crc, buffer[offset+i]); + + if ((crc & 0xFF) == buffer[offset+9]) + { + total_length = (buffer[offset+3] << 24) | (buffer[offset+4] << 16) | (buffer[offset+5] << 8) | (buffer[offset+6]); + codec_delay = (buffer[offset+1] << 8) | (buffer[offset+2]); + additional_delay= (buffer[offset+7] << 8) | (buffer[offset+8]); + return NErr_Success; + } + } + } + } + return NErr_False; +} diff --git a/Src/Plugins/Input/in_mp3/OFL.h b/Src/Plugins/Input/in_mp3/OFL.h new file mode 100644 index 00000000..11e787bf --- /dev/null +++ b/Src/Plugins/Input/in_mp3/OFL.h @@ -0,0 +1,21 @@ +#pragma once +#include "LAMEInfo.h" +#include <bfc/platform/types.h> +class OFL +{ +public: + int Read(const MPEGFrame &header, const uint8_t *buffer, size_t buffer_len); + double GetLengthSeconds() const; + uint64_t GetSamples() const; + uint32_t GetFrames() const; + int GetGaps(size_t *pregap, size_t *postgap); + +private: + int samples_per_frame; + uint32_t total_length; + uint16_t codec_delay; + uint16_t additional_delay; + + unsigned int sample_rate; +}; + diff --git a/Src/Plugins/Input/in_mp3/RawMediaReader.cpp b/Src/Plugins/Input/in_mp3/RawMediaReader.cpp new file mode 100644 index 00000000..5a532639 --- /dev/null +++ b/Src/Plugins/Input/in_mp3/RawMediaReader.cpp @@ -0,0 +1,84 @@ +#include "RawMediaReader.h" +#include <limits.h> + +bool IsMyExtension(const wchar_t *filename); + +int RawMediaReaderService::CreateRawMediaReader(const wchar_t *filename, ifc_raw_media_reader **out_reader) +{ + if (IsMyExtension(filename)) + { + CGioFile *file = new CGioFile(); + if (!file) + return NErr_OutOfMemory; + + if (file->Open(filename, 0) != NErr_Success) + { + delete file; + return NErr_FileNotFound; + } + + RawMediaReader *reader = new RawMediaReader(file); + if (!reader) + { + file->Close(); + delete file; + return NErr_OutOfMemory; + } + + *out_reader = reader; + return NErr_Success; + } + else + { + return NErr_False; + } +} + +#define CBCLASS RawMediaReaderService +START_DISPATCH; +CB(CREATERAWMEDIAREADER, CreateRawMediaReader); +END_DISPATCH; +#undef CBCLASS + +RawMediaReader::RawMediaReader(CGioFile *file) : file(file) +{} + +int RawMediaReader::Read( void *buffer, size_t buffer_size, size_t *bytes_read ) +{ + if ( buffer_size > INT_MAX ) + return NErr_BadParameter; + + int file_bytes_read = 0; + int ret = file->Read( buffer, (int)buffer_size, &file_bytes_read ); + + if ( ret == NErr_Success ) + { + *bytes_read = (size_t)file_bytes_read; + if ( !file_bytes_read && file->IsEof() ) + return NErr_EndOfFile; + + return NErr_Success; + } + else + return NErr_Error; +} + +size_t RawMediaReader::Release() +{ + file->Close(); + + delete file; + file = NULL; + + delete this; + + return 0; +} + + +#define CBCLASS RawMediaReader +START_DISPATCH; +CB( RELEASE, Release ); +CB( RAW_READ, Read ); +END_DISPATCH; +#undef CBCLASS diff --git a/Src/Plugins/Input/in_mp3/RawMediaReader.h b/Src/Plugins/Input/in_mp3/RawMediaReader.h new file mode 100644 index 00000000..5da1238c --- /dev/null +++ b/Src/Plugins/Input/in_mp3/RawMediaReader.h @@ -0,0 +1,30 @@ +#pragma once +#include "../Agave/DecodeFile/svc_raw_media_reader.h" +#include "../Agave/DecodeFile/ifc_raw_media_reader.h" +#include "giofile.h" + +// {5EC19CF3-E1ED-4AA5-AFD3-8E93149692BD} +static const GUID mpeg_audio_raw_reader_guid = +{ 0x5ec19cf3, 0xe1ed, 0x4aa5, { 0xaf, 0xd3, 0x8e, 0x93, 0x14, 0x96, 0x92, 0xbd } }; + +class RawMediaReaderService : public svc_raw_media_reader +{ +public: + static const char *getServiceName() { return "MPEG-1/2 Audio Raw Reader"; } + static GUID getServiceGuid() { return mpeg_audio_raw_reader_guid; } + int CreateRawMediaReader(const wchar_t *filename, ifc_raw_media_reader **reader); +protected: + RECVS_DISPATCH; +}; + +class RawMediaReader : public ifc_raw_media_reader +{ +public: + RawMediaReader(CGioFile *file); + int Read(void *buffer, size_t buffer_size, size_t *bytes_read); + size_t Release(); +protected: + RECVS_DISPATCH; +private: + CGioFile *file; +};
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mp3/Stopper.cpp b/Src/Plugins/Input/in_mp3/Stopper.cpp new file mode 100644 index 00000000..53c8d749 --- /dev/null +++ b/Src/Plugins/Input/in_mp3/Stopper.cpp @@ -0,0 +1,44 @@ +#include "Stopper.h" +#include "main.h" +#include "../Winamp/wa_ipc.h" + +Stopper::Stopper() : isplaying(0), timems(0) +{ +} + +void Stopper::ChangeTracking(bool mode) +{ + SendMessage(mod.hMainWindow, WM_USER, mode, IPC_ALLOW_PLAYTRACKING); // enable / disable stats updating +} + +void Stopper::Stop() +{ + isplaying = (int)SendMessage(mod.hMainWindow, WM_USER, 0, IPC_ISPLAYING); + if (isplaying) + { + ChangeTracking(0); // disable stats updating + timems = (int)SendMessage(mod.hMainWindow, WM_USER, 0, IPC_GETOUTPUTTIME); + SendMessage(mod.hMainWindow, WM_COMMAND, 40047, 0); // Stop + } +} + +void Stopper::Play() +{ + if (isplaying) // this works _most_ of the time, not sure why a small portion of the time it doesnt hrmph :/ + // ideally we should replace it with a system that pauses the decode thread, closes its file, + // does the shit, and reopens and reseeks to the new offset. for gaplessness + { + if (timems) + { + m_force_seek = timems; // SendMessage(mod.hMainWindow,WM_USER,timems,106); + } + else m_force_seek = -1; + SendMessage(mod.hMainWindow, WM_COMMAND, 40045, 0); // Play + m_force_seek = -1; + if (isplaying & 2) + { + SendMessage(mod.hMainWindow, WM_COMMAND, 40046, 0); // Pause + } + ChangeTracking(1); // enable stats updating + } +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mp3/Stopper.h b/Src/Plugins/Input/in_mp3/Stopper.h new file mode 100644 index 00000000..05d5ac6f --- /dev/null +++ b/Src/Plugins/Input/in_mp3/Stopper.h @@ -0,0 +1,10 @@ +#pragma once +class Stopper +{ +public: + Stopper(); + void ChangeTracking(bool); + void Stop(); + void Play(); + int isplaying, timems; +};
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mp3/StreamInfo.cpp b/Src/Plugins/Input/in_mp3/StreamInfo.cpp new file mode 100644 index 00000000..1a550255 --- /dev/null +++ b/Src/Plugins/Input/in_mp3/StreamInfo.cpp @@ -0,0 +1,9 @@ +#include "main.h" +#include "MP3Info.h" + +StreamInfo::StreamInfo(void *buffer) : ID3Info() +{ + unsigned __int8 *header = (unsigned __int8 *)buffer; + da_tag.Parse(header, &header[ID3_TAGHEADERSIZE]); + GetID3V2Values(); +} diff --git a/Src/Plugins/Input/in_mp3/WasabiMetadata.cpp b/Src/Plugins/Input/in_mp3/WasabiMetadata.cpp new file mode 100644 index 00000000..15838b7f --- /dev/null +++ b/Src/Plugins/Input/in_mp3/WasabiMetadata.cpp @@ -0,0 +1,67 @@ +#include "WasabiMetadata.h" +#include <shlwapi.h> +#include "../nu/AutoChar.h" + +const wchar_t *MP3StreamMetadata::GetName() +{ + return L"MP3 Stream Metadata"; +} + +GUID MP3StreamMetadata::getGUID() +{ + return MP3StreamMetadataGUID; +} + +int MP3StreamMetadata::getFlags() +{ + return METATAG_FILE_INFO; +} + +int MP3StreamMetadata::isOurFile(const wchar_t *filename) +{ + if (PathIsURL(filename) && !_wcsicmp(PathFindExtension(filename), L".mp3")) + return 1; + else + return 0; +} + +int MP3StreamMetadata::metaTag_open(const wchar_t *filename) +{ + if (metadata.Open(filename) == METADATA_SUCCESS) + return METATAG_SUCCESS; + else + return METATAG_FAILED; +} + +void MP3StreamMetadata::metaTag_close() +{ + delete this; +} + +int MP3StreamMetadata::getMetaData(const wchar_t *tag, __int8 *buf, int buflenBytes, int datatype) +{ + if (datatype == METATYPE_STRING) + { + if (metadata.GetExtendedData(AutoChar(tag), (wchar_t *)buf, buflenBytes/sizeof(wchar_t))) + return METATAG_SUCCESS; + else + return METATAG_UNKNOWN_TAG; + } + else + return METATAG_FAILED; +} + +#define CBCLASS MP3StreamMetadata +START_DISPATCH; +CB(SVC_METATAG_GETNAME,getName) +CB(SVC_METATAG_GETGUID,getGUID) +CB(SVC_METATAG_GETFLAGS,getFlags) +CB(SVC_METATAG_ISOURFILE,isOurFile) +CB(SVC_METATAG_OPEN,metaTag_open) +VCB(SVC_METATAG_CLOSE,metaTag_close) +//CB(SVC_METATAG_ENUMTAGS,enumSupportedTag) +//CB(SVC_METATAG_GETTAGSIZE,getTagSize) +CB(SVC_METATAG_GETMETADATA,getMetaData) +//CB(SVC_METATAG_SETMETADATA,setMetaData) +END_DISPATCH; +#undef CBCLASS
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mp3/WasabiMetadata.h b/Src/Plugins/Input/in_mp3/WasabiMetadata.h new file mode 100644 index 00000000..dcd5dc24 --- /dev/null +++ b/Src/Plugins/Input/in_mp3/WasabiMetadata.h @@ -0,0 +1,30 @@ +#pragma once +#include "../Agave/Metadata/svc_metatag.h" +#include "Metadata.h" + +// {9937E02D-205B-4964-86A9-F784D9C05F5D} +static const GUID MP3StreamMetadataGUID = + { 0x9937e02d, 0x205b, 0x4964, { 0x86, 0xa9, 0xf7, 0x84, 0xd9, 0xc0, 0x5f, 0x5d } }; + +class MP3StreamMetadata : public svc_metaTag +{ +private: + /* These methods are to be used by api_metadata */ + const wchar_t *GetName(); + GUID getGUID(); // this needs to be the same GUID that you use when registering your service factory + int getFlags(); // how this service gets its info + int isOurFile(const wchar_t *filename); + int metaTag_open(const wchar_t *filename); + void metaTag_close(); // self-destructs when this is called (you don't need to call serviceFactory->releaseInterface) + + /* user API starts here */ + const wchar_t *enumSupportedTag(int n, int *datatype = NULL); // returns a list of understood tags. might not be complete (see note [1]) + int getTagSize(const wchar_t *tag, size_t *sizeBytes); // always gives you BYTES, not characters (be careful with your strings) + int getMetaData(const wchar_t *tag, __int8 *buf, int buflenBytes, int datatype = METATYPE_STRING); // buflen is BYTES, not characters (be careful with your strings) + int setMetaData(const wchar_t *tag, const __int8 *buf, int buflenBytes, int datatype = METATYPE_STRING); +private: + Metadata metadata; + + RECVS_DISPATCH; +}; + diff --git a/Src/Plugins/Input/in_mp3/about.cpp b/Src/Plugins/Input/in_mp3/about.cpp new file mode 100644 index 00000000..7bc3ae04 --- /dev/null +++ b/Src/Plugins/Input/in_mp3/about.cpp @@ -0,0 +1,576 @@ +#include <windows.h> +#include "main.h" +#if 1 +BOOL CALLBACK AboutProc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam) +{ + if (uMsg == WM_COMMAND && (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL)) EndDialog(hwndDlg,0); + return 0; +} + +#else +#include <commctrl.h> + +#include ".\graphics\image.h" +#include ".\graphics\imagefilters.h" +#include <strsafe.h> + + + +#define random( min, max ) (( rand() % (int)((( max ) + 1 ) - ( min ))) + ( min )) + +HBITMAP LoadImageFromResource(INT_PTR handle); + +#define LEGAL_COUNT 4 +wchar_t *legal[] = {L"Copyright (C) 1998-2006 - Nullsoft, Inc.", + L"MPEG Layer-3 audio compression technology licensed by Fraunhofer IIS and THOMSON multimedia.", + L"VLB decoding copyright 1998-2002 by Dolby Laboratories, Inc. All rights reserved.", + L"AAC && aacPlus decoding copyright 1998-2006 by Coding Technologies, Inc. All rights reserved."}; + +HFONT fntPlugin, fntC, fntLegal; +HBRUSH brhDlgBG; + +#define IMAGES_COUNT 6 +#define IMAGES_INDEX_MY 0 +#define IMAGES_INDEX_WA 1 +#define IMAGES_INDEX_CT 2 +#define IMAGES_INDEX_IIS 3 +#define IMAGES_INDEX_ID3V2 4 +#define IMAGES_INDEX_MP3S 5 + +RECT rectLogo[IMAGES_COUNT]; +MLImage *imgLogo[IMAGES_COUNT] = {NULL, NULL, NULL, NULL, NULL, NULL}; +MLImage *imgLlama = NULL, *imgLlamaOrig = NULL, *imgMy = NULL; +RECT rcLlama; + +int idxSelected; + +wchar_t *url[IMAGES_COUNT - 1] = { L"http://winamp.com/", + L"http://www.codingtechnologies.com/index.htm", + L"http://www.iis.fraunhofer.de/index.html", + L"http://www.id3.org/", + L"http://www.iis.fraunhofer.de/amm/download/mp3surround/index.html"}; + +HWND hwndTT; +wchar_t strTT[] = L"click here to visit this website"; + +#define TIMER_ID_WATERRENDER 1980 +#define TIMER_ID_WATERPULSE 1978 +#define TIMER_DELAY_WATERRENDER 48 +#define TIMER_DELAY_WATERPULSE 12000 +#define TIMER_ID_LLAMAFADE 1987 +#define TIMER_DELAY_LLAMAFADE 200 + +#define TIMER_ID_LOADDATA 1959 +#define TIMER_DELAY_LOADDATA 10 +MLImage *tmpImage = NULL; + +MLImageFilterWater *fltrWater = NULL; +HCURSOR curHand = NULL; + +void about_OnInit(HWND hwndDlg); +void about_OnDestroy(HWND hwndDlg); +void about_OnMouseDown(HWND hwndDlg, int cx, int cy); +void about_OnMouseMove(HWND hwndDlg, int cx, int cy); +void about_OnDraw(HWND hwndDlg); +void timer_OnWaterPulse(HWND hwndDlg); +void timer_OnLlamaFade(HWND hwndDlg); +void timer_OnLoadData(HWND hwndDlg); + + +void SetRects(int cx, int cy) +{ + int i, xl, yl; + + i = IMAGES_INDEX_MY; xl = (cx - imgLogo[i]->GetWidth())/2; yl = 64; + if (imgLogo[i]) SetRect(&rectLogo[i], xl, yl, xl + imgLogo[i]->GetWidth(), yl + imgLogo[i]->GetHeight()); + xl = 2; yl = 2; i = IMAGES_INDEX_WA; + if (imgLogo[i]) SetRect(&rectLogo[i], xl, yl, xl + imgLogo[i]->GetWidth(), yl + imgLogo[i]->GetHeight()); + xl = 2; yl = cy - 88; i = IMAGES_INDEX_CT; + if (imgLogo[i]) SetRect(&rectLogo[i], xl, yl, xl + imgLogo[i]->GetWidth(), yl + imgLogo[i]->GetHeight()); + xl = rectLogo[i].right; i = IMAGES_INDEX_IIS; + if (imgLogo[i]) SetRect(&rectLogo[i], xl, yl, xl + imgLogo[i]->GetWidth(), yl + imgLogo[i]->GetHeight()); + xl = rectLogo[i].right; i = IMAGES_INDEX_ID3V2; + if (imgLogo[i]) SetRect(&rectLogo[i], xl, yl, xl + imgLogo[i]->GetWidth(), yl + imgLogo[i]->GetHeight()); + xl = rectLogo[i].right; i = IMAGES_INDEX_MP3S; + if (imgLogo[i]) SetRect(&rectLogo[i], xl, yl, xl + imgLogo[i]->GetWidth(), yl + imgLogo[i]->GetHeight()); + + if(imgLlama) SetRect(&rcLlama, 2, 1, imgLlama->GetWidth() + 2, imgLlama->GetHeight() + 1); +} +void CreateToolTipWnd(HWND hwndDlg) +{ + INITCOMMONCONTROLSEX iccex; + iccex.dwICC = ICC_WIN95_CLASSES; + iccex.dwSize = sizeof(INITCOMMONCONTROLSEX); + InitCommonControlsEx(&iccex); + + hwndTT = CreateWindowExW(WS_EX_TOPMOST, TOOLTIPS_CLASSW, NULL, WS_POPUP | TTS_NOPREFIX | TTS_ALWAYSTIP, + CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, hwndDlg, NULL, mod.hDllInstance, NULL); + + SetWindowPos(hwndTT, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE); +} + +void UpdateToolTips(HWND hwndDlg) +{ + TOOLINFOW ti; + unsigned int uid = 0; + + // delete tools + int ttCount = SendMessage(hwndTT, TTM_GETTOOLCOUNT, 0, 0); + for(int i = 0; i < ttCount; i++) + { + if (SendMessageW(hwndTT, TTM_ENUMTOOLSW, (WPARAM)i, (LPARAM)&ti)) SendMessageW(hwndTT, TTM_DELTOOLW, 0, (LPARAM)&ti); + } + + /// add tools + + for (int i = 1; i < IMAGES_COUNT -1; i++) + { + ti.cbSize = sizeof(TOOLINFO); + ti.uFlags = TTF_SUBCLASS; + ti.hwnd = hwndDlg; + ti.hinst = mod.hDllInstance; + ti.uId = uid; + ti.lpszText = strTT; + ti.rect = rectLogo[i]; + SendMessageW(hwndTT, TTM_ADDTOOLW, 0, (LPARAM) (LPTOOLINFO) &ti); + } +} +BOOL CALLBACK AboutProc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam) +{ + switch(uMsg) + { + case WM_INITDIALOG: + about_OnInit(hwndDlg); + break; + case WM_DESTROY: + about_OnDestroy(hwndDlg); + break; + case WM_COMMAND: + if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL) + { + EndDialog(hwndDlg,0); + } + break; + case WM_SIZE: + if (wParam != SIZE_MINIMIZED) + { + SetRects(LOWORD(lParam), HIWORD(lParam)); + UpdateToolTips(hwndDlg); + } + case WM_MOUSEMOVE: + about_OnMouseMove(hwndDlg, LOWORD(lParam), HIWORD(lParam)); + break; + case WM_LBUTTONDOWN: + about_OnMouseDown(hwndDlg, LOWORD(lParam), HIWORD(lParam)); + break; + case WM_PAINT: + about_OnDraw(hwndDlg); + break; + case WM_TIMER: + switch(wParam) + { + case TIMER_ID_WATERRENDER: + if (idxSelected != -1 && fltrWater) + { + fltrWater->Render(tmpImage, imgLogo[idxSelected]); + InvalidateRect(hwndDlg, &rectLogo[idxSelected], FALSE); + } + break; + case TIMER_ID_WATERPULSE: + timer_OnWaterPulse(hwndDlg); + break; + case TIMER_ID_LLAMAFADE: + timer_OnLlamaFade(hwndDlg); + break; + case TIMER_ID_LOADDATA: + timer_OnLoadData(hwndDlg); + break; + } + break; + case WM_CTLCOLORDLG: + return (BOOL)brhDlgBG; + break; + } + return 0; +} +void about_OnInit(HWND hwndDlg) +{ + HDC hdc = GetDC(hwndDlg); + fntPlugin = CreateFontW(-MulDiv(20, GetDeviceCaps(hdc, LOGPIXELSY), 72), 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, L"Microsoft Sans Serif"); + fntC = CreateFontW(-MulDiv(8, GetDeviceCaps(hdc, LOGPIXELSY), 72), 0, 0, 0, 0, TRUE, 0, 0, 0, 0, 0, ANTIALIASED_QUALITY, 0, L"Arial"); + fntLegal = CreateFontW(-MulDiv(6, GetDeviceCaps(hdc, LOGPIXELSY), 72), 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ANTIALIASED_QUALITY, 0, L"Microsoft Sans Serif"); + ReleaseDC(hwndDlg, hdc); + + brhDlgBG = CreateSolidBrush(RGB(255,255,255)); + + SetTimer(hwndDlg, TIMER_ID_LOADDATA, TIMER_DELAY_LOADDATA, NULL); +} + +void about_OnDestroy(HWND hwndDlg) +{ + for (int i = 0; i < IMAGES_COUNT; i++) + { + if (imgLogo[i]) delete(imgLogo[i]); + imgLogo[i] = NULL; + } + + if (imgLlama) delete(imgLlama); + imgLlama = NULL; + + if (imgLlamaOrig) delete(imgLlamaOrig); + imgLlamaOrig = NULL; + + if (imgMy) delete(imgMy); + imgMy = NULL; + + if (fntPlugin) DeleteObject(fntPlugin); + if (fntC) DeleteObject(fntC); + if (fntLegal) DeleteObject(fntLegal); + fntPlugin = NULL; + fntC = NULL; + fntLegal = NULL; + + if (brhDlgBG) DeleteObject(brhDlgBG); + brhDlgBG = NULL; + + if (fltrWater) delete (fltrWater); + fltrWater = NULL; + + if (tmpImage) delete(tmpImage); + tmpImage = NULL; + + if (curHand) DestroyCursor(curHand); + curHand = NULL; +} + +void about_OnMouseDown(HWND hwndDlg, int cx, int cy) +{ + + if (idxSelected == -1) return; + + HCURSOR curWait = (HCURSOR)LoadImage(NULL, MAKEINTRESOURCE(32514/*OCR_WAIT*/), IMAGE_CURSOR, 0, 0, LR_DEFAULTCOLOR); + SetCursor(curWait); + ShellExecuteW(hwndDlg, L"open", url[idxSelected -1], NULL, L"c:\\", SW_SHOW); + SetCursor(curHand); + DestroyCursor(curWait); +} +void about_OnMouseMove(HWND hwndDlg, int cx, int cy) +{ + POINT pt = {cx, cy}; + int idxNew = -1; + for (int i = 1; i < IMAGES_COUNT - 1; i ++) + { + if (PtInRect(&rectLogo[i], pt)) + { + if (!curHand) + { + curHand = (HCURSOR)LoadImage(mod.hDllInstance, MAKEINTRESOURCE(IDC_CUR_HAND), IMAGE_CURSOR, 0, 0, LR_DEFAULTCOLOR); + } + SetCursor(curHand); + idxNew = i; + } + } + if (idxNew != idxSelected) + { + // stop animation + KillTimer(hwndDlg, TIMER_ID_WATERPULSE); + KillTimer(hwndDlg, TIMER_ID_WATERRENDER); + + if (idxSelected != -1) // redraw previously animated + { + InvalidateRect(hwndDlg, &rectLogo[idxSelected], FALSE); + } + // set new one + idxSelected = idxNew; + if (fltrWater) delete(fltrWater); + fltrWater = NULL; + if (tmpImage) delete(tmpImage); + tmpImage = NULL; + if (idxSelected != -1 && idxSelected != IMAGES_INDEX_WA) SetTimer(hwndDlg, TIMER_ID_WATERPULSE, 30, NULL); // start delay + } +} +void about_OnDraw(HWND hwndDlg) +{ + PAINTSTRUCT ps; + HDC hdc; + hdc = BeginPaint(hwndDlg, &ps); + RECT rc, ri; + GetClientRect(hwndDlg, &rc); + + // Draw Llama + RECT rl; + SetRect(&rl, rcLlama.left, rcLlama.top, rcLlama.right, rcLlama.bottom); + if (imgLlama && IntersectRect(&ri, &rl, &ps.rcPaint)) + { + HRGN hrgn = CreateRectRgn(rcLlama.left, rcLlama.top, rcLlama.right, rcLlama.bottom); + + HRGN hrgn1 = CreateRectRgn(rectLogo[IMAGES_INDEX_MY].left, + rectLogo[IMAGES_INDEX_MY].top, + rectLogo[IMAGES_INDEX_MY].right, + rectLogo[IMAGES_INDEX_MY].bottom); + CombineRgn(hrgn, hrgn, hrgn1, RGN_DIFF); + DeleteObject(hrgn1); + hrgn1 = CreateRectRgn(rectLogo[IMAGES_INDEX_WA].left, + rectLogo[IMAGES_INDEX_WA].top, + rectLogo[IMAGES_INDEX_WA].right, + rectLogo[IMAGES_INDEX_WA].bottom); + CombineRgn(hrgn, hrgn, hrgn1, RGN_DIFF); + SelectClipRgn(hdc, hrgn); + DeleteObject(hrgn); + DeleteObject(hrgn1); + + imgLlama->Draw(hdc, rl.left, rl.top); + SelectClipRgn(hdc, NULL); + } + + MLImage *img; + for (int i = 0; i < IMAGES_COUNT -1; i++) + { + + if (IntersectRect(&ri, &rectLogo[i], &ps.rcPaint)) + { + if (idxSelected == i && tmpImage) + { + img = tmpImage; + HRGN hrgn = CreateRectRgn(rectLogo[i].left + 3, rectLogo[i].top + 3 , rectLogo[i].right - 3, rectLogo[i].bottom - 3); + SelectClipRgn(hdc, hrgn); + DeleteObject(hrgn); + } + else img = imgLogo[i]; + if (img == NULL || imgLlama == NULL) continue; + if (i == IMAGES_INDEX_MY) + { // blend Llama First + MLImageFilter_Blend1(imgMy, img, 0, 0, + imgLlama->GetWidth() - (rectLogo[i].left - rcLlama.left), img->GetHeight(), imgLlama, + rectLogo[i].left - rcLlama.left, rectLogo[i].top - rcLlama.top, RGB(255,255,255)); + img = imgMy; + } + img->Draw(hdc, rectLogo[i].left, rectLogo[i].top); + SelectClipRgn(hdc, NULL); + } + } + + RECT rt; + SetBkMode(hdc, TRANSPARENT); + rc.left += 6; + rc.bottom -= 2; + rc.right -= 4; + + HFONT oldF = NULL; + + SetRect(&rt, rc.right - 192, rc.top, rc.right, rc.top + 16); + if (IntersectRect(&ri, &rt, &ps.rcPaint)) + { + /// Nullsoft (c) + HFONT oldF = (HFONT)SelectObject(hdc, fntC); + SetTextColor(hdc, RGB(100,100,200)); + DrawTextW(hdc, legal[0], -1, &rt, DT_LEFT | DT_SINGLELINE); + } + + SetRect(&rt, rc.left - 2, rc.bottom - 33, rc.right, rc.bottom); + if (IntersectRect(&ri, &rt, &ps.rcPaint)) + { + /// Separator + MoveToEx(hdc, rt.left, rt.top, NULL); + HPEN lp = CreatePen(PS_SOLID,1, RGB(190, 190, 190)); + HPEN op = (HPEN)SelectObject(hdc, lp); + LineTo(hdc, rt.right, rt.top); + SelectObject(hdc, lp); + DeleteObject(lp); + + /// Legal... + oldF = (oldF != NULL) ? oldF : (HFONT)SelectObject(hdc, fntLegal); + SetTextColor(hdc, RGB(0,0,0)); + for(int i = 1; i < LEGAL_COUNT; i++) + { + int y = rc.bottom - (LEGAL_COUNT - i)*10; + SetRect(&rt, rc.left, y, rc.right, y +10); + if (IntersectRect(&ri, &rt, &ps.rcPaint)) + DrawTextW(hdc, legal[i], -1, &rt, DT_LEFT | DT_SINGLELINE); + } + } + if (oldF != NULL) SelectObject(hdc, oldF); + EndPaint(hwndDlg, &ps); +} +void timer_OnLoadData(HWND hwndDlg) +{ + static step = 0; + KillTimer(hwndDlg, TIMER_ID_LOADDATA); + RECT rc; + GetClientRect(hwndDlg, &rc); + for (int i = 0; i < IMAGES_COUNT; i++) + { + imgLogo[i] = new MLImage(LoadImageFromResource, TRUE); + imgLogo[i]->Load(); + } + + imgMy = new MLImage(); + MLImage::Copy(imgMy, imgLogo[IMAGES_INDEX_MY]); + + imgLlamaOrig = new MLImage(LoadImageFromResource, TRUE); + imgLlamaOrig->Load(); + imgLlama = new MLImage(); + MLImage::Copy(imgLlama, imgLlamaOrig); + CreateToolTipWnd(hwndDlg); + UpdateToolTips(hwndDlg); + idxSelected = -1; + SetRects(rc.right - rc.left, rc.bottom - rc.top); + InvalidateRect(hwndDlg, &rc, FALSE); +// SetTimer(hwndDlg, TIMER_ID_LLAMAFADE, TIMER_DELAY_LLAMAFADE, NULL); + +} +void timer_OnWaterPulse(HWND hwndDlg) +{ + if (idxSelected == -1) return; + + BOOL startRender = (!fltrWater); + + if(!fltrWater) + { + KillTimer(hwndDlg, TIMER_ID_WATERPULSE); // stop timer - will change to slower interval + fltrWater = new MLImageFilterWater(); + fltrWater->CreateFor(imgLogo[idxSelected]); + tmpImage = new MLImage(imgLogo[idxSelected]->GetWidth(), imgLogo[idxSelected]->GetHeight()); + } + + int ow = imgLogo[idxSelected]->GetWidth(); + int oh = imgLogo[idxSelected]->GetHeight(); + + for (int i = 0; i < oh*ow/1024; i++) + { + fltrWater->SineBlob(-1, -1, random(4,min(oh,ow)), 600, 0); + } + + if (startRender) + { + SetTimer(hwndDlg, TIMER_ID_WATERRENDER, TIMER_DELAY_WATERRENDER, NULL); + SetTimer(hwndDlg, TIMER_ID_WATERPULSE, TIMER_DELAY_WATERPULSE, NULL); + } +} +void timer_OnLlamaFade(HWND hwndDlg) +{ + static int c = -2; + static int step = 2; + c = c + step; + if (c > 30 && step > 0) {step = 0 - step; c = 30;} + else if (c < 0 && step < 0) { step = 0 - step; c = 0; } + MLImageFilter_Fader3(imgLlama, imgLlamaOrig, c); + InvalidateRect(hwndDlg, &rcLlama, FALSE); +} + +HBITMAP LoadImageFromResource(INT_PTR handle) +{ + int id = 0; + BOOL incSize; + MLImage *img = (MLImage*)handle; + + if (img == imgLogo[IMAGES_INDEX_WA]) {id = IDB_LOGO_WAPLUGINS; incSize = FALSE;} + else if (img == imgLogo[IMAGES_INDEX_IIS]) {id = IDB_LOGO_IIS; incSize = TRUE;} + else if (img == imgLogo[IMAGES_INDEX_CT]) {id = IDB_LOGO_CODETECH; incSize = TRUE;} + else if (img == imgLogo[IMAGES_INDEX_ID3V2]) {id = IDB_LOGO_ID3V2; incSize = TRUE;} + else if (img == imgLogo[IMAGES_INDEX_MP3S]) {id = IDB_LOGO_MP3SURROUND; incSize = FALSE;} // because we don't use it for now + else if (img == imgLogo[IMAGES_INDEX_MY]) {id = IDB_LOGO_MY; incSize = FALSE;} + else if (img == imgLlamaOrig) {id = IDB_LLAMA; incSize = FALSE;} + if (id == 0) return NULL; + + HBITMAP hbmp = LoadBitmap(mod.hDllInstance, MAKEINTRESOURCE(id)); + if (hbmp == NULL) return hbmp; + + if (incSize) + {// we want to add additional white border + HDC hdcWnd = GetWindowDC(NULL); + HDC hdcSrc = CreateCompatibleDC(hdcWnd); + HDC hdcDest = CreateCompatibleDC(hdcWnd); + BITMAP bmp; + GetObject(hbmp, sizeof(BITMAP), &bmp); + int extraW = 40; + int extraH = 16; + HBITMAP hbmpNew = CreateBitmap(bmp.bmWidth + extraW, bmp.bmHeight + extraH, bmp.bmPlanes, bmp.bmBitsPixel, NULL); + + SelectObject(hdcSrc, hbmp); + SelectObject(hdcDest, hbmpNew); + + // fill borders + HBRUSH br = CreateSolidBrush(RGB(255,255,255)); + RECT rc; + SetRect(&rc, 0,0, bmp.bmWidth + extraW, extraH/2); + FillRect(hdcDest, &rc, br); + SetRect(&rc, 0, bmp.bmHeight + extraH/2, bmp.bmWidth + extraW, bmp.bmHeight + extraH); + FillRect(hdcDest, &rc, br); + SetRect(&rc, 0, extraH/2, extraW/2, bmp.bmHeight + extraH/2); + FillRect(hdcDest, &rc, br); + SetRect(&rc, bmp.bmWidth + extraW/2, extraH/2, bmp.bmWidth + extraW, bmp.bmHeight + extraH/2); + FillRect(hdcDest, &rc, br); + // copy original + BitBlt(hdcDest, extraW/2, extraH/2, bmp.bmWidth, bmp.bmHeight, hdcSrc, 0,0, SRCCOPY); + + DeleteObject(br); + DeleteObject(hbmp); + DeleteDC(hdcSrc); + DeleteDC(hdcDest); + ReleaseDC(NULL, hdcWnd); + + hbmp = hbmpNew; + } + if (img == imgLogo[IMAGES_INDEX_MY]) + { // need to add vesion number + HDC hdcWnd = GetWindowDC(NULL); + HDC hdcSrc = CreateCompatibleDC(hdcWnd); + HDC hdcDest = CreateCompatibleDC(hdcWnd); + + HFONT fnt = CreateFontW(-MulDiv(90, GetDeviceCaps(hdcDest, LOGPIXELSY), 72), 22, 0, 0, FW_BOLD, 0, 0, 0, 0, 0, 0, ANTIALIASED_QUALITY, 0, L"Agency FB"); + SelectObject(hdcDest, fnt); + + char *ver = mod.description + lstrlen(mod.description); + while(ver != mod.description && *ver != ' ') + { + ver = CharPrevA(mod.description, ver); + } + if (*ver == ' ') ver++; + + RECT rc; + SetRect(&rc, 0, 0, 0, 0); + DrawText(hdcDest, ver, -1, &rc, DT_CALCRECT | DT_RIGHT |DT_SINGLELINE); + + int extraW = rc.right + 6; + int extraH = 16; + + BITMAP bmp; + GetObject(hbmp, sizeof(BITMAP), &bmp); + HBITMAP hbmpNew = CreateBitmap(bmp.bmWidth + extraW, bmp.bmHeight + extraH, bmp.bmPlanes, bmp.bmBitsPixel, NULL); + + SelectObject(hdcSrc, hbmp); + SelectObject(hdcDest, hbmpNew); + + // fill new area + HBRUSH br = CreateSolidBrush(RGB(255,255,255)); + + SetRect(&rc, bmp.bmWidth,0, bmp.bmWidth + extraW, bmp.bmHeight + extraH); + FillRect(hdcDest, &rc, br); + SetRect(&rc, 0,0, bmp.bmWidth, extraH); + FillRect(hdcDest, &rc, br); + // copy original + BitBlt(hdcDest, 0, extraH, bmp.bmWidth, bmp.bmHeight, hdcSrc, 0,0, SRCCOPY); + // draw number + + SetTextColor(hdcDest, RGB(80, 60, 10)); + SetBkMode(hdcDest, TRANSPARENT); + SetRect(&rc, bmp.bmWidth + 6, rc.top -= 22, bmp.bmWidth + extraW, bmp.bmHeight + extraH); + DrawText(hdcDest, ver, -1, &rc, DT_RIGHT |DT_SINGLELINE); + DeleteObject(fnt); + DeleteObject(br); + DeleteObject(hbmp); + DeleteDC(hdcSrc); + DeleteDC(hdcDest); + ReleaseDC(NULL, hdcWnd); + + hbmp = hbmpNew; + + } + + + return hbmp; + +} + +#endif
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mp3/adts.h b/Src/Plugins/Input/in_mp3/adts.h new file mode 100644 index 00000000..d6e4fb95 --- /dev/null +++ b/Src/Plugins/Input/in_mp3/adts.h @@ -0,0 +1,37 @@ +#ifndef NULLSOFT_IN_MP3_ADTS_H +#define NULLSOFT_IN_MP3_ADTS_H + +#include "ifc_mpeg_stream_reader.h" +#include <bfc/std_mkncc.h> // for MKnCC() +class adts +{ +protected: + adts() {} + ~adts() {} +public: + static FOURCC getServiceType() { return MK4CC('a','d','t','s'); } + virtual int Initialize(bool forceMono, bool reverseStereo, bool allowSurround, int maxBits, bool allowRG, bool _useFloat = false, bool _useCRC = false)=0; + virtual bool Open(ifc_mpeg_stream_reader *file)=0; + virtual int Sync(ifc_mpeg_stream_reader *file, unsigned __int8 *output, size_t outputSize, size_t *outputWritten, size_t *bitrate)=0; + virtual void CalculateFrameSize(int *frameSize)=0; + virtual void GetOutputParameters(size_t *numBits, int *numChannels, int *sampleRate)=0; + virtual void Flush(ifc_mpeg_stream_reader *file)=0; + virtual void Close() = 0; + + enum + { + SUCCESS = 0, + FAILURE=1, + ENDOFFILE = 2, + NEEDMOREDATA = 3, + NEEDSYNC = 4, + }; + virtual int Decode(ifc_mpeg_stream_reader *file, unsigned __int8 *output, size_t outputSize, size_t *outputWritten, size_t *bitrate, size_t *endCut)=0; + virtual size_t GetCurrentBitrate()=0; + virtual size_t GetDecoderDelay()=0; + virtual int GetLayer()=0; + virtual void Release()=0; + virtual void SetDecoderHooks(void *layer3_vis, void *layer2_eq, void *layer3_eq) {} +}; + +#endif
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mp3/adts_mp2.cpp b/Src/Plugins/Input/in_mp3/adts_mp2.cpp new file mode 100644 index 00000000..0ad9858e --- /dev/null +++ b/Src/Plugins/Input/in_mp3/adts_mp2.cpp @@ -0,0 +1,400 @@ +#include "main.h" +#include "adts_mp2.h" +#include "../winamp/wa_ipc.h" +#include <math.h> +#include "mpegutil.h" +#include "../nsutil/pcm.h" + +extern int g_ds; + +<<<<<<< HEAD:in_mp3/adts_mp2.cpp +DecoderHooks hooks={mp3GiveVisData, mp2Equalize, mp3Equalize}; + +======= +>>>>>>> 5058463... fix old-school vis/eq mp3 stuff:mp3/adts_mp2.cpp +ADTS_MP2::ADTS_MP2() : decoder(0), gain(1.f) +{ + memset(&hooks, 0, sizeof(hooks)); +#ifndef NO_MP3SURROUND + lineFilled=false; + saDecHandle=0; + saMode = SA_DEC_OFF; +#endif + decoderDelay = 529; + endcut=0; + + outputFrameSize = 0; + bitsPerSample = 0; + allowRG = false; + useFloat = false; + channels = 0; + sampleRate = 0; + + memset(&delayline, 0, sizeof(delayline)); + delaylineSize = 0; +} + +void ADTS_MP2::SetDecoderHooks(void *layer3_vis, void *layer2_eq, void *layer3_eq) +{ +<<<<<<< HEAD:in_mp3/adts_mp2.cpp + //*(void **)&hooks.layer3_vis = layer3_vis; +======= + *(void **)&hooks.layer3_vis = layer3_vis; +>>>>>>> 5058463... fix old-school vis/eq mp3 stuff:mp3/adts_mp2.cpp + *(void **)&hooks.layer2_eq = layer2_eq; + *(void **)&hooks.layer3_eq = layer3_eq; +} + +int ADTS_MP2::Initialize(bool forceMono, bool reverseStereo, bool allowSurround, int maxBits, bool _allowRG, bool _useFloat, bool _useCRC) +{ + allowRG = _allowRG; + useFloat = _useFloat; + int downmix = 0; + if (reverseStereo) + downmix = 2; + else + downmix = forceMono ? 1 : 0; + bitsPerSample = maxBits; +<<<<<<< HEAD:in_mp3/adts_mp2.cpp + decoder = new CMpgaDecoder(&hooks, g_ds, downmix, !!_useCRC); + +======= + decoder = new CMpgaDecoder(hooks.layer3_vis?&hooks:(DecoderHooks *)0, MPEGAUDIO_QUALITY_FULL/*g_ds*/, downmix, _useCRC); +>>>>>>> 5058463... fix old-school vis/eq mp3 stuff:mp3/adts_mp2.cpp +#ifndef NO_MP3SURROUND + if (allowSurround) + IIS_SADec_Init(&saDecHandle, 6); +#endif + return 0; +} + +bool ADTS_MP2::Open(ifc_mpeg_stream_reader *file) +{ + decoder->Connect((CGioFile *)file); + if (allowRG) + gain = file->MPEGStream_Gain(); + return true; +} + +void ADTS_MP2::Close() +{ + if (decoder) + { + delete decoder; + decoder = 0; + } +#ifndef NO_MP3SURROUND + if (saDecHandle) + IIS_SADec_Free(&saDecHandle); + saDecHandle=0; +#endif +} + +void ADTS_MP2::GetOutputParameters(size_t *numBits, int *numChannels, int *sampleRate) +{ + *sampleRate = this->sampleRate; + *numChannels = channels; + *numBits = bitsPerSample; +} + +void ADTS_MP2::CalculateFrameSize(int *frameSize) +{ + *frameSize = outputFrameSize; + if (decoder->GetStreamInfo()->GetLayer() == 1) + *frameSize *= 3; +} + +void ADTS_MP2::Flush(ifc_mpeg_stream_reader *file) +{ + decoder->Reset(); +#ifndef NO_MP3SURROUND + if (saDecHandle) + IIS_SADec_Reset(saDecHandle); + lineFilled=false; +#endif +} + +size_t ADTS_MP2::GetCurrentBitrate() +{ + return decoder->GetStreamInfo()->GetBitrate() / 1000; +} + +size_t ADTS_MP2::GetDecoderDelay() +{ +#ifndef NO_MP3SURROUND + if (!saDecHandle || saMode == SA_DEC_OFF || saMode == SA_DEC_BYPASS) + return decoderDelay; + else /* bcc adds 576 delay */ + return decoderDelay+576; +#else + return decoderDelay; +#endif +} + +static void Decimate(const float *input, void *output, size_t numSamples, size_t *outputWritten, int bitsPerSample, bool useFloat, float gain) +{ + if (!useFloat) + { + // TODO seen a few crashes reported where 'output' is 0 + nsutil_pcm_FloatToInt_Interleaved_Gain(output, input, bitsPerSample, numSamples, gain); + } + else if (gain != 1.f) + { + float *data = (float *)output; + for (size_t i=0;i<numSamples;i++) + data[i]*=gain; + //data[i]=input[i]*gain; + } + + *outputWritten = numSamples * (bitsPerSample / 8); +} +/* +notes for mp3 surround implementations +need to check the first two frames for ancillary data +store first valid in temp +store second valid frame in delay line +decimate first valid into output buffer +ancillary data is stored one frame behind, so PCM data decoded from mp3 frame n combines with anc data from frame n+1 +*/ +int ADTS_MP2::Sync(ifc_mpeg_stream_reader *_file, unsigned __int8 *output, size_t outputSize, size_t *outputWritten, size_t *bitrate) +{ + SSC ssc; +CGioFile *file = (CGioFile *)_file; + unsigned char ancBytes[8192] = {0}; + int numAncBytes = 0; + + unsigned int delay=0, totalLength=0; + + float floatTemp[1152*2] = {0}; + float *flData=useFloat?(float *)output:floatTemp; + ssc = decoder->DecodeFrame(flData, sizeof(floatTemp), &outputFrameSize, +<<<<<<< HEAD:in_mp3/adts_mp2.cpp + ancBytes, &numAncBytes, 1, &delay, &totalLength); + + // TODO: benski> we should really have CGioFile try to read this stuff + if (delay && !file->prepad) + { + // validate + if (delay >= 529) + { + decoderDelay = delay; + endcut = 1152 - ((totalLength + delay) % 1152); // how many 0 samples had to be added? + endcut += decoderDelay; // also need to cut out the encoder+decoder delay + file->m_vbr_samples = totalLength; + } + } +======= + ancBytes, &numAncBytes); +>>>>>>> fd5c493... have CGioFile read the OFL (from newer fraunhofer encoders):mp3/adts_mp2.cpp + + switch (ssc) + { + case SSC_OK: + { + channels = decoder->GetStreamInfo()->GetEffectiveChannels(); + sampleRate = decoder->GetStreamInfo()->GetEffectiveSFreq(); +#ifndef NO_MP3SURROUND + if (!numAncBytes && saDecHandle) + { + ssc = decoder->DecodeFrame(delayline, sizeof(delayline), &delaylineSize, + ancBytes, &numAncBytes); + + if (SSC_SUCCESS(ssc)) + { + lineFilled=true; + } + else if (ssc == SSC_W_MPGA_SYNCEOF) + return ENDOFFILE; + else + return NEEDMOREDATA; + } + + if (saDecHandle) + { + SA_DEC_ERROR sa_error = IIS_SADec_DecodeAncData(saDecHandle, ancBytes, numAncBytes, 0, 0); + if (sa_error == SA_DEC_NO_ERROR) + { + IIS_SADec_InitInfo(saDecHandle, sampleRate, channels); + SA_DEC_INFO saInfo = IIS_SADec_GetInfo(saDecHandle); + sampleRate = saInfo.SampleRate; + channels = saInfo.nChannelsOut; + saMode = saInfo.configuredMode; + } + else if (saMode == SA_DEC_OFF) + { + IIS_SADec_Free(&saDecHandle); + saDecHandle=0; + } + else + { + lineFilled=false; + return NEEDMOREDATA; + } + } + + if (saDecHandle) + { + float surroundFloatTemp[1152*6] = {0}; + int outputSamples = 0; + /*SA_DEC_ERROR sa_error = */IIS_SADec_DecodeFrame(saDecHandle, + flData, outputFrameSize/sizeof(float), + (char *)ancBytes, numAncBytes, + surroundFloatTemp, sizeof(surroundFloatTemp), + &outputSamples, saMode, 0, 0, + 0, 0); + if (useFloat) + memcpy(output, surroundFloatTemp, sizeof(surroundFloatTemp)); + Decimate(surroundFloatTemp, output, outputSamples, outputWritten, bitsPerSample, useFloat, (float)gain); + outputFrameSize = *outputWritten; + } + else +#endif + { + Decimate(floatTemp, output, outputFrameSize / sizeof(float), outputWritten, bitsPerSample, useFloat, (float)gain); + outputFrameSize = *outputWritten; + } + } + return SSC_OK; + case SSC_W_MPGA_SYNCSEARCHED: + return NEEDMOREDATA; + case SSC_W_MPGA_SYNCEOF: + return ENDOFFILE; + case SSC_E_MPGA_WRONGLAYER: + decoder->m_Mbs.Seek(1); + default: + return NEEDMOREDATA; + + } +} + +int ADTS_MP2::Decode(ifc_mpeg_stream_reader *file, unsigned __int8 *output, size_t outputSize, size_t *outputWritten, size_t *bitrate, size_t *endCut) +{ + if (endcut) + { + *endCut = endcut; + endcut=0; + } +#ifndef NO_MP3SURROUND + if (!saDecHandle && lineFilled) // if we don't have surround info, go ahead and flush out the delayline buffer + { + Decimate(delayline, output, delaylineSize / sizeof(float), outputWritten, bitsPerSample, useFloat, (float)gain); + *bitrate = decoder->GetStreamInfo()->GetBitrate() / 1000; + lineFilled=false; + return adts::SUCCESS; + } + + if (saDecHandle && !lineFilled && !decoder->IsEof()) // have surround info, but don't have a previously decoded frame + { + // resync + int ret = Sync(file, output, outputSize, outputWritten, bitrate); + if (ret == SSC_OK && saDecHandle) + { + *bitrate = decoder->GetStreamInfo()->GetBitrate() / 1000; + return adts::SUCCESS; + } + else if (saDecHandle) + return ret; + else + return adts::FAILURE; + } +#endif + + unsigned char ancBytes[8192] = {0}; + int numAncBytes = 0; + + int newl; + + float floatTemp[1152*2] = {0}; + float *flData=useFloat?(float *)output:floatTemp; + SSC ssc = decoder->DecodeFrame(flData, sizeof(floatTemp), &newl, ancBytes, &numAncBytes); + + if (SSC_SUCCESS(ssc)) + { +#ifndef NO_MP3SURROUND + if (saDecHandle && lineFilled) + { + float surroundFloatTemp[1152*6] = {0}; + int outputSamples; + /*SA_DEC_ERROR sa_error = */IIS_SADec_DecodeFrame(saDecHandle, + delayline, delaylineSize/sizeof(float), + (char *)ancBytes, numAncBytes, + surroundFloatTemp, sizeof(surroundFloatTemp), + &outputSamples, saMode, 0, 0, + 0, 0); + + if (useFloat) + memcpy(output, surroundFloatTemp, sizeof(surroundFloatTemp)); + Decimate(surroundFloatTemp, output, outputSamples, outputWritten, bitsPerSample, useFloat, (float)gain); + memcpy(delayline, flData, delaylineSize); + } + else +#endif + { + Decimate(floatTemp, output, newl / sizeof(float), outputWritten, bitsPerSample, useFloat, (float)gain); + } + *bitrate = decoder->GetStreamInfo()->GetBitrate() / 1000; + return adts::SUCCESS; + } + else if (decoder->IsEof()) + { + #ifndef NO_MP3SURROUND + /* In case of SA processing one ancillary data + package and maybe fill samples left in the dynamic + buffer of the mp3 decoder. Take care, that the + ancillary data buffer is greater then the dynamic buffer of + the mp3 decoder. */ + if (saDecHandle && lineFilled) + { + decoder->GetLastAncData(ancBytes, &numAncBytes); + float surroundFloatTemp[1152*6]; + int outputSamples = 0; + /*SA_DEC_ERROR sa_error = */IIS_SADec_DecodeFrame(saDecHandle, + delayline, delaylineSize/sizeof(float), + (char *)ancBytes, numAncBytes, + surroundFloatTemp, sizeof(surroundFloatTemp), + &outputSamples, saMode, 0, 0, + 0, 0); + + if (useFloat) + memcpy(output, surroundFloatTemp, sizeof(surroundFloatTemp)); + Decimate(surroundFloatTemp, output, outputSamples, outputWritten, bitsPerSample, useFloat, (float)gain); + lineFilled=false; + *bitrate = decoder->GetStreamInfo()->GetBitrate() / 1000; + return adts::SUCCESS; + } + else +#endif + return adts::ENDOFFILE; + } + else if (ssc == SSC_W_MPGA_SYNCNEEDDATA) + { + *bitrate = decoder->GetStreamInfo()->GetBitrate() / 1000; + return adts::NEEDMOREDATA; + } + else if (ssc==SSC_W_MPGA_SYNCLOST || ssc==SSC_W_MPGA_SYNCSEARCHED) + { + *bitrate = decoder->GetStreamInfo()->GetBitrate() / 1000; + return adts::NEEDSYNC; + } + else + { + if (ssc == SSC_E_MPGA_WRONGLAYER) + decoder->m_Mbs.Seek(1); + + return adts::FAILURE; + } + +} + +int ADTS_MP2::GetLayer() +{ + if (decoder) + return decoder->GetStreamInfo()->GetLayer(); + else + return 0; +} + +void ADTS_MP2::Release() +{ + delete this; +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mp3/adts_mp2.h b/Src/Plugins/Input/in_mp3/adts_mp2.h new file mode 100644 index 00000000..9678c72a --- /dev/null +++ b/Src/Plugins/Input/in_mp3/adts_mp2.h @@ -0,0 +1,58 @@ +#ifndef NULLSOFT_IN_MP3_ADTS_MP2_H +#define NULLSOFT_IN_MP3_ADTS_MP2_H + +#include "adts.h" +#include "api.h" +#include "config.h" +#ifndef NO_MP3SURROUND +#include "../mp3/bccDecLinklib/include/bccDecLink.h" // Binaural Cue Coding (aka mp3 surround) +#endif + +class ADTS_MP2 : public adts +{ +public: + ADTS_MP2(); + int Initialize(bool forceMono, bool reverse_stereo, bool allowSurround, int maxBits, bool allowRG, bool _useFloat, bool _useCRC); + bool Open(ifc_mpeg_stream_reader *file); + void Close(); + void GetOutputParameters(size_t *numBits, int *numChannels, int *sampleRate); + void CalculateFrameSize(int *frameSize); + void Flush(ifc_mpeg_stream_reader *file); + size_t GetCurrentBitrate(); + size_t GetDecoderDelay(); + int Sync(ifc_mpeg_stream_reader *file, unsigned __int8 *output, size_t outputSize, size_t *outputWritten, size_t *bitrate); + int Decode(ifc_mpeg_stream_reader *file, unsigned __int8 *output, size_t outputSize, size_t *outputWritten, size_t *bitrate, size_t *endCut); + int GetLayer(); + void Release(); + void SetDecoderHooks(void *layer3_vis, void *layer2_eq, void *layer3_eq); +<<<<<<< HEAD:in_mp3/adts_mp2.h + +======= +>>>>>>> 5058463... fix old-school vis/eq mp3 stuff:mp3/adts_mp2.h +private: + DecoderHooks hooks; + CMpgaDecoder *decoder; + int outputFrameSize; + size_t bitsPerSample; + double gain; + bool allowRG; + bool useFloat; + + int channels; + int sampleRate; + unsigned int decoderDelay; + unsigned int endcut; + +#ifndef NO_MP3SURROUND + float delayline[1152*2]; + int delaylineSize; + bool lineFilled; + SADEC_HANDLE saDecHandle; + SA_DEC_MODE saMode; +#endif + + +}; + + +#endif diff --git a/Src/Plugins/Input/in_mp3/adts_vlb.cpp b/Src/Plugins/Input/in_mp3/adts_vlb.cpp new file mode 100644 index 00000000..c8121381 --- /dev/null +++ b/Src/Plugins/Input/in_mp3/adts_vlb.cpp @@ -0,0 +1,153 @@ +#include "adts_vlb.h" +#include "giofile.h" +#include "in2.h" +extern In_Module mod; + +ADTS_VLB::ADTS_VLB() : decoder(0), needsync(1) +{} + +int ADTS_VLB::Initialize(bool forceMono, bool reverseStereo, bool allowSurround, int maxBits, bool allowRG, bool _useFloat, bool _useCRC) +{ + return 0; +} + +bool ADTS_VLB::Open(ifc_mpeg_stream_reader *file) +{ + waServiceFactory *factory = mod.service->service_getServiceByGuid(obj_vlbDecoderGUID); + if (factory) + decoder = (obj_vlbDecoder *)factory->getInterface(); + + if (decoder) + { + int status = decoder->Open((DataIOControl *)(CGioFile *)file); + if (status == 0) + return true; + } + + return false; +} + +void ADTS_VLB::Close() +{ + if (decoder) + { + waServiceFactory *factory = mod.service->service_getServiceByGuid(obj_vlbDecoderGUID); + if (factory) + factory->releaseInterface(decoder); + } + + decoder = 0; +} + +void ADTS_VLB::GetOutputParameters(size_t *numBits, int *numChannels, int *sampleRate) +{ + *sampleRate = params.sampling_frequency; + *numChannels = params.num_channels; + *numBits = 16; +} + +void ADTS_VLB::CalculateFrameSize( int *frameSize ) +{ + *frameSize = 576 * 2 * params.num_channels; + if ( *frameSize > 576 * 2 * 2 ) + *frameSize = 576 * 2 * 2; +} + +void ADTS_VLB::Flush(ifc_mpeg_stream_reader *file) +{ + decoder->Flush(); + decoder->Close(); + decoder->Open((DataIOControl *)(CGioFile *)file); + + needsync = 1; +} + +size_t ADTS_VLB::GetCurrentBitrate() +{ + return params.bitrate / 1000; +} + +size_t ADTS_VLB::GetDecoderDelay() +{ + return 0; // not really sure +} + +int ADTS_VLB::Sync(ifc_mpeg_stream_reader *file, unsigned __int8 *output, size_t outputSize, size_t *outputWritten, size_t *bitrate) +{ + int status = decoder->Synchronize(¶ms); + if (!status) + { + needsync = 0; + return SUCCESS; + } + + if (file->MPEGStream_EOF()) + return ENDOFFILE; + + return NEEDMOREDATA; +} + +int ADTS_VLB::Decode(ifc_mpeg_stream_reader *file, unsigned __int8 *output, size_t outputSize, size_t *outputWritten, size_t *bitrate, size_t *endCut) +{ + if (*outputWritten = decoder->Read(output, outputSize)) + { + // TODO: benski> verify that params is valid here + *bitrate = params.bitrate / 1000; + + return adts::SUCCESS; + } + + if (needsync) + { + int status = decoder->Synchronize(¶ms); + if (!status) + { + needsync = 0; + } + else if (file->MPEGStream_EOF()) + { + return adts::ENDOFFILE; + } + } + + if (!needsync) + { + int status = decoder->DecodeFrame(¶ms); + + if (status > ERR_NO_ERROR && status != ERR_END_OF_FILE) + { + needsync = 1; + return adts::FAILURE; + } + else + { + if (status == ERR_END_OF_FILE) + { + if (file->MPEGStream_EOF()) + { + return adts::ENDOFFILE; + } + else + { + *bitrate = params.bitrate / 1000; + return adts::NEEDMOREDATA; + } + } + + *bitrate = params.bitrate / 1000; + return adts::SUCCESS; + } + } + + return adts::SUCCESS; +} + +int ADTS_VLB::GetLayer() +{ + return 4; +} + +void ADTS_VLB::Release() +{ + delete this; +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mp3/adts_vlb.h b/Src/Plugins/Input/in_mp3/adts_vlb.h new file mode 100644 index 00000000..7b6d3a9d --- /dev/null +++ b/Src/Plugins/Input/in_mp3/adts_vlb.h @@ -0,0 +1,37 @@ +#ifndef NULLSOFT_IN_MP3_ADTS_VLB_H +#define NULLSOFT_IN_MP3_ADTS_VLB_H +#include "adts.h" + +#include "../vlb/obj_vlbDecoder.h" +#include "api__in_mp3.h" +#include <api/service/waServiceFactory.h> + +class ADTS_VLB : public adts +{ +public: + ADTS_VLB(); + int Initialize( bool forceMono, bool reverseStereo, bool allowSurround, int maxBits, bool allowRG, bool _useFloat, bool _useCRC ); + + bool Open( ifc_mpeg_stream_reader *file ); + void Close(); + + void GetOutputParameters( size_t *numBits, int *numChannels, int *sampleRate ); + void CalculateFrameSize( int *frameSize ); + + void Flush( ifc_mpeg_stream_reader *file ); + + size_t GetCurrentBitrate(); + size_t GetDecoderDelay(); + + int Sync( ifc_mpeg_stream_reader *file, unsigned __int8 *output, size_t outputSize, size_t *outputWritten, size_t *bitrate ); + int Decode( ifc_mpeg_stream_reader *file, unsigned __int8 *output, size_t outputSize, size_t *outputWritten, size_t *bitrate, size_t *endCut ); + + int GetLayer(); + void Release(); + +private: + obj_vlbDecoder *decoder; + int needsync; + AACStreamParameters params; +}; +#endif diff --git a/Src/Plugins/Input/in_mp3/apev2.cpp b/Src/Plugins/Input/in_mp3/apev2.cpp new file mode 100644 index 00000000..a4ae0d95 --- /dev/null +++ b/Src/Plugins/Input/in_mp3/apev2.cpp @@ -0,0 +1,169 @@ +#include "apev2.h" + +APE::APE() +{ + hasData=false; + dirty=false; +} + +int APE::Decode(const void *data, size_t len) +{ + if (APEv2::Tag::Parse(data, len) == APEv2::APEV2_SUCCESS) + { + hasData=true; + return 1; + } + + return 0; +} + +// return -1 for empty, 1 for OK, 0 for "don't understand metadata name" +int APE::GetString(const char *metadata, wchar_t *data, int dataLen) +{ + if (!hasData) + return 0; + + if (!_stricmp(metadata, "replaygain_track_gain") + || !_stricmp(metadata, "replaygain_track_peak") + || !_stricmp(metadata, "replaygain_album_gain") + || !_stricmp(metadata, "replaygain_album_peak")) + { + if (APEv2::Tag::GetString(metadata, data, dataLen) == APEv2::APEV2_SUCCESS) + return 1; + return -1; + } + else + { + const char *ape_key = MapWinampKeyToApeKey(metadata); + if (ape_key) + { + if (APEv2::Tag::GetString(ape_key, data, dataLen) == APEv2::APEV2_SUCCESS) + return 1; + return -1; + } + } + + return 0; +} + +int APE::SetString(const char *metadata, const wchar_t *data) +{ + if (!_stricmp(metadata, "replaygain_track_gain") + || !_stricmp(metadata, "replaygain_track_peak") + || !_stricmp(metadata, "replaygain_album_gain") + || !_stricmp(metadata, "replaygain_album_peak")) + { + APEv2::Tag::SetString(metadata, data); + dirty=true; + hasData=true; + return 1; + } + else + { + const char *ape_key = MapWinampKeyToApeKey(metadata); + if (ape_key) + { + APEv2::Tag::SetString(ape_key, data); + dirty=true; + hasData=true; + return 1; + } + } + return 0; +} + +void APE::Clear() +{ + APEv2::Tag::Clear(); + dirty=true; + hasData=false; +} + +void APE::MarkClear() +{ + dirty=true; + hasData=false; +} + +int APE::SetKeyValueByIndex(size_t index, const char *key, const wchar_t *data) +{ + dirty=true; + return APEv2::Tag::SetKeyValueByIndex(index, key, data); +} + +int APE::RemoveItem(size_t index) +{ + dirty=true; + return APEv2::Tag::RemoveItem(index); +} + +int APE::AddItem() +{ + dirty=true; + hasData=true; + APEv2::Tag::AddItem(); + return APEv2::APEV2_SUCCESS; +} + +struct ApeKeyMapping +{ + const char *ape_key; + const char *winamp_key; + const wchar_t *winamp_keyW; +}; + +static ApeKeyMapping apeKeyMapping[] = +{ + { "Track", "track", L"track" }, + {"Album", "album", L"album" }, + {"Artist", "artist", L"artist" }, + {"Comment", "comment", L"comment" }, + {"Year", "year", L"year" }, + {"Genre", "genre", L"genre" }, + {"Title", "title", L"title"}, + {"Composer", "composer", L"composer"}, + {"Performer", "performer", L"performer"}, + {"Album artist", "albumartist", L"albumartist"}, +}; + +const wchar_t *APE::MapApeKeyToWinampKeyW(const char *ape_key) +{ + size_t num_mappings = sizeof(apeKeyMapping)/sizeof(apeKeyMapping[0]); + for (size_t i=0;i!=num_mappings;i++) + { + if (!_stricmp(ape_key, apeKeyMapping[i].ape_key)) + return apeKeyMapping[i].winamp_keyW; + } + return NULL; +} + +const char *APE::MapApeKeyToWinampKey(const char *ape_key) +{ + size_t num_mappings = sizeof(apeKeyMapping)/sizeof(apeKeyMapping[0]); + for (size_t i=0;i!=num_mappings;i++) + { + if (!_stricmp(ape_key, apeKeyMapping[i].ape_key)) + return apeKeyMapping[i].winamp_key; + } + return NULL; +} + +const char *APE::MapWinampKeyToApeKey(const char *winamp_key) +{ + size_t num_mappings = sizeof(apeKeyMapping)/sizeof(apeKeyMapping[0]); + for (size_t i=0;i!=num_mappings;i++) + { + if (!_stricmp(winamp_key, apeKeyMapping[i].winamp_key)) + return apeKeyMapping[i].ape_key; + } + return NULL; +} + +int APE::AddKeyValue(const char *key, const wchar_t *data) +{ + dirty=true; + hasData=true; + APEv2::Item *newItem = APEv2::Tag::AddItem(); + newItem->SetKey(key); + return APEv2::Tag::SetItemData(newItem, data); +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mp3/apev2.h b/Src/Plugins/Input/in_mp3/apev2.h new file mode 100644 index 00000000..76ca319c --- /dev/null +++ b/Src/Plugins/Input/in_mp3/apev2.h @@ -0,0 +1,46 @@ +#ifndef NULLSOFT_IN_MP3_APEV2_H +#define NULLSOFT_IN_MP3_APEV2_H + +#include "../apev2/tag.h" + +class APE : private APEv2::Tag +{ +public: + APE(); + bool HasData() { return hasData; } + bool IsDirty() { return dirty; } + void ResetDirty() { dirty=0; hasData = true; } + void Clear(); + void MarkClear(); + int Decode(const void *data, size_t len); + + // return -1 for empty, 1 for OK, 0 for "don't understand tag name" + int GetString(const char *tag, wchar_t *data, int dataLen); + int SetString(const char *tag, const wchar_t *data); + + int AddKeyValue(const char *key, const wchar_t *data); + int SetKeyValueByIndex(size_t index, const char *key, const wchar_t *data); + int RemoveItem(size_t index); + + int AddItem(); + + + static const char *MapApeKeyToWinampKey(const char *ape_key); + static const wchar_t *MapApeKeyToWinampKeyW(const char *ape_key); + static const char *MapWinampKeyToApeKey(const char *winamp_key); + + using APEv2::Tag::EnumValue; + using APEv2::Tag::EncodeSize; + using APEv2::Tag::Encode; + using APEv2::Tag::FindItemByKey; + using APEv2::Tag::GetNumItems; + using APEv2::Tag::IsItemReadOnly; + using APEv2::Tag::SetFlags; + +private: + bool hasData; + bool dirty; +}; + + +#endif
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mp3/api__in_mp3.h b/Src/Plugins/Input/in_mp3/api__in_mp3.h new file mode 100644 index 00000000..832db5bc --- /dev/null +++ b/Src/Plugins/Input/in_mp3/api__in_mp3.h @@ -0,0 +1,16 @@ +#ifndef NULLSOFT_IN_MP3_API_H +#define NULLSOFT_IN_MP3_API_H + +#include "../Agave/Config/api_config.h" + +#include "api/memmgr/api_memmgr.h" +extern api_memmgr *memmgr; +#define WASABI_API_MEMMGR memmgr + +#include "../Agave/Language/api_language.h" + +#include "api/application/api_application.h" +extern api_application *applicationApi; +#define WASABI_API_APP applicationApi + +#endif // !NULLSOFT_IN_MP3_API_H
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mp3/config.cpp b/Src/Plugins/Input/in_mp3/config.cpp new file mode 100644 index 00000000..fcff41e8 --- /dev/null +++ b/Src/Plugins/Input/in_mp3/config.cpp @@ -0,0 +1,685 @@ +#include "main.h" +#include <shlobj.h> +#include <commctrl.h> +#include <windows.h> +#include "../winamp/wa_ipc.h" +#include "config.h" +#include "api__in_mp3.h" +#include "resource.h" + +char g_http_tmp[MAX_PATH] = {0}; + +int config_write_mode = WRITE_UTF16; +int config_read_mode = READ_LOCAL; + +int config_parse_apev2 = 1; +int config_parse_lyrics3 = 1; +int config_parse_id3v1 = 1; +int config_parse_id3v2 = 1; + +int config_write_apev2 = 1; +int config_write_id3v1 = 1; +int config_write_id3v2 = 1; + +int config_create_id3v1 = 1; +int config_create_id3v2 = 1; +int config_create_apev2 = 0; + +int config_apev2_header = RETAIN_HEADER; +int config_lp = 0; + +BOOL CALLBACK browseEnumProc(HWND hwnd, LPARAM lParam) +{ + wchar_t cl[32] = {0}; + GetClassNameW(hwnd, cl, ARRAYSIZE(cl)); + if (!lstrcmpiW(cl, WC_TREEVIEW)) + { + PostMessage(hwnd, TVM_ENSUREVISIBLE, 0, (LPARAM)TreeView_GetSelection(hwnd)); + return FALSE; + } + + return TRUE; +} + +static int CALLBACK BrowseCallbackProc( HWND hwnd, UINT uMsg, LPARAM lParam, LPARAM lpData) +{ + switch (uMsg) + { + case BFFM_INITIALIZED: + { + SetWindowText(hwnd, WASABI_API_LNGSTRINGW(IDS_SELECT_DIRECTORY_TO_SAVE_TO)); + if (g_http_tmp[0]) SendMessage(hwnd, BFFM_SETSELECTIONA, 1, (LPARAM)g_http_tmp); + + // this is not nice but it fixes the selection not working correctly on all OSes + EnumChildWindows(hwnd, browseEnumProc, 0); + } + } + return 0; +} + +static char app_name[] = "Nullsoft MPEG Decoder"; + +char *get_inifile() { return INI_FILE; } + +int _r_i(char *name, int def) +{ + if (!_strnicmp(name, "config_", 7)) name += 7; + return GetPrivateProfileIntA(app_name, name, def, INI_FILE); +} + +#define RI(x) (( x ) = _r_i(#x,( x ))) +void _w_i(char *name, int d) +{ + char str[120] = {0}; + wsprintfA(str, "%d", d); + if (!_strnicmp(name, "config_", 7)) name += 7; + WritePrivateProfileStringA(app_name, name, str, INI_FILE); +} +#define WI(x) _w_i(#x,( x )) + +void _r_s(char *name, char *data, int mlen) +{ + char buf[2048] = {0}; + lstrcpynA(buf, data, 2048); + if (!_strnicmp(name, "config_", 7)) name += 7; + GetPrivateProfileStringA(app_name, name, buf, data, mlen, INI_FILE); +} +#define RS(x) (_r_s(#x,x,sizeof(x))) + +void _w_s(char *name, char *data) +{ + if (!_strnicmp(name, "config_", 7)) name += 7; + WritePrivateProfileStringA(app_name, name, data, INI_FILE); +} +#define WS(x) (_w_s(#x,x)) + +static void config_init() +{ + char *p; + if (mod.hMainWindow && + (p = (char *)SendMessage(mod.hMainWindow, WM_WA_IPC, 0, IPC_GETINIFILE)) + && p != (char *)1) + { + strncpy(INI_FILE, p, MAX_PATH); + } + else + { + GetModuleFileNameA(NULL, INI_FILE, sizeof(INI_FILE)); + p = INI_FILE + strlen(INI_FILE); + while (p >= INI_FILE && *p != '.') p--; + strcpy(++p, "ini"); + } +} + +#ifdef AAC_SUPPORT +#define DEF_EXT_LIST "MP3;MP2;MP1;AAC;VLB" +#else +#define DEF_EXT_LIST "MP3;MP2;MP1" +#endif + +#define __STR2WSTR(str) L##str +#define WIDEN(str) __STR2WSTR(str) +#define DEF_EXT_LISTW WIDEN(DEF_EXT_LIST) + +#ifdef AAC_SUPPORT +char config_extlist_aac[129] = DEF_EXT_LIST; +#else +char config_extlist[129] = DEF_EXT_LIST; +#endif + +char config_rating_email[255] = {0}; + +void config_read() +{ + config_init(); + RI(allow_scartwork); + RI(allow_sctitles); + RI(sctitle_format); + RI(config_http_buffersize); + RI(config_http_prebuffer); + RI(config_http_prebuffer_underrun); + RI(config_downmix); + RI(config_downsample); + RI(config_max_bufsize_k); + RI(config_eqmode); + RI(config_gapless); + + if(FAILED(SHGetFolderPathA(NULL, CSIDL_MYMUSIC, NULL, SHGFP_TYPE_CURRENT, config_http_save_dir))) + { + if(FAILED(SHGetFolderPathA(NULL, CSIDL_PERSONAL, NULL, SHGFP_TYPE_CURRENT, config_http_save_dir))) + { + lstrcpynA(config_http_save_dir, "C:\\", MAX_PATH); + } + } + + RS(config_http_save_dir); + RI(config_miscopts); + RI(config_fastvis); + +#ifdef AAC_SUPPORT + RS(config_extlist_aac); +#else + RS(config_extlist); +#endif + + RI(config_write_mode); + RI(config_read_mode); + + RI(config_parse_apev2); + RI(config_parse_lyrics3); + RI(config_parse_id3v1); + RI(config_parse_id3v2); + + RI(config_write_apev2); + RI(config_write_id3v1); + RI(config_write_id3v2); + + RI(config_create_apev2); + RI(config_create_id3v1); + RI(config_create_id3v2); + + RI(config_apev2_header); + + RI(config_lp); + + RS(config_rating_email); +} + +void config_write() +{ + WI(allow_scartwork); + WI(config_fastvis); + WI(config_miscopts); + WI(allow_sctitles); + WI(sctitle_format); + WI(config_http_buffersize); + WI(config_http_buffersize); + WI(config_http_prebuffer); + WI(config_http_prebuffer_underrun); + WI(config_downmix); + WI(config_downsample); + WI(config_max_bufsize_k); + WI(config_eqmode); + WS(config_http_save_dir); +#ifdef AAC_SUPPORT + WS(config_extlist_aac); +#else + WS(config_extlist); +#endif + + WI(config_write_mode); + WI(config_read_mode); + + WI(config_parse_apev2); + WI(config_parse_lyrics3); + WI(config_parse_id3v1); + WI(config_parse_id3v2); + + WI(config_write_apev2); + WI(config_write_id3v1); + WI(config_write_id3v2); + + WI(config_create_apev2); + WI(config_create_id3v1); + WI(config_create_id3v2); + + WI(config_apev2_header); + + WI(config_lp); + + WS(config_rating_email); +} + +static INT_PTR CALLBACK prefsProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam); +static INT_PTR CALLBACK id3Proc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam); +static INT_PTR CALLBACK advancedTaggingProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam); +static INT_PTR CALLBACK httpProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam); +static INT_PTR CALLBACK outputProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam); + +#define ISSEP(x) ((x) == ' ' || (x) == ';' || (x) == ',' || (x) == ':' || (x) == '.') +char *getfileextensions() +{ + static char list[512]; + char *op = list; + // char *g_fileassos="MP3;MP2;MP1\0MPEG Audio Files (*.MP3;*.MP2;*.MP1)\0"; + + char *p = config_extlist; + int s = 0; + while (p && *p) + { + while (ISSEP(*p)) p++; + if (!p || !*p) break; + if (s) *op++ = ';'; + s = 1; + while (p && *p && !ISSEP(*p)) *op++ = *p++; + } + *op++ = 0; + strcpy(op, WASABI_API_LNGSTRING(IDS_MPEG_AUDIO_FILES)); + while (op && *op) op++; + p = config_extlist; + s = 0; + while (p && *p) + { + while (ISSEP(*p)) p++; + if (!p || !*p) break; + if (s) *op++ = ';'; + s = 1; + *op++ = '*'; + *op++ = '.'; + while (p && *p && !ISSEP(*p)) *op++ = *p++; + } + *op++ = ')'; + *op++ = 0; + *op++ = 0; + return list; +} + +void config(HWND hwndParent) +{ + wchar_t title[128] = {0}; + int x; + PROPSHEETHEADER pshead; + PROPSHEETPAGE pspage[5]; + ZeroMemory(&pshead, sizeof(PROPSHEETHEADER)); + pshead.dwSize = sizeof(PROPSHEETHEADER); + pshead.hwndParent = hwndParent; + pshead.dwFlags = PSH_PROPSHEETPAGE | PSH_NOAPPLYNOW | PSH_NOCONTEXTHELP; + pshead.hInstance = WASABI_API_LNG_HINST; + pshead.pszCaption = WASABI_API_LNGSTRINGW_BUF(IDS_MPEG_AUDIO_DECODER_SETTINGS,title,128);//"MPEG Audio Decoder Settings"; + pshead.nPages = sizeof(pspage) / sizeof(pspage[0]); + pshead.nStartPage = config_lp; + pshead.ppsp = pspage; + + ZeroMemory(pspage, sizeof(pspage)); + for ( x = 0; x < sizeof(pspage) / sizeof(pspage[0]); x ++) + pspage[x].dwSize = sizeof(PROPSHEETPAGE); + for ( x = 0; x < sizeof(pspage) / sizeof(pspage[0]); x ++) + pspage[x].hInstance = WASABI_API_LNG_HINST; + pspage[0].pszTemplate = MAKEINTRESOURCE(IDD_PREFS); + pspage[1].pszTemplate = MAKEINTRESOURCE(IDD_TAGOPTS); + pspage[2].pszTemplate = MAKEINTRESOURCE(IDD_ADVANCED_TAGGING); + pspage[3].pszTemplate = MAKEINTRESOURCE(IDD_OUTPUT); + pspage[4].pszTemplate = MAKEINTRESOURCE(IDD_HTTP); + pspage[0].pfnDlgProc = prefsProc; + pspage[1].pfnDlgProc = id3Proc; + pspage[2].pfnDlgProc = advancedTaggingProc; + pspage[3].pfnDlgProc = outputProc; + pspage[4].pfnDlgProc = httpProc; + PropertySheet((PROPSHEETHEADER*)&pshead); + config_write(); + extern char *g_fileassos; + mod.FileExtensions = getfileextensions(); +} + +static INT_PTR CALLBACK id3Proc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + switch (uMsg) + { + case WM_INITDIALOG: + if (config_parse_id3v1) CheckDlgButton(hwndDlg, IDC_READ_ID3V1, BST_CHECKED); + if (config_parse_id3v2) CheckDlgButton(hwndDlg, IDC_READ_ID3V2, BST_CHECKED); + + if (config_write_id3v1) CheckDlgButton(hwndDlg, IDC_WRITE_ID3V1, BST_CHECKED); + if (config_write_id3v2) CheckDlgButton(hwndDlg, IDC_WRITE_ID3V2, BST_CHECKED); + + if (config_create_id3v1) CheckDlgButton(hwndDlg, IDC_CREATE_ID3V1, BST_CHECKED); + if (config_create_id3v2) CheckDlgButton(hwndDlg, IDC_CREATE_ID3V2, BST_CHECKED); + + SendDlgItemMessage(hwndDlg,IDC_COMBO1,CB_ADDSTRING,0,(LPARAM)WASABI_API_LNGSTRINGW(IDS_LATIN_1)); + SendDlgItemMessage(hwndDlg,IDC_COMBO1,CB_ADDSTRING,0,(LPARAM)WASABI_API_LNGSTRINGW(IDS_SYSTEM_LANGUAGE)); + SendDlgItemMessage(hwndDlg,IDC_COMBO1,CB_SETCURSEL,(config_read_mode == READ_LOCAL),0); + + SendDlgItemMessage(hwndDlg,IDC_COMBO2,CB_ADDSTRING,0,(LPARAM)WASABI_API_LNGSTRINGW(IDS_UNICODE_UTF_16)); + SendDlgItemMessage(hwndDlg,IDC_COMBO2,CB_ADDSTRING,0,(LPARAM)WASABI_API_LNGSTRINGW(IDS_LATIN_1)); + SendDlgItemMessage(hwndDlg,IDC_COMBO2,CB_ADDSTRING,0,(LPARAM)WASABI_API_LNGSTRINGW(IDS_SYSTEM_LANGUAGE)); + SendDlgItemMessage(hwndDlg,IDC_COMBO2,CB_SETCURSEL,config_write_mode,0); + + SetDlgItemTextA(hwndDlg,IDC_RATING_EMAIL,(config_rating_email[0] ? config_rating_email : "rating@winamp.com\0")); + + return FALSE; + case WM_NOTIFY: + { + LPNMHDR pnmh = (LPNMHDR) lParam; + if (pnmh->code == PSN_SETACTIVE) + { + config_lp = 1; + } + if (pnmh->code == PSN_APPLY) + { + config_parse_id3v1 = IsDlgButtonChecked(hwndDlg, IDC_READ_ID3V1); + config_parse_id3v2 = IsDlgButtonChecked(hwndDlg, IDC_READ_ID3V2); + + config_write_id3v1 = IsDlgButtonChecked(hwndDlg, IDC_WRITE_ID3V1); + config_write_id3v2 = IsDlgButtonChecked(hwndDlg, IDC_WRITE_ID3V2); + + config_create_id3v1 = IsDlgButtonChecked(hwndDlg, IDC_CREATE_ID3V1); + config_create_id3v2 = IsDlgButtonChecked(hwndDlg, IDC_CREATE_ID3V2); + + GetDlgItemTextA(hwndDlg,IDC_RATING_EMAIL,config_rating_email,sizeof(config_rating_email)); + if (!stricmp(config_rating_email, "rating@winamp.com\0")) config_rating_email[0] = 0; + + return TRUE; + } + } + return FALSE; + case WM_COMMAND: + switch (LOWORD(wParam)) + { + case IDC_COMBO1: + if(HIWORD(wParam) == CBN_SELCHANGE) + { + int cur = (int)SendMessage((HWND)lParam,CB_GETCURSEL,0,0); + if(!cur) config_read_mode = READ_LATIN; + else if(cur == 1) config_read_mode = READ_LOCAL; + } + break; + case IDC_COMBO2: + if(HIWORD(wParam) == CBN_SELCHANGE) + { + int cur = (int)SendMessage((HWND)lParam,CB_GETCURSEL,0,0); + if(!cur) config_write_mode = WRITE_UTF16; + else if(cur == 1) config_write_mode = WRITE_LATIN; + else if(cur == 2) config_write_mode = WRITE_LOCAL; + } + break; + case IDC_RATING_EMAIL_RESET: + if(HIWORD(wParam) == BN_CLICKED) + { + config_rating_email[0] = 0; + SetDlgItemTextA(hwndDlg,IDC_RATING_EMAIL,(config_rating_email[0] ? config_rating_email : "rating@winamp.com\0")); + } + } + return FALSE; + } + return FALSE; +} + +static INT_PTR CALLBACK advancedTaggingProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + switch (uMsg) + { + case WM_INITDIALOG: + + if (config_parse_apev2) CheckDlgButton(hwndDlg, IDC_READ_APEV2, BST_CHECKED); + if (config_write_apev2) CheckDlgButton(hwndDlg, IDC_WRITE_APEV2, BST_CHECKED); + if (config_create_apev2) CheckDlgButton(hwndDlg, IDC_CREATE_APEV2, BST_CHECKED); + + if (config_parse_lyrics3) CheckDlgButton(hwndDlg, IDC_READ_LYRICS3, BST_CHECKED); + + SendDlgItemMessage(hwndDlg,IDC_APEV2_HEADER_OPTIONS,CB_ADDSTRING,0,(LPARAM)WASABI_API_LNGSTRINGW(IDS_APEV2_RETAIN_HEADER)); + SendDlgItemMessage(hwndDlg,IDC_APEV2_HEADER_OPTIONS,CB_ADDSTRING,0,(LPARAM)WASABI_API_LNGSTRINGW(IDS_APEV2_ADD_HEADER)); + SendDlgItemMessage(hwndDlg,IDC_APEV2_HEADER_OPTIONS,CB_ADDSTRING,0,(LPARAM)WASABI_API_LNGSTRINGW(IDS_APEV2_REMOVE_HEADER)); + SendDlgItemMessage(hwndDlg,IDC_APEV2_HEADER_OPTIONS,CB_SETCURSEL,config_apev2_header, 0); + + return FALSE; + case WM_NOTIFY: + { + LPNMHDR pnmh = (LPNMHDR) lParam; + if (pnmh->code == PSN_SETACTIVE) + { + config_lp = 2; + } + if (pnmh->code == PSN_APPLY) + { + config_parse_apev2 = IsDlgButtonChecked(hwndDlg, IDC_READ_APEV2); + config_write_apev2 = IsDlgButtonChecked(hwndDlg, IDC_WRITE_APEV2); + config_create_apev2 = IsDlgButtonChecked(hwndDlg, IDC_CREATE_APEV2); + + config_parse_lyrics3 = IsDlgButtonChecked(hwndDlg, IDC_READ_LYRICS3); + + return TRUE; + } + } + return FALSE; + case WM_COMMAND: + switch (LOWORD(wParam)) + { + case IDC_APEV2_HEADER_OPTIONS: + if(HIWORD(wParam) == CBN_SELCHANGE) + { + int cur = (int)SendMessage((HWND)lParam,CB_GETCURSEL,0,0); + if(!cur) config_apev2_header = RETAIN_HEADER; + else if(cur == 1) config_apev2_header = ADD_HEADER; + else if(cur == 2) config_apev2_header = REMOVE_HEADER; + } + break; + } + return FALSE; + } + return FALSE; +} + +static INT_PTR CALLBACK prefsProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + switch (uMsg) + { + case WM_INITDIALOG: + SetDlgItemTextA(hwndDlg, IDC_EDIT1, config_extlist); + SendDlgItemMessage(hwndDlg, IDC_EDIT1, EM_LIMITTEXT, 128, 0); + { + wchar_t str[10] = L""; + wsprintf(str, L"%d", config_max_bufsize_k); + SetDlgItemText(hwndDlg, IDC_BUFMAX, str); + SendMessage(GetDlgItem(hwndDlg, IDC_BUFMAX), EM_LIMITTEXT, 5, 0); + } + + return FALSE; + case WM_COMMAND: + switch (LOWORD(wParam)) + { + case IDC_BUTTON1: + SetDlgItemText(hwndDlg, IDC_EDIT1, DEF_EXT_LISTW); + break; + } + return FALSE; + case WM_NOTIFY: + { + LPNMHDR pnmh = (LPNMHDR) lParam; + if (pnmh->code == PSN_SETACTIVE) + { + config_lp = 0; + } + if (pnmh->code == PSN_APPLY) + { + config_max_bufsize_k = GetDlgItemInt(hwndDlg, IDC_BUFMAX, NULL, 0); + GetDlgItemTextA(hwndDlg, IDC_EDIT1, config_extlist, 128); + return TRUE; + } + } + return FALSE; + } + return FALSE; +} + +static INT_PTR CALLBACK outputProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + switch (uMsg) + { + case WM_INITDIALOG: + if (config_eqmode&1) CheckDlgButton(hwndDlg, IDC_RADIO2, 1); + else CheckDlgButton(hwndDlg, IDC_RADIO1, 1); + + if (!(config_eqmode&4)) CheckDlgButton(hwndDlg, IDC_FASTL3EQ, 1); + if (config_eqmode&8) CheckDlgButton(hwndDlg, IDC_FASTL12EQ, 1); + if (config_miscopts&1) CheckDlgButton(hwndDlg, IDC_CHECK1, BST_CHECKED); + if (config_miscopts&2) CheckDlgButton(hwndDlg, IDC_CHECK2, BST_CHECKED); + if (config_downmix == 2) CheckDlgButton(hwndDlg, IDC_REVSTEREO, BST_CHECKED); + if (config_downsample == 1) + CheckDlgButton(hwndDlg, IDC_HALFRATE, BST_CHECKED); + else if (config_downsample == 2) + CheckDlgButton(hwndDlg, IDC_QRATE, BST_CHECKED); + else + CheckDlgButton(hwndDlg, IDC_FULLRATE, BST_CHECKED); + return FALSE; + case WM_NOTIFY: + { + LPNMHDR pnmh = (LPNMHDR) lParam; + if (pnmh->code == PSN_SETACTIVE) + { + config_lp = 3; + } + + if (pnmh->code == PSN_APPLY) + { + config_miscopts &= ~3; + config_miscopts |= IsDlgButtonChecked(hwndDlg, IDC_CHECK1) ? 1 : 0; + config_miscopts |= IsDlgButtonChecked(hwndDlg, IDC_CHECK2) ? 2 : 0; + + config_eqmode = IsDlgButtonChecked(hwndDlg, IDC_RADIO1) ? 0 : 1; + config_eqmode |= IsDlgButtonChecked(hwndDlg, IDC_FASTL3EQ) ? 0 : 4; + config_eqmode |= IsDlgButtonChecked(hwndDlg, IDC_FASTL12EQ) ? 8 : 0; + + config_downmix = IsDlgButtonChecked(hwndDlg, IDC_REVSTEREO) ? 2 : 0; + + config_downsample = IsDlgButtonChecked(hwndDlg, IDC_HALFRATE) ? 1 : 0; + config_downsample = IsDlgButtonChecked(hwndDlg, IDC_QRATE) ? 2 : config_downsample; + + return TRUE; + } + } + return FALSE; + } + return FALSE; +} + +void SetHTTPSaveButtonText(HWND hwndDlg, char* path) +{ + HWND control = GetDlgItem(hwndDlg, IDC_BUTTON2); + HDC hdc = GetDC(control); + RECT r = {0}; + char temp[MAX_PATH] = {0}; + + lstrcpynA(temp, path, MAX_PATH); + SelectObject(hdc, (HFONT)SendMessage(control, WM_GETFONT, 0, 0)); + GetClientRect(control, &r); + r.left += 5; + r.right -= 5; + DrawTextA(hdc, temp, -1, &r, DT_PATH_ELLIPSIS|DT_WORD_ELLIPSIS|DT_MODIFYSTRING); + SetWindowTextA(control, temp); + ReleaseDC(control, hdc); +} + +static INT_PTR CALLBACK httpProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + switch (uMsg) + { + case WM_COMMAND: + switch (LOWORD(wParam)) + { + case IDC_CHECK2: + EnableWindow(GetDlgItem(hwndDlg, IDC_BUTTON2), IsDlgButtonChecked(hwndDlg, IDC_CHECK2)); + break; + case IDC_BUTTON2: + { + BROWSEINFO bi = {0}; + wchar_t name[MAX_PATH] = {0}; + bi.hwndOwner = hwndDlg; + bi.pszDisplayName = name; + bi.ulFlags = BIF_RETURNONLYFSDIRS | BIF_NEWDIALOGSTYLE; + bi.lpfn = BrowseCallbackProc; + LPITEMIDLIST idlist = SHBrowseForFolder(&bi); + if (idlist) + { + SHGetPathFromIDListA(idlist, g_http_tmp); + IMalloc *m = 0; + SHGetMalloc(&m); + m->Free(idlist); + SetHTTPSaveButtonText(hwndDlg, g_http_tmp); + } + } + + return 0; + } + return 0; + case WM_INITDIALOG: + SetDlgItemInt(hwndDlg, IDC_BUFFERS_NUMBUFS, config_http_buffersize, 0); + SendMessage(GetDlgItem(hwndDlg, IDC_PREBUFSLIDER), TBM_SETRANGEMAX, 0, 50); + SendMessage(GetDlgItem(hwndDlg, IDC_PREBUFSLIDER), TBM_SETRANGEMIN, 0, 0); + SendMessage(GetDlgItem(hwndDlg, IDC_PREBUFSLIDER), TBM_SETPOS, 1, config_http_prebuffer / 2); + SendMessage(GetDlgItem(hwndDlg, IDC_PREBUFSLIDER2), TBM_SETRANGEMAX, 0, 50); + SendMessage(GetDlgItem(hwndDlg, IDC_PREBUFSLIDER2), TBM_SETRANGEMIN, 0, 0); + SendMessage(GetDlgItem(hwndDlg, IDC_PREBUFSLIDER2), TBM_SETPOS, 1, config_http_prebuffer_underrun / 2); + CheckDlgButton(hwndDlg, IDC_CHECK1, allow_sctitles); + CheckDlgButton(hwndDlg, IDC_SC_ARTWORK, allow_scartwork); + CheckDlgButton(hwndDlg, IDC_CHECK3, sctitle_format); + + if (config_miscopts&16) + { + CheckDlgButton(hwndDlg, IDC_CHECK2, BST_CHECKED); + } + EnableWindow(GetDlgItem(hwndDlg, IDC_BUTTON2), (config_miscopts&16)); + SetHTTPSaveButtonText(hwndDlg, config_http_save_dir); + lstrcpynA(g_http_tmp, config_http_save_dir, MAX_PATH); + + return FALSE; + case WM_NOTIFY: + { + LPNMHDR pnmh = (LPNMHDR) lParam; + if (pnmh->code == PSN_SETACTIVE) + { + config_lp = 4; + } + + if (pnmh->code == PSN_APPLY) + { + sctitle_format = !!IsDlgButtonChecked(hwndDlg, IDC_CHECK3); + allow_sctitles = !!IsDlgButtonChecked(hwndDlg, IDC_CHECK1); + allow_scartwork = !!IsDlgButtonChecked(hwndDlg, IDC_SC_ARTWORK); + { + int s; + int t; + t = GetDlgItemInt(hwndDlg, IDC_BUFFERS_NUMBUFS, &s, 0); + if (s) config_http_buffersize = t; + if (config_http_buffersize < 16) config_http_buffersize = 16; + } + config_http_prebuffer = (int)SendMessage(GetDlgItem(hwndDlg, IDC_PREBUFSLIDER), TBM_GETPOS, 0, 0) * 2; + config_http_prebuffer_underrun = (int)SendMessage(GetDlgItem(hwndDlg, IDC_PREBUFSLIDER2), TBM_GETPOS, 0, 0) * 2; + lstrcpynA(config_http_save_dir, g_http_tmp, MAX_PATH); + if (IsDlgButtonChecked(hwndDlg, IDC_CHECK2)) + { + config_miscopts |= 16; + } + else + { + config_miscopts &= ~16; + } + + return TRUE; + } + } + return FALSE; + } + + const int controls[] = + { + IDC_PREBUFSLIDER, + IDC_PREBUFSLIDER2, + }; + if (FALSE != WASABI_API_APP->DirectMouseWheel_ProcessDialogMessage(hwndDlg, uMsg, wParam, lParam, controls, ARRAYSIZE(controls))) + { + return TRUE; + } + + return FALSE; +} + +int DoAboutMessageBox(HWND parent, wchar_t* title, wchar_t* message) +{ + MSGBOXPARAMSW msgbx = {sizeof(MSGBOXPARAMSW),0}; + msgbx.lpszText = message; + msgbx.lpszCaption = title; + msgbx.lpszIcon = MAKEINTRESOURCE(102); + msgbx.hInstance = GetModuleHandle(0); + msgbx.dwStyle = MB_USERICON; + msgbx.hwndOwner = parent; + return MessageBoxIndirectW(&msgbx); +} + +void about(HWND hwndParent) +{ + wchar_t message[1024] = {0}, text[1024] = {0}; + WASABI_API_LNGSTRINGW_BUF(IDS_NULLSOFT_MPEG_AUDIO_DECODER_OLD,text,1024); + wsprintfW(message, WASABI_API_LNGSTRINGW(IDS_ABOUT_TEXT), + mod.description, __DATE__); + DoAboutMessageBox(hwndParent,text,message); +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mp3/config.h b/Src/Plugins/Input/in_mp3/config.h new file mode 100644 index 00000000..a3ca37c3 --- /dev/null +++ b/Src/Plugins/Input/in_mp3/config.h @@ -0,0 +1,51 @@ +#ifndef NULLSOFT_IN_MP3_CONFIG_H +#define NULLSOFT_IN_MP3_CONFIG_H + +#include <bfc/platform/guid.h> +enum +{ + WRITE_UTF16 = 0, + WRITE_LATIN = 1, + WRITE_LOCAL = 2, + WRITE_UTF8 = 3, +}; +extern int config_write_mode; + +enum +{ + READ_LATIN = 0, + READ_LOCAL = 1, +}; + +extern int config_read_mode; + +extern int config_parse_apev2; +extern int config_parse_lyrics3; +extern int config_parse_id3v1; +extern int config_parse_id3v2; + +extern int config_write_apev2; +extern int config_write_id3v1; +extern int config_write_id3v2; + +extern int config_create_id3v1; +extern int config_create_id3v2; +extern int config_create_apev2; + +enum +{ + RETAIN_HEADER = 0, + ADD_HEADER = 1, + REMOVE_HEADER = 2, +}; +extern int config_apev2_header; + +extern int config_id3v2_version; +extern int config_lp; +extern char config_rating_email[255]; + +// {B6CB4A7C-A8D0-4c55-8E60-9F7A7A23DA0F} +static const GUID playbackConfigGroupGUID = +{ 0xb6cb4a7c, 0xa8d0, 0x4c55, { 0x8e, 0x60, 0x9f, 0x7a, 0x7a, 0x23, 0xda, 0xf } }; + +#endif diff --git a/Src/Plugins/Input/in_mp3/giofile.cpp b/Src/Plugins/Input/in_mp3/giofile.cpp new file mode 100644 index 00000000..4949c268 --- /dev/null +++ b/Src/Plugins/Input/in_mp3/giofile.cpp @@ -0,0 +1,2475 @@ +/***************************************************************************\ +* +* (C) copyright Fraunhofer - IIS (1998) +* All Rights Reserved +* +* filename: giofile.cpp +* project : MPEG Decoder +* author : Martin Sieler +* date : 1998-02-11 +* contents/description: file I/O class for MPEG Decoder +* +* +\***************************************************************************/ + +/* ------------------------ includes --------------------------------------*/ + +#include "main.h" +#include "api__in_mp3.h" +#include <time.h> +#include <locale.h> +#include "../Winamp/wa_ipc.h" + +#include "LAMEinfo.h" +#include "OFL.h" +#include "../nu/AutoChar.h" +#include "../nu/AutoWide.h" + +#include <api/service/waservicefactory.h> +#include <shlwapi.h> +#include "MP3Info.h" +#include "config.h" +#include <math.h> +#include "id3.h" +#include "../apev2/header.h" +#include "uvox_3902.h" +#include <foundation/error.h> +#include <strsafe.h> + +#define MAX_REDIRECTS 10 + +#define SAFE_MALLOC_MATH(orig, x) (((orig)<x)?x:orig) +// seems like a reasonable limit, but we should check with tag first +#define UVOX_MAXMSG_CAP 1048576 + +static jnl_connection_t CreateConnection(const char *url, jnl_dns_t dns, size_t sendbufsize, size_t recvbufsize) +{ + if (!_strnicmp(url, "https:", strlen("https:"))) + return jnl_sslconnection_create(dns, sendbufsize, recvbufsize); + else + return jnl_connection_create(dns, sendbufsize, recvbufsize); +} + +static int64_t Seek64(HANDLE hf, int64_t distance, DWORD MoveMethod) +{ + LARGE_INTEGER li; + li.QuadPart = distance; + li.LowPart = SetFilePointer (hf, li.LowPart, &li.HighPart, MoveMethod); + if (li.LowPart == INVALID_SET_FILE_POINTER && GetLastError() != NO_ERROR) + { + li.QuadPart = -1; + } + return li.QuadPart; +} + +HWND GetDialogBoxParent() +{ + HWND parent = (HWND)SendMessage(mod.hMainWindow, WM_WA_IPC, 0, IPC_GETDIALOGBOXPARENT); + if (!parent || parent == (HWND)1) + return mod.hMainWindow; + return parent; +} + +/*-------------------------- defines --------------------------------------*/ + +/*-------------------------------------------------------------------------*/ + +//-------------------------------------------------------------------------* +// +// CGioFile +// +//-------------------------------------------------------------------------* + +//-------------------------------------------------------------------------* +// constructor +//-------------------------------------------------------------------------* + + +static char ultravoxUserAgent[128] = ""; +char *GetUltravoxUserAgent() +{ + if (!ultravoxUserAgent[0]) + { + StringCchPrintfA(ultravoxUserAgent, 128, "User-Agent: WinampMPEG/%01x.%02x, Ultravox/2.1\r\n" + "Ultravox-transport-type: TCP\r\n", + WINAMP_VERSION_MAJOR(winampVersion), + WINAMP_VERSION_MINOR(winampVersion)); + } + return ultravoxUserAgent; +} + +static char userAgent[128] = ""; +char *GetUserAgent() +{ + if (!userAgent[0]) + { + + StringCchPrintfA(userAgent, 128, "User-Agent: WinampMPEG/%01x.%02x\r\n", + WINAMP_VERSION_MAJOR(winampVersion), + WINAMP_VERSION_MINOR(winampVersion)); + } + return userAgent; +} + +extern int lastfn_status_err; + +CGioFile::CGioFile() +{ + req=0; + proxy_host=0; + host=0; + request=0; + proxy_lp=0; + lpinfo=0; + mpeg_length=0; + file_position=0; + mpeg_position=0; + no_peek_hack=false; + encodingMethod=0; + uvox_3901=0; + uvox_3902=0; + stream_url[0]=0; + stream_genre[0]=0; + stream_current_title[0]=0; + stream_name[0]=0; + ZeroMemory(&last_write_time, sizeof(last_write_time)); + ZeroMemory(id3v1_data, sizeof(id3v1_data)); + lengthVerified=false; + m_vbr_bytes = 0; + uvox_last_message = 0; + prepad = 0; + postpad = 0; + m_vbr_ms = 0; + m_vbr_hdr = 0; + stream_id3v2_buf = 0; + lyrics3_size=0; + lyrics3_data=0; + apev2_data=0; + m_content_type = 0; + ZeroMemory(uvox_meta, sizeof(uvox_meta)); + is_uvox = 0; + uvox_sid = uvox_maxbr = uvox_avgbr = 0; + uvox_message_cnt = uvox_desyncs = 0; + uvox_stream_data = 0; + uvox_stream_data_len = 0; + ZeroMemory(&uvox_artwork, sizeof(uvox_artwork)); + uvox_maxmsg = 65535; + force_lpinfo[0] = 0; + is_stream_seek = 0; + last_full_url[0] = 0; + m_is_stream = 0; + m_redircnt = 0; + m_auth_tries = 0; + m_full_buffer = NULL; + fEof = false; + m_connection = NULL; + m_dns = NULL; + hFile = INVALID_HANDLE_VALUE; + m_http_response = 0; + m_seek_reset = false; + m_is_stream_seek = false; + m_is_stream_seekable = false; + initTitleList(); + m_vbr_frames = 0; + ZeroMemory(&m_vbr_toc, sizeof(m_vbr_toc)); + m_vbr_frame_len = 0; + m_vbr_flag = 0; + m_id3v2_len = 0; + m_id3v1_len = 0; + port = 80; + constate = 0; + ZeroMemory(&save_filename, sizeof(save_filename)); + ZeroMemory(&server_name, sizeof(server_name)); + recvbuffersize = 32768; + + timeout_start = GetTickCount(); + meta_interval = 0; + meta_pos = 0; + ZeroMemory(&stream_title_save, sizeof(stream_title_save)); + ZeroMemory(&last_title_sent, sizeof(last_title_sent)); + ZeroMemory(&dlg_realm, sizeof(dlg_realm)); + m_useaproxy = 0; +} + +//-------------------------------------------------------------------------* +// destructor +//-------------------------------------------------------------------------* + +CGioFile::~CGioFile() +{ + int x, y; + for (x = 0; x < 2; x ++) + for (y = 0; y < 32; y ++) + free(uvox_meta[x][y]); + free(m_content_type); + free(uvox_stream_data); + free(stream_id3v2_buf); + free(lyrics3_data); + free(apev2_data); + free(uvox_3901); + free(uvox_3902); + + Close(); + delete m_vbr_hdr; + m_vbr_hdr = 0; + clearTitleList(); + free(lpinfo); + free(proxy_lp); + free(request); + free(host); + free(req); + free(proxy_host); +} + +static bool GetRG(const char *key, ID3v2 &info, APEv2::Tag &apev2, wchar_t *val, int len) +{ + val[0]=0; + if (info.GetString(key, val, len) == 1 && val[0]) + return true; + if (apev2.GetString(key, val, len) == APEv2::APEV2_SUCCESS && val[0]) + return true; + return false; +} + +float CGioFile::GetGain() +{ + if (AGAVE_API_CONFIG->GetBool(playbackConfigGroupGUID, L"replaygain", false)) + { + // if (info.HasData()) + { + float dB = 0, peak = 1.0f; + wchar_t gain[128] = L"", peakVal[128] = L""; + _locale_t C_locale = WASABI_API_LNG->Get_C_NumericLocale(); + + switch (AGAVE_API_CONFIG->GetUnsigned(playbackConfigGroupGUID, L"replaygain_source", 0)) + { + case 0: // track + if ((GetRG("replaygain_track_gain", info, apev2, gain, 128) == false || !gain[0]) + && !AGAVE_API_CONFIG->GetBool(playbackConfigGroupGUID, L"replaygain_preferred_only", false)) + GetRG("replaygain_album_gain", info, apev2, gain, 128); + + if ((GetRG("replaygain_track_peak", info, apev2, peakVal, 128) == false || !peakVal[0]) + && !AGAVE_API_CONFIG->GetBool(playbackConfigGroupGUID, L"replaygain_preferred_only", false)) + GetRG("replaygain_album_peak", info, apev2, peakVal, 128); + break; + case 1: + if ((GetRG("replaygain_album_gain", info, apev2, gain, 128) == false || !gain[0]) + && !AGAVE_API_CONFIG->GetBool(playbackConfigGroupGUID, L"replaygain_preferred_only", false)) + GetRG("replaygain_track_gain", info, apev2, gain, 128); + + if ((GetRG("replaygain_album_peak", info, apev2, peakVal, 128) == false || !peakVal[0]) + && !AGAVE_API_CONFIG->GetBool(playbackConfigGroupGUID, L"replaygain_preferred_only", false)) + GetRG("replaygain_track_peak", info, apev2, peakVal, 128); + break; + } + + if (gain[0]) + { + if (gain[0] == L'+') + dB = (float)_wtof_l(&gain[1], C_locale); + else + dB = (float)_wtof_l(gain, C_locale); + } + else + { + dB = AGAVE_API_CONFIG->GetFloat(playbackConfigGroupGUID, L"non_replaygain", -6.0); + return (float)pow(10.0f, dB / 20.0f); + } + + if (peakVal[0]) + { + peak = (float)_wtof_l(peakVal, C_locale); + } + + switch (AGAVE_API_CONFIG->GetUnsigned(playbackConfigGroupGUID, L"replaygain_mode", 1)) + { + case 0: // apply gain + return (float)pow(10.0f, dB / 20.0f); + case 1: // apply gain, but don't clip + return min((float)pow(10.0f, dB / 20.0f), 1.0f / peak); + case 2: // normalize + return 1.0f / peak; + case 3: // prevent clipping + if (peak > 1.0f) + return 1.0f / peak; + else + return 1.0f; + } + } + /* else + { + float dB = AGAVE_API_CONFIG->GetFloat(playbackConfigGroupGUID, L"non_replaygain", -6.0); + return pow(10.0f, dB / 20.0f); + }*/ + } + + return 1.0f; // no gain +} +//-------------------------------------------------------------------------* +// Open +//-------------------------------------------------------------------------* + +static char *jnl_strndup(const char *str, size_t n) +{ + char *o = (char *)calloc(n+1, sizeof(char)); + if (!o) + return 0; + + strncpy(o, str, n); + o[n]=0; + return o; +} + +static int parse_url(const char *url, char **prot, char **host, unsigned short *port, char **req, char **lp) +{ + free(*prot); *prot=0; + free(*host); *host = 0; + free(*req); *req = 0; + free(*lp); *lp = 0; + *port = 0; + + const char *p; + const char *protocol = strstr(url, "://"); + if (protocol) + { + *prot = jnl_strndup(url, protocol-url); + p = protocol + 3; + + } + else + { + p = url; + } + + while (p && *p == '/') p++; // skip extra / + + size_t end = strcspn(p, "@/"); + + // check for username + if (p[end] == '@') + { + *lp = jnl_strndup(p, end); + p = p+end+1; + end = strcspn(p, "[:/"); + } + + if (p[0] == '[') // IPv6 style address + { + p++; + const char *ipv6_end = strchr(p, ']'); + if (!ipv6_end) + return 1; + + *host = jnl_strndup(p, ipv6_end-p); + p = ipv6_end+1; + } + else + { + end = strcspn(p, ":/"); + *host = jnl_strndup(p, end); + p += end; + } + + // is there a port number? + if (p[0] == ':') + { + char *new_end; + *port = (unsigned short)strtoul(p+1, &new_end, 10); + p = new_end; + } + + if (p[0]) + { + *req = _strdup(p); + } + + return 0; +} + +static void parseURL(const char *url, char **host, unsigned short *port, char **req, char **lp) +{ + char *prot=0; + + parse_url(url, &prot, host, port, req, lp); + if (!*port) + { + if (prot) + { + if (!stricmp(prot, "https")) + *port = 443; + else + *port = 80; + } + else + *port=80; + } + + free(prot); + + if (!*req) + *req = _strdup("/"); +} + +static void encodeMimeStr(char *in, char *out) +{ + char alphabet[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + int shift = 0; + int accum = 0; + + while (in && *in) + { + if (*in) + { + accum <<= 8; + shift += 8; + accum |= *in++; + } + while (shift >= 6) + { + shift -= 6; + *out++ = alphabet[(accum >> shift) & 0x3F]; + } + } + if (shift == 4) + { + *out++ = alphabet[(accum & 0xF) << 2]; + *out++ = '='; + } + else if (shift == 2) + { + *out++ = alphabet[(accum & 0x3) << 4]; + *out++ = '='; + *out++ = '='; + } + + *out++ = 0; +} + +int CGioFile::doConnect(const char *str, int start_offset) +{ + char *http_ver_str = " HTTP/1.0\r\n"; + + unsigned short proxy_port = 80; + char str2[1024]={0}; + + if (!str) + str = last_full_url; + else + lstrcpynA( last_full_url, str, sizeof( last_full_url ) ); + + if (start_offset > 0) + { + http_ver_str = " HTTP/1.1\r\n"; + } + + lstrcpynA(str2, str, 1024); + + is_stream_seek = start_offset || !str; + + lstrcpynA(g_stream_title, str, 256); + + meta_interval = meta_pos = 0; + server_name[0] = 0; + last_title_sent[0] = 0; + stream_bytes_read = start_offset; + stream_metabytes_read = 0; + m_is_stream = 1; + constate = 0; + parseURL(str2, &host, &port, &request, &lpinfo); + if (port == 80 || !GetPrivateProfileIntA("Winamp", "proxyonly80", 0, INI_FILE)) + { + const char *p; + const char *winamp_proxy = (const char *)SendMessage(mod.hMainWindow, WM_WA_IPC, 0, IPC_GET_PROXY_STRING); + if (!winamp_proxy || winamp_proxy == (char *)1) + { + char temp[256] = {0}; + GetPrivateProfileStringA("Winamp", "proxy", "", temp, sizeof(temp), INI_FILE); + p = temp; + } + else + p = winamp_proxy; + + while (p && (*p == ' ' || *p == '\t')) p++; + char config_proxy[512] = "http://"; + StringCchCatA(config_proxy, 512, p); + parseURL(config_proxy, &proxy_host, &proxy_port, &req, &proxy_lp); + } + + if (!host || !host[0]) + return 1; + char *p = request + strlen(request); + while (p >= request && *p != '/') p--; + if (p[1]) + lstrcpynA(g_stream_title, ++p, 256); + lstrcpynA(stream_title_save, g_stream_title, 580); + lstrcpynA(stream_name, g_stream_title, 256); + g_stream_title[255] = 0; + fEof = false; + timeout_start = GetTickCount(); + EnterCriticalSection(&g_lfnscs); + WASABI_API_LNGSTRING_BUF(IDS_CONNECTING,lastfn_status,256); + LeaveCriticalSection(&g_lfnscs); + PostMessage(mod.hMainWindow, WM_USER, 0, IPC_UPDTITLE); + if (force_lpinfo[0]) + { + free(lpinfo); + lpinfo = _strdup(force_lpinfo); + } + + if (!m_dns) jnl_dns_create(&m_dns); + m_connection = CreateConnection(str, m_dns, 16384, recvbuffersize = max(32768, config_http_buffersize * 1024)); + if (!m_connection) + return 1; + + if (!proxy_host || !proxy_host[0]) + { + jnl_connection_connect(m_connection, host, port); + jnl_connection_send_string(m_connection, "GET "); + jnl_connection_send_string(m_connection, request); + jnl_connection_send_string(m_connection, http_ver_str); + } + else + { + char s[32]={0}; + jnl_connection_connect(m_connection, proxy_host, proxy_port); + jnl_connection_send_string(m_connection, "GET http://"); + if (lpinfo && lpinfo[0]) + { + jnl_connection_send_string(m_connection, lpinfo); + jnl_connection_send_string(m_connection, "@"); + } + jnl_connection_send_string(m_connection, host); + StringCchPrintfA(s, 32, ":%d", port); + jnl_connection_send_string(m_connection, s); + jnl_connection_send_string(m_connection, request); + jnl_connection_send_string(m_connection, http_ver_str); + if (proxy_lp && proxy_lp[0]) + { + char temp[1024]={0}; + jnl_connection_send_string(m_connection, "Proxy-Authorization: Basic "); + encodeMimeStr(proxy_lp, temp); + jnl_connection_send_string(m_connection, temp); + jnl_connection_send_string(m_connection, "\r\n"); + } + } + + jnl_connection_send_string(m_connection, "Host: "); + jnl_connection_send_string(m_connection, host); + jnl_connection_send_string(m_connection, "\r\n"); + + if (!start_offset) + { + jnl_connection_send_string(m_connection, GetUltravoxUserAgent()); + } + else + jnl_connection_send_string(m_connection, GetUserAgent()); + + jnl_connection_send_string(m_connection, "Accept: */*\r\n"); + + if (allow_sctitles && !start_offset) jnl_connection_send_string(m_connection, "Icy-MetaData:1\r\n"); + + if (lpinfo && lpinfo[0]) + { + char str[512] = {0}; + encodeMimeStr(lpinfo, str); + jnl_connection_send_string(m_connection, "Authorization: Basic "); + jnl_connection_send_string(m_connection, str); + jnl_connection_send_string(m_connection, "\r\n"); + } + if (start_offset > 0) + { + char str[512] = {0}; + StringCchPrintfA(str, 512, "Range: bytes=%d-\r\n", start_offset); + jnl_connection_send_string(m_connection, str); + } + jnl_connection_send_string(m_connection, "Connection: close\r\n"); + jnl_connection_send_string(m_connection, "\r\n"); + + return 0; +} + +int CGioFile::Open(const wchar_t *pszName, int maxbufsizek) +{ + peekBuffer.reserve(16384); + + m_vbr_flag = 0; + m_vbr_frames = 0; + m_vbr_samples = 0; + m_vbr_ms = 0; + m_vbr_frame_len = 0; + + m_id3v2_len = 0; + m_id3v1_len = 0; + m_apev2_len = 0; + + lyrics3_size = 0; + + m_is_stream = 0; + + mpeg_length = 0; + mpeg_position = 0; + file_position = 0; + + if ( !_wcsnicmp( pszName, L"file://", 7 ) ) + pszName += 7; + + if ( PathIsURL( pszName ) ) + { + wchar_t str[ 8192 ] = { 0 }; + wchar_t *p; + hFile = INVALID_HANDLE_VALUE; + lstrcpyn( str, pszName, 8192 ); + save_filename[ 0 ] = 0; + if ( p = wcsstr( str, L">save.file:" ) ) + { + *p = 0; + p += 11; + if ( !wcsstr( p, L".." ) && !wcsstr( p, L"\\" ) && !wcsstr( p, L"/" ) ) + { + lstrcpynA( save_filename, AutoChar( p ), 256 ); + } + } + + if ( doConnect( AutoChar( str ), 0 ) ) + return NErr_ConnectionFailed; + } + else + { + hFile = CreateFile(pszName, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, 0); + + if (hFile != INVALID_HANDLE_VALUE) + { + GetFileTime( hFile, 0, 0, &last_write_time ); + mpeg_length = Seek64( hFile, 0, FILE_END ); + + uint64_t startOffset = 0; + unsigned char buf[ 1448 ] = { 0 }; + + DWORD len = 0; + while ( 1 ) // read all tags (sometimes programs get stupid and make multiple tags) + { + len = 0; + Seek64( hFile, startOffset, FILE_BEGIN ); + ReadFile( hFile, buf, 10, &len, NULL ); + + if ( len >= 10 && + buf[ 0 ] == 'I' && + buf[ 1 ] == 'D' && + buf[ 2 ] == '3' && + buf[ 3 ] != 255 && + buf[ 4 ] != 255 && + buf[ 6 ] < 0x80 && + buf[ 7 ] < 0x80 && + buf[ 8 ] < 0x80 && + buf[ 9 ] < 0x80 ) + { + DWORD thisLen = 10; + if ( buf[ 5 ] & 0x10 ) // check for footer flag + thisLen += 10; + + thisLen += ( (int)buf[ 6 ] ) << 21; + thisLen += ( (int)buf[ 7 ] ) << 14; + thisLen += ( (int)buf[ 8 ] ) << 7; + thisLen += ( (int)buf[ 9 ] ); + + SetFilePointer( hFile, -10, NULL, FILE_CURRENT ); + + if ( stream_id3v2_buf ) // already read a tag? + { + startOffset += thisLen; + m_id3v2_len += thisLen; + mpeg_length -= thisLen; + mpeg_position += thisLen; + + SetFilePointer( hFile, thisLen, NULL, FILE_CURRENT ); + + continue; + } + + stream_id3v2_buf = (char *)malloc( thisLen ); + if ( stream_id3v2_buf ) + { + memcpy( stream_id3v2_buf, buf, 10 ); + + DWORD dummy = 0; + if ( !ReadFile( hFile, stream_id3v2_buf, thisLen, &dummy, 0 ) || dummy < thisLen ) + { + free( stream_id3v2_buf ); + stream_id3v2_buf = NULL; + + thisLen = 0; + SetFilePointer( hFile, 0, NULL, FILE_END ); + + break; + } + else + { + mpeg_position += thisLen; + stream_id3v2_read = thisLen; + mpeg_length -= thisLen; + startOffset += thisLen; + + info.Decode( stream_id3v2_buf, thisLen ); + } + } + else + { + /* memory allocation failed, let's assume the ID3v2 tag was valid ... */ + mpeg_position += thisLen; + stream_id3v2_read = thisLen; + mpeg_length -= thisLen; + startOffset += thisLen; + } + + m_id3v2_len += thisLen; + } + else + { + // benski> unnecessary because we call SetFilePointer immediately after the loop: + // CUT: SetFilePointer(hFile, -10, NULL, FILE_CURRENT); + break; + } + } + + /* Read ID3v1 Tag */ + if ( mpeg_length >= 128 ) + { + SetFilePointer( hFile, -128, NULL, FILE_END ); + + len = 0; + if ( ReadFile( hFile, id3v1_data, 128, &len, NULL ) && len == 128 ) + { + if ( id3v1_data[ 0 ] == 'T' && id3v1_data[ 1 ] == 'A' && id3v1_data[ 2 ] == 'G' ) + { + m_id3v1_len = 128; + mpeg_length -= m_id3v1_len; + } + } + } + + /* read appended ID3v2.4 tag */ + if ( mpeg_length >= 10 && Seek64( hFile, mpeg_position + mpeg_length - 10, FILE_BEGIN ) != -1 ) + { + len = 0; + ReadFile( hFile, buf, 10, &len, NULL ); + if ( len >= 10 && + buf[ 0 ] == '3' && + buf[ 1 ] == 'D' && + buf[ 2 ] == 'I' && + buf[ 3 ] != 255 && + buf[ 4 ] != 255 && + buf[ 6 ] < 0x80 && + buf[ 7 ] < 0x80 && + buf[ 8 ] < 0x80 && + buf[ 9 ] < 0x80 ) + { + DWORD thisLen = 10; + if ( buf[ 5 ] & 0x10 ) // check for header flag + thisLen += 10; + + thisLen += ( (int)buf[ 6 ] ) << 21; + thisLen += ( (int)buf[ 7 ] ) << 14; + thisLen += ( (int)buf[ 8 ] ) << 7; + thisLen += ( (int)buf[ 9 ] ); + + mpeg_length -= thisLen; + } + } + + /* Read Lyrics3 Tag */ + if ( mpeg_length >= 15 ) + { + free( lyrics3_data ); + lyrics3_data = NULL; + + lyrics3_size = 0; + char lyrics3_end_signature[ 15 ] = { 0 }; + + Seek64( hFile, ( mpeg_position + mpeg_length - 15 ), FILE_BEGIN ); + + len = 0; + if ( ReadFile( hFile, lyrics3_end_signature, 15, &len, NULL ) && len == 15 ) + { + if ( !memcmp( lyrics3_end_signature + 6, "LYRICS200", 9 ) ) + { + lyrics3_size = strtoul( lyrics3_end_signature, 0, 10 ); + if ( lyrics3_size ) + { + lyrics3_data = (char *)malloc( lyrics3_size ); + if ( lyrics3_data ) + { + SetFilePointer( hFile, -(LONG)( 15 + lyrics3_size ), NULL, FILE_CURRENT ); + + len = 0; + ReadFile( hFile, lyrics3_data, lyrics3_size, &len, 0 ); + if ( len != lyrics3_size || memcmp( lyrics3_data, "LYRICSBEGIN", 11 ) ) + { + free( lyrics3_data ); + lyrics3_data = NULL; + + lyrics3_size = 0; + } + else + { + mpeg_length -= lyrics3_size + 15; + } + } + } + } + } + } + + if ( mpeg_length >= 32 ) + { + /* Read APEv2 Tag */ + free( apev2_data ); + apev2_data = NULL; + + char ape[ 32 ] = { 0 }; + Seek64( hFile, ( mpeg_position + mpeg_length - 32 ), FILE_BEGIN ); + + len = 0; + if ( ReadFile( hFile, ape, 32, &len, NULL ) && len == 32 ) + { + APEv2::Header footer( ape ); + if ( footer.Valid() ) + { + m_apev2_len = footer.TagSize(); + if ( mpeg_length >= m_apev2_len ) + { + Seek64( hFile, -(int64_t)( m_apev2_len ), FILE_CURRENT ); + + apev2_data = (char *)malloc( m_apev2_len ); + if ( apev2_data ) + { + len = 0; + ReadFile( hFile, apev2_data, m_apev2_len, &len, NULL ); + if ( len != m_apev2_len || apev2.Parse( apev2_data, m_apev2_len ) != APEv2::APEV2_SUCCESS ) + { + free( apev2_data ); + apev2_data = NULL; + + m_apev2_len = 0; + } + } + + mpeg_length -= m_apev2_len; + } + } + } + } + + { + Seek64( hFile, mpeg_position, FILE_BEGIN ); + + len = 0; + ReadFile( hFile, buf, sizeof( buf ), &len, NULL ); + + delete m_vbr_hdr; + m_vbr_hdr = NULL; + + LAMEinfo lame; + lame.toc = m_vbr_toc; + + m_vbr_frame_len = ReadLAMEinfo( buf, &lame ); + if ( m_vbr_frame_len ) + { + lengthVerified = false; + prepad = lame.encoderDelay; + postpad = lame.padding; + + encodingMethod = lame.encodingMethod; + if ( !encodingMethod && lame.cbr ) + encodingMethod = ENCODING_METHOD_CBR; + + if ( lame.flags & TOC_FLAG ) + { + int x; + for ( x = 0; x < 100; x++ ) + if ( m_vbr_toc[ x ] ) break; + + if ( x != 100 ) + m_vbr_flag = 1; + } + + if ( lame.flags & BYTES_FLAG ) + { + // some programs are stupid and count the id3v2 length in the lame header + if ( mpeg_length + m_id3v2_len == lame.bytes || mpeg_length + m_id3v2_len + m_id3v1_len == lame.bytes ) + { + m_vbr_bytes = mpeg_length; + lengthVerified = true; + } + else if ( abs( (int)mpeg_length - lame.bytes ) < MAX_ACCEPTABLE_DEVIANCE ) + { + m_vbr_bytes = lame.bytes; + lengthVerified = true; + } + } + + if ( lame.flags & FRAMES_FLAG + && m_vbr_bytes && lengthVerified ) // only use the length if we're sure it's unmodified + { + m_vbr_frames = lame.frames; + m_vbr_samples = Int32x32To64( lame.frames, lame.h_id ? 1152 : 576 ); + m_vbr_samples -= ( prepad + postpad ); + m_vbr_ms = MulDiv( (int)m_vbr_samples, 1000, lame.samprate ); + } + + if ( !m_vbr_frames || encodingMethod == ENCODING_METHOD_CBR ) + m_vbr_flag = 0; + + mpeg_length -= m_vbr_frame_len; + mpeg_position += m_vbr_frame_len; + } + else + { + m_vbr_hdr = new CVbriHeader; + + m_vbr_frame_len = m_vbr_hdr->readVbriHeader(buf); + if (m_vbr_frame_len) + { + m_vbr_bytes = m_vbr_hdr->getBytes(); + m_vbr_frames = m_vbr_hdr->getNumFrames(); + m_vbr_ms = m_vbr_hdr->getNumMS(); + + lengthVerified = true; + + mpeg_length -= m_vbr_frame_len; + mpeg_position += m_vbr_frame_len; + } + else + { + delete m_vbr_hdr; + m_vbr_hdr = NULL; + } + } + } + + // read OFL + { + Seek64( hFile, mpeg_position, FILE_BEGIN ); + len = 0; + ReadFile( hFile, buf, sizeof( buf ), &len, NULL ); + MPEGFrame frame; + frame.ReadBuffer( buf ); + OFL ofl; + if ( ofl.Read( frame, &buf[ 4 ], len - 4 ) == NErr_Success ) + { + m_vbr_ms = (int)ofl.GetLengthSeconds() * 1000; + m_vbr_samples = ofl.GetSamples(); + + m_vbr_frames = ofl.GetFrames(); + size_t pre, post; + if ( ofl.GetGaps( &pre, &post ) == NErr_Success ) + { + prepad = (int)pre - 529; + postpad = (int)post + 529; + } + } + } + + ReadiTunesGaps(); + + Seek64( hFile, mpeg_position, FILE_BEGIN ); + + if ( maxbufsizek * 1024 >= mpeg_length ) + { + DWORD m_full_buffer_len = 0; + m_full_buffer = new unsigned char[ (unsigned int)mpeg_length ]; + if ( !ReadFile( hFile, m_full_buffer, (DWORD)mpeg_length, &m_full_buffer_len, NULL ) ) + { + CloseHandle( hFile ); + hFile = INVALID_HANDLE_VALUE; + delete[] m_full_buffer; + m_full_buffer = NULL; + return NErr_Error; + } + CloseHandle( hFile ); + hFile = INVALID_HANDLE_VALUE; + m_full_buffer_pos = 0; + } + else + { + m_full_buffer = NULL; + } + + fEof = false; + return NErr_Success; + } + else + { + //DWORD dwError = GetLastError(); + return NErr_Error; + } + } + + return NErr_Success; +} + +//-------------------------------------------------------------------------* +// Close +//-------------------------------------------------------------------------* + +int CGioFile::Close() +{ + int dwReturn = NErr_Success; + + if (m_is_stream) + { + jnl_connection_release(m_connection); + if (m_dns) jnl_dns_release(m_dns); + m_dns = 0; + if (hFile != INVALID_HANDLE_VALUE) CloseHandle(hFile); + hFile = INVALID_HANDLE_VALUE; + m_is_stream = 0; + } + else + { + delete [] m_full_buffer; + m_full_buffer = NULL; + + if (hFile != INVALID_HANDLE_VALUE) + { + dwReturn = CloseHandle(hFile) ? NErr_Success : NErr_Error; + hFile = INVALID_HANDLE_VALUE; + } + } + + return dwReturn; +} + +void CGioFile::processMetaData(char *data, int len, int msgId) +{ + if (len && *data) + { + char *ld = NULL; + int x; + if (len > 4096) return ; + for (x = 0; x < len; x ++) + if (!data[x]) break; + if (x == len) return ; + while ((ld = strstr(data, "='"))) + { + char * n = data; + ld[0] = 0; + ld += 2; + data = strstr(ld, "';"); + if (data) + { + data[0] = 0; + data += 2; + if (!_stricmp(n, "StreamTitle")) + { + lstrcpynA(g_stream_title, ld, sizeof(g_stream_title)); + lstrcpynA(last_title_sent, g_stream_title, sizeof(last_title_sent)); + lstrcpynA(stream_current_title, g_stream_title, sizeof(stream_current_title)); + if (sctitle_format) + { + StringCchCatA(g_stream_title, 256, *stream_title_save ? *g_stream_title ? " (" : "(" : ""); + StringCchCatA(g_stream_title, 256, stream_title_save); + StringCchCatA(g_stream_title, 256, *stream_title_save ? ")" : ""); + } + PostMessage(mod.hMainWindow, WM_USER, 0, IPC_UPDTITLE); + } + else if (!_stricmp(n, "StreamUrl")) + { + lstrcpynA(stream_url, ld, sizeof(stream_url)); + DWORD_PTR dw; + if (stream_url[0]) SendMessageTimeout(mod.hMainWindow, WM_USER, (WPARAM)stream_url, IPC_MBOPEN, SMTO_NORMAL, 500, &dw); + } + } + else break; + } + } +} + +INT_PTR CALLBACK CGioFile::httpDlgProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + CGioFile *_this; + switch (uMsg) + { + case WM_INITDIALOG: + SetWindowLongPtr(hwndDlg, GWLP_USERDATA, (LONG_PTR)lParam); + _this = (CGioFile *)GetWindowLongPtr(hwndDlg, GWLP_USERDATA); + if (_this->force_lpinfo[0]) + SetDlgItemTextA(hwndDlg, IDC_EDIT1, _this->force_lpinfo); + else SetDlgItemTextA(hwndDlg, IDC_EDIT1, _this->lpinfo?_this->lpinfo:""); + SetDlgItemTextA(hwndDlg, IDC_REALM, _this->dlg_realm); + return 1; + case WM_COMMAND: + _this = (CGioFile *)GetWindowLongPtr(hwndDlg, GWLP_USERDATA); + if (LOWORD(wParam) == IDOK) + { + GetDlgItemTextA(hwndDlg, IDC_EDIT1, _this->force_lpinfo, sizeof(_this->force_lpinfo)); + EndDialog(hwndDlg, 1); + } + else if (LOWORD(wParam) == IDCANCEL) + { + EndDialog(hwndDlg, 0); + } + break; + } + return 0; +} + +class GioFileFiller : public Filler +{ +public: + GioFileFiller(CGioFile *_file) + { + ret=NErr_Success; + file=_file; + } + + size_t Read(void *dest, size_t len) + { + int bytesRead=0; + ret = file->Read(dest, (int)len, &bytesRead); + return bytesRead; + } + + CGioFile *file; + int ret; +}; + +int CGioFile::Peek(void *pBuffer, int cbToRead, int *pcbRead) +{ + GioFileFiller filler(this); + + // do we need to fill up? + if (cbToRead > (int)peekBuffer.size()) + { + no_peek_hack = true;// benski> HACK ALERT! we have to set this or else Read() will try to use the peek buffer + peekBuffer.fill(&filler, cbToRead - peekBuffer.size()); + no_peek_hack=false; + } + + *pcbRead = (int)peekBuffer.peek(pBuffer, cbToRead); + return filler.ret; +} + +//-------------------------------------------------------------------------* +// Read +//-------------------------------------------------------------------------* + +int CGioFile::Read(void *pBuffer, int cbToRead, int *pcbRead) +{ + TITLELISTTYPE *mylist = TitleLinkedList; + // these are used for SHOUTCAST2 metadata and artwork since it can provide + // multi-packet metadata chunks which are out of order or are received in + // a way which would otherwise cause these to be cleared incorrectly + static int title_parts = 0, stream_art_parts = 0, stream_art_parts_total_len = 0, + playing_art_parts = 0, playing_art_parts_total_len = 0; + static char **title_blocks = 0, **stream_art_blocks = 0, **playing_art_blocks = 0; + + if (mylist != &TitleListTerminator && mod.outMod) + { + while (mylist->Next && mylist != &TitleListTerminator) + { + TITLELISTTYPE *next = (TITLELISTTYPE *)mylist->Next; + long now = mod.outMod->GetOutputTime(); + + if (mylist->title[0] || mylist->part_len) + { + if (!mylist->timer || now >= mylist->timer) + { + switch(mylist->style) + { + case UVOX_METADATA_STYLE_AOLRADIO: + { + EnterCriticalSection(&streamInfoLock); + free(uvox_3901); + uvox_3901 = _strdup(mylist->title); + LeaveCriticalSection(&streamInfoLock); + if (g_playing_file) + { + PostMessage(mod.hMainWindow, WM_USER, (WPARAM) "0x3901", IPC_METADATA_CHANGED); + PostMessage(mod.hMainWindow, WM_WA_IPC, 0, IPC_UPDTITLE); + } + } + break; + + case UVOX_METADATA_STYLE_SHOUTCAST: + { + processMetaData(mylist->title, (int)strlen(mylist->title) + 1); + } + break; + + case UVOX_METADATA_STYLE_SHOUTCAST2: + { + EnterCriticalSection(&streamInfoLock); + // so we can recombine multi-packets we'll store things and + // when all of the packets have been read then we form them + if(!title_blocks) + title_blocks = (char **)malloc(sizeof(char*)*(mylist->total_parts)); + + title_blocks[mylist->part-1] = _strdup(mylist->title); + title_parts++; + + // sanity check so we only try to get the metadata if all parts were processed + if (title_parts == mylist->total_parts) + { + free(uvox_3902); + uvox_3902 = (char *)malloc(16377 * mylist->total_parts); + uvox_3902[0] = 0; + title_parts = 0; + + for(int i = 0; i < mylist->total_parts; i++) + { + StringCchCatA(uvox_3902, mylist->total_parts * 16384, title_blocks[i]); + free(title_blocks[i]); + } + free(title_blocks); + title_blocks = 0; + + // attempt to form a title as 'artist - album - title' as sc_serv2 feeds + // changed for 5.61 to be just 'artist - title' to match v1 and v2 tools + Ultravox3902 uvox_metadata; + if (uvox_metadata.Parse(uvox_3902) != API_XML_FAILURE) + { + wchar_t stream_title[256] = {0}; + char* fields[] = {"artist", "title"}; + for(int i = 0; i < sizeof(fields)/sizeof(fields[0]); i++) + { + wchar_t temp[256] = {0}; + int ret = uvox_metadata.GetExtendedData(fields[i], temp, 256); + if(ret && temp[0]) + { + if(stream_title[0]) StringCchCatW(stream_title, 256, L" - "); + StringCchCatW(stream_title, 256, temp); + } + } + + lstrcpynA(g_stream_title, AutoChar(stream_title, CP_UTF8), sizeof(g_stream_title)); + lstrcpynA(last_title_sent, g_stream_title, sizeof(last_title_sent)); + lstrcpynA(stream_current_title, g_stream_title, sizeof(stream_current_title)); + } + } + else + { + g_stream_title[0] = 0; + last_title_sent[0] = 0; + } + + if (sctitle_format) + { + StringCchCatA(g_stream_title, 256, *stream_title_save ? *g_stream_title ? " (" : "(" : ""); + StringCchCatA(g_stream_title, 256, stream_title_save); + StringCchCatA(g_stream_title, 256, *stream_title_save ? ")" : ""); + } + LeaveCriticalSection(&streamInfoLock); + + if (g_playing_file) + { + PostMessage(mod.hMainWindow, WM_WA_IPC, 0, IPC_UPDTITLE); + } + } + break; + + case UVOX_METADATA_STYLE_SHOUTCAST2_ARTWORK: + { + EnterCriticalSection(&streamInfoLock); + // so we can recombine multi-packets we'll store things and + // when all of the packets have been read then we form them + if(!stream_art_blocks) + { + stream_art_parts_total_len = 0; + stream_art_blocks = (char **)malloc(sizeof(char*)*(mylist->total_parts)); + } + + stream_art_blocks[mylist->part-1] = (char *)malloc(mylist->part_len+1); + memcpy(stream_art_blocks[mylist->part-1], mylist->title, mylist->part_len); + stream_art_parts++; + stream_art_parts_total_len += mylist->part_len; + + // sanity check so we only try to get the metadata if all parts were processed + if (stream_art_parts == mylist->total_parts) + { + free(uvox_artwork.uvox_stream_artwork); + if (stream_art_parts_total_len <= 0) break; + uvox_artwork.uvox_stream_artwork = (char *)malloc(stream_art_parts_total_len); + uvox_artwork.uvox_stream_artwork[0] = 0; + uvox_artwork.uvox_stream_artwork_len = stream_art_parts_total_len; + uvox_artwork.uvox_stream_artwork_type = mylist->type; + stream_art_parts = 0; + + char *art = uvox_artwork.uvox_stream_artwork; + for(int i = 0; i < mylist->total_parts; i++) + { + int size = min(stream_art_parts_total_len, 16371); + if (size > 0 && size <= 16371) + { + memcpy(art, stream_art_blocks[i], size); + stream_art_parts_total_len -= size; + art += size; + } + free(stream_art_blocks[i]); + } + free(stream_art_blocks); + stream_art_blocks = 0; + } + LeaveCriticalSection(&streamInfoLock); + + if (g_playing_file) + { + PostMessage(mod.hMainWindow, WM_WA_IPC, 0, IPC_UPDTITLE); + } + } + break; + + case UVOX_METADATA_STYLE_SHOUTCAST2_ARTWORK_PLAYING: + { + EnterCriticalSection(&streamInfoLock); + // so we can recombine multi-packets we'll store things and + // when all of the packets have been read then we form them + if(!playing_art_blocks) + { + playing_art_parts_total_len = 0; + playing_art_blocks = (char **)malloc(sizeof(char*)*(mylist->total_parts)); + } + + playing_art_blocks[mylist->part-1] = (char *)malloc(mylist->part_len+1); + memcpy(playing_art_blocks[mylist->part-1], mylist->title, mylist->part_len); + playing_art_parts++; + playing_art_parts_total_len += mylist->part_len; + + // sanity check so we only try to get the metadata if all parts were processed + if (playing_art_parts == mylist->total_parts) + { + free(uvox_artwork.uvox_playing_artwork); + if (playing_art_parts_total_len <= 0) break; + uvox_artwork.uvox_playing_artwork = (char *)malloc(playing_art_parts_total_len); + uvox_artwork.uvox_playing_artwork[0] = 0; + uvox_artwork.uvox_playing_artwork_len = playing_art_parts_total_len; + uvox_artwork.uvox_playing_artwork_type = mylist->type; + + playing_art_parts = 0; + + char *art = uvox_artwork.uvox_playing_artwork; + for(int i = 0; i < mylist->total_parts; i++) + { + int size = min(playing_art_parts_total_len, 16371); + if (size > 0 && size <= 16371) + { + memcpy(art, playing_art_blocks[i], size); + playing_art_parts_total_len -= size; + art += size; + } + free(playing_art_blocks[i]); + } + free(playing_art_blocks); + playing_art_blocks = 0; + } + LeaveCriticalSection(&streamInfoLock); + + if (g_playing_file) + { + PostMessage(mod.hMainWindow, WM_WA_IPC, 0, IPC_UPDTITLE); + } + } + break; + } + removeTitleListEntry(mylist); + } + } + mylist = next; + } + } + + if (!no_peek_hack && peekBuffer.size()) + { + *pcbRead = (int)peekBuffer.read(pBuffer, cbToRead); + return NErr_Success; + } + + BOOL bSuccess; + if (m_is_stream) + { + if (m_connection) + { + char str[4096]={0}; + if (pcbRead) *pcbRead = 0; + jnl_connection_run(m_connection, -1, -1, 0, 0); + if (constate == 0) + { + if ( jnl_connection_receive_lines_available( m_connection ) > 0 ) + { + char *p = str; + jnl_connection_receive_line( m_connection, str, 4096 ); + + // check http version and type of data, partial or full + if ( strlen( str ) >= 13 && !_strnicmp( str, "HTTP/1.1", 8 ) ) + { + char *p = str + 8; while ( p && *p == ' ' ) p++; + m_http_response = ( p ? atoi( p ) : 0 ); + if ( m_http_response == 200 ) + m_seek_reset = true; + } + + EnterCriticalSection( &g_lfnscs ); + lstrcpynA( lastfn_status, str, 256 ); + LeaveCriticalSection( &g_lfnscs ); + PostMessage( mod.hMainWindow, WM_USER, 0, IPC_UPDTITLE ); + while ( p && *p && *p != ' ' ) p++; + if ( p && *p ) p++; + + if ( p[ 0 ] == '2' ) constate = 1; + else if ( strstr( p, "301" ) == p || strstr( p, "302" ) == p || + strstr( p, "303" ) == p || strstr( p, "307" ) == p ) + { + constate = 69; + } + else if ( strstr( p, "401" ) == p && m_auth_tries++ < 3 ) + { + constate = 75; + } + else if ( strstr( p, "403" ) == p ) + { + EnterCriticalSection( &g_lfnscs ); + lstrcpynA( lastfn_status, "access denied", 256 ); + lastfn_status_err = 1; + LeaveCriticalSection( &g_lfnscs ); + jnl_connection_close( m_connection, 1 ); + } + else if ( strstr( p, "503" ) == p ) + { + EnterCriticalSection( &g_lfnscs ); + lstrcpynA( lastfn_status, "server full", 256 ); + lastfn_status_err = 1; + LeaveCriticalSection( &g_lfnscs ); + jnl_connection_close( m_connection, 1 ); + } + else + { + lastfn_status_err = 1; + jnl_connection_close( m_connection, 1 ); + } + } + else if ( jnl_connection_get_state( m_connection ) == JNL_CONNECTION_STATE_CLOSED || jnl_connection_get_state( m_connection ) == JNL_CONNECTION_STATE_ERROR || jnl_connection_get_state( m_connection ) == JNL_CONNECTION_STATE_NOCONNECTION ) + { + const char *t = jnl_connection_get_error( m_connection ); + if ( t && *t ) + { + EnterCriticalSection( &g_lfnscs ); + lstrcpynA( lastfn_status, t, 256 ); + lastfn_status_err = 1; + LeaveCriticalSection( &g_lfnscs ); + } + PostMessage( mod.hMainWindow, WM_USER, 0, IPC_UPDTITLE ); + } + } + if (constate == 75) // authorization required + { + while (jnl_connection_receive_lines_available(m_connection) > 0) + { + char *wwwa = "WWW-Authenticate:"; + jnl_connection_receive_line(m_connection, str, 4096); + if (!str[0]) + { + lastfn_status_err = 1; jnl_connection_close(m_connection, 1); break; + } + if (!_strnicmp(str, wwwa, strlen(wwwa))) + { + int has = 0; + char *s2 = "Basic realm=\""; + char *p = str + strlen(wwwa); while (p && *p == ' ') p++; + if (!_strnicmp(p, s2, strlen(s2))) + { + p += strlen(s2); + if (strstr(p, "\"")) + { + if (p && *p) + { + strstr(p, "\"")[0] = 0; + extern char *get_inifile(); + if (!force_lpinfo[0]) GetPrivateProfileStringA("HTTP-AUTH", p, "", force_lpinfo, sizeof(force_lpinfo), get_inifile()); + if (!force_lpinfo[0] || (lpinfo && lpinfo[0])) + { + lstrcpynA(dlg_realm, p, sizeof(dlg_realm)); + if (!WASABI_API_DIALOGBOXPARAM(IDD_HTTPAUTH, GetDialogBoxParent(), httpDlgProc, (LPARAM)this)) + { + force_lpinfo[0] = 0; + } + else + { + WritePrivateProfileStringA("HTTP-AUTH", p, force_lpinfo, get_inifile()); + } + } + if (force_lpinfo[0]) + { + jnl_connection_release(m_connection); + m_connection = NULL; + doConnect(NULL, 0); + has = 1; + } + } + } + } + if (!has) + { + lastfn_status_err = 1; + jnl_connection_close(m_connection, 1); + } + break; + } + } + } + if (constate == 69) // redirect city + { + while (jnl_connection_receive_lines_available(m_connection) > 0) + { + jnl_connection_receive_line(m_connection, str, 4096); + if (!str[0]) + { + jnl_connection_close(m_connection, 1); break; + } + if (!_strnicmp(str, "Location:", 9)) + { + char *p = str + 9; while (p && *p == ' ') p++; + if (p && *p) + { + if (m_redircnt++ < MAX_REDIRECTS) + { + jnl_connection_release(m_connection); + m_connection = NULL; + doConnect(p, 0); + } + else + { + EnterCriticalSection(&g_lfnscs); + WASABI_API_LNGSTRING_BUF(IDS_REDIRECT_LIMIT_EXCEEDED,lastfn_status,256); + lastfn_status_err = 1; + LeaveCriticalSection(&g_lfnscs); + jnl_connection_close(m_connection, 1); + } + break; + } + } + } + } + if (constate == 1) + { + while (jnl_connection_receive_lines_available(m_connection) > 0) + { + jnl_connection_receive_line(m_connection, str, 4096); + + if (!str[0]) + { + if (config_http_save_dir[0] && (config_miscopts&16)) + { + if (!save_filename[0] && m_is_stream == 1 && + strlen(stream_title_save) > 4 && + !strstr(stream_title_save, "..") && + !strstr(stream_title_save, "\\") && + !strstr(stream_title_save, "/")) + { + lstrcpynA(save_filename, stream_title_save, 251); + char *p = strstr(save_filename, ".mp"); + if (!p) p = strstr(save_filename, ".MP"); + if (!p) p = strstr(save_filename, ".mP"); + if (!p) p = strstr(save_filename, ".Mp"); + if (!p) + { + StringCchCatA(save_filename, 256, ".mp3"); + } + else + { + while (p && *p && *p != ' ') p++; + if (p) *p = 0; + } + } + + if (save_filename[0]) + { + char buf[4096] = {0}; + StringCchPrintfA(buf, 4096, "%s%s%s", config_http_save_dir, config_http_save_dir[strlen(config_http_save_dir) - 1] == '\\' ? "" : "\\", save_filename); + hFile = CreateFileA(buf, GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + } + } + else save_filename[0] = 0; + constate = meta_interval ? 4 : 2; + break; + } + + // check if stream is seekable + if (strlen(str) >= 14 && !_strnicmp(str, "Accept-Ranges:", 14)) + { + m_is_stream_seekable = true; + } + + if (!_strnicmp(str, "Content-Length:", 15)) + { + char *p = str + 15; while (p && *p == ' ') p++; + if (!is_stream_seek) + mpeg_length = atoi(p); + m_is_stream_seekable = true; + } + + if (!_strnicmp(str, "content-type:", 13)) + { + char *p = str + 13; while (p && *p == ' ') p++; + free(m_content_type); + m_content_type = _strdup(p); + if (!_strnicmp(m_content_type, "misc/ultravox",13)) + { + stream_title_save[0] = 0; + stream_name[0]=0; + g_stream_title[0] = 0; + is_uvox = 1; + // TODO get this to id as SC2 stream if possible... + m_is_stream = 3; + } + } + + if (!_strnicmp(str, "Server:", 7)) + { + char *p = str + 7; while (p && *p == ' ') p++; + lstrcpynA(server_name, p, sizeof(server_name)); + } + + if (!_strnicmp(str, "ultravox-max-msg:", 17)) + { + char *p = str + 17; while (p && *p == ' ') p++; + uvox_maxmsg = (p ? atoi(p) : 0); + if (uvox_maxmsg > UVOX_MAXMSG_CAP) // benski> security vulnerability fix, too high of value was causing an integer overflow (because we malloc uvox_maxmsg*2 + uvox_maxmsg = UVOX_MAXMSG_CAP; + m_is_stream = 3; + + } + else if (!_strnicmp(str, "Ultravox-SID:", 13)) + { + char *p = str + 13; while (p && *p == ' ') p++; + uvox_sid = (p ? atoi(p) : 0); + m_is_stream = 3; + } + if (!_strnicmp(str, "Ultravox-Avg-Bitrate:", 21)) + { + char *p = str + 21; while (p && *p == ' ') p++; + uvox_avgbr = (p ? atoi(p) : 0); + m_is_stream = 3; + } + if (!_strnicmp(str, "Ultravox-Max-Bitrate:", 21)) + { + char *p = str + 21; while (p && *p == ' ') p++; + uvox_maxbr = (p ? atoi(p) : 0); + m_is_stream = 3; + } + if (!_strnicmp(str, "Ultravox-Bitrate:", 17)) + { + char *p = str + 17; while (p && *p == ' ') p++; + uvox_avgbr = uvox_maxbr = (p ? atoi(p) : 0); + m_is_stream = 3; + } + if (!_strnicmp(str, "Ultravox-Title:", 15)) + { + char *p = str + 15; while (p && *p == ' ') p++; + lstrcpynA(stream_title_save, p, 580); + m_is_stream = 3; + } + if (!_strnicmp(str, "Ultravox-Genre:", 15)) + { + char *p = str + 15; while (p && *p == ' ') p++; + lstrcpynA(stream_genre, p, sizeof(stream_genre)); + m_is_stream = 3; + } + if (!_strnicmp(str, "Ultravox-URL:", 13)/* && !strstr(str, "shoutcast.com")*/) + { + char *p = str + 13; while (p && *p == ' ') p++; + lstrcpynA(stream_url, p, sizeof(stream_url)); + DWORD_PTR dw = 0; + if (stream_url[0]) SendMessageTimeout(mod.hMainWindow, WM_USER, (WPARAM)stream_url, IPC_MBOPEN, SMTO_NORMAL, 500, &dw); + m_is_stream = 3; + } + if (!_strnicmp(str, "Ultravox-Class-Type:", 19)) + { + // id our stream for 'streamtype' + // added for 5.63 as is a better way to check for + // a SC2/UVOX2.1 stream when no metadata is sent + m_is_stream = 5; + } + + if (!_strnicmp(str, "icy-notice2:", 12)) + { + char *p = str + 12; while (p && *p == ' ') p++; + char *p2 = (p ? strstr(p, "<BR>") : 0); + if (p2) + { + *p2 = 0; + lstrcpynA(server_name, p, sizeof(server_name)); + } + m_is_stream=2; + } + + if (!_strnicmp(str, "content-disposition:", 20)) + { + if (strstr(str, "filename=")) + lstrcpynA(g_stream_title, strstr(str, "filename=") + 9, sizeof(g_stream_title)); + lstrcpynA(stream_title_save, g_stream_title, 580); + } + + if (!_strnicmp(str, "icy-name:", 9)) + { + char *p = str + 9; while (p && *p == ' ') p++; + lstrcpynA(g_stream_title, p, sizeof(g_stream_title)); + lstrcpynA(stream_title_save, g_stream_title, 580); + lstrcpynA(stream_name, g_stream_title, 256); + // m_is_stream = 2; // benski> cut: some things use icy-name as a hack to show a title - not a reliable indicator of a SHOUTcast stream + } + + if (!_strnicmp(str, "icy-metaint:", 12)) + { + char *p = str + 12; while (p && *p == ' ') p++; + meta_interval = (p ? atoi(p) : 0); + m_is_stream = 2; + } + if (!_strnicmp(str, "icy-genre:", 10)) + { + char *p = str + 10; while (p && *p == ' ') p++; + lstrcpynA(stream_genre, p, sizeof(stream_genre)); + m_is_stream = 2; + } + if (!_strnicmp(str, "icy-url:", 8) && !strstr(str, "shoutcast.com")) + { + char *p = str + 8; while (p && *p == ' ') p++; + lstrcpynA(stream_url, p, sizeof(stream_url)); + //if (!strncmp(stream_url,"hTtP",4)) + //{ + // DWORD dw; + // SendMessageTimeout(mod.hMainWindow,WM_USER,(WPARAM)0,241,SMTO_NORMAL,500,&dw); + //} // Removed by Tag, Annoying. + DWORD_PTR dw = 0; + if (stream_url[0]) SendMessageTimeout(mod.hMainWindow, WM_USER, (WPARAM)stream_url, IPC_MBOPEN, SMTO_NORMAL, 500, &dw); + m_is_stream = 2; + } + } + } + + if (constate == 2) // check for id3v2 + { + if (jnl_connection_receive_bytes_available(m_connection) >= 10) + { + char buf[10]={0}; + constate = 4; + jnl_connection_peek(m_connection, buf, 10); + if (buf[0] == 'I' + && buf[1] == 'D' + && buf[2] == '3' + && buf[3] != 255 + && buf[4] != 255 + && buf[6] < 0x80 + && buf[7] < 0x80 + && buf[8] < 0x80 + && buf[9] < 0x80) + { + jnl_connection_receive(m_connection, buf, 10); + m_id3v2_len = 10; + if (buf[5] & 0x10) // check for footer flag + m_id3v2_len += 10; + m_id3v2_len += ((int)buf[6]) << 21; + m_id3v2_len += ((int)buf[7]) << 14; + m_id3v2_len += ((int)buf[8]) << 7; + m_id3v2_len += ((int)buf[9]); + if (m_id3v2_len < 0) m_id3v2_len = 0; + if (mpeg_length && m_id3v2_len > mpeg_length) + { + m_id3v2_len = 0; + } + if (m_id3v2_len) + { + constate = 3; + stream_id3v2_read = 0; + if (m_id3v2_len < 16*1024*1024) + { + stream_id3v2_buf = (char*)malloc(10 + m_id3v2_len); + stream_id3v2_read = 10; + memcpy(stream_id3v2_buf, buf, 10); + } + EnterCriticalSection(&g_lfnscs); + WASABI_API_LNGSTRING_BUF(IDS_READING_ID3,lastfn_status,256); + LeaveCriticalSection(&g_lfnscs); + PostMessage(mod.hMainWindow, WM_USER, 0, IPC_UPDTITLE); + } + } + } + } + + if (constate == 3) // id3v2 found + { + while ((int)stream_id3v2_read < m_id3v2_len) + { + char buf[1024]={0}; + int btoread = m_id3v2_len - stream_id3v2_read; + if (btoread > sizeof(buf)) btoread = sizeof(buf); + + int avail = (int)jnl_connection_receive_bytes_available(m_connection); + if (btoread > avail) btoread = avail; + if (!btoread) break; + + if (stream_id3v2_buf) + stream_id3v2_read += (uint32_t)jnl_connection_receive(m_connection, stream_id3v2_buf + stream_id3v2_read, btoread); + else + stream_id3v2_read += (uint32_t)jnl_connection_receive(m_connection, buf, btoread); + //stream_id3v2_read+=m_connection->ReceiveBytes(0,btoread); + } + + if ((int)stream_id3v2_read >= m_id3v2_len) + { + if (stream_id3v2_buf) + { + if (m_is_stream_seekable /*m_is_stream != 2*/) /* don't want to do id3v2 on an internet stream */ + { + EnterCriticalSection(&streamInfoLock); + info.Decode(stream_id3v2_buf, stream_id3v2_read); + // TODO: streamInfo = info; + LeaveCriticalSection(&streamInfoLock); + PostMessage(mod.hMainWindow, WM_USER, 0, IPC_UPDTITLE); + } + } + constate = 4; + } + } + + if (constate == 4) // check for xing header + { + if (GetContentLength() < 1 || is_stream_seek) constate = 5; + else + { + int avail = (int)jnl_connection_receive_bytes_available(m_connection); + if (avail > 4096 || jnl_connection_get_state(m_connection) == JNL_CONNECTION_STATE_CLOSED) + { + char buf[4096]={0}; + jnl_connection_peek(m_connection, buf, sizeof(buf)); + constate++; + { + delete m_vbr_hdr; + m_vbr_hdr = 0; + LAMEinfo lame; + lame.toc = m_vbr_toc; + m_vbr_frame_len = ReadLAMEinfo((unsigned char *)buf, &lame); + if (m_vbr_frame_len) + { + prepad = lame.encoderDelay; + postpad = lame.padding; + if (lame.flags&TOC_FLAG) + { + int x; + for (x = 0; x < 100; x++) + if (m_vbr_toc[x]) break; + if (x != 100) + m_vbr_flag = 1; + } + if (lame.flags&FRAMES_FLAG) + { + m_vbr_frames = lame.frames; + m_vbr_samples = Int32x32To64(lame.frames, lame.h_id ? 1152 : 576); + m_vbr_samples -= (prepad + postpad); + m_vbr_ms = MulDiv((int)m_vbr_samples, 1000, lame.samprate); + } + if (!m_vbr_frames) m_vbr_flag = 0; + jnl_connection_receive(m_connection, buf, m_vbr_frame_len); + } + else + { + m_vbr_hdr = new CVbriHeader; + m_vbr_frame_len = m_vbr_hdr->readVbriHeader((unsigned char *)buf); + if (m_vbr_frame_len) + { + m_vbr_bytes = m_vbr_hdr->getBytes(); + m_vbr_frames = m_vbr_hdr->getNumFrames(); + m_vbr_ms = m_vbr_hdr->getNumMS(); + } + else + { + delete m_vbr_hdr; + m_vbr_hdr = 0; + } + } + } + // TODO OFL + } + } + } + + if (constate == 5) // time to stream + { + while (1) + { + // Process any timed titles + + int len = (int)jnl_connection_receive_bytes_available(m_connection); + if (meta_interval && meta_pos >= meta_interval) + { + unsigned char b; + if (len > 0 && jnl_connection_peek(m_connection, (char*)&b, 1) && len > (b << 4)) + { + char metabuf[4096]={0}; + jnl_connection_receive(m_connection, metabuf, 1); + jnl_connection_receive(m_connection, metabuf, b << 4); + processMetaData(metabuf, b << 4); + stream_metabytes_read += (b << 4) + 1; + meta_pos = 0; + } + else break; + } + else if (is_uvox) + { + if (!uvox_stream_data) + { + /* benski> this was a security vulnerability. + don't blindly multiply by 2 and pass to malloc + if uvox_maxmsg happens to be 2^31, multiplying by 2 turns it into 0! + CUT!!! uvox_stream_data = (unsigned char*)malloc(uvox_maxmsg * 2 + 4096); + */ + uvox_stream_data = (unsigned char*)malloc(SAFE_MALLOC_MATH(uvox_maxmsg * 2 + 4096, uvox_maxmsg)); + } + if (uvox_stream_data_len < 1) + { +again: + if (len < 6) break; + jnl_connection_peek(m_connection, (char*)uvox_stream_data, 6); + + int uvox_qos = uvox_stream_data[1]; + int classtype = (uvox_stream_data[2] << 8) | uvox_stream_data[3]; + int uvox_len = (uvox_stream_data[4] << 8) | uvox_stream_data[5]; + + int uvox_class = (classtype >> 12) & 0xF; + int uvox_type = classtype & 0xFFF; + + if (uvox_stream_data[0] != 0x5A || uvox_len > uvox_maxmsg) + { + jnl_connection_receive(m_connection, NULL, 1); + len--; + uvox_desyncs++; + + goto again; + } + + if (uvox_len + 7 > len) break; + // process uvox chunk + + jnl_connection_peek(m_connection, (char*)uvox_stream_data, 6 + uvox_len + 1); + if (uvox_stream_data[6 + uvox_len]) + { + jnl_connection_receive(m_connection, NULL, 1); + uvox_desyncs++; + len--; + goto again; + } + else if (uvox_class == 0x2 && uvox_type == 0x003) + { + // failover gayness + } + else if (uvox_class == 0x2 && uvox_type == 0x002) + { + EnterCriticalSection(&g_lfnscs); + WASABI_API_LNGSTRING_BUF(IDS_STREAM_TERMINATED,lastfn_status,256); + LeaveCriticalSection(&g_lfnscs); + PostMessage(mod.hMainWindow, WM_USER, 0, IPC_UPDTITLE); + + jnl_connection_close(m_connection, 0); + } + else if (uvox_class == 0x2 && uvox_type == 0x001) + { + EnterCriticalSection(&g_lfnscs); + WASABI_API_LNGSTRING_BUF(IDS_STREAM_TEMPORARILY_INTERRUPTED,lastfn_status,256); + LeaveCriticalSection(&g_lfnscs); + PostMessage(mod.hMainWindow, WM_USER, 0, IPC_UPDTITLE); + } + + else if (uvox_class == 0x3 && (uvox_type == 0x902 || uvox_type == 0x901)) // SHOUTcast 2 metadata + { + // id our stream for 'streamtype' + m_is_stream = 5; + + // this will allow us to cope with multi-packet metadata messages + // (used to be needed when the old SC2 spec used an APIC tag to + // to send the artwork in the metadata message - now it's sent in + // in the uvox_class == 0x4 messages for stream and playing cases) + if (!uvox_stream_data[8]) + { + char *mbuf = (char*)uvox_stream_data + 12; + if (mbuf) + { + unsigned long delay = 0; + + if (uvox_avgbr) + { + long byterate = (uvox_avgbr / 8); + long bytes = 1024 * 11; + + float localdelay = (float)bytes / (float)byterate; + delay = (long)localdelay * 1000; + } + + // make sure that we've got a packet which is within the specs + // as id can only be 1-32, total parts can only be 1-32, + // id cannot be more than total parts and if not then skip it. + if(uvox_stream_data[11] < 1 || uvox_stream_data[11] > 32 || + uvox_stream_data[11] > uvox_stream_data[9] || + uvox_stream_data[9] < 1 || uvox_stream_data[9] > 32) + { + break; + } + + TITLELISTTYPE *newtitle = newTitleListEntry(); + newtitle->style = (uvox_type == 0x902 ? UVOX_METADATA_STYLE_SHOUTCAST2 : UVOX_METADATA_STYLE_AOLRADIO); + // we make sure to only copy up to the maximum metadata payload size + newtitle->part_len = min((uvox_len - 6), 16371); + memcpy(newtitle->title, mbuf, newtitle->part_len); + newtitle->part = uvox_stream_data[11]; + newtitle->total_parts = uvox_stream_data[9]; + newtitle->timer = (stream_bytes_read && mod.outMod ? delay + mod.outMod->GetOutputTime() : 0); + } + } + } + + else if (uvox_class == 0x4) // SHOUTcast 2 albumart + { + if (allow_scartwork && !uvox_stream_data[8]) + { + char *mbuf = (char*)uvox_stream_data + 12; + if (mbuf) + { + // [0x4] [0|1] [00|01|02|03] + unsigned long delay = 0; + + if (uvox_avgbr) + { + long byterate = (uvox_avgbr / 8); + long bytes = 1024 * 11; + + float localdelay = (float)bytes / (float)byterate; + delay = (long)localdelay * 1000; + } + + // make sure that we've got a packet which is within the specs + // as id can only be 1-32, total parts can only be 1-32, + // id cannot be more than total parts and if not then skip it. + if(uvox_stream_data[11] < 1 || uvox_stream_data[11] > 32 || + uvox_stream_data[11] > uvox_stream_data[9] || + uvox_stream_data[9] < 1 || uvox_stream_data[9] > 32) + { + break; + } + + TITLELISTTYPE *newtitle = newTitleListEntry(); + newtitle->style = (uvox_type & 0x100 ? UVOX_METADATA_STYLE_SHOUTCAST2_ARTWORK_PLAYING : UVOX_METADATA_STYLE_SHOUTCAST2_ARTWORK); + // we make sure to only copy up to the maximum metadata payload size + newtitle->part_len = min((uvox_len - 6), 16371); + memcpy(newtitle->title, mbuf, newtitle->part_len); + newtitle->part = uvox_stream_data[11]; + newtitle->total_parts = uvox_stream_data[9]; + newtitle->type = (uvox_type & 0x00FF); + newtitle->timer = (stream_bytes_read && mod.outMod ? delay + mod.outMod->GetOutputTime() : 0); + } + } + } + + else if (uvox_class == 0x3 && (uvox_type == 0x001 || uvox_type == 0x002)) + { + int w = uvox_type - 1; // should be ID? + int n = (uvox_stream_data[8] << 8) | uvox_stream_data[9]; + if (n && n < 33) + { + int t = (uvox_stream_data[10] << 8) | uvox_stream_data[11]; + if (t && t <= n) + { + int l = 0; + int a; + char *p = (char*)uvox_stream_data + 12; // this almost works + free(uvox_meta[w][t - 1]); + uvox_meta[w][t - 1] = _strdup(p); + for (a = 0;a < n;a++) + { + if (!uvox_meta[w][a]) break; + l += (int)strlen(uvox_meta[w][a]); + } + if (a == n) + { + char *outtext = (char*)malloc(l + 1); + p = outtext; + for (a = 0;a < n;a++) + { + lstrcpynA(p, uvox_meta[w][a], l + 1); + free(uvox_meta[w][a]); + uvox_meta[w][a] = 0; + p += strlen(p); + } + processMetaData(outtext, l + 1); + free(outtext); + } + } + } + } + + else if ((uvox_class == 0x7 && (uvox_type == 0x0 || uvox_type == 0x1)) + || (uvox_class == 0x8 && (uvox_type == 0x0 || uvox_type == 0x1 || uvox_type == 0x3))) + { + memcpy(uvox_stream_data, uvox_stream_data + 6, uvox_len); + uvox_stream_data_len = uvox_len; + uvox_last_message = uvox_class << 12 | uvox_type; + } + jnl_connection_receive(m_connection, NULL, 6 + uvox_len + 1); + + uvox_message_cnt++; + } + if (uvox_stream_data_len > 0) + { + len = min(cbToRead, uvox_stream_data_len); + + memcpy(pBuffer, uvox_stream_data, len); + if (pcbRead) *pcbRead = len; + stream_bytes_read += len; + if (len < uvox_stream_data_len) + { + memcpy(uvox_stream_data, uvox_stream_data + len, uvox_stream_data_len - len); + } + uvox_stream_data_len -= len; + break; + } + } + else + { + len = min(cbToRead, len); + if (meta_interval) len = min(meta_interval - meta_pos, len); + if (len > 0) + { + DWORD dw = 0; + len = (int)jnl_connection_receive(m_connection, (char*)pBuffer, len); + if (hFile != INVALID_HANDLE_VALUE) WriteFile(hFile, pBuffer, len, &dw, NULL); + if (pcbRead) *pcbRead = len; + meta_pos += len; + stream_bytes_read += len; + } + else if (pcbRead) *pcbRead = 0; + break; + } + } + } + + int state = m_connection ? jnl_connection_get_state(m_connection) : JNL_CONNECTION_STATE_CLOSED; + if (state == JNL_CONNECTION_STATE_ERROR || state == JNL_CONNECTION_STATE_CLOSED) + { + if ((!pcbRead || !*pcbRead) && (!m_connection || jnl_connection_receive_bytes_available(m_connection) < 1)) + { + fEof = 1; + return NErr_Error; + } + } + else if (constate != 5 && constate != 4 && constate != 3 && timeout_start + 15000 < GetTickCount()) // 15 second net connect timeout + { + EnterCriticalSection(&g_lfnscs); + WASABI_API_LNGSTRING_BUF(IDS_TIMED_OUT,lastfn_status,256); + lastfn_status_err = 1; + LeaveCriticalSection(&g_lfnscs); + PostMessage(mod.hMainWindow, WM_USER, 0, IPC_UPDTITLE); + fEof = 1; + + return NErr_Error; + } + return NErr_Success; + } + return NErr_Error; + } + else if (m_full_buffer) + { + int len = cbToRead; + if ((uint64_t)len > (mpeg_length - m_full_buffer_pos)) + { + len = (int)(mpeg_length - m_full_buffer_pos); + } + if (pcbRead) *pcbRead = len; + if (len) + { + memcpy(pBuffer, m_full_buffer + m_full_buffer_pos, len); + m_full_buffer_pos += len; + } + else + { + fEof = true; + return NErr_EndOfFile; + } + return NErr_Success; + } + else + { + if (hFile != INVALID_HANDLE_VALUE) + { + if ((uint64_t)cbToRead >= (mpeg_length - file_position)) + { + cbToRead = (int)(mpeg_length - file_position); + fEof = true; + } + + if (cbToRead == 0) + { + if (pcbRead) + *pcbRead = 0; + return NErr_Success; + } + + DWORD dwRead = 0; + bSuccess = ReadFile(hFile, pBuffer, cbToRead, &dwRead, NULL); + + if (bSuccess) + { + file_position += dwRead; + // update pcbRead + if (pcbRead) + *pcbRead = dwRead; + + // check for EOF + if (dwRead == 0) + fEof = true; + + return NErr_Success; + } + else + { + // error reading from file + return NErr_Error; + } + } + else + { + // no valid file handle + return NErr_Error; + } + } +} + +//-------------------------------------------------------------------------* +// IsEof +//-------------------------------------------------------------------------* + +bool CGioFile::IsEof() const +{ + return fEof; +} + +//-------------------------------------------------------------------------* +// GetContentLength +//-------------------------------------------------------------------------* + +DWORD CGioFile::GetContentLength(void) const +{ + DWORD dwSize = 0 ; + + dwSize = (DWORD)mpeg_length; + + return dwSize ; +} + +//-------------------------------------------------------------------------* +// GetCurrentPosition +//-------------------------------------------------------------------------* + +DWORD CGioFile::GetCurrentPosition(void) const +{ + DWORD dwPos = 0; + + if (m_is_stream) + { + dwPos = (DWORD)stream_bytes_read; + } + else if (m_full_buffer) + { + dwPos = (DWORD)m_full_buffer_pos; + } + else if (hFile != INVALID_HANDLE_VALUE) + { + dwPos = SetFilePointer(hFile, 0, NULL, FILE_CURRENT); + dwPos -= (DWORD)(mpeg_position + peekBuffer.size()); + } + + return dwPos ; +} + +//-------------------------------------------------------------------------* +// SetCurrentPosition +//-------------------------------------------------------------------------* + +void CGioFile::SetCurrentPosition(long dwPos, int How) // GIO_FILE_BEGIN only +{ + fEof = false; + if (m_full_buffer) + { + m_full_buffer_pos = dwPos; + if (m_full_buffer_pos < 0) m_full_buffer_pos = 0; + if (m_full_buffer_pos > mpeg_length) m_full_buffer_pos = mpeg_length; + } + else if (hFile != INVALID_HANDLE_VALUE) + { + Seek64(hFile, mpeg_position + dwPos, FILE_BEGIN); + file_position = dwPos; + peekBuffer.clear(); + } +} + +int CGioFile::GetHeaderOffset() +{ + return (int)mpeg_position; +} +/*-------------------------------------------------------------------------*/ + +void CGioFile::Seek(int posms, int br) +{ + int offs = 0; + if (m_vbr_hdr) + { + offs = m_vbr_hdr->seekPointByTime((float)posms); + } + else if (!m_vbr_flag) + offs = MulDiv(posms, br, 8); + else + { + int ms = 0; + int fl = GetContentLength(); + if (!m_vbr_frames) + { + ms = MulDiv(fl, 8 * 1000, br); + } + else ms = m_vbr_ms; + offs = SeekPoint(m_vbr_toc, fl, (float)posms / ((float)ms / 100.0f)); + } + if (m_is_stream) + { + if (GetContentLength() > 0) + { + if (m_connection && m_is_stream_seekable) + { + jnl_connection_release(m_connection); + m_connection = NULL; + m_is_stream_seek = true; + m_seek_reset = false; + doConnect(NULL, offs); + } + } + } + else + { + SetCurrentPosition(offs, GIO_FILE_BEGIN); + } +} + +bool CGioFile::IsSeekable() +{ + return !m_is_stream || GetContentLength() > 0; +} + +void CGioFile::GetStreamInfo(wchar_t *obuf, size_t len) +{ + if (m_is_stream) + { + wchar_t langbuf[2048]={0}; + StringCchPrintfEx(obuf, len, &obuf, &len, 0, WASABI_API_LNGSTRINGW_BUF(IDS_NETWORK_RECEIVED_X_BYTES, langbuf, 2048), stream_bytes_read + stream_metabytes_read); + if (server_name[0]) + StringCchPrintfEx(obuf, len, &obuf, &len, 0, WASABI_API_LNGSTRINGW_BUF(IDS_SERVER, langbuf, 2048), AutoWide(server_name,CP_UTF8)); + if (m_content_type && m_content_type[0]) + { + if(is_uvox) + { + // report the actual content type instead of just misc/ultravox to make it easier to see what the stream type is (helps debugging) + static const int MP3_DATA = 0x7000; + static const int VLB_DATA = 0x8000; + static const int AAC_LC_DATA = 0x8001; + static const int AACP_DATA = 0x8003; + static const int OGG_DATA = 0x8004; + switch(uvox_last_message) + { + case MP3_DATA: + StringCchPrintfEx(obuf, len, &obuf, &len, 0, WASABI_API_LNGSTRINGW_BUF(IDS_CONTENT_TYPE, langbuf, 2048), L"audio/mpeg"); + break; + + case VLB_DATA: + StringCchPrintfEx(obuf, len, &obuf, &len, 0, WASABI_API_LNGSTRINGW_BUF(IDS_CONTENT_TYPE, langbuf, 2048), L"audio/vlb"); + break; + + case AAC_LC_DATA: + case AACP_DATA: + StringCchPrintfEx(obuf, len, &obuf, &len, 0, WASABI_API_LNGSTRINGW_BUF(IDS_CONTENT_TYPE, langbuf, 2048), L"audio/aacp"); + break; + + case OGG_DATA: + StringCchPrintfEx(obuf, len, &obuf, &len, 0, WASABI_API_LNGSTRINGW_BUF(IDS_CONTENT_TYPE, langbuf, 2048), L"audio/ogg"); + break; + + default: + StringCchPrintfEx(obuf, len, &obuf, &len, 0, WASABI_API_LNGSTRINGW_BUF(IDS_CONTENT_TYPE, langbuf, 2048), AutoWide(m_content_type,CP_UTF8)); + break; + } + } + else + { + StringCchPrintfEx(obuf, len, &obuf, &len, 0, WASABI_API_LNGSTRINGW_BUF(IDS_CONTENT_TYPE, langbuf, 2048), AutoWide(m_content_type,CP_UTF8)); + } + } + + if (is_uvox) + { + StringCchPrintfEx(obuf, len, &obuf, &len, 0, WASABI_API_LNGSTRINGW_BUF(IDS_ULTRAVOX_SYNC, langbuf, 2048), uvox_message_cnt, uvox_desyncs); + StringCchPrintfEx(obuf, len, &obuf, &len, 0, WASABI_API_LNGSTRINGW_BUF(IDS_ULTRAVOX_DATA_MESSAGE, langbuf, 2048), uvox_last_message); + StringCchPrintfEx(obuf, len, &obuf, &len, 0, WASABI_API_LNGSTRINGW_BUF(IDS_ULTRAVOX_SID_AVGBR_MAXBR, langbuf, 2048), uvox_sid, uvox_avgbr, uvox_maxbr); + } + + if (stream_metabytes_read) + StringCchPrintfEx(obuf, len, &obuf, &len, 0, WASABI_API_LNGSTRINGW_BUF(IDS_METADATA_RECEIVED, langbuf, 2048), stream_metabytes_read); + if (meta_interval) + StringCchPrintfEx(obuf, len, &obuf, &len, 0, WASABI_API_LNGSTRINGW_BUF(IDS_METADATA_INTERVAL, langbuf, 2048), meta_interval); + if (m_id3v2_len) + StringCchPrintfEx(obuf, len, &obuf, &len, 0, WASABI_API_LNGSTRINGW_BUF(IDS_ID3v2_TAG, langbuf, 2048), m_id3v2_len); + if (m_vbr_frame_len) + StringCchPrintfEx(obuf, len, &obuf, &len, 0, WASABI_API_LNGSTRINGW_BUF(IDS_VBR_LEADING_FRAME, langbuf, 2048), m_vbr_frame_len); + if (stream_title_save[0]) + { + wchar_t name[580]={0}; + ConvertTryUTF8(stream_title_save, name, 580); + StringCchPrintfEx(obuf, len, &obuf, &len, 0, WASABI_API_LNGSTRINGW_BUF(IDS_STREAM_NAME, langbuf, 2048), name/*AutoWide(stream_title_save,CP_UTF8)*/); + } + if (last_title_sent[0]) + { + wchar_t title[256]={0}; + ConvertTryUTF8(last_title_sent, title, 256); + StringCchPrintfEx(obuf, len, &obuf, &len, 0, WASABI_API_LNGSTRINGW_BUF(IDS_CURRENT_TITLE, langbuf, 2048), title); + } + + if (mpeg_length) + StringCchPrintfEx(obuf, len, &obuf, &len, 0, WASABI_API_LNGSTRINGW_BUF(IDS_CONTENT_LENGTH, langbuf, 2048), mpeg_length); + if (save_filename[0] && hFile != INVALID_HANDLE_VALUE) + StringCchPrintfEx(obuf, len, &obuf, &len, 0, WASABI_API_LNGSTRINGW_BUF(IDS_SAVING_TO, langbuf, 2048), AutoWide(save_filename,CP_UTF8)); + } +} + +static inline const wchar_t *IncSafe(const wchar_t *val, int x) +{ + while (x--) + { + if (val && *val) + val++; + } + return val; +} + +void CGioFile::ReadiTunesGaps() +{ + if (info.HasData() && !prepad && !postpad) + { + wchar_t str[128] = {0}; + if (info.GetString("pregap", str, 128) == 1) + prepad = _wtoi(str); + + str[0]=0; + if (info.GetString("postgap", str, 128) == 1) + postpad = _wtoi(str); + } +} + +#define CBCLASS CGioFile +START_DISPATCH; +CB( MPEGSTREAM_PEEK, Peek ) +CB( MPEGSTREAM_READ, Read ) +CB( MPEGSTREAM_EOF, EndOf ) +CB( MPEGSTREAM_GAIN, GetGain ) +END_DISPATCH; +#undef CBCLASS
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mp3/giofile.h b/Src/Plugins/Input/in_mp3/giofile.h new file mode 100644 index 00000000..121d77c9 --- /dev/null +++ b/Src/Plugins/Input/in_mp3/giofile.h @@ -0,0 +1,306 @@ + +/***************************************************************************\ + * + * (C) copyright Fraunhofer - IIS (1998) + * All Rights Reserved + * + * filename: giofile.h + * project : MPEG Decoder + * author : Martin Sieler + * date : 1998-02-11 + * contents/description: HEADER - file I/O class for MPEG Decoder + * + * +\***************************************************************************/ + +#ifndef _GIOFILE_H +#define _GIOFILE_H + +/* ------------------------ includes --------------------------------------*/ +#include <windows.h> + +#include "CVbriHeader.h" +#include "jnetlib/jnetlib.h" + +#include "../vlb/dataio.h" +#include "ID3v2.h" +#include "LAMEInfo.h" +#include "../nu/RingBuffer.h" +#include "../apev2/tag.h" +#include "ifc_mpeg_stream_reader.h" + +/*-------------------------- defines --------------------------------------*/ + +/*-------------------------------------------------------------------------*/ + +class CGioFile : public DataIOControl, public ifc_mpeg_stream_reader +{ +public: + CGioFile(); + virtual ~CGioFile(); + + int Open(const wchar_t *pszName, int maxbufsizek); + int Close(); + int Read(void *pBuffer, int cbToRead, int *pcbRead); + int Peek(void *pBuffer, int cbToRead, int *pcbRead); + + //dataiocontrol interface + int IO(void *buf, int size, int count) + { + int l=0; + Read(buf,count,&l); + return l; + } + int Seek(long offset, int origin) + { + return 0; + } + //int Close() { return 0; } + int EndOf(void) + { + return IsEof(); + } + int DICGetLastError() + { + return DATA_IO_ERROR_NONE; + } + int DICGetDirection() + { + return DATA_IO_READ; + } + + unsigned int GetAvgVBRBitrate(void) + { + if (m_vbr_ms && m_vbr_frames) + { + if (m_vbr_bytes && encodingMethod != ENCODING_METHOD_CBR) + return (unsigned int)(m_vbr_bytes * 8 / m_vbr_ms); + else + return (unsigned int)(mpeg_length * 8 / m_vbr_ms); + } + return 0; + } + bool IsEof() const; + bool lengthVerified; + bool isSeekReset() + { + + if (m_is_stream && m_seek_reset && m_is_stream_seek) + { + m_seek_reset = false; + m_is_stream_seek = false; + return true; + } + else + return false; + } + + bool IsStreamSeekable() + { + return m_is_stream_seekable; + } + + int GetHeaderOffset(); + + int PercentAvailable() + { + if (!m_is_stream) return 0; + if (!recvbuffersize) return 0; + if (!m_connection) return 0; + if (constate != 5) return 0; + uint64_t bytes_100 = jnl_connection_receive_bytes_available(m_connection)*100; + bytes_100 /= recvbuffersize; + return (int)bytes_100; + } + + int RunStream() + { + if (m_is_stream && m_connection) + { + if (constate != 5) Read(NULL,0,NULL); + else jnl_connection_run(m_connection, -1, -1, NULL, NULL); + int p=jnl_connection_get_state(m_connection); + if (p==JNL_CONNECTION_STATE_ERROR|| + p==JNL_CONNECTION_STATE_CLOSED) + return 2; + return 1; + } + return 0; + } + int IsStream() + { + return m_is_stream; + } + void GetStreamInfo(wchar_t *, size_t len); + + DWORD GetContentLength(void) const; + DWORD GetCurrentPosition(void) const; + void SetCurrentPosition(long dwPos, int How); + + enum { GIO_FILE_BEGIN, GIO_FILE_CURRENT, GIO_FILE_END }; + + uint64_t mpeg_length; /* length of valid audio data */ + uint64_t mpeg_position; /* starting position of first valid decodable non-header MPEG frame */ + uint64_t file_position; /* position within the MPEG data that we've read so far */ + + uint64_t m_vbr_bytes; + int m_vbr_frames, m_vbr_ms; + int encodingMethod; + uint64_t m_vbr_samples; + int prepad, postpad; + void Seek(int posms, int br); + bool IsSeekable(); + + unsigned char id3v1_data[128]; + + char stream_url[256]; + char stream_name[256]; + char stream_genre[256]; + char stream_current_title[256]; + + char *m_content_type; + int uvox_last_message; + char *uvox_3901; + char *uvox_3902; + + typedef struct { + char *uvox_stream_artwork; + int uvox_stream_artwork_len; + int uvox_stream_artwork_type; + char *uvox_playing_artwork; + int uvox_playing_artwork_len; + int uvox_playing_artwork_type; + } UVOX_ARTWORK; + UVOX_ARTWORK uvox_artwork; + + ID3v2 info; + float GetGain(); + unsigned char m_vbr_toc[100]; + int m_vbr_frame_len; + int m_vbr_flag; + CVbriHeader *m_vbr_hdr; + + FILETIME last_write_time; + + void *GetID3v1() + { + if (m_id3v1_len == 128) + return id3v1_data; + else + return 0; + } + + void *GetID3v2(uint32_t *len) + { + if (stream_id3v2_buf) + { + *len = stream_id3v2_read; + return stream_id3v2_buf; + } + else + return 0; + } + + void *GetLyrics3(uint32_t *len) + { + if (lyrics3_data) + { + *len = lyrics3_size; + return lyrics3_data; + } + else + return 0; + } + + void *GetAPEv2(uint32_t *len) + { + if (apev2_data) + { + *len = m_apev2_len; + return apev2_data; + } + return 0; + } + +protected: + void ReadiTunesGaps(); +private: + /* ID3v2 */ + int m_id3v1_len; + + /* ID3v2 */ + uint32_t stream_id3v2_read; + char *stream_id3v2_buf; + int m_id3v2_len; + + /* Lyrics3 */ + uint32_t lyrics3_size; + char *lyrics3_data; + + /* APEv2 */ + uint32_t m_apev2_len; + char *apev2_data; + APEv2::Tag apev2; + + int m_is_stream; + jnl_dns_t m_dns; + jnl_connection_t m_connection; + char last_full_url[4096]; + int is_stream_seek; + char *host; + char *proxy_host; + char *req; + unsigned short port; + char *lpinfo; + char *proxy_lp; + char *request; + int constate; + char save_filename[256]; + char server_name[128]; + int recvbuffersize; + + int64_t stream_bytes_read; + int64_t stream_metabytes_read; + unsigned int timeout_start; + char force_lpinfo[256]; + unsigned char *m_full_buffer; + int is_uvox; + int uvox_stream_data_len; + int uvox_message_cnt, uvox_desyncs; + int uvox_sid,uvox_maxbr, uvox_avgbr,uvox_maxmsg; + + unsigned char *uvox_stream_data; + + char *uvox_meta[2][32]; + int meta_interval,meta_pos; + uint64_t m_full_buffer_pos/*,m_full_buffer_len*/; + char stream_title_save[580]; + char last_title_sent[256]; + + RingBuffer peekBuffer; + //unsigned char m_peekbuf[8192]; + //int m_peekbuf_used; + bool no_peek_hack; + + int doConnect(const char *str, int start_offset); + void processMetaData(char *data, int lent, int msgId = 0); + + int m_redircnt; + int m_auth_tries; + + HANDLE hFile; + bool fEof; + + + char dlg_realm[256]; + static INT_PTR CALLBACK httpDlgProc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam); + int m_http_response; + bool m_seek_reset; + bool m_is_stream_seek; + bool m_is_stream_seekable; + bool m_useaproxy; + RECVS_DISPATCH; +}; + +/*-------------------------------------------------------------------------*/ +#endif diff --git a/Src/Plugins/Input/in_mp3/graphics/filterWater.cpp b/Src/Plugins/Input/in_mp3/graphics/filterWater.cpp new file mode 100644 index 00000000..d6128738 --- /dev/null +++ b/Src/Plugins/Input/in_mp3/graphics/filterWater.cpp @@ -0,0 +1,452 @@ +#include ".\filterwater.h" +#include <math.h> + +#define random( min, max ) (( rand() % (int)((( max ) + 1 ) - ( min ))) + ( min )) + +MLImageFilterWater::MLImageFilterWater(void) +{ + hField1 = NULL; + hField2 = NULL; + hHandle = NULL; + + width = 0; + height = 0; + + drawWithLight = TRUE; + lightModifier = 1; + hPage = 0; + density = 5; + +} + +MLImageFilterWater::~MLImageFilterWater(void) +{ + ClearData(); +} + +void MLImageFilterWater::ClearData(void) +{ + if (hHandle) + { + if (hField1) HeapFree(hHandle, NULL, hField1); + if (hField2) HeapFree(hHandle, NULL, hField2); + HeapDestroy(hHandle); + hField1 = NULL; + hField2 = NULL; + hHandle = NULL; + } + +} + +BOOL MLImageFilterWater::CreateFor(const MLImage *image) +{ + ClearData(); + width = image->GetWidth(); + height = image->GetHeight(); + hPage = 0; + int len = height * width * sizeof(int); + hHandle = HeapCreate(NULL, 3*len, 3*len); + if (!hHandle) + { + width = 0; + height = 0; + return FALSE; + } + + hField1 = (int*)HeapAlloc(hHandle, HEAP_ZERO_MEMORY, len); + hField2 = (int*)HeapAlloc(hHandle, HEAP_ZERO_MEMORY, len); + return hField1 && hField2; +} + +void MLImageFilterWater::Render(MLImage* destination, const MLImage* source) +{ + if(!drawWithLight) DrawWaterNoLight(hPage, destination, source); + else DrawWaterWithLight(destination, source); + CalculateWater(hPage, density); +// CalcWaterBigFilter(hPage, density); + hPage ^= 1; +} + +void MLImageFilterWater::CalculateWater(int page, int density) +{ + int newh; + int count = width + 1; + int *newptr; + int *oldptr; + + if(page == 0) + { + newptr = hField1; + oldptr = hField2; + } + else + { + newptr = hField2; + oldptr = hField1; + } + + int x, y; + for (y = (height - 1) * width; count < y; count += 2) + { + for (x = count + width - 2; count < x; count++) + { + // This does the eight-pixel method. + + newh = ((oldptr[count + width] + + oldptr[count - width] + + oldptr[count + 1] + + oldptr[count - 1] + + oldptr[count - width - 1] + + oldptr[count - width + 1] + + oldptr[count + width - 1] + + oldptr[count + width + 1] + ) >> 2 ) + - newptr[count]; + newptr[count] = newh - (newh >> density); + /* + // This is the "sludge" method... + newh = (oldptr[count]<<2) + + oldptr[count-1-m_iWidth] + + oldptr[count+1-m_iWidth] + + oldptr[count-1+m_iWidth] + + oldptr[count+1+m_iWidth] + + ((oldptr[count-1] + + oldptr[count+1] + + oldptr[count-m_iWidth] + + oldptr[count+m_iWidth])<<1); + + newptr[count] = (newh-(newh>>6)) >> density; + */ + } + } +} + +void MLImageFilterWater::SmoothWater(int page) +{ + int newh; + int count = width + 1; + + int *newptr; + int *oldptr; + + if(page == 0) + { + newptr = hField1; + oldptr = hField2; + } + else + { + newptr = hField2; + oldptr = hField1; + } + + int x, y; + + for(y = 1; y < height; y++, count += 2) + { + for( x = 1; x < width; x++, count++) + { + // This does the eight-pixel method. + + newh = ((oldptr[count + width] + + oldptr[count - width] + + oldptr[count + 1] + + oldptr[count - 1] + + oldptr[count - width - 1] + + oldptr[count - width + 1] + + oldptr[count + width - 1] + + oldptr[count + width + 1] + ) >> 3 ) + + newptr[count]; + newptr[count] = newh>>1; + } + } +} +void MLImageFilterWater::FlattenWater(void) +{ + int len = width * height * sizeof(int); + SecureZeroMemory(hField1, len); + SecureZeroMemory(hField2, len); +} +void MLImageFilterWater::SineBlob(int x, int y, int radius, int height, int page) +{ + int cx, cy; + int left,top,right,bottom; + double square, dist; + double radsquare = radius * radius; + double length = double((1024.0/(double)radius)*(1024.0/(double)radius)); + int *newptr; + + if(page == 0) + { + newptr = hField1; + } + else + { + newptr = hField2; + } + + int t = (this->width - 2*radius - 1); + if (t == 0) t = 1; + if(x<0) x = 1 + radius + rand() % t; + t = (this->height - 2*radius - 1); + if (t == 0) t = 1; + if(y<0) y = 1 + radius + rand() % t; + + radsquare = (radius*radius); + + left = -radius; right = radius; + top = -radius; bottom = radius; + + // Perform edge clipping... + if(x - radius < 1) left -= (x-radius-1); + if(y - radius < 1) top -= (y-radius-1); + if(x + radius > this->width - 1) right -= (x + radius - this->width + 1); + if(y + radius > this->height - 1) bottom -= (y + radius - this->height + 1); + + for(cy = top; cy < bottom; cy++) + { + for(cx = left; cx < right; cx++) + { + square = cy*cy + cx*cx; + if(square < radsquare) + { + dist = sqrt(square*length); + newptr[this->width*(cy+y) + cx+x] += (int)((cos(dist)+0xffff)*(height)) >> 19; + } + } + } +} + +void MLImageFilterWater::WarpBlob(int x, int y, int radius, int height, int page) +{ + int cx, cy; + int left,top,right,bottom; + int square; + int radsquare = radius * radius; + int *newptr; + + if(page == 0) + { + newptr = hField1; + } + else + { + newptr = hField2; + } + + radsquare = (radius*radius); + + height /= 64; + + left=-radius; right = radius; + top=-radius; bottom = radius; + + // Perform edge clipping... + if(x - radius < 1) left -= (x-radius-1); + if(y - radius < 1) top -= (y-radius-1); + if(x + radius > this->width-1) right -= (x+ radius - this->width + 1); + if(y + radius > this->height-1) bottom-= (y + radius - this->height + 1); + + for(cy = top; cy < bottom; cy++) + { + for(cx = left; cx < right; cx++) + { + square = cy*cy + cx*cx; + if(square < radsquare) + { + newptr[this->width*(cy+y) + cx+x] += int((radius-sqrt((float)square))*(float)(height)); + } + } + } +} +void MLImageFilterWater::HeightBox (int x, int y, int radius, int height, int page) +{ + int cx, cy; + int left,top,right,bottom; + int *newptr; + + if(page == 0) + { + newptr = hField1; + } + else + { + newptr = hField2; + } + + int t = (this->width - 2*radius - 1); + if (t == 0) t = 1; + if(x<0) x = 1 + radius + rand() % t; + t = (this->height - 2*radius - 1); + if (t == 0) t = 1; + if(y<0) y = 1 + radius + rand() % t; + + left=-radius; right = radius; + top=-radius; bottom = radius; + + // Perform edge clipping... + if(x - radius < 1) left -= (x-radius-1); + if(y - radius < 1) top -= (y-radius-1); + if(x + radius > this->width-1) right -= (x+ radius - this->width + 1); + if(y + radius > this->height-1) bottom-= (y + radius - this->height + 1); + + for(cy = top; cy < bottom; cy++) + { + for(cx = left; cx < right; cx++) + { + newptr[this->width*(cy+y) + cx+x] = height; + } + } +} +void MLImageFilterWater::HeightBlob(int x, int y, int radius, int height, int page) +{ + int rquad; + int cx, cy; + int left, top, right, bottom; + + rquad = radius * radius; + + // Make a randomly-placed blob... + int t = (this->width - 2*radius - 1); + if (t == 0) t = 1; + if(x<0) x = 1 + radius + rand() % t; + t = (this->height - 2*radius - 1); + if (t == 0) t = 1; + if(y<0) y = 1 + radius + rand() % t; + + left = -radius; right = radius; + top = -radius; bottom = radius; + + // Perform edge clipping... + if(x - radius < 1) left -= (x-radius-1); + if(y - radius < 1) top -= (y-radius-1); + if(x + radius > this->width-1) right -= (x+ radius - this->width + 1); + if(y + radius > this->height-1) bottom-= (y + radius - this->height + 1); + + for(cy = top; cy < bottom; cy++) + { + int cyq = cy*cy; + for(cx = left; cx < right; cx++) + { + if(cx*cx + cyq < rquad) newptr[this->width * (cy+y) + (cx+x)] += height; + } + } +} + +void MLImageFilterWater::CalcWaterBigFilter(int page, int density) +{ + int newh; + int count = (2 * width) + 2; + + int *newptr; + int *oldptr; + + // Set up the pointers + if(page == 0) + { + newptr = hField1; + oldptr = hField2; + } + else + { + newptr = hField2; + oldptr = hField1; + } + + int x, y; + + for(y=2; y < height-2; y++, count += 4) + { + for(x=2; x < width-2; x++, count++) + { + // This does the 25-pixel method. It looks much okay. + + newh = ( + ( + ( + (oldptr[count + width] + + oldptr[count - width] + + oldptr[count + 1] + + oldptr[count - 1] + )<<1) + + ((oldptr[count - width - 1] + + oldptr[count - width + 1] + + oldptr[count + width - 1] + + oldptr[count + width + 1])) + + ( ( + oldptr[count - (width*2)] + + oldptr[count + (width*2)] + + oldptr[count - 2] + + oldptr[count + 2] + ) >> 1 ) + + ( ( + oldptr[count - (width*2) - 1] + + oldptr[count - (width*2) + 1] + + oldptr[count + (width*2) - 1] + + oldptr[count + (width*2) + 1] + + oldptr[count - 2 - width] + + oldptr[count - 2 + width] + + oldptr[count + 2 - width] + + oldptr[count + 2 + width] + ) >> 2 ) + ) + >> 3) + - (newptr[count]); + + + newptr[count] = newh - (newh >> density); + } + } +} + +void MLImageFilterWater::DrawWaterNoLight(int page, MLImage* destination, const MLImage* source) +{ + unsigned int brk = width * height; + + int *ptr = hField1; + + DWORD *dataS = (DWORD*)source->GetData(); + DWORD *dataD = (DWORD*)destination->GetData(); + + for (unsigned int offset = 0; offset < brk; offset++) + { + int dx = ptr[offset] - ptr[offset + 1]; + int dy = ptr[offset] - ptr[offset + width]; + unsigned int index = offset + width * (dy>>3) + (dx>>3); + dataD[offset] = (index < brk ) ? dataS[offset + width*(dy>>3) + (dx>>3)] : dataS[offset]; + } +} + +void MLImageFilterWater::DrawWaterWithLight(MLImage* destination, const MLImage* source) +{ + unsigned int brk = width * height; + + int *ptr = hField1; + + DWORD *dataS = (DWORD*)source->GetData(); + DWORD *dataD = (DWORD*)destination->GetData(); + + for (unsigned int offset = 0; offset < brk; offset++) + { + int dx = ptr[offset] - ptr[offset + 1]; + int dy = ptr[offset] - ptr[offset + width]; + unsigned int index = offset + width * (dy>>3) + (dx>>3); + dataD[offset] = (index < brk ) ? GetShiftedColor(dataS[index], dx) : dataS[offset]; + } +} +COLORREF MLImageFilterWater::GetShiftedColor(COLORREF color,int shift) +{ + int R,G, B; + int r, g, b; + + R = GetRValue(color)-shift; + G = GetGValue(color)-shift; + B = GetBValue(color)-shift; + + r = (R < 0) ? 0 : (R > 255) ? 255 : R; + g = (G < 0) ? 0 : (G > 255) ? 255 : G; + b = (B < 0) ? 0 : (B > 255) ? 255 : B; + + return RGB(r,g,b); +} diff --git a/Src/Plugins/Input/in_mp3/graphics/filterWater.h b/Src/Plugins/Input/in_mp3/graphics/filterWater.h new file mode 100644 index 00000000..21ed92c0 --- /dev/null +++ b/Src/Plugins/Input/in_mp3/graphics/filterWater.h @@ -0,0 +1,47 @@ +#ifndef NULLSOFT_ML_IMAGE_FILTERWATER_HEADER +#define NULLSOFT_ML_IMAGE_FILTERWATER_HEADER + +#include <windows.h> +#include ".\image.h" + +class MLImageFilterWater +{ +public: + MLImageFilterWater(void); + ~MLImageFilterWater(void); + +public: + BOOL CreateFor(const MLImage *image); + void Render(MLImage* destination, const MLImage* source); + + void CalculateWater(int page, int density); + void SmoothWater(int page); + void FlattenWater(void); + + void SineBlob(int x, int y, int radius, int height, int page); + void WarpBlob(int x, int y, int radius, int height, int page); + void HeightBox (int x, int y, int radius, int height, int page); + void HeightBlob(int x, int y, int radius, int height, int page); + +protected: + void ClearData(void); + void CalcWaterBigFilter(int page, int density); + void DrawWaterNoLight(int page,MLImage* destination, const MLImage* source); + void DrawWaterWithLight(MLImage* destination, const MLImage* source); + COLORREF GetShiftedColor(COLORREF color,int shift); + +private: + HANDLE hHandle; + int height; + int width; + + BOOL drawWithLight; + int lightModifier; + int hPage; + int density; + + int* hField1; + int* hField2; +}; + +#endif //#define NULLSOFT_ML_IMAGE_FILTERWATER_HEADER
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mp3/graphics/image.cpp b/Src/Plugins/Input/in_mp3/graphics/image.cpp new file mode 100644 index 00000000..2e3cda46 --- /dev/null +++ b/Src/Plugins/Input/in_mp3/graphics/image.cpp @@ -0,0 +1,209 @@ +#include ".\image.h" + +MLImage::MLImage(void) +{ + loader = NULL; + loaderDelete = TRUE; + ResetData(); +} + +MLImage::MLImage(IMGLOADFUNC loader, BOOL deleteDone) +{ + ResetData(); + SetLoader(loader, deleteDone, FALSE); +} + +MLImage::MLImage(int width, int height) +{ + loader = NULL; + ResetData(); + Init(width,height); +} + +MLImage::~MLImage(void) +{ + ResetData(); +} + +INT_PTR MLImage::SetLoader(IMGLOADFUNC loader, BOOL deleteDone, BOOL forceLoad) +{ + this->loader = loader; + this->loaderDelete = deleteDone; + if (loader && forceLoad) Load(); + return (loader != NULL) ? (INT_PTR) this : FALSE; +} + +BOOL MLImage::Load(void) +{ + ResetData(); + + if (!loader) return FALSE; + HBITMAP hbmpLoaded = loader((INT_PTR)this); + if(hbmpLoaded == NULL) return FALSE; + + BITMAP bi; + if (GetObject(hbmpLoaded, sizeof(bi), &bi)) + { + hbmp = ConvertTo32BppDIB(hbmpLoaded, bi.bmWidth, bi.bmHeight, &info, &data); + } + + if (loaderDelete) DeleteObject(hbmpLoaded); + return (hbmp != NULL); +} + +void MLImage::ResetData(void) +{ + if (hbmp) DeleteObject(hbmp); + hbmp = NULL; + SecureZeroMemory(&info, sizeof(BITMAPINFO)); + data = NULL; +} + +BOOL MLImage::Draw(HDC hdcDest, int destX, int destY, int destWidth, int destHeight, int sourceX, int sourceY) +{ + if (!hbmp) return FALSE; + + int realheight = abs(info.bmiHeader.biHeight); + int rsX = min(sourceX, info.bmiHeader.biWidth); + int rsY = min(sourceY, info.bmiHeader.biWidth); + int height = min(destHeight, realheight - rsY); + + BOOL bResult = SetDIBitsToDevice( hdcDest, destX, destY, + min(destWidth, info.bmiHeader.biWidth - rsX), height, + rsX, realheight - height - rsY, + 0, height, + data, &info, DIB_RGB_COLORS); + return bResult; +} + +BOOL MLImage::Draw(HDC hdcDest, int destX, int destY) +{ + return (!hbmp) ? FALSE : SetDIBitsToDevice( hdcDest, destX, destY, + info.bmiHeader.biWidth, abs(info.bmiHeader.biHeight), + 0, 0, + 0, abs(info.bmiHeader.biHeight), + data, &info, DIB_RGB_COLORS); +} + +int MLImage::GetWidth(void) const +{ + return (hbmp) ? info.bmiHeader.biWidth : 0; +} + +int MLImage::GetHeight(void) const +{ + return (hbmp) ? abs(info.bmiHeader.biHeight) : 0; +} + +void* MLImage::GetData(void) const +{ + return data; +} + +HBITMAP MLImage::ConvertTo32BppDIB(HBITMAP bmpHandle, int bmpWidth, int bmpHeight, LPBITMAPINFO bmpInfo, LPVOID *bmpData) +{ + HBITMAP hbmpNew = NULL; + + HDC hdc = GetWindowDC(NULL); + HDC hdcTmp = CreateCompatibleDC(hdc); + HBITMAP hbmpTmp = CreateCompatibleBitmap(hdc, bmpWidth, bmpHeight); + HBITMAP hbmpOld = (HBITMAP) SelectObject(hdcTmp, hbmpTmp); + + // render original bitmap to the temp dc + HDC hdcBmp = CreateCompatibleDC(hdc); + SelectObject(hdcBmp, bmpHandle); + BitBlt(hdcTmp, 0, 0, bmpWidth, bmpHeight, hdcBmp, 0,0, SRCCOPY); + SelectObject(hdcBmp, NULL); + DeleteDC(hdcBmp); + + // Create a 32 bit bitmap + BITMAPINFO bih; + // create DIB Section + bih.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); + bih.bmiHeader.biWidth = bmpWidth; + bih.bmiHeader.biHeight = 0 - bmpHeight; + bih.bmiHeader.biPlanes = 1; + bih.bmiHeader.biBitCount = 32; + bih.bmiHeader.biCompression = BI_RGB; + bih.bmiHeader.biSizeImage = 0; + bih.bmiHeader.biXPelsPerMeter = 0; + bih.bmiHeader.biYPelsPerMeter = 0; + bih.bmiHeader.biClrUsed = 0; + bih.bmiHeader.biClrImportant = 0; + + // Create a DC which will be used to get DIB, then create DIBsection + hbmpNew = CreateDIBSection(hdc, (const BITMAPINFO*) &bih, DIB_RGB_COLORS, bmpData, NULL, 0); + + + DWORD* line = (DWORD*)(*bmpData); + // Copy the bits into our 32 bit dib.. + for(int i=0; i<bmpHeight; i++) + { + for(int j=0; j<bmpWidth; j++) + { + line[(i*bmpWidth) + j] = FIXCOLORREF(GetPixel(hdcTmp, j, i)); + } + } + + SelectObject(hdcTmp, hbmpOld); + ReleaseDC(NULL, hdc); + DeleteDC(hdcTmp); + + memcpy(bmpInfo, &bih, sizeof(BITMAPINFO)); + + return hbmpNew; +} + +MLImage* MLImage::Copy(MLImage* destination, const MLImage* original) +{ + if (!destination) return NULL; + + destination->ResetData(); + + destination->loader = original->loader; + destination->loaderDelete = original->loaderDelete; + destination->info = original->info; + HDC hdc = GetWindowDC(NULL); + destination->hbmp = CreateDIBSection(hdc, (const BITMAPINFO*) &destination->info, DIB_RGB_COLORS, &destination->data, NULL, 0); + + CopyMemory(destination->data, original->data, 4*destination->GetHeight() * destination->GetWidth()); + ReleaseDC(NULL, hdc); + return destination; +} + +MLImage* MLImage::Init(int width, int height) +{ + ResetData(); + + loader = NULL; + loaderDelete = TRUE; + + // create DIB Section + info.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); + info.bmiHeader.biWidth = width; + info.bmiHeader.biHeight = 0 - height; + info.bmiHeader.biPlanes = 1; + info.bmiHeader.biBitCount = 32; + info.bmiHeader.biCompression = BI_RGB; + info.bmiHeader.biSizeImage = 0; + info.bmiHeader.biXPelsPerMeter = 0; + info.bmiHeader.biYPelsPerMeter = 0; + info.bmiHeader.biClrUsed = 0; + info.bmiHeader.biClrImportant = 0; + + HDC hdc = GetWindowDC(NULL); + hbmp = CreateDIBSection(hdc, (const BITMAPINFO*) &info, DIB_RGB_COLORS, &data, NULL, 0); + ReleaseDC(NULL, hdc); + return this; +} + +MLImage* MLImage::Init(int width, int height, COLORREF color) +{ + Init(width, height); + + int rColor = FIXCOLORREF(color); + DWORD *line = (DWORD*)(data); + DWORD *end = line + GetHeight() * GetWidth(); + for(;line != end; line++) *line = rColor; + return this; +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mp3/graphics/image.h b/Src/Plugins/Input/in_mp3/graphics/image.h new file mode 100644 index 00000000..72a4aa5d --- /dev/null +++ b/Src/Plugins/Input/in_mp3/graphics/image.h @@ -0,0 +1,61 @@ +#ifndef NULLSOFT_ML_IMAGE_HEADER +#define NULLSOFT_ML_IMAGE_HEADER + +#include <windows.h> + +#define RGBA(r,g,b,a) ((COLORREF)(((BYTE)(r)|((WORD)(g)<<8))|(((DWORD)(BYTE)(b))<<16)|(((DWORD)(BYTE)(a))<<24))) +#define FIXCOLORREF(clr) RGBA(GetBValue(clr),GetGValue(clr), GetRValue(clr),((DWORD)(clr)) >> 24) + +// loader function will be called every time MLImage need to +// reload picture. Input parameter - handle to the calling object +// Output - loaded bitmap +typedef HBITMAP (*IMGLOADFUNC)(INT_PTR handle); + +class MLImage +{ +public: + MLImage(void); + MLImage(IMGLOADFUNC loader, BOOL deleteDone); + MLImage(int width, int height); + ~MLImage(void); + +public: + // sets the loader function and returns handle to the class or NULL if error + // loader - pointer to the loader function + // deleteDone - if TRUE MLImage will delete HBITMAP object from loader every time it is done loading + // forceLoad - forcing to load bitamp immedialty by calling Load() + INT_PTR SetLoader(IMGLOADFUNC loader, BOOL deleteDone, BOOL forceLoad); + BOOL Load(void); // load image + + MLImage* Init(int width, int height); // init image (allocates memory) + MLImage* Init(int width, int height, COLORREF color); // init image (allocates memory) and set + + BOOL Draw(HDC hdcDest, int destX, int destY, int destWidth, int destHeight, int sourceX, int sourceY); // draw image + BOOL Draw(HDC hdcDest, int destX, int destY); // draw image + +public: + int GetWidth(void) const; + int GetHeight(void) const; + void* GetData(void) const; + + +private: + void ResetData(void); + +public: + static MLImage* Copy(MLImage* destination, const MLImage* original);// copy all data from the original object (including image data) to the destination + +private: + static HBITMAP ConvertTo32BppDIB(HBITMAP bmpHandle, int bmpWidth, int bmpHeight, LPBITMAPINFO bmpInfo, LPVOID *bmpData); + +private: + IMGLOADFUNC loader; // pointer to the loader function + BOOL loaderDelete; // TRUE - delete HBITMAP from loader after load + + HBITMAP hbmp; // my bitmap + BITMAPINFO info; + void *data; +}; + +#endif // NULLSOFT_ML_IMAGE_HEADER + diff --git a/Src/Plugins/Input/in_mp3/graphics/imageFilters.cpp b/Src/Plugins/Input/in_mp3/graphics/imageFilters.cpp new file mode 100644 index 00000000..59d994ed --- /dev/null +++ b/Src/Plugins/Input/in_mp3/graphics/imageFilters.cpp @@ -0,0 +1,91 @@ +#include ".\imagefilters.h" + +void MLImageFilter_GrayScale(MLImage *image) +{ + DWORD *line = (DWORD*)(image->GetData()); + DWORD *end = line + image->GetHeight() * image->GetWidth(); + for(;line != end; line++) + { + BYTE y = (BYTE)(0.3f * GetBValue(*line) + 0.59f *GetGValue(*line) + 0.11f *GetRValue(*line)); + *line = RGB(y,y,y); + } +} + +void MLImageFilter_Invert(MLImage *image) +{ + DWORD *line = (DWORD*)(image->GetData()); + DWORD *end = line + image->GetHeight() * image->GetWidth(); + for(;line != end; line++) *line = ((~*line) & 0x00FFFFFF) | (*line & 0xFF000000); +} + +void MLImageFilter_SetToColor(MLImage *image, COLORREF color) +{ + COLORREF rColor = FIXCOLORREF(color); + DWORD *line = (DWORD*)(image->GetData()); + DWORD *end = line + image->GetHeight() * image->GetWidth(); + for(;line != end; line++) *line = rColor; +} + +void MLImageFilter_Fader1(MLImage *dest, const MLImage* source, COLORREF color) +{ + int len = dest->GetHeight() * dest->GetWidth(); + BYTE r = GetRValue(color), g = GetGValue(color), b = GetBValue(color); + + DWORD *dataS = (DWORD*)(source->GetData()); + DWORD *dataD = (DWORD*)(dest->GetData()); + DWORD *end = dataD + len; + for(;dataD != end; dataD++, dataS++) + { + *dataD = RGB( max(b, GetRValue(*dataS)), max(g, GetGValue(*dataS)), max(r, GetBValue(*dataS))) ; + } +} + +void MLImageFilter_Fader2(MLImage *dest, const MLImage* source, COLORREF color) +{ + int len = dest->GetHeight() * dest->GetWidth(); + BYTE r = GetRValue(color), g = GetGValue(color), b = GetBValue(color); + + DWORD *dataS = (DWORD*)(source->GetData()); + DWORD *dataD = (DWORD*)(dest->GetData()); + DWORD *end = dataD + len; + for(;dataD != end; dataD++, dataS++) + { + *dataD = RGB( min(b, GetRValue(*dataS)), min(g, GetGValue(*dataS)), min(r, GetBValue(*dataS))) ; + } +} + +void MLImageFilter_Fader3(MLImage *dest, const MLImage* source, int koeff) +{ + int len = dest->GetHeight() * dest->GetWidth(); + + DWORD *dataS = (DWORD*)(source->GetData()); + DWORD *dataD = (DWORD*)(dest->GetData()); + DWORD *end = dataD + len; + for(;dataD != end; dataD++, dataS++) + { + *dataD = RGB(min(255,GetRValue(*dataS) + koeff), min(255,GetGValue(*dataS) + koeff), min(255, GetBValue(*dataS) + koeff)); + } +} + +void MLImageFilter_Blend1(MLImage *dest, MLImage *src1, int destX, int destY, int width, int height, const MLImage* src2, int srcX, int srcY, COLORREF color) +{ + int widthS1 = src1->GetWidth(); + int widthS2 = src2->GetWidth(); + + DWORD *dataD = (DWORD*)(dest->GetData()) + destY * widthS1 + destX; + DWORD *dataS1 = (DWORD*)(src1->GetData()) + destY * widthS1 + destX; + DWORD *dataS2 = (DWORD*)(src2->GetData()) + srcY * widthS2 + srcX; + + DWORD *curS1; + + for (int y = 0; y < height; y++) + { + DWORD *curD = dataD + y * widthS1; + curS1 = dataS1 + y * widthS1; + DWORD *curS2 = dataS2 + y * widthS2; + for (DWORD *end = curS1 + width; end != curS1; curD++, curS1++, curS2++) + { + *curD = (*curS1 == color) ? *curS2 : *curS1; + } + } +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mp3/graphics/imageFilters.h b/Src/Plugins/Input/in_mp3/graphics/imageFilters.h new file mode 100644 index 00000000..ebb1c752 --- /dev/null +++ b/Src/Plugins/Input/in_mp3/graphics/imageFilters.h @@ -0,0 +1,17 @@ +#ifndef NULLSOFT_ML_IMAGE_FILTER_HEADER +#define NULLSOFT_ML_IMAGE_FILTER_HEADER + +#include <windows.h> +#include ".\image.h" +#include ".\filterwater.h" + +void MLImageFilter_GrayScale(MLImage *image); +void MLImageFilter_Invert(MLImage *image); +void MLImageFilter_SetToColor(MLImage *image, COLORREF color); +void MLImageFilter_Fader1(MLImage *dest, const MLImage* source, COLORREF color); +void MLImageFilter_Fader2(MLImage *dest, const MLImage* source, COLORREF color); +void MLImageFilter_Fader3(MLImage *dest, const MLImage* source, int koeff); +void MLImageFilter_Blend1(MLImage *dest, MLImage *src1, int destX, int destY, int width, int height, const MLImage* src2, int srcX, int srcY, COLORREF color); + + +#endif //NULLSOFT_ML_IMAGE_FILTER_HEADER diff --git a/Src/Plugins/Input/in_mp3/id3.cpp b/Src/Plugins/Input/in_mp3/id3.cpp new file mode 100644 index 00000000..2ab12c74 --- /dev/null +++ b/Src/Plugins/Input/in_mp3/id3.cpp @@ -0,0 +1,556 @@ +#include "../id3v2/id3_tag.h" +#include "id3.h" +#include "config.h" +#include "../nu/ns_wc.h" +#include <strsafe.h> +#define _isdigit(x) (( x ) >= '0' && ( x ) <= '9') + +/* id3 helper functions */ + +void SetFrameEncoding(ID3_Frame *frame, int encoding) +{ + switch (encoding) + { + case ENCODING_AUTO: + if (config_write_mode == WRITE_UTF16) + frame->Field(ID3FN_TEXTENC).Set(ID3TE_UNICODE); + else + frame->Field(ID3FN_TEXTENC).Set(ID3TE_ASCII); + break; + case ENCODING_FORCE_ASCII: + frame->Field(ID3FN_TEXTENC).Set(ID3TE_ASCII); + break; + case ENCODING_FORCE_UNICODE: + frame->Field(ID3FN_TEXTENC).Set(ID3TE_UNICODE); + break; + } +} + +char *ID3_GetString(ID3_Frame *frame, ID3_FieldID fldName, size_t nIndex) +{ + char *text = NULL; + if (NULL != frame) + { + size_t nText = frame->Field(fldName).Size(); + text = (char *)calloc(nText + 1, sizeof(char)); + frame->Field(fldName).GetLocal(text, nText + 1, nIndex); + } + return text; +} + +wchar_t *ID3_GetUnicodeString(ID3_Frame *frame, ID3_FieldID fldName, size_t nIndex) +{ + wchar_t *text = NULL; + if (NULL != frame) + { + size_t nText = frame->Field(fldName).Size(); + text = (wchar_t *)calloc(sizeof(wchar_t) * (nText + 1), sizeof(wchar_t)); + frame->Field(fldName).GetUnicode(text, nText + 1, nIndex); + } + return text; +} + +wchar_t *ID3_FillUnicodeString(ID3_Frame *frame, ID3_FieldID fldName, wchar_t *dest, size_t destlen, size_t nIndex) +{ + memset(dest, 0, destlen * sizeof(wchar_t)); + if (NULL != frame) + { + frame->Field(fldName).GetUnicode(dest, destlen, nIndex); + return dest; + } + else + return NULL; +} + +wchar_t *ID3_GetTitle(ID3_Tag *tag) +{ + wchar_t*sTitle = NULL; + if (NULL == tag) + { + return sTitle; + } + ID3_Frame *frame = tag->Find(ID3FID_TITLE); + if (frame != NULL) + { + sTitle = ID3_GetUnicodeString(frame, ID3FN_TEXT); + } + return sTitle; +} + +wchar_t *ID3_GetArtist(ID3_Tag *tag) +{ + if (!tag) return 0; + wchar_t *sArtist = NULL; + ID3_Frame *frame = NULL; + if ((frame = tag->Find(ID3FID_LEADARTIST)) || (frame = tag->Find(ID3FID_BAND))) + { + sArtist = ID3_GetUnicodeString(frame, ID3FN_TEXT); + } + return sArtist; +} + +wchar_t *ID3_GetAlbum(ID3_Tag *tag) +{ + wchar_t *sAlbum = NULL; + if (NULL == tag) + { + return sAlbum; + } + ID3_Frame *frame = tag->Find(ID3FID_ALBUM); + if (frame != NULL) + { + sAlbum = ID3_GetUnicodeString(frame, ID3FN_TEXT); + } + return sAlbum; +} + +wchar_t *ID3_GetYear(ID3_Tag *tag) +{ + wchar_t *sYear = NULL; + if (NULL == tag) + { + return sYear; + } + ID3_Frame *frame = tag->Find(ID3FID_RECORDINGTIME); + if (frame != NULL) + sYear = ID3_GetUnicodeString(frame, ID3FN_TEXT); + + if (!sYear || !*sYear) + { + frame = tag->Find(ID3FID_YEAR); + if (frame != NULL) + sYear = ID3_GetUnicodeString(frame, ID3FN_TEXT); + } + + return sYear; +} + +void ID3_AddSetComment(ID3_Tag *tag, const wchar_t *comment) +{ + ID3_Frame *frame = tag->Find(ID3FID_COMMENT, ID3FN_DESCRIPTION, L""); + if (frame) + { + if (!comment || !comment[0]) + tag->RemoveFrame(frame); + else + { + SetFrameEncoding(frame); + frame->Field(ID3FN_TEXT).SetUnicode(comment); + unsigned char null3[3] = {0, 0, 0}; + frame->Field(ID3FN_LANGUAGE).Get(null3, 3); + if (!null3[0]) frame->Field(ID3FN_LANGUAGE).SetLatin("eng"); + } + } + else if (comment && comment[0]) + { + frame = new ID3_Frame(ID3FID_COMMENT); + SetFrameEncoding(frame); + frame->Field(ID3FN_LANGUAGE).SetLatin("eng"); + //frame->Field(ID3FN_LANGUAGE).Set(null3, 3); + frame->Field(ID3FN_DESCRIPTION).SetUnicode(L""); + frame->Field(ID3FN_TEXT).SetUnicode(comment); + tag->AddFrame(frame, TRUE); + } +} + +void ID3_AddSetRating(ID3_Tag *tag, const wchar_t *rating) +{ + luint rating_integer = 0; + if (rating) + rating_integer = _wtoi(rating); + + bool custom_frame = false, own_frame = false; + ID3_Frame* frame = NULL; + if (config_rating_email[0]) + { + frame = tag->Find(ID3FID_POPULARIMETER, ID3FN_EMAIL, config_rating_email); + if (!frame) custom_frame = true; + } + if (!frame) + { + frame = tag->Find(ID3FID_POPULARIMETER, ID3FN_EMAIL, "rating@winamp.com\0"); + if (frame) own_frame = true; + } + if (!frame) + { + frame = tag->Find(ID3FID_POPULARIMETER); + if (frame) own_frame = true; + } + // try to use a custom field if our own was present and the custom wasn't + if (custom_frame && own_frame) + { + frame->Clear(); + frame = NULL; + } + if (!frame) + { + frame = new ID3_Frame(ID3FID_POPULARIMETER); + if (!config_rating_email[0]) + frame->Field(ID3FN_EMAIL).Set((uchar *)"rating@winamp.com\0", 18); + else + { + frame->Field(ID3FN_EMAIL).Set((uchar *)config_rating_email, strlen(config_rating_email)+1); + } + tag->AddFrame(frame, TRUE); + } + if (frame) + { + switch(rating_integer) + { + case 2: + rating_integer=64; + break; + case 3: + rating_integer=128; + break; + case 4: + rating_integer=196; + break; + case 5: + rating_integer = 255; + break; + } + + if (!rating_integer) + tag->RemoveFrame(frame); + else + frame->Field(ID3FN_RATING).Set(rating_integer); + } +} + +wchar_t *ID3_GetComment(ID3_Tag *tag, wchar_t *dest, size_t destlen) +{ + wchar_t *comment = NULL; + if (NULL == tag) + { + return comment; + } + ID3_Frame* frame = tag->Find(ID3FID_COMMENT, ID3FN_DESCRIPTION, L""); + if (frame) + { + comment = ID3_FillUnicodeString(frame, ID3FN_TEXT, dest, destlen); + } + return comment; +} + +wchar_t *ID3_GetRating(ID3_Tag *tag, wchar_t *dest, size_t destlen) +{ + if (NULL == tag) + { + return NULL; + } + ID3_Frame* frame = NULL; + if (config_rating_email[0]) + frame = tag->Find(ID3FID_POPULARIMETER, ID3FN_EMAIL, config_rating_email); + if (!frame) + frame = tag->Find(ID3FID_POPULARIMETER, ID3FN_EMAIL, "rating@winamp.com\0"); + if (!frame) + frame = tag->Find(ID3FID_POPULARIMETER); + if (frame) + { + int rating = (int)frame->Field(ID3FN_RATING).Get(); + + if (rating >= 224 && rating <= 255) + rating = 5; + else if (rating >= 160 && rating <= 223) + rating = 4; + else if (rating >= 96 && rating <= 159) + rating = 3; + else if (rating >= 32 && rating <= 95) + rating = 2; + else if (rating >= 1 && rating <= 31) + rating = 1; + else + rating = 0; + + StringCchPrintfW(dest, destlen, L"%u", rating); + return dest; + } + return 0; +} + +wchar_t *ID3_GetComment(ID3_Tag *tag, const wchar_t *desc, wchar_t *dest, size_t destlen) +{ + wchar_t *comment = NULL; + if (NULL == tag) + { + return comment; + } + ID3_Frame* frame = tag->Find(ID3FID_COMMENT, ID3FN_DESCRIPTION, desc); + if (frame) + { + comment = ID3_FillUnicodeString(frame, ID3FN_TEXT, dest, destlen); + } + return comment; +} + +wchar_t *ID3_GetMusicbrainzRecordingID(ID3_Tag *tag, wchar_t *dest, size_t destlen) +{ + if (NULL == tag) + { + return 0; + } + ID3_Frame* frame = tag->Find(ID3FID_UNIQUEFILEID, ID3FN_OWNER, L"http://musicbrainz.org"); + if (frame) + { + uchar data[64] = {0}; + luint dataSize = frame->Field(ID3FN_DATA).Size(); + frame->Field(ID3FN_DATA).Get(data, 64); + int converted = MultiByteToWideCharSZ(CP_ACP, 0, (const char *)data, (int)dataSize, dest, (int)destlen); + dest[converted]=0; + return dest; + } + return 0; +} + +wchar_t *ID3_GetGracenoteTagID(ID3_Tag *tag) +{ + if (NULL == tag) + { + return 0; + } + ID3_Frame* frame = tag->Find(ID3FID_UNIQUEFILEID, ID3FN_OWNER, L"http://www.cddb.com/id3/taginfo1.html"); + if (frame) + { + uchar data[64] = {0}; + luint dataSize = frame->Field(ID3FN_DATA).Size(); + frame->Field(ID3FN_DATA).Get(data, 64); + int converted = MultiByteToWideChar(CP_ACP, 0, (const char *)data, (int)dataSize, 0, 0); + wchar_t *dest = (wchar_t *)calloc((converted+1), sizeof(wchar_t)); + converted = MultiByteToWideChar(CP_ACP, 0, (const char *)data, (int)dataSize, dest, converted); + dest[converted]=0; + return dest; + } + return 0; +} + +wchar_t *ID3_GetGracenoteTagID(ID3_Tag *tag, wchar_t *dest, size_t destlen) +{ + if (NULL == tag) + { + return 0; + } + ID3_Frame* frame = tag->Find(ID3FID_UNIQUEFILEID, ID3FN_OWNER, L"http://www.cddb.com/id3/taginfo1.html"); + if (frame) + { + uchar data[64] = {0}; + luint dataSize = frame->Field(ID3FN_DATA).Size(); + frame->Field(ID3FN_DATA).Get(data, 64); + int converted = MultiByteToWideCharSZ(CP_ACP, 0, (const char *)data, (int)dataSize, dest, (int)destlen); + dest[converted]=0; + return dest; + } + return 0; +} + +void ID3_AddSetGracenoteTagID(ID3_Tag *tag, const wchar_t *tagID) +{ + ID3_Frame *frame = tag->Find(ID3FID_UNIQUEFILEID, ID3FN_OWNER, L"http://www.cddb.com/id3/taginfo1.html"); + if (frame) + { + if (!tagID || !tagID[0]) + tag->RemoveFrame(frame); + else + { + size_t origLen = wcslen(tagID); // so we can not write the null terminator + uchar data[64] = {0}; + luint dataSize = WideCharToMultiByte(CP_ACP, 0, tagID, (int)origLen, (char *)data, 64, 0, 0); + frame->Field(ID3FN_DATA).Set(data, dataSize); + } + } + else if (tagID && tagID[0]) + { + frame = new ID3_Frame(ID3FID_UNIQUEFILEID); + SetFrameEncoding(frame, ENCODING_FORCE_ASCII); + frame->Field(ID3FN_OWNER).SetLatin("http://www.cddb.com/id3/taginfo1.html"); + size_t origLen = wcslen(tagID); // so we can not write the null terminator + uchar data[64] = {0}; + luint dataSize = WideCharToMultiByte(CP_ACP, 0, tagID, (int)origLen, (char *)data, 64, 0, 0); + frame->Field(ID3FN_DATA).Set(data, dataSize); + tag->AddFrame(frame, TRUE); + } +} + +#if 0 // benski> CUT +char *ID3_GetTUID(ID3_Tag *tag) +{ + char *tuid = NULL; + if (NULL == tag) + { + return tuid; + } + ID3_Frame* frame = NULL; + frame = tag->Find(ID3FID_UNIQUEFILEID); + if (frame) + { + char *tmp = ID3_GetString(frame, ID3FN_DATA); + if (tmp) + { + // verify first four characters are '3CD3' + if (!strncmp(tmp, "3CD3", 4)) + { + char m, n; + char *p = tmp + 4; + n = *p++; + m = 'P' - n; + p += m; + + n = *p++; + m = 'Z' - n; // length of TUID; + tuid = _strdup(p); + tuid[m] = 0; // null terminate + } + + free(tmp); + } + } + return tuid; +} +#endif + +char *ID3_GetGenre(ID3_Tag *tag) +{ + char *sGenre = NULL; + if (NULL == tag) + { + return sGenre; + } + ID3_Frame *frame = tag->Find(ID3FID_CONTENTTYPE); + if (frame != NULL) + { + sGenre = ID3_GetString(frame, ID3FN_TEXT); + } + return sGenre; +} + +void ID3_AddUserText(ID3_Tag *tag, wchar_t *desc, const wchar_t *value, int encoding) +{ + ID3_Frame *frame = tag->Find(ID3FID_USERTEXT, ID3FN_DESCRIPTION, desc); + if (frame) + { + if (!value || !value[0]) + tag->RemoveFrame(frame); + else + { + SetFrameEncoding(frame, encoding); + frame->Field(ID3FN_TEXT).SetUnicode(value); + } + } + else if (value && value[0]) + { + frame = new ID3_Frame(ID3FID_USERTEXT); + SetFrameEncoding(frame, encoding); + frame->Field(ID3FN_DESCRIPTION).SetUnicode(desc); + frame->Field(ID3FN_TEXT).SetUnicode(value); + tag->AddFrame(frame, TRUE); + } +} + +wchar_t *ID3_GetUserText(ID3_Tag *tag, wchar_t *desc) +{ + if (tag == NULL) + return NULL; + + ID3_Frame *frame = tag->Find(ID3FID_USERTEXT, ID3FN_DESCRIPTION, desc); + if (frame) + return ID3_GetUnicodeString(frame, ID3FN_TEXT); + else + return 0; +} + +wchar_t *ID3_GetUserText(ID3_Tag *tag, wchar_t *desc, wchar_t *dest, size_t destlen) +{ + if (tag == NULL) + return NULL; + + ID3_Frame *frame = tag->Find(ID3FID_USERTEXT, ID3FN_DESCRIPTION, desc); + if (frame) + return ID3_FillUnicodeString(frame, ID3FN_TEXT, dest, destlen); + else + return 0; +} + +wchar_t *ID3_GetTagText(ID3_Tag *tag, ID3_FrameID f) +{ + wchar_t *sComposer = NULL; + if (NULL == tag) + { + return sComposer; + } + ID3_Frame *frame = tag->Find(f); + if (frame != NULL) + { + sComposer = ID3_GetUnicodeString(frame, ID3FN_TEXT); + } + return sComposer; +} + +wchar_t *ID3_GetTagText(ID3_Tag *tag, ID3_FrameID f, wchar_t *dest, size_t destlen) +{ + wchar_t *sComposer = NULL; + if (NULL == tag) + { + return sComposer; + } + ID3_Frame *frame = tag->Find(f); + if (frame != NULL) + { + sComposer = ID3_FillUnicodeString(frame, ID3FN_TEXT, dest, destlen); + } + return sComposer; +} + +wchar_t *ID3_GetTagUrl(ID3_Tag *tag, ID3_FrameID f, wchar_t *dest, size_t destlen) +{ + wchar_t *sComposer = NULL; + if (NULL == tag) + { + return sComposer; + } + ID3_Frame *frame = tag->Find(f); + if (frame != NULL) + { + sComposer = ID3_FillUnicodeString(frame, ID3FN_URL, dest, destlen); + } + return sComposer; +} + +#if 0 +char *ID3_GetGenreDisplayable(ID3_Tag *tag) +{ + char *sGenre = ID3_GetGenre(tag); + if (!sGenre) return NULL; + + while (sGenre && *sGenre == ' ') sGenre++; + + if (sGenre[0] == '(' || _isdigit(sGenre[0])) + { + int isparam = !_isdigit(sGenre[0]); + char *pCur = &sGenre[isparam]; + int cnt = 0; + while (_isdigit(*pCur)) + { + cnt++; + pCur++; + } + while (pCur && *pCur == ' ') pCur++; + + if (cnt > 0 && (isparam && *pCur == ')') || (!isparam && !*pCur)) + { + // if the genre number is greater than 255, its invalid. + size_t ulGenre = atoi(&sGenre[isparam]); + if (ulGenre >= 0 && ulGenre < numberOfGenres) + { + char *tmp = (char*)malloc(strlen(genres[ulGenre]) + 1); + if (tmp) + { + memcpy(tmp, genres[ulGenre], strlen(genres[ulGenre]) + 1); + free(sGenre); + sGenre = tmp; + } + } + } + } + return sGenre; +} +#endif
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mp3/id3.h b/Src/Plugins/Input/in_mp3/id3.h new file mode 100644 index 00000000..1bd40590 --- /dev/null +++ b/Src/Plugins/Input/in_mp3/id3.h @@ -0,0 +1,43 @@ +#ifndef NULLSOFT_IN_MP3_IN_H +#define NULLSOFT_IN_MP3_IN_H + +extern char *genres[]; +extern size_t numberOfGenres; +enum +{ + ENCODING_AUTO=0, + ENCODING_FORCE_ASCII = 1, + ENCODING_FORCE_UNICODE = 2, +}; + +char *ID3_GetString(ID3_Frame *frame, ID3_FieldID fldName, size_t nIndex=1); +wchar_t *ID3_GetUnicodeString(ID3_Frame *frame, ID3_FieldID fldName, size_t nIndex=1); +wchar_t *ID3_FillUnicodeString(ID3_Frame *frame, ID3_FieldID fldName, wchar_t *dest, size_t destlen, size_t nIndex=1); + +wchar_t *ID3_GetTitle(ID3_Tag *tag); +wchar_t *ID3_GetArtist(ID3_Tag *tag); +//char *ID3_GetAlbumLocal(ID3_Tag *tag); +wchar_t *ID3_GetAlbum(ID3_Tag *tag); +wchar_t *ID3_GetYear(ID3_Tag *tag); +wchar_t *ID3_GetComment(ID3_Tag *tag, wchar_t *dest, size_t destlen); +wchar_t *ID3_GetComment(ID3_Tag *tag, const wchar_t *desc, wchar_t *dest, size_t destlen); +char *ID3_GetTUID(ID3_Tag *tag); +char *ID3_GetGenre(ID3_Tag *tag); +wchar_t *ID3_GetTagText(ID3_Tag *tag, ID3_FrameID f); +wchar_t *ID3_GetTagText(ID3_Tag *tag, ID3_FrameID f, wchar_t *dest, size_t destlen); +wchar_t *ID3_GetTagUrl(ID3_Tag *tag, ID3_FrameID f, wchar_t *dest, size_t destlen); +char *ID3_GetGenreDisplayable(ID3_Tag *tag); +wchar_t *ID3_GetUserText(ID3_Tag *tag, wchar_t *desc); +wchar_t *ID3_GetUserText(ID3_Tag *tag, wchar_t *desc, wchar_t *dest, size_t destlen); +void ID3_AddUserText(ID3_Tag *tag, wchar_t *desc, const wchar_t *value, int encoding=ENCODING_AUTO); +void ID3_AddSetComment(ID3_Tag *tag, const wchar_t *comment); +void ID3_AddSetRating(ID3_Tag *tag, const wchar_t *rating); +wchar_t *ID3_GetRating(ID3_Tag *tag, wchar_t *dest, size_t destlen); +wchar_t *ID3_GetMusicbrainzRecordingID(ID3_Tag *tag, wchar_t *dest, size_t destlen); +wchar_t *ID3_GetGracenoteTagID(ID3_Tag *tag); +wchar_t *ID3_GetGracenoteTagID(ID3_Tag *tag, wchar_t *dest, size_t destlen); +void ID3_AddSetGracenoteTagID(ID3_Tag *tag, const wchar_t *tagID); + +void SetFrameEncoding(ID3_Frame *frame, int encoding = ENCODING_AUTO); + +#endif diff --git a/Src/Plugins/Input/in_mp3/id3dlg.cpp b/Src/Plugins/Input/in_mp3/id3dlg.cpp new file mode 100644 index 00000000..85de0393 --- /dev/null +++ b/Src/Plugins/Input/in_mp3/id3dlg.cpp @@ -0,0 +1,1071 @@ +#include "main.h" +#include "Metadata.h" +#include "../Winamp/wa_ipc.h" +// ID3v2 stuff +#include "../id3v2/id3_tag.h" +#include "FactoryHelper.h" +#include "id3.h" +#include "../nu/AutoWide.h" +#include "../nu/AutoChar.h" +#include "AACFrame.h" +#include "LAMEinfo.h" +#include <shlwapi.h> +#include "../nu/ns_wc.h" +#include "../nu/ListView.h" +#include "resource.h" +#include "Stopper.h" +#include "config.h" +#include <strsafe.h> + + +// TODO: benski> CUT!!! +char g_stream_title[256] = {0}; + +int fixAACCBRbitrate(int br) +{ + static short brs[] = + { + 8, 12, 16, 20, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 0 + }; + int x; + for (x = 0; x < sizeof(brs) / sizeof(brs[0]); x ++) + { + int delta = (brs[x] * 8) / 128; + if (delta < 2) delta = 2; + if (br < brs[x] - delta) break; + if (br < brs[x] + delta) return brs[x]; + } + return br; +} + +void ConvertTryUTF8(const char *in, wchar_t *out, size_t outlen) +{ + out[0]=0; + int x = MultiByteToWideCharSZ(CP_UTF8, MB_ERR_INVALID_CHARS, in, -1, out, (int)outlen); + if (!x) + { + if (GetLastError() == ERROR_NO_UNICODE_TRANSLATION) + MultiByteToWideCharSZ(CP_ACP, 0, in, -1, out, (int)outlen); + else + MultiByteToWideCharSZ(CP_UTF8, 0, in, -1, out, (int)outlen); + } +} + +void getfileinfo(const wchar_t *filename, wchar_t *title, int *length_in_ms) +{ + const wchar_t *fn; + if (length_in_ms) *length_in_ms = -1000; + if (filename && filename[0]) + fn = filename; + else + fn = lastfn; + if (!_wcsnicmp(fn, L"file://", 7)) fn += 7; + if (PathIsURL(fn)) + { + if (title) + { + if (fn != filename || !_wcsicmp(fn, lastfn)) + { + EnterCriticalSection(&g_lfnscs); + if (lastfn_status[0]) + { + char buf[4096] = {0}; + StringCchPrintfA(buf, 4096, "[%s] %s", lastfn_status, lastfn_data_ready ? g_stream_title : (char *)AutoChar(fn)); + ConvertTryUTF8(buf, title, 256); + } + else + { + if (!lastfn_data_ready) + lstrcpynW(title, fn, 256); + else + { + ConvertTryUTF8(g_stream_title, title, 256); + } + } + LeaveCriticalSection(&g_lfnscs); + if (length_in_ms) *length_in_ms = getlength(); + } + else + { + lstrcpynW(title, fn, 256); + } + } + return ; + } + else + { + Metadata info; + if (info.Open(fn) == METADATA_SUCCESS) + { + if (title) + { + wchar_t mp3artist[256] = L"", mp3title[256] = L""; + info.GetExtendedData("artist", mp3artist, 256); + info.GetExtendedData("title", mp3title, 256); + if (mp3artist[0] && mp3title[0]) + StringCchPrintfW(title, 256, L"%s - %s", mp3artist, mp3title); + else if (mp3title[0]) + lstrcpynW(title, mp3title, 256); + else + { + lstrcpynW(title, fn, MAX_PATH); + PathStripPathW(title); + PathRemoveExtensionW(title); + } + } + + if (fn == filename) + { + wchar_t ln[128]=L""; + info.GetExtendedData("length", ln, 128); + *length_in_ms = _wtoi(ln); + } + else + *length_in_ms = getlength(); + } + else if (fn != filename) + *length_in_ms = getlength(); + } +} + +int id3Dlg(const wchar_t *fn, HWND hwnd) +{ + return 1; +} + +extern const wchar_t *id3v1_genres[]; +extern size_t numGenres; + +static int our_change=0; + +#define GetMeta(hwnd) (Metadata *)GetPropW(GetParent(hwnd),L"mp3info") +#define SetMeta(hwnd,meta) SetPropW(GetParent(hwnd),L"mp3info",(HANDLE)meta) + +static INT_PTR CALLBACK id3v1_dlgproc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + static int my_change_v1=0; + static const int ctrls[] = + { + IDC_ID3V11_TRACK, + IDC_ID3_TITLE, + IDC_ID3_ARTIST, + IDC_ID3_ALBUM, + IDC_ID3_YEAR, + IDC_ID3_COMMENT, + IDC_ID3_GENRE, + }; + static const int strs_lim[] = + { + 3, + 30, + 30, + 30, + 4, + 28, + 1, + }; + static const char * strs[] = + { + "track", + "title", + "artist", + "album", + "year", + "comment", + "genre", + }; + switch (uMsg) + { + case WM_INITDIALOG: + { + Metadata *meta = GetMeta(hwndDlg); + if (meta) + meta->AddRef(); + else + { + meta = new Metadata(); + meta->Open((wchar_t*)lParam); + SetMeta(hwndDlg,meta); + } + + wchar_t genre_buf[32] = {0}; + meta->id3v1.GetString("genre",genre_buf,32); + + our_change=1; + + SendDlgItemMessage(hwndDlg, IDC_ID3_GENRE, CB_RESETCONTENT, 0, 0); + SendDlgItemMessage(hwndDlg, IDC_ID3_GENRE, CB_SETCURSEL, -1, 0); + for (size_t x = 0; x != numGenres; x ++) + { + int y = (int)SendDlgItemMessage(hwndDlg, IDC_ID3_GENRE, CB_ADDSTRING, 0, (LPARAM)id3v1_genres[x]); + SendDlgItemMessage(hwndDlg, IDC_ID3_GENRE, CB_SETITEMDATA, y, x); + + if (_wcsicmp(genre_buf,id3v1_genres[x])==0) + SendDlgItemMessage(hwndDlg, IDC_ID3_GENRE, CB_SETCURSEL, y, 0); + } + + for (int i=0; i<sizeof(strs)/sizeof(char*); i++) + { + // make sure the edit boxes are limited to id3v1 spec sizes (trickier on number type fields) + wchar_t buf[32] = {0}; + SendDlgItemMessage(hwndDlg,ctrls[i],EM_SETLIMITTEXT,strs_lim[i],0); + meta->id3v1.GetString(strs[i],buf,32); + SetDlgItemTextW(hwndDlg,ctrls[i],buf); + } + + if (meta->id3v1.HasData()) + { + CheckDlgButton(hwndDlg,IDC_ID3V1,TRUE); + if (!config_write_id3v1) + { // if we have id3v1 writing turned off, disable controls + for (int i=0; i<sizeof(ctrls)/sizeof(int); i++) + EnableWindow(GetDlgItem(hwndDlg,ctrls[i]),FALSE); + } + } + else + { // no id3v1 tag present + for (int i=0; i<sizeof(ctrls)/sizeof(int); i++) + EnableWindow(GetDlgItem(hwndDlg,ctrls[i]),FALSE); + + if (!config_create_id3v1) + { // don't allow one to be created if the settings disallow + EnableWindow(GetDlgItem(hwndDlg,IDC_ID3V1),FALSE); + } + } + + our_change=0; + } + break; + case WM_USER: + if (wParam && lParam && !our_change && !my_change_v1) + { + Metadata *meta = GetMeta(hwndDlg); + if (!meta) break; + + if (!config_write_id3v1) + break; + + if (!config_create_id3v1 && !meta->id3v1.HasData()) + break; + + wchar_t *key = (wchar_t*)wParam; + wchar_t *value = (wchar_t*)lParam; + AutoChar keyA(key); + for (int i=0; i<sizeof(strs)/sizeof(char*); i++) + { + if (_stricmp(keyA,strs[i])==0) + { + // benski> i don't think this is what we want? meta->SetExtendedData(strs[i],value); + meta->id3v1.SetString(strs[i], value); + wchar_t buf[2048]=L""; + meta->id3v1.GetString(strs[i],buf,2048); + + if (!IsDlgButtonChecked(hwndDlg,IDC_ID3V1)) + { + // re-enable stuff + CheckDlgButton(hwndDlg,IDC_ID3V1,TRUE); + for (int j=0; j<sizeof(ctrls)/sizeof(int); j++) + { + EnableWindow(GetDlgItem(hwndDlg,ctrls[j]),TRUE); + my_change_v1++; + SetDlgItemTextW(hwndDlg,ctrls[j],L""); + my_change_v1--; + } + } + my_change_v1++; + if (ctrls[i] == IDC_ID3_GENRE) + { + int n = (int)SendDlgItemMessage(hwndDlg, IDC_ID3_GENRE, CB_FINDSTRINGEXACT, -1, (LPARAM)buf); + SendDlgItemMessage(hwndDlg, IDC_ID3_GENRE, CB_SETCURSEL, n, 0); + } + else + SetDlgItemTextW(hwndDlg,ctrls[i],buf); + my_change_v1--; + break; + } + } + } + break; + case WM_COMMAND: + switch (LOWORD(wParam)) + { + case IDOK: + { + // this should be done by one pane ONLY. Doesn't matter which one. + Metadata *meta = GetMeta(hwndDlg); + if (!meta) break; + + Stopper stopper; + if (!_wcsicmp(lastfn, meta->filename)) + stopper.Stop(); + int ret = meta->Save(); + stopper.Play(); + + wchar_t boxtitle[256] = {0}; + switch(ret) + { + case SAVE_SUCCESS: + { + // cheap way to trigger a metadata reset in a thread-safe manner + wchar_t d[10] = {0}; + extendedFileInfoStructW reset_info = {0}; + reset_info.filename=L".mp3"; + reset_info.metadata=L"artist"; + reset_info.ret = d; + reset_info.retlen=10; + SendMessage(mod.hMainWindow, WM_WA_IPC, (WPARAM)&reset_info, IPC_GET_EXTENDED_FILE_INFOW); + } + break; + case SAVE_ERROR_READONLY: + MessageBox(hwndDlg, + WASABI_API_LNGSTRINGW(IDS_METADATA_ERROR_READONLY), + WASABI_API_LNGSTRINGW_BUF(IDS_ERROR_SAVING_METADATA, boxtitle, 256), + MB_OK); + break; + case SAVE_ERROR_OPENING_FILE: + MessageBox(hwndDlg, + WASABI_API_LNGSTRINGW(IDS_METADATA_ERROR_OPENING_FILE), + WASABI_API_LNGSTRINGW_BUF(IDS_ERROR_SAVING_METADATA, boxtitle, 256), + MB_OK); + break; + case SAVE_APEV2_WRITE_ERROR: + MessageBox(hwndDlg, + WASABI_API_LNGSTRINGW(IDS_METADATA_ERROR_APEV2), + WASABI_API_LNGSTRINGW_BUF(IDS_ERROR_SAVING_METADATA, boxtitle, 256), + MB_OK); + break; + case SAVE_LYRICS3_WRITE_ERROR: + MessageBox(hwndDlg, + WASABI_API_LNGSTRINGW(IDS_METADATA_ERROR_LYRICS3), + WASABI_API_LNGSTRINGW_BUF(IDS_ERROR_SAVING_METADATA, boxtitle, 256), + MB_OK); + break; + case SAVE_ID3V1_WRITE_ERROR: + MessageBox(hwndDlg, + WASABI_API_LNGSTRINGW(IDS_METADATA_ERROR_ID3V1), + WASABI_API_LNGSTRINGW_BUF(IDS_ERROR_SAVING_METADATA, boxtitle, 256), + MB_OK); + break; + case SAVE_ID3V2_WRITE_ERROR: + MessageBox(hwndDlg, + WASABI_API_LNGSTRINGW(IDS_METADATA_ERROR_ID3V2), + WASABI_API_LNGSTRINGW_BUF(IDS_ERROR_SAVING_METADATA, boxtitle, 256), + MB_OK); + break; + default: + MessageBox(hwndDlg, + WASABI_API_LNGSTRINGW(IDS_METADATA_ERROR_UNSPECIFIED), + WASABI_API_LNGSTRINGW_BUF(IDS_ERROR_SAVING_METADATA, boxtitle, 256), + MB_OK); + break; + } + } + break; + case IDC_ID3V1_TO_V2: + { + my_change_v1=1; + Metadata *meta = GetMeta(hwndDlg); + if (!meta) break; + for (int i=0; i<sizeof(ctrls)/sizeof(int); i++) + { + wchar_t buf[2048]=L""; + GetDlgItemTextW(hwndDlg,ctrls[i],buf,2048); + meta->id3v2.SetString(strs[i],buf); + SendMessage(GetParent(hwndDlg),WM_USER,(WPARAM)(wchar_t*)AutoWide(strs[i]),(LPARAM)buf); + } + my_change_v1=0; + } + break; + case IDC_ID3V1: + { + our_change=1; + BOOL checked = IsDlgButtonChecked(hwndDlg,IDC_ID3V1); + Metadata *meta = GetMeta(hwndDlg); + if (!meta) break; + if (!checked) + meta->id3v1.Clear(); + + for (int i=0; i<sizeof(ctrls)/sizeof(int); i++) + { + EnableWindow(GetDlgItem(hwndDlg,ctrls[i]),checked); + + wchar_t buf[2048]=L""; + if (checked) + { + GetDlgItemText(hwndDlg,ctrls[i],buf,2048); + if (buf[0]) + meta->id3v1.SetString(strs[i],buf); + } + SendMessage(GetParent(hwndDlg),WM_USER,(WPARAM)(wchar_t*)AutoWide(strs[i]),(LPARAM)buf); + } + our_change=0; + } + break; + default: + if (!our_change && !my_change_v1 && (HIWORD(wParam) == EN_CHANGE || HIWORD(wParam) == CBN_SELCHANGE)) + { + our_change=1; + for (int i=0; i<sizeof(strs)/sizeof(char*); i++) + { + if (LOWORD(wParam) == ctrls[i]) + { + wchar_t buf[2048]=L""; + if (HIWORD(wParam) == EN_CHANGE) + GetDlgItemTextW(hwndDlg,ctrls[i],buf,2048); + else + { + LRESULT n = SendDlgItemMessage(hwndDlg, ctrls[i], CB_GETCURSEL, 0, 0); + n = SendDlgItemMessage(hwndDlg, ctrls[i], CB_GETITEMDATA, n, 0); + if (n>=0 && n<(LRESULT)numGenres) + lstrcpyn(buf,id3v1_genres[n],2048); + } + Metadata *meta = GetMeta(hwndDlg); + if (!meta) break; + meta->id3v1.SetString(strs[i],buf); + if (!meta->id3v2.HasData()) + SendMessage(GetParent(hwndDlg),WM_USER,(WPARAM)(wchar_t*)AutoWide(strs[i]),(LPARAM)buf); + } + } + our_change=0; + } + } + break; + case WM_DESTROY: + { + Metadata *meta = GetMeta(hwndDlg); + if (meta) meta->Release(); + } + break; + } + return 0; +} + +static INT_PTR CALLBACK id3v2_dlgproc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + static int my_change_v2=0; + static const int ctrls[] = + { + IDC_ID3V2_TRACK, + IDC_ID3V2_TITLE, + IDC_ID3V2_ARTIST, + IDC_ID3V2_ALBUM, + IDC_ID3V2_YEAR, + IDC_ID3V2_COMMENT, + IDC_ID3V2_GENRE, + IDC_ID3V2_COMPOSER, + IDC_ID3V2_PUBLISHER, + IDC_ID3V2_MARTIST, + IDC_ID3V2_RECORD, + IDC_ID3V2_URL, + IDC_ID3V2_ENCODER, + IDC_ID3V2_ALBUM_ARTIST, + IDC_ID3V2_DISC, + IDC_TRACK_GAIN, + IDC_ALBUM_GAIN, + IDC_ID3V2_BPM, + }; + static const char * strs[] = + { + "track", + "title", + "artist", + "album", + "year", + "comment", + "genre", + "composer", + "publisher", + "originalartist", + "copyright", + "url", + "tool", + "albumartist", + "disc", + "replaygain_track_gain", // 15 + "replaygain_album_gain", // 16 + "bpm", + }; + switch (uMsg) + { + case WM_INITDIALOG: + { + our_change=1; + SendDlgItemMessage(hwndDlg, IDC_ID3V2_GENRE, CB_RESETCONTENT, 0, 0); + for (size_t x = 0; x != numGenres; x ++) + { + int y = (int)SendDlgItemMessage(hwndDlg, IDC_ID3V2_GENRE, CB_ADDSTRING, 0, (LPARAM)id3v1_genres[x]); + SendDlgItemMessage(hwndDlg, IDC_ID3V2_GENRE, CB_SETITEMDATA, y, x); + } + + Metadata *meta = GetMeta(hwndDlg); + if (meta) + meta->AddRef(); + else + { + meta = new Metadata(); + meta->Open((wchar_t*)lParam); + SetMeta(hwndDlg,meta); + } + + for (int i=0; i<sizeof(strs)/sizeof(char*); i++) + { + wchar_t buf[32768] = {0}; + meta->id3v2.GetString(strs[i],buf,32768); + if((i == 15 || i == 16) && buf[0]) + { + SetDlgItemTextW(hwndDlg,ctrls[i],L"buf"); + // this isn't nice but it localises the RG values + // for display as they're saved in the "C" locale + double value = _wtof_l(buf,WASABI_API_LNG->Get_C_NumericLocale()); + StringCchPrintfW(buf,64,L"%-+.2f dB", value); + } + SetDlgItemTextW(hwndDlg,ctrls[i],buf); + } + + if (meta->id3v2.HasData()) + CheckDlgButton(hwndDlg,IDC_ID3V2,TRUE); + else + for (int i=0; i<sizeof(ctrls)/sizeof(int); i++) + EnableWindow(GetDlgItem(hwndDlg,ctrls[i]),FALSE); + + our_change=0; + } + break; + case WM_USER: + if (wParam && lParam && !our_change && !my_change_v2) + { + Metadata *meta = GetMeta(hwndDlg); + if (!meta) break; + wchar_t *key = (wchar_t*)wParam; + wchar_t *value = (wchar_t*)lParam; + AutoChar keyA(key); + for (int i=0; i<sizeof(strs)/sizeof(char*); i++) + { + if (_stricmp(keyA,strs[i])==0) + { + // benski> cut? i don't think this is what we want: meta->SetExtendedData(strs[i],value); + meta->id3v2.SetString(strs[i], value); + wchar_t buf[32768]=L""; + meta->id3v2.GetString(strs[i],buf,32768); + + if (!IsDlgButtonChecked(hwndDlg,IDC_ID3V2)) + { + // re-enable items + CheckDlgButton(hwndDlg,IDC_ID3V2,TRUE); + for (int j=0; j<sizeof(ctrls)/sizeof(int); j++) + { + EnableWindow(GetDlgItem(hwndDlg,ctrls[j]),TRUE); + my_change_v2++; + SetDlgItemTextW(hwndDlg,ctrls[j],L""); + my_change_v2--; + } + } + my_change_v2++; + SetDlgItemTextW(hwndDlg,ctrls[i],buf); + my_change_v2--; + break; + } + } + } + break; + case WM_COMMAND: + switch (LOWORD(wParam)) + { + case IDC_ID3V2_TO_V1: + { + Metadata *meta = GetMeta(hwndDlg); + if (!meta) break; + my_change_v2=1; + for (int i=0; i<sizeof(ctrls)/sizeof(int); i++) + { + wchar_t buf[32768]=L""; + GetDlgItemTextW(hwndDlg,ctrls[i],buf,32768); + meta->id3v1.SetString(strs[i],buf); + meta->GetExtendedData(strs[i], buf, 32768); + SendMessage(GetParent(hwndDlg),WM_USER,(WPARAM)(wchar_t*)AutoWide(strs[i]),(LPARAM)buf); + } + my_change_v2=0; + } + break; + case IDC_ID3V2: + { + our_change=1; + BOOL checked = IsDlgButtonChecked(hwndDlg,IDC_ID3V2); + Metadata *meta = GetMeta(hwndDlg); + if (!meta) break; + if (!checked) + meta->id3v2.Clear(); + + for (int i=0; i<sizeof(ctrls)/sizeof(int); i++) + { + EnableWindow(GetDlgItem(hwndDlg,ctrls[i]),checked); + + wchar_t buf[32768]=L""; + if (checked) + { + GetDlgItemText(hwndDlg,ctrls[i],buf,32768); + if (buf[0]) + meta->id3v2.SetString(strs[i],buf); + } + meta->GetExtendedData(strs[i],buf,32768); + SendMessage(GetParent(hwndDlg),WM_USER,(WPARAM)(wchar_t*)AutoWide(strs[i]),(LPARAM)buf); + } + our_change=0; + } + break; + case IDOK: + { + extern Metadata *m_ext_get_mp3info; + if (m_ext_get_mp3info) + m_ext_get_mp3info->Release(); + m_ext_get_mp3info=0; + } + break; + default: + if (!our_change && !my_change_v2 && (HIWORD(wParam) == EN_CHANGE || HIWORD(wParam) == CBN_SELCHANGE || HIWORD(wParam) == CBN_EDITCHANGE || HIWORD(wParam) == CBN_EDITUPDATE)) + { + our_change=1; + for (int i=0; i<sizeof(strs)/sizeof(char*); i++) + { + if (LOWORD(wParam) == ctrls[i]) + { + wchar_t buf[32768] = {0}; + if (HIWORD(wParam) == EN_CHANGE) + GetDlgItemTextW(hwndDlg,ctrls[i],buf,32768); + else + { + LRESULT n = SendMessage(GetDlgItem(hwndDlg, ctrls[i]), CB_GETCURSEL, 0, 0); + n = SendMessage(GetDlgItem(hwndDlg, ctrls[i]), CB_GETITEMDATA, n, 0); + if (n>=0 && n<(LRESULT)numGenres) + lstrcpyn(buf,id3v1_genres[n],32768); + else{ + GetDlgItemTextW(hwndDlg,ctrls[i],buf,32768); + } + } + Metadata *meta = GetMeta(hwndDlg); + if (!meta) break; + meta->id3v2.SetString(strs[i],buf); + meta->GetExtendedData(strs[i],buf,32768); + SendMessage(GetParent(hwndDlg),WM_USER,(WPARAM)(wchar_t*)AutoWide(strs[i]),(LPARAM)buf); + } + } + our_change=0; + } + } + break; + case WM_DESTROY: + { + Metadata *meta = GetMeta(hwndDlg); + if (meta) + meta->Release(); + } + break; + } + return 0; +} + +static INT_PTR CALLBACK lyrics3_dlgproc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + static const int ctrls[] = + { + IDC_LYRICS3_TITLE, + IDC_LYRICS3_ARTIST, + IDC_LYRICS3_ALBUM, + }; + static const char * strs[] = + { + "title", + "artist", + "album", + }; + switch (uMsg) + { + case WM_INITDIALOG: + { + Metadata *meta = GetMeta(hwndDlg); + if (meta) + meta->AddRef(); + else + { + meta = new Metadata(); + meta->Open((wchar_t*)lParam); + SetMeta(hwndDlg,meta); + } + + for (int i=0; i<sizeof(strs)/sizeof(char*); i++) + { + wchar_t buf[2048] = {0}; + SendDlgItemMessage(hwndDlg,ctrls[i],EM_SETLIMITTEXT,250,0); + meta->lyrics3.GetString(strs[i],buf,250); + SetDlgItemTextW(hwndDlg,ctrls[i],buf); + } + + if (meta->lyrics3.HasData()) + CheckDlgButton(hwndDlg,IDC_LYRICS3,TRUE); + } + break; + case WM_COMMAND: + switch (LOWORD(wParam)) + { + case IDC_LYRICS3: + { + BOOL checked = IsDlgButtonChecked(hwndDlg,IDC_LYRICS3); + Metadata *meta = GetMeta(hwndDlg); + if (!meta) break; + if (!checked) + meta->lyrics3.Clear(); + else // clear the dirty state if re-enabled so we don't lose the lyrics3 tag + meta->lyrics3.ResetDirty(); + + for (int i=0; i<sizeof(ctrls)/sizeof(int); i++) + { + EnableWindow(GetDlgItem(hwndDlg,ctrls[i]),checked); + + wchar_t buf[2048]=L""; + if (checked) + { + GetDlgItemText(hwndDlg,ctrls[i],buf,2048); + if (buf[0]) + meta->lyrics3.SetString(strs[i],buf); + } + meta->GetExtendedData(strs[i],buf,2048); + // if we don't flag this then we can lose info in the id3v1 and v2 tags which is definitely bad + our_change++; + SendMessage(GetParent(hwndDlg),WM_USER,(WPARAM)(wchar_t*)AutoWide(strs[i]),(LPARAM)buf); + our_change--; + } + } + break; + } + break; + case WM_DESTROY: + { + Metadata *meta = GetMeta(hwndDlg); + if (meta) meta->Release(); + } + break; + } + return 0; +} +/* ================ +APEv2 Editor Tab +================ */ + + + +static INT_PTR CALLBACK apev2_dlgproc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + static W_ListView listview; + static int my_change_ape=0; + switch (uMsg) + { + case WM_NOTIFYFORMAT: + return NFR_UNICODE; + + case WM_INITDIALOG: + { + our_change++; + Metadata *meta = GetMeta(hwndDlg); + if (meta) + { + meta->AddRef(); + } + else + { + meta = new Metadata(); + meta->Open((wchar_t*)lParam); + SetMeta(hwndDlg,meta); + } + + if (meta->apev2.HasData()) + CheckDlgButton(hwndDlg,IDC_APEV2,TRUE); + + listview.setwnd(GetDlgItem(hwndDlg, IDC_APE_LIST)); + listview.SetDoubleBuffered(true); + listview.AddCol(WASABI_API_LNGSTRINGW(IDS_NAME), 82); + listview.AddCol(WASABI_API_LNGSTRINGW(IDS_VALUE), 160); + + listview.SetVirtualCount((int)meta->apev2.GetNumItems()); + listview.AutoSizeColumn(0); + listview.AutoSizeColumn(1); + + SetDlgItemTextW(hwndDlg,IDC_APE_KEY,L""); + SetDlgItemTextW(hwndDlg,IDC_APE_VALUE,L""); + EnableWindow(GetDlgItem(hwndDlg,IDC_APE_KEY),FALSE); + EnableWindow(GetDlgItem(hwndDlg,IDC_APE_VALUE),FALSE); + EnableWindow(GetDlgItem(hwndDlg,IDC_APE_DELETE),FALSE); + our_change--; + return 1; + } + break; + + case WM_COMMAND: + switch(LOWORD(wParam)) + { + case IDC_APE_KEY: + case IDC_APE_VALUE: + if(HIWORD(wParam) == EN_CHANGE) + { + int selected = listview.GetNextSelected(); + if (selected != LB_ERR) + { + listview.RefreshItem(selected); + } + } + else if(HIWORD(wParam) == EN_KILLFOCUS) + { + Metadata *meta = GetMeta(hwndDlg); + if (!meta) break; + + my_change_ape++; + + char key[1024] = {0}; + wchar_t value[32768] = {0}; + GetDlgItemTextA(hwndDlg, IDC_APE_KEY, key, 1024); + GetDlgItemText(hwndDlg, IDC_APE_VALUE, value, 32768); + int selected = listview.GetNextSelected(); + if (selected != LB_ERR) + { + meta->apev2.SetKeyValueByIndex(selected, key, value); + } + + const wchar_t *winamp_key = APE::MapApeKeyToWinampKeyW(key); + if (winamp_key) + { + our_change++; + SendMessage(GetParent(hwndDlg),WM_USER,(WPARAM)winamp_key,(WPARAM)value); + our_change--; + } + my_change_ape--; + } + break; + + case IDC_APEV2: + { + BOOL checked = IsDlgButtonChecked(hwndDlg,IDC_APEV2); + Metadata *meta = GetMeta(hwndDlg); + if (!meta) break; + if (!checked) + meta->apev2.MarkClear(); + else // clear the dirty state if re-enabled so we don't lose the apev2 tag + meta->apev2.ResetDirty(); + } + break; + + case IDC_DELETE_ALL: + { + Metadata *meta = GetMeta(hwndDlg); + if (!meta) break; + my_change_ape++; + listview.UnselectAll(); + meta->apev2.Clear(); + listview.SetVirtualCount((int)meta->apev2.GetNumItems()); + my_change_ape--; + } + break; + + case IDC_APE_ADD: + { + Metadata *meta = GetMeta(hwndDlg); + if (!meta) break; + int index = listview.GetCount(); + if (meta->apev2.AddItem() == APEv2::APEV2_SUCCESS) + { + listview.SetVirtualCount((int)meta->apev2.GetNumItems()); + listview.SetSelected(index); + listview.ScrollTo(index); + SetFocus(GetDlgItem(hwndDlg, IDC_APE_KEY)); + } + } + break; + + case IDC_APE_DELETE: + { + Metadata *meta = GetMeta(hwndDlg); + if (!meta) break; + int selected = listview.GetNextSelected(); + if (selected != LB_ERR) + { + listview.UnselectAll(); + meta->apev2.RemoveItem(selected); + listview.SetVirtualCount((int)meta->apev2.GetNumItems()); + } + } + break; + } + break; + + case WM_NOTIFY: + { + LPNMHDR l=(LPNMHDR)lParam; + if (l->idFrom==IDC_APE_LIST) switch (l->code) + { + case LVN_GETDISPINFO: + { + Metadata *meta = GetMeta(hwndDlg); + if (meta) + { + NMLVDISPINFO *lpdi = (NMLVDISPINFO*) l; + if (lpdi->item.mask & LVIF_TEXT) + { + int selected = listview.GetNextSelected(); + switch (lpdi->item.iSubItem) + { + case 0: + { + if (lpdi->item.iItem == selected) + { + GetDlgItemText(hwndDlg, IDC_APE_KEY, lpdi->item.pszText, lpdi->item.cchTextMax); + } + else + { + const char *key=0; + meta->apev2.EnumValue(lpdi->item.iItem, &key, 0, 0); + MultiByteToWideCharSZ(CP_ACP, 0, key?key:"", -1, lpdi->item.pszText, lpdi->item.cchTextMax); + } + } + return 0; + + case 1: + { + if (lpdi->item.iItem == selected) + { + GetDlgItemText(hwndDlg, IDC_APE_VALUE, lpdi->item.pszText, lpdi->item.cchTextMax); + } + else + { + const char *key=0; + meta->apev2.EnumValue(lpdi->item.iItem, &key, lpdi->item.pszText, lpdi->item.cchTextMax); + } + } + return 0; + } + } + } + } + break; + + case LVN_KEYDOWN: + break; + + case LVN_ITEMCHANGED: + { + my_change_ape++; + LPNMLISTVIEW lv=(LPNMLISTVIEW)lParam; + if (lv->uNewState & LVIS_SELECTED) + { + Metadata *meta = GetMeta(hwndDlg); + if (meta) + { + const char *key=0; + wchar_t value[32768] = {0}; + meta->apev2.EnumValue(lv->iItem, &key, value, 32768); + SetDlgItemTextA(hwndDlg,IDC_APE_KEY,key); + SetDlgItemText(hwndDlg,IDC_APE_VALUE,value); + BOOL editable = meta->apev2.IsItemReadOnly(lv->iItem)?FALSE:TRUE; + EnableWindow(GetDlgItem(hwndDlg,IDC_APE_KEY),editable); + EnableWindow(GetDlgItem(hwndDlg,IDC_APE_VALUE),editable); + EnableWindow(GetDlgItem(hwndDlg,IDC_APE_DELETE),TRUE); + listview.RefreshItem(lv->iItem); + } + } + if (lv->uOldState & LVIS_SELECTED) + { + SetDlgItemTextW(hwndDlg,IDC_APE_KEY,L""); + SetDlgItemTextW(hwndDlg,IDC_APE_VALUE,L""); + EnableWindow(GetDlgItem(hwndDlg,IDC_APE_KEY),FALSE); + EnableWindow(GetDlgItem(hwndDlg,IDC_APE_VALUE),FALSE); + EnableWindow(GetDlgItem(hwndDlg,IDC_APE_DELETE),FALSE); + } + my_change_ape--; + } + } + } + break; + + case WM_USER: + if (wParam && lParam && !our_change && !my_change_ape) + { + Metadata *meta = GetMeta(hwndDlg); + if (meta) + { + const wchar_t *keyW = (const wchar_t *)wParam; + const wchar_t *value = (const wchar_t *)lParam; + AutoChar key(keyW); + my_change_ape++; + meta->apev2.SetString(key, value); + listview.UnselectAll(); + listview.SetVirtualCount((int)meta->apev2.GetNumItems()); + listview.RefreshAll(); + my_change_ape--; + } + } + break; + + case WM_DESTROY: + { + Metadata *meta = GetMeta(hwndDlg); + if (meta) meta->Release(); + } + break; + } + return 0; +} + +extern "C" +{ + // return 1 if you want winamp to show it's own file info dialogue, 0 if you want to show your own (via In_Module.InfoBox) + // if returning 1, remember to implement winampGetExtendedFileInfo("formatinformation")! + __declspec(dllexport) int winampUseUnifiedFileInfoDlg(const wchar_t * fn) + { + if (!_wcsnicmp(fn, L"file://", 7)) fn += 7; + if (PathIsURLW(fn)) return 2; + return 1; + } + + // should return a child window of 513x271 pixels (341x164 in msvc dlg units), or return NULL for no tab. + // Fill in name (a buffer of namelen characters), this is the title of the tab (defaults to "Advanced"). + // filename will be valid for the life of your window. n is the tab number. This function will first be + // called with n == 0, then n == 1 and so on until you return NULL (so you can add as many tabs as you like). + // The window you return will recieve WM_COMMAND, IDOK/IDCANCEL messages when the user clicks OK or Cancel. + // when the user edits a field which is duplicated in another pane, do a SendMessage(GetParent(hwnd),WM_USER,(WPARAM)L"fieldname",(LPARAM)L"newvalue"); + // this will be broadcast to all panes (including yours) as a WM_USER. + __declspec(dllexport) HWND winampAddUnifiedFileInfoPane(int n, const wchar_t * filename, HWND parent, wchar_t *name, size_t namelen) + { + if (n == 0) + { + SetPropW(parent,L"INBUILT_NOWRITEINFO", (HANDLE)1); + lstrcpyn(name,L"ID3v1", (int)namelen); + return WASABI_API_CREATEDIALOGPARAMW(IDD_INFO_ID3V1, parent, id3v1_dlgproc, (LPARAM)filename); + } + if (n == 1) + { + lstrcpyn(name,L"ID3v2", (int)namelen); + return WASABI_API_CREATEDIALOGPARAMW(IDD_INFO_ID3V2, parent, id3v2_dlgproc, (LPARAM)filename); + } + if (n == 2) + { + Metadata *meta = (Metadata *)GetPropW(parent, L"mp3info"); + if (meta->lyrics3.HasData()) + { + lstrcpyn(name,L"Lyrics3", (int)namelen); + return WASABI_API_CREATEDIALOGPARAMW(IDD_INFO_LYRICS3, parent, lyrics3_dlgproc, (LPARAM)filename); + } + else if (meta->apev2.HasData()) + { + lstrcpyn(name,L"APEv2", (int)namelen); + return WASABI_API_CREATEDIALOGPARAMW(IDD_INFO_APEV2, parent, apev2_dlgproc, (LPARAM)filename); + } + } + if (n == 3) + { + Metadata *meta = (Metadata *)GetPropW(parent, L"mp3info"); + if (meta->lyrics3.HasData() && meta->apev2.HasData()) + { + lstrcpyn(name,L"APEv2", (int)namelen); + return WASABI_API_CREATEDIALOGPARAMW(IDD_INFO_APEV2, parent, apev2_dlgproc, (LPARAM)filename); + } + } + return NULL; + } +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mp3/ifc_mpeg_stream_reader.h b/Src/Plugins/Input/in_mp3/ifc_mpeg_stream_reader.h new file mode 100644 index 00000000..7370ed79 --- /dev/null +++ b/Src/Plugins/Input/in_mp3/ifc_mpeg_stream_reader.h @@ -0,0 +1,45 @@ +#pragma once + +#include <bfc/dispatch.h> + +class ifc_mpeg_stream_reader : public Dispatchable +{ +protected: + ifc_mpeg_stream_reader() {} + ~ifc_mpeg_stream_reader() {} + +public: + int MPEGStream_Peek( void *buffer, size_t to_read, size_t *bytes_read ); + int MPEGStream_Read( void *buffer, size_t to_read, size_t *bytes_read ); + int MPEGStream_EOF(); + float MPEGStream_Gain(); + + DISPATCH_CODES + { + MPEGSTREAM_PEEK = 0, + MPEGSTREAM_READ = 1, + MPEGSTREAM_EOF = 2, + MPEGSTREAM_GAIN = 3, + }; +}; + +inline int ifc_mpeg_stream_reader::MPEGStream_Peek( void *buffer, size_t to_read, size_t *bytes_read ) +{ + return _call( MPEGSTREAM_PEEK, (int)1, buffer, to_read, bytes_read ); +} + +inline int ifc_mpeg_stream_reader::MPEGStream_Read( void *buffer, size_t to_read, size_t *bytes_read ) +{ + return _call( MPEGSTREAM_READ, (int)1, buffer, to_read, bytes_read ); +} + +inline int ifc_mpeg_stream_reader::MPEGStream_EOF() +{ + return _call( MPEGSTREAM_EOF, (int)true ); +} + +inline float ifc_mpeg_stream_reader::MPEGStream_Gain() +{ + return _call( MPEGSTREAM_GAIN, (float)1.0f ); +} + diff --git a/Src/Plugins/Input/in_mp3/in_mp3.rc b/Src/Plugins/Input/in_mp3/in_mp3.rc new file mode 100644 index 00000000..714bc992 --- /dev/null +++ b/Src/Plugins/Input/in_mp3/in_mp3.rc @@ -0,0 +1,503 @@ +// Microsoft Visual C++ generated resource script. +// +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "afxres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (U.S.) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +#ifdef _WIN32 +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US +#pragma code_page(1252) +#endif //_WIN32 + +///////////////////////////////////////////////////////////////////////////// +// +// DESIGNINFO +// + +#ifdef APSTUDIO_INVOKED +GUIDELINES DESIGNINFO +BEGIN + IDD_PREFS, DIALOG + BEGIN + LEFTMARGIN, 6 + RIGHTMARGIN, 220 + TOPMARGIN, 6 + BOTTOMMARGIN, 121 + END + + IDD_OUTPUT, DIALOG + BEGIN + LEFTMARGIN, 6 + RIGHTMARGIN, 220 + TOPMARGIN, 6 + BOTTOMMARGIN, 156 + END + + IDD_HTTP, DIALOG + BEGIN + LEFTMARGIN, 6 + RIGHTMARGIN, 220 + TOPMARGIN, 6 + BOTTOMMARGIN, 148 + END + + IDD_TAGOPTS, DIALOG + BEGIN + LEFTMARGIN, 6 + RIGHTMARGIN, 220 + TOPMARGIN, 6 + BOTTOMMARGIN, 163 + END + + IDD_HTTPAUTH, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 153 + TOPMARGIN, 7 + BOTTOMMARGIN, 71 + END + + IDD_ADVANCED_TAGGING, DIALOG + BEGIN + LEFTMARGIN, 6 + RIGHTMARGIN, 220 + TOPMARGIN, 6 + BOTTOMMARGIN, 116 + END +END +#endif // APSTUDIO_INVOKED + + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""afxres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE +BEGIN + "#include ""version.rc2""\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Dialog +// + +IDD_PREFS DIALOGEX 0, 0, 226, 127 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | DS_CENTER | WS_POPUP | WS_CAPTION +CAPTION "General" +FONT 8, "MS Shell Dlg", 0, 0, 0x0 +BEGIN + GROUPBOX "File Association",IDC_STATIC,6,6,214,61 + LTEXT "Extension list (semicolon delimited):",IDC_STATIC,12,18,140,8 + EDITTEXT IDC_EDIT1,12,31,201,12,ES_AUTOHSCROLL + PUSHBUTTON "Set to default",IDC_BUTTON1,12,49,50,13 + LTEXT "Default: MP3;MP2;MP1;AAC;VLB",IDC_STATIC,68,51,144,8 + GROUPBOX "Full File Buffering",IDC_STATIC,6,71,214,32 + LTEXT "Buffer the entire file from disk if the file is smaller than:",IDC_STATIC,13,81,115,16 + EDITTEXT IDC_BUFMAX,141,83,24,12,ES_AUTOHSCROLL | ES_NUMBER + LTEXT "kilobytes",IDC_STATIC,169,85,28,8 +END + +IDD_OUTPUT DIALOGEX 0, 0, 226, 163 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION +CAPTION "Decoder" +FONT 8, "MS Shell Dlg", 0, 0, 0x0 +BEGIN + GROUPBOX "Quality",IDC_STATIC,6,2,213,50 + CONTROL "&Full",IDC_FULLRATE,"Button",BS_AUTORADIOBUTTON | WS_GROUP,12,12,30,10 + CONTROL "&Half",IDC_HALFRATE,"Button",BS_AUTORADIOBUTTON,12,24,30,10 + CONTROL "&Quarter",IDC_QRATE,"Button",BS_AUTORADIOBUTTON,12,36,40,10 + LTEXT "Selecting 'Half' or 'Quarter' quality will downsample the output, resulting in less processor usage.",IDC_STATIC,80,12,125,34 + GROUPBOX "Misc Options",IDC_STATIC,6,55,213,40 + CONTROL "CRC checking",IDC_CHECK1,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,12,67,105,10 + CONTROL "Show average bitrate on VBR files",IDC_CHECK2,"Button",BS_AUTOCHECKBOX | BS_MULTILINE | WS_TABSTOP,12,80,180,10 + GROUPBOX "Equalizer",IDC_STATIC,6,98,213,61 + CONTROL "Logarithmic EQ (default)",IDC_RADIO1,"Button",BS_AUTORADIOBUTTON | WS_GROUP | WS_TABSTOP,12,110,128,10 + CONTROL "Linear EQ",IDC_RADIO2,"Button",BS_AUTORADIOBUTTON,12,120,72,10 + CONTROL "Fast Layer 3 EQ",IDC_FASTL3EQ,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,12,132,98,10 + CONTROL "Fast Layer 1/2 EQ",IDC_FASTL12EQ,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,12,144,106,10 +END + +IDD_HTTP DIALOGEX 0, 0, 226, 154 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION +CAPTION "Streaming" +FONT 8, "MS Shell Dlg", 0, 0, 0x0 +BEGIN + GROUPBOX "Streaming Data Buffer",IDC_STATIC,6,6,86,64 + EDITTEXT IDC_BUFFERS_NUMBUFS,12,18,27,12 + LTEXT "KB",IDC_STATIC,42,20,10,8 + LTEXT "Increase this value to give better skip protection on slower connections.",IDC_STATIC,12,33,73,33 + GROUPBOX "Streaming Prebuffer",IDC_STATIC,96,6,124,100 + CONTROL "Slider1",IDC_PREBUFSLIDER,"msctls_trackbar32",TBS_AUTOTICKS | WS_TABSTOP,102,18,111,10 + CTEXT "0% 50% 100%",IDC_STATIC,103,31,110,8 + CTEXT "(how much to prebuffer at the start of a stream)",IDC_STATIC,105,42,105,16 + CONTROL "Slider1",IDC_PREBUFSLIDER2,"msctls_trackbar32",TBS_AUTOTICKS | WS_TABSTOP,102,61,111,10 + CTEXT "0% 50% 100%",IDC_STATIC,103,74,110,8 + CTEXT "(how much to prebuffer after a buffer underrun)",IDC_STATIC,105,85,105,16 + GROUPBOX "Streaming Extensions",IDC_STATIC,6,72,86,76 + CONTROL "Enable SHOUTcast title support",IDC_CHECK1,"Button",BS_AUTOCHECKBOX | BS_TOP | BS_MULTILINE | WS_TABSTOP,12,83,74,18 + CONTROL "Include stream name in title",IDC_CHECK3,"Button",BS_AUTOCHECKBOX | BS_TOP | BS_MULTILINE | WS_TABSTOP,12,105,74,18 + CONTROL "Enable SHOUTcast v2 artwork support",IDC_SC_ARTWORK, + "Button",BS_AUTOCHECKBOX | BS_TOP | BS_MULTILINE | WS_TABSTOP,12,126,74,18 + GROUPBOX "Saving",IDC_STATIC,96,110,124,38 + CONTROL "Save files to:",IDC_CHECK2,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,102,120,57,10 + PUSHBUTTON "",IDC_BUTTON2,102,132,112,11 +END + +IDD_TAGOPTS DIALOGEX 0, 0, 226, 168 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | DS_CENTER | WS_POPUP | WS_CAPTION +CAPTION "ID3 Tags" +FONT 8, "MS Shell Dlg", 0, 0, 0x0 +BEGIN + GROUPBOX "ID3 Tag Reading",IDC_STATIC,6,6,214,122 + CONTROL "Read ID3v1 tags",IDC_READ_ID3V1,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,13,19,70,10 + CONTROL "Read ID3v2 tags",IDC_READ_ID3V2,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,13,31,70,10 + LTEXT "Read ASCII tags as:",IDC_STATIC,98,16,65,8 + COMBOBOX IDC_COMBO1,109,28,80,40,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP + GROUPBOX "ID3 Tag Writing",IDC_STATIC,6,47,214,81 + CONTROL "Modify ID3v1 tags",IDC_WRITE_ID3V1,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,13,60,73,10 + CONTROL "Modify ID3v2 tags",IDC_WRITE_ID3V2,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,13,72,73,10 + LTEXT "Write tags as:",IDC_STATIC,98,56,45,8 + COMBOBOX IDC_COMBO2,109,67,80,40,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP + GROUPBOX "ID3 Tag Creation",IDC_STATIC,6,86,214,42 + CONTROL "Create new ID3v1 tags when adding metadata",IDC_CREATE_ID3V1, + "Button",BS_AUTOCHECKBOX | WS_TABSTOP,13,100,163,10 + LTEXT "",IDC_STATIC,17,107,8,8 + CONTROL "Create new ID3v2 tags when adding metadata",IDC_CREATE_ID3V2, + "Button",BS_AUTOCHECKBOX | WS_TABSTOP,13,112,163,10 + GROUPBOX "ID3v2 Tag Rating Field Email Address",IDC_STATIC,6,132,214,31 + EDITTEXT IDC_RATING_EMAIL,13,144,162,12,ES_AUTOHSCROLL + PUSHBUTTON "Reset",IDC_RATING_EMAIL_RESET,179,143,36,14 +END + +IDD_HTTPAUTH DIALOGEX 0, 0, 160, 76 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "HTTP Login Required" +FONT 8, "MS Shell Dlg", 0, 0, 0x0 +BEGIN + LTEXT "Realm: ",IDC_STATIC,7,7,25,8 + LTEXT "",IDC_REALM,40,7,113,8 + LTEXT "Enter your login and password in the form of:\n\tlogin:password",IDC_STATIC,7,18,146,17 + EDITTEXT IDC_EDIT1,7,39,146,12,ES_AUTOHSCROLL + DEFPUSHBUTTON "OK",IDOK,7,58,50,13 + PUSHBUTTON "Cancel",IDCANCEL,61,58,50,13 +END + +IDD_INFO_ID3V1 DIALOGEX 0, 0, 341, 164 +STYLE DS_SETFONT | DS_FIXEDSYS | DS_CONTROL | WS_CHILD | WS_SYSMENU +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + GROUPBOX "ID3v1",IDC_STATIC,0,0,175,133 + CONTROL "&Include ID3v1 tag in file",IDC_ID3V1,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,6,9,100,10 + RTEXT "Track #",IDC_STATIC,7,24,36,8 + EDITTEXT IDC_ID3V11_TRACK,46,22,19,12,ES_AUTOHSCROLL | ES_NUMBER + RTEXT "Title",IDC_STATIC,7,39,36,8 + EDITTEXT IDC_ID3_TITLE,46,37,124,12,ES_AUTOHSCROLL + RTEXT "Artist",IDC_STATIC,7,54,36,8 + EDITTEXT IDC_ID3_ARTIST,46,52,124,12,ES_AUTOHSCROLL + RTEXT "Album",IDC_STATIC,7,69,36,8 + EDITTEXT IDC_ID3_ALBUM,46,67,124,12,ES_AUTOHSCROLL + RTEXT "Year",IDC_STATIC,7,83,36,8 + EDITTEXT IDC_ID3_YEAR,46,81,31,12,ES_NUMBER + RTEXT "Genre",IDC_STATIC,81,83,22,8 + COMBOBOX IDC_ID3_GENRE,106,81,64,159,CBS_DROPDOWNLIST | CBS_SORT | WS_VSCROLL | WS_TABSTOP + RTEXT "Comment",IDC_STATIC,7,99,36,8 + EDITTEXT IDC_ID3_COMMENT,46,97,123,12,ES_AUTOHSCROLL + PUSHBUTTON "Copy &to ID3v2",IDC_ID3V1_TO_V2,107,113,62,14 +END + +IDD_INFO_ID3V2 DIALOGEX 0, 0, 341, 164 +STYLE DS_SETFONT | DS_FIXEDSYS | DS_CONTROL | WS_CHILD | WS_SYSMENU +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + GROUPBOX "ID3v2",IDC_STATIC,0,0,341,164 + CONTROL "&Include ID3v2 tag in file",IDC_ID3V2,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,6,9,108,10 + RTEXT "Track #",IDC_STATIC,7,24,36,8 + EDITTEXT IDC_ID3V2_TRACK,46,22,36,12,ES_AUTOHSCROLL + RTEXT "Disc #",IDC_STATIC,90,24,36,8 + EDITTEXT IDC_ID3V2_DISC,128,22,42,12,ES_AUTOHSCROLL + RTEXT "Title",IDC_STATIC,7,39,36,8 + EDITTEXT IDC_ID3V2_TITLE,46,37,124,12,ES_AUTOHSCROLL + RTEXT "Artist",IDC_STATIC,7,54,36,8 + EDITTEXT IDC_ID3V2_ARTIST,46,52,124,12,ES_AUTOHSCROLL + RTEXT "Album",IDC_STATIC,7,69,36,8 + EDITTEXT IDC_ID3V2_ALBUM,46,67,124,12,ES_AUTOHSCROLL + RTEXT "Year",IDC_STATIC,7,83,36,8 + EDITTEXT IDC_ID3V2_YEAR,46,81,31,12,ES_AUTOHSCROLL | ES_NUMBER + RTEXT "Genre",IDC_STATIC,81,83,22,8 + COMBOBOX IDC_ID3V2_GENRE,106,81,64,159,CBS_DROPDOWN | CBS_AUTOHSCROLL | CBS_SORT | WS_VSCROLL | WS_TABSTOP + RTEXT "Comment",IDC_STATIC,7,99,36,8 + EDITTEXT IDC_ID3V2_COMMENT,46,97,124,29,ES_MULTILINE | ES_AUTOVSCROLL | ES_WANTRETURN | WS_VSCROLL + RTEXT "Album Artist",IDC_STATIC,3,131,40,8 + EDITTEXT IDC_ID3V2_ALBUM_ARTIST,46,129,124,12,ES_AUTOHSCROLL + RTEXT "Composer",IDC_STATIC,7,146,36,8 + EDITTEXT IDC_ID3V2_COMPOSER,46,144,124,12,ES_AUTOHSCROLL + RTEXT "Publisher",IDC_STATIC,172,24,37,8 + EDITTEXT IDC_ID3V2_PUBLISHER,212,22,124,12,ES_AUTOHSCROLL + RTEXT "Orig. Artist",IDC_STATIC,173,39,36,8 + EDITTEXT IDC_ID3V2_MARTIST,212,37,124,12,ES_AUTOHSCROLL + RTEXT "Copyright",IDC_STATIC,173,54,36,8 + EDITTEXT IDC_ID3V2_RECORD,212,52,124,12,ES_AUTOHSCROLL + RTEXT "URL",IDC_STATIC,173,69,36,8 + EDITTEXT IDC_ID3V2_URL,212,67,124,12,ES_AUTOHSCROLL + RTEXT "Encoded by",IDC_STATIC,173,84,36,8 + EDITTEXT IDC_ID3V2_ENCODER,212,82,124,12,ES_AUTOHSCROLL + RTEXT "BPM",IDC_STATIC,173,99,36,8 + EDITTEXT IDC_ID3V2_BPM,212,97,41,12,ES_AUTOHSCROLL + RTEXT "Track Gain",IDC_STATIC,173,114,36,8 + EDITTEXT IDC_TRACK_GAIN,212,112,41,12,ES_AUTOHSCROLL | ES_READONLY + RTEXT "Album Gain",IDC_STATIC,255,114,36,8 + EDITTEXT IDC_ALBUM_GAIN,294,112,41,12,ES_AUTOHSCROLL | ES_READONLY + PUSHBUTTON "Copy &to ID3v1",IDC_ID3V2_TO_V1,272,144,62,14 +END + +IDD_INFO_LYRICS3 DIALOGEX 0, 0, 341, 164 +STYLE DS_SETFONT | DS_FIXEDSYS | DS_CONTROL | WS_CHILD | WS_SYSMENU +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + GROUPBOX "Lyrics3",-1,0,0,196,70 + CONTROL "&Include Lyrics3 tag in file",IDC_LYRICS3,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,6,9,108,10 + RTEXT "Title",-1,7,24,36,8 + EDITTEXT IDC_LYRICS3_TITLE,46,22,144,12,ES_AUTOHSCROLL | ES_READONLY + RTEXT "Artist",-1,7,39,36,8 + EDITTEXT IDC_LYRICS3_ARTIST,46,37,144,12,ES_AUTOHSCROLL | ES_READONLY + RTEXT "Album",-1,7,54,36,8 + EDITTEXT IDC_LYRICS3_ALBUM,46,52,144,12,ES_AUTOHSCROLL | ES_READONLY +END + +IDD_INFO_APEV2 DIALOGEX 0, 0, 341, 164 +STYLE DS_SETFONT | DS_FIXEDSYS | DS_CONTROL | WS_CHILD | WS_SYSMENU +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + GROUPBOX "APEv2",IDC_STATIC,0,0,341,164 + CONTROL "&Include APEv2 tag in file",IDC_APEV2,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,6,9,108,10 + CONTROL "",IDC_APE_LIST,"SysListView32",LVS_REPORT | LVS_SINGLESEL | LVS_SHOWSELALWAYS | LVS_ALIGNLEFT | LVS_OWNERDATA | WS_BORDER | WS_TABSTOP,6,20,163,136 + LTEXT "Name:",IDC_STATIC,176,20,22,8 + EDITTEXT IDC_APE_KEY,176,31,158,14,ES_AUTOHSCROLL + LTEXT "Value:",IDC_STATIC,176,47,21,8 + EDITTEXT IDC_APE_VALUE,176,57,158,81,ES_MULTILINE | ES_AUTOHSCROLL | ES_OEMCONVERT | ES_WANTRETURN | WS_VSCROLL + PUSHBUTTON "Add New",IDC_APE_ADD,176,142,50,14 + PUSHBUTTON "Delete",IDC_APE_DELETE,230,142,50,14 + PUSHBUTTON "Delete All",IDC_DELETE_ALL,284,142,50,14 +END + +IDD_ADVANCED_TAGGING DIALOGEX 0, 0, 226, 122 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | DS_CENTER | WS_POPUP | WS_CAPTION +CAPTION "APEv2 and Lyrics3" +FONT 8, "MS Shell Dlg", 0, 0, 0x0 +BEGIN + GROUPBOX "APEv2 Tags",-1,6,6,214,111 + CONTROL "Read APEv2 tags",IDC_READ_APEV2,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,13,17,73,10 + CONTROL "Modify existing APEv2 tags when updating metadata",IDC_WRITE_APEV2, + "Button",BS_AUTOCHECKBOX | WS_TABSTOP,13,29,181,10 + CONTROL "Create new APEv2 tags when adding metadata",IDC_CREATE_APEV2, + "Button",BS_AUTOCHECKBOX | WS_TABSTOP,13,40,165,10 + LTEXT "When writing APEv2 headers:",IDC_STATIC_APEV2_HEADER,13,54,96,8 + COMBOBOX IDC_APEV2_HEADER_OPTIONS,25,66,166,40,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP + GROUPBOX "Lyrics3 Tags",-1,6,90,214,27 + CONTROL "Read Lyrics3 tags",IDC_READ_LYRICS3,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,13,102,73,10 +END + + +///////////////////////////////////////////////////////////////////////////// +// +// String Table +// + +STRINGTABLE +BEGIN + IDS_NULLSOFT_MPEG_AUDIO_DECODER_OLD "Nullsoft MPEG Audio Decoder" + IDS_CANNOT_WRITE_STREAMS_TO_DISK "Sorry cannot write streams to disk" + IDS_BUFFER_X "Buffer: %d%%" + IDS_ERROR_SYNCING_TO_STREAM "Error syncing to stream" + IDS_CONNECTING "Connecting" + IDS_NO_VALID_MULTICONNECT_URL "No valid Multiconnect URL" + IDS_REDIRECT_LIMIT_EXCEEDED "Redirect limit exceeded" + IDS_READING_ID3 "Reading ID3" + IDS_STREAM_TERMINATED "Stream terminated" + IDS_STREAM_TEMPORARILY_INTERRUPTED "Stream temporarily interrupted" +END + +STRINGTABLE +BEGIN + IDS_NULLSOFT_MPEG_AUDIO_DECODER "Nullsoft MPEG Audio Decoder v%s" + 65535 "{CD3EEF98-011C-4213-BC16-3F91C937B9B8}" +END + +STRINGTABLE +BEGIN + IDS_NETWORK_RECEIVED_X_BYTES "Network received: %llu bytes\n" + IDS_SERVER "Server: %s\n" + IDS_CONTENT_TYPE "Content-Type: %s\n" + IDS_ULTRAVOX_SYNC "Ultravox sync: %d messages, %d desyncs\n" + IDS_ULTRAVOX_DATA_MESSAGE "Ultravox Data Message: 0x%X\n" + IDS_ULTRAVOX_SID_AVGBR_MAXBR "Ultravox SID/AvgBR/MaxBR: %d/%d/%d\n" + IDS_METADATA_RECEIVED "Metadata received: %d bytes\n" + IDS_METADATA_INTERVAL "Metadata interval: %d bytes\n" + IDS_ID3v2_TAG "ID3v2 tag: %d bytes\n" + IDS_VBR_LEADING_FRAME "VBR leading frame: %d bytes\n" + IDS_STREAM_NAME "Stream name: %s\n" + IDS_CURRENT_TITLE "Current title: %s\n" + IDS_CONTENT_LENGTH "Content length: %d bytes\n" + IDS_SAVING_TO "Saving to: %s\n" +END + +STRINGTABLE +BEGIN + IDS_CUSTOM "Custom" + IDS_MONO "Mono" + IDS_STEREO "Stereo" + IDS_3_CHANNEL "3 channel" + IDS_4_CHANNEL "4 channel" + IDS_SURROUND "Surround" + IDS_5_1 "5.1" + IDS_7_1 "7.1" + IDS_ERROR "error" + IDS_NONE "None" + IDS_50_15_MICROSEC "50/15 microsec" + IDS_INVALID "invalid" + IDS_JOINT_STEREO "Joint Stereo" + IDS_2_CHANNEL "2 Channel" + IDS_PAYLOAD_SIZE "Payload Size: %u bytes" + IDS_FORMAT_AAC "\nFormat: AAC" +END + +STRINGTABLE +BEGIN + IDS_MPEG2_HE_AAC_IS "\nMPEG-2 HE-AAC (Implicitly Signalled)" + IDS_MPEG4_HE_AAC_IS "\nMPEG-4 HE-AAC (Implicitly Signalled)" + IDS_SAMPLE_RATE_OUTPUT "\nSample Rate: %u (Output: %u)" + IDS_SAMPLE_RATE "\nSample Rate: %u " + IDS_SBR_PRESENT "\nSBR: Present" + IDS_SBR_NOT_PRESENT "\nSBR: Not Present" + IDS_CHANNELS_OUTPUT "\nChannels: %u (Output: %u)" + IDS_CHANNELS "\nChannels: %u" + IDS_MODE_MONO " Mode: Mono" + IDS_MODE_STEREO " Mode: Stereo" + IDS_MODE_PARAMETRIC_STEREO " Mode: Parametric Stereo" + IDS_MODE_DUAL_CHANNEL " Mode: Dual Channel" + IDS_MODE_4_CHANNEL_2_CPE " Mode: 4 Channel 2 CPE" + IDS_MODE_4_CHANNEL_MPEG " Mode: 4 Channel MPEG" + IDS_MODE_5_CHANNEL " Mode: 5 Channel" + IDS_MODE_5_1 " Mode: 5.1" +END + +STRINGTABLE +BEGIN + IDS_MODE_6_1 " Mode: 6.1" + IDS_MODE_7_1 " Mode: 7.1" + IDS_BITRATE "\nBitrate: %u" + IDS_AVERAGE_BITRATE "\nBitrate: VBR (%u)" + IDS_HEADER_FOUND_AT_X_BYTES "\nHeader found at: %d bytes" + IDS_LENGTH_X_SECONDS "\nLength: %u seconds" + IDS_PROFILE "\nProfile: %s" + IDS_YES "Yes" + IDS_NO "No" + IDS_ENC_DELAY_ZERO_PADDING "\nEnc Delay: %u, Zero Padding: %u" + IDS_S_LAYER_X "\n%s layer %d" + IDS_X_KBIT "\n%d kbps, %d frames" + IDS_X_KBIT_APPROX "\n%d kbps, approx. %d frames" + IDS_X_KBIT_VBR "\n%d kbps (VBR%s), %d frames" + IDS_X_HZ_S "\n%d Hz %s" + IDS_COPYRIGHTED ", Copyrighted: %s" +END + +STRINGTABLE +BEGIN + IDS_ORIGINAL "\nOriginal: %s" + IDS_EMPHASIS ", Emphasis: %s" + IDS_EXT_MP3_SURROUND "\nExtension: MP3 Surround" + IDS_MP3_HAS_BEEN_MODIFIED_NOT_ALL_MAY_BE_CORRECT + "\nMP3 has been modified from its original encoding. Some information may be incorrect." + IDS_MPEG_AUDIO_FILES "MPEG Audio Files (" +END + +STRINGTABLE +BEGIN + IDS_MPEG_AUDIO_DECODER_SETTINGS "MPEG Audio Decoder Settings" + IDS_LATIN_1 "Latin-1" + IDS_SYSTEM_LANGUAGE "System Language" + IDS_UNICODE_UTF_16 "Unicode (UTF-16)" + IDS_SELECT_DIRECTORY_TO_SAVE_TO "Select directory to save streamed mp3s" +END + +STRINGTABLE +BEGIN + IDS_X_KBIT_ABR "\n%d kbps (ABR), %d frames" + IDS_NAME "Name" +END + +STRINGTABLE +BEGIN + IDS_VALUE "Value" + IDS_APEV2_RETAIN_HEADER "Retain existing header" + IDS_APEV2_ADD_HEADER "Add header" + IDS_APEV2_REMOVE_HEADER "Remove header" + IDS_ERROR_SAVING_METADATA "Error saving metadata" + IDS_METADATA_ERROR_READONLY "Cannot save metadata: File is read-only!" + IDS_METADATA_ERROR_OPENING_FILE + "Cannot save metadata: Error opening file." + IDS_METADATA_ERROR_APEV2 "Cannot save metadata: APEv2 tag writing failed." + IDS_METADATA_ERROR_LYRICS3 + "Cannot save metadata: Lyrics3 tag writing failed." + IDS_METADATA_ERROR_ID3V1 "Cannot save metadata: ID3v1 tag writing failed." + IDS_METADATA_ERROR_ID3V2 "Cannot save metadata: ID3v2 tag writing failed." + IDS_METADATA_ERROR_UNSPECIFIED "Cannot save metadata: Unspecified error." + IDS_TIMED_OUT "timed out" + IDS_FAMILY_STRING_MP3 "MPEG Layer 3 Audio File" + IDS_FAMILY_STRING_MP2 "MPEG Layer 2 Audio File" + IDS_FAMILY_STRING_MP1 "MPEG Layer 1 Audio File" +END + +STRINGTABLE +BEGIN + IDS_FAMILY_STRING_MPEG2_AAC "MPEG-2 Advanced Audio Coding File" + IDS_FAMILY_STRING_DOLBY "Dolby Very Low Bitrate AAC File" + IDS_ABOUT_TEXT "%s\n© 1998-2023 Winamp SA\n\nBuild date: %hs\n\nMPEG Audio Decoding provided by libmpg123"//MPEG Layer-3 audio compression technology\nlicensed by Fraunhofer IIS & THOMSON multimedia.\n\nMPEG-4 AAC decoding licensed by Fraunhofer IIS" + IDS_LENGTH_X_PART_SECONDS "\nLength: %.2f seconds" +END + +#endif // English (U.S.) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// +#include "version.rc2" + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + diff --git a/Src/Plugins/Input/in_mp3/in_mp3.sln b/Src/Plugins/Input/in_mp3/in_mp3.sln new file mode 100644 index 00000000..77d30f44 --- /dev/null +++ b/Src/Plugins/Input/in_mp3/in_mp3.sln @@ -0,0 +1,102 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.29424.173 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "in_mp3", "in_mp3.vcxproj", "{AEA9FF14-57EC-49AB-BC3A-764A011AC002}" + ProjectSection(ProjectDependencies) = postProject + {DABE6307-F8DD-416D-9DAC-673E2DECB73F} = {DABE6307-F8DD-416D-9DAC-673E2DECB73F} + {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915} = {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915} + {E105A0A2-7391-47C5-86AC-718003524C3D} = {E105A0A2-7391-47C5-86AC-718003524C3D} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "apev2", "..\apev2\apev2.vcxproj", "{48387E27-2666-4ACF-9C21-AE508C529580}" + ProjectSection(ProjectDependencies) = postProject + {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915} = {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "zlib", "..\replicant\zlib\zlib.vcxproj", "{0F9730E4-45DA-4BD2-A50A-403A4BC9751A}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "id3v2", "..\id3v2\id3v2.vcxproj", "{0C7AAFA2-2A78-4B91-99A3-3E866B484ADB}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "nu", "..\replicant\nu\nu.vcxproj", "{F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "jnetlib", "..\replicant\jnetlib\jnetlib.vcxproj", "{E105A0A2-7391-47C5-86AC-718003524C3D}" + ProjectSection(ProjectDependencies) = postProject + {DABE6307-F8DD-416D-9DAC-673E2DECB73F} = {DABE6307-F8DD-416D-9DAC-673E2DECB73F} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "nsutil", "..\nsutil\nsutil.vcxproj", "{DABE6307-F8DD-416D-9DAC-673E2DECB73F}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Debug|x64 = Debug|x64 + Release|Win32 = Release|Win32 + Release|x64 = Release|x64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {AEA9FF14-57EC-49AB-BC3A-764A011AC002}.Debug|Win32.ActiveCfg = Debug|Win32 + {AEA9FF14-57EC-49AB-BC3A-764A011AC002}.Debug|Win32.Build.0 = Debug|Win32 + {AEA9FF14-57EC-49AB-BC3A-764A011AC002}.Debug|x64.ActiveCfg = Debug|x64 + {AEA9FF14-57EC-49AB-BC3A-764A011AC002}.Debug|x64.Build.0 = Debug|x64 + {AEA9FF14-57EC-49AB-BC3A-764A011AC002}.Release|Win32.ActiveCfg = Release|Win32 + {AEA9FF14-57EC-49AB-BC3A-764A011AC002}.Release|Win32.Build.0 = Release|Win32 + {AEA9FF14-57EC-49AB-BC3A-764A011AC002}.Release|x64.ActiveCfg = Release|x64 + {AEA9FF14-57EC-49AB-BC3A-764A011AC002}.Release|x64.Build.0 = Release|x64 + {48387E27-2666-4ACF-9C21-AE508C529580}.Debug|Win32.ActiveCfg = Debug|Win32 + {48387E27-2666-4ACF-9C21-AE508C529580}.Debug|Win32.Build.0 = Debug|Win32 + {48387E27-2666-4ACF-9C21-AE508C529580}.Debug|x64.ActiveCfg = Debug|x64 + {48387E27-2666-4ACF-9C21-AE508C529580}.Debug|x64.Build.0 = Debug|x64 + {48387E27-2666-4ACF-9C21-AE508C529580}.Release|Win32.ActiveCfg = Release|Win32 + {48387E27-2666-4ACF-9C21-AE508C529580}.Release|Win32.Build.0 = Release|Win32 + {48387E27-2666-4ACF-9C21-AE508C529580}.Release|x64.ActiveCfg = Release|x64 + {48387E27-2666-4ACF-9C21-AE508C529580}.Release|x64.Build.0 = Release|x64 + {0F9730E4-45DA-4BD2-A50A-403A4BC9751A}.Debug|Win32.ActiveCfg = Debug|Win32 + {0F9730E4-45DA-4BD2-A50A-403A4BC9751A}.Debug|Win32.Build.0 = Debug|Win32 + {0F9730E4-45DA-4BD2-A50A-403A4BC9751A}.Debug|x64.ActiveCfg = Debug|x64 + {0F9730E4-45DA-4BD2-A50A-403A4BC9751A}.Debug|x64.Build.0 = Debug|x64 + {0F9730E4-45DA-4BD2-A50A-403A4BC9751A}.Release|Win32.ActiveCfg = Release|Win32 + {0F9730E4-45DA-4BD2-A50A-403A4BC9751A}.Release|Win32.Build.0 = Release|Win32 + {0F9730E4-45DA-4BD2-A50A-403A4BC9751A}.Release|x64.ActiveCfg = Release|x64 + {0F9730E4-45DA-4BD2-A50A-403A4BC9751A}.Release|x64.Build.0 = Release|x64 + {0C7AAFA2-2A78-4B91-99A3-3E866B484ADB}.Debug|Win32.ActiveCfg = Debug|Win32 + {0C7AAFA2-2A78-4B91-99A3-3E866B484ADB}.Debug|Win32.Build.0 = Debug|Win32 + {0C7AAFA2-2A78-4B91-99A3-3E866B484ADB}.Debug|x64.ActiveCfg = Debug|x64 + {0C7AAFA2-2A78-4B91-99A3-3E866B484ADB}.Debug|x64.Build.0 = Debug|x64 + {0C7AAFA2-2A78-4B91-99A3-3E866B484ADB}.Release|Win32.ActiveCfg = Release|Win32 + {0C7AAFA2-2A78-4B91-99A3-3E866B484ADB}.Release|Win32.Build.0 = Release|Win32 + {0C7AAFA2-2A78-4B91-99A3-3E866B484ADB}.Release|x64.ActiveCfg = Release|x64 + {0C7AAFA2-2A78-4B91-99A3-3E866B484ADB}.Release|x64.Build.0 = Release|x64 + {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}.Debug|Win32.ActiveCfg = Debug|Win32 + {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}.Debug|Win32.Build.0 = Debug|Win32 + {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}.Debug|x64.ActiveCfg = Debug|x64 + {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}.Debug|x64.Build.0 = Debug|x64 + {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}.Release|Win32.ActiveCfg = Release|Win32 + {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}.Release|Win32.Build.0 = Release|Win32 + {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}.Release|x64.ActiveCfg = Release|x64 + {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}.Release|x64.Build.0 = Release|x64 + {E105A0A2-7391-47C5-86AC-718003524C3D}.Debug|Win32.ActiveCfg = Debug|Win32 + {E105A0A2-7391-47C5-86AC-718003524C3D}.Debug|Win32.Build.0 = Debug|Win32 + {E105A0A2-7391-47C5-86AC-718003524C3D}.Debug|x64.ActiveCfg = Debug|x64 + {E105A0A2-7391-47C5-86AC-718003524C3D}.Debug|x64.Build.0 = Debug|x64 + {E105A0A2-7391-47C5-86AC-718003524C3D}.Release|Win32.ActiveCfg = Release|Win32 + {E105A0A2-7391-47C5-86AC-718003524C3D}.Release|Win32.Build.0 = Release|Win32 + {E105A0A2-7391-47C5-86AC-718003524C3D}.Release|x64.ActiveCfg = Release|x64 + {E105A0A2-7391-47C5-86AC-718003524C3D}.Release|x64.Build.0 = Release|x64 + {DABE6307-F8DD-416D-9DAC-673E2DECB73F}.Debug|Win32.ActiveCfg = Debug|Win32 + {DABE6307-F8DD-416D-9DAC-673E2DECB73F}.Debug|Win32.Build.0 = Debug|Win32 + {DABE6307-F8DD-416D-9DAC-673E2DECB73F}.Debug|x64.ActiveCfg = Debug|x64 + {DABE6307-F8DD-416D-9DAC-673E2DECB73F}.Debug|x64.Build.0 = Debug|x64 + {DABE6307-F8DD-416D-9DAC-673E2DECB73F}.Release|Win32.ActiveCfg = Release|Win32 + {DABE6307-F8DD-416D-9DAC-673E2DECB73F}.Release|Win32.Build.0 = Release|Win32 + {DABE6307-F8DD-416D-9DAC-673E2DECB73F}.Release|x64.ActiveCfg = Release|x64 + {DABE6307-F8DD-416D-9DAC-673E2DECB73F}.Release|x64.Build.0 = Release|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {FC646532-2050-40A5-A2AB-F699F1C071C4} + EndGlobalSection +EndGlobal diff --git a/Src/Plugins/Input/in_mp3/in_mp3.vcxproj b/Src/Plugins/Input/in_mp3/in_mp3.vcxproj new file mode 100644 index 00000000..7a08280a --- /dev/null +++ b/Src/Plugins/Input/in_mp3/in_mp3.vcxproj @@ -0,0 +1,386 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <ItemGroup Label="ProjectConfigurations"> + <ProjectConfiguration Include="Debug|Win32"> + <Configuration>Debug</Configuration> + <Platform>Win32</Platform> + </ProjectConfiguration> + <ProjectConfiguration Include="Debug|x64"> + <Configuration>Debug</Configuration> + <Platform>x64</Platform> + </ProjectConfiguration> + <ProjectConfiguration Include="Release|Win32"> + <Configuration>Release</Configuration> + <Platform>Win32</Platform> + </ProjectConfiguration> + <ProjectConfiguration Include="Release|x64"> + <Configuration>Release</Configuration> + <Platform>x64</Platform> + </ProjectConfiguration> + </ItemGroup> + <PropertyGroup Label="Globals"> + <ProjectGuid>{AEA9FF14-57EC-49AB-BC3A-764A011AC002}</ProjectGuid> + <RootNamespace>in_mp3</RootNamespace> + <WindowsTargetPlatformVersion>10.0.19041.0</WindowsTargetPlatformVersion> + </PropertyGroup> + <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" /> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration"> + <ConfigurationType>DynamicLibrary</ConfigurationType> + <PlatformToolset>v142</PlatformToolset> + <CharacterSet>Unicode</CharacterSet> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration"> + <ConfigurationType>DynamicLibrary</ConfigurationType> + <PlatformToolset>v142</PlatformToolset> + <CharacterSet>Unicode</CharacterSet> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration"> + <ConfigurationType>DynamicLibrary</ConfigurationType> + <PlatformToolset>v142</PlatformToolset> + <CharacterSet>Unicode</CharacterSet> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration"> + <ConfigurationType>DynamicLibrary</ConfigurationType> + <PlatformToolset>v142</PlatformToolset> + <CharacterSet>Unicode</CharacterSet> + </PropertyGroup> + <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" /> + <ImportGroup Label="ExtensionSettings"> + </ImportGroup> + <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="PropertySheets"> + <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> + </ImportGroup> + <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="PropertySheets"> + <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> + </ImportGroup> + <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="PropertySheets"> + <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> + </ImportGroup> + <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="PropertySheets"> + <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> + </ImportGroup> + <PropertyGroup Label="UserMacros" /> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> + <LinkIncremental>false</LinkIncremental> + <OutDir>$(PlatformShortName)_$(Configuration)\</OutDir> + <IntDir>$(PlatformShortName)_$(Configuration)\</IntDir> + <IncludePath>$(IncludePath)</IncludePath> + <LibraryPath>$(LibraryPath)</LibraryPath> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'"> + <LinkIncremental>false</LinkIncremental> + <OutDir>$(PlatformShortName)_$(Configuration)\</OutDir> + <IntDir>$(PlatformShortName)_$(Configuration)\</IntDir> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> + <LinkIncremental>false</LinkIncremental> + <OutDir>$(PlatformShortName)_$(Configuration)\</OutDir> + <IntDir>$(PlatformShortName)_$(Configuration)\</IntDir> + <IncludePath>$(IncludePath)</IncludePath> + <LibraryPath>$(LibraryPath)</LibraryPath> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'"> + <LinkIncremental>false</LinkIncremental> + <OutDir>$(PlatformShortName)_$(Configuration)\</OutDir> + <IntDir>$(PlatformShortName)_$(Configuration)\</IntDir> + </PropertyGroup> + <PropertyGroup Label="Vcpkg"> + <VcpkgEnableManifest>false</VcpkgEnableManifest> + </PropertyGroup> + <PropertyGroup Label="Vcpkg" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> + <VcpkgInstalledDir> + </VcpkgInstalledDir> + <VcpkgUseStatic>false</VcpkgUseStatic> + <VcpkgConfiguration>Debug</VcpkgConfiguration> + <VcpkgTriplet>x86-windows-static-md</VcpkgTriplet> + </PropertyGroup> + <PropertyGroup Label="Vcpkg" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> + <VcpkgInstalledDir> + </VcpkgInstalledDir> + <VcpkgUseStatic>false</VcpkgUseStatic> + <VcpkgTriplet>x86-windows-static-md</VcpkgTriplet> + </PropertyGroup> + <PropertyGroup Label="Vcpkg" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'"> + <VcpkgInstalledDir> + </VcpkgInstalledDir> + <VcpkgUseStatic>false</VcpkgUseStatic> + <VcpkgTriplet>x86-windows-static-md</VcpkgTriplet> + <VcpkgConfiguration>Debug</VcpkgConfiguration> + </PropertyGroup> + <PropertyGroup Label="Vcpkg" Condition="'$(Configuration)|$(Platform)'=='Release|x64'"> + <VcpkgInstalledDir> + </VcpkgInstalledDir> + <VcpkgUseStatic>false</VcpkgUseStatic> + <VcpkgTriplet>x86-windows-static-md</VcpkgTriplet> + </PropertyGroup> + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> + <ClCompile> + <Optimization>Disabled</Optimization> + <AdditionalIncludeDirectories>.;..\..\..\Wasabi;..\..\..\replicant;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + <PreprocessorDefinitions>_DEBUG;AAC_SUPPORT;WIN32;_WINDOWS;_USRDLL;UNICODE_INPUT_PLUGIN;DO_LAYER12;_CRT_SECURE_NO_WARNINGS;WIN32_LEAN_AND_MEAN;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <MinimalRebuild>false</MinimalRebuild> + <MultiProcessorCompilation>true</MultiProcessorCompilation> + <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks> + <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary> + <WarningLevel>Level3</WarningLevel> + <DebugInformationFormat>ProgramDatabase</DebugInformationFormat> + <CompileAs>Default</CompileAs> + <DisableSpecificWarnings>4996;%(DisableSpecificWarnings)</DisableSpecificWarnings> + <ProgramDataBaseFileName>$(IntDir)$(TargetName).pdb</ProgramDataBaseFileName> + </ClCompile> + <ResourceCompile> + <PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <Culture>0x0409</Culture> + </ResourceCompile> + <Link> + <AdditionalDependencies>wsock32.lib;comctl32.lib;shlwapi.lib;%(AdditionalDependencies)</AdditionalDependencies> + <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories> + <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile> + <DelayLoadDLLs>comctl32.dll;%(DelayLoadDLLs)</DelayLoadDLLs> + <GenerateDebugInformation>true</GenerateDebugInformation> + <ProgramDatabaseFile>$(IntDir)$(TargetName).pdb</ProgramDatabaseFile> + <RandomizedBaseAddress>false</RandomizedBaseAddress> + <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary> + <TargetMachine>MachineX86</TargetMachine> + <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers> + <MapFileName>$(IntDir)$(TargetName).map</MapFileName> + <SubSystem>Windows</SubSystem> + </Link> + <PostBuildEvent> + <Command>xcopy /Y /D $(OutDir)$(TargetName)$(TargetExt) ..\..\..\..\Build\Winamp_$(PlatformShortName)_$(Configuration)\Plugins\ +xcopy /Y /D $(IntDir)$(TargetName).pdb ..\..\..\..\Build\Winamp_$(PlatformShortName)_$(Configuration)\Plugins\ </Command> + <Message>Post build event: 'xcopy /Y /D $(OutDir)$(TargetName)$(TargetExt) ..\..\..\..\Build\Winamp_$(PlatformShortName)_$(Configuration)\Plugins\'</Message> + </PostBuildEvent> + </ItemDefinitionGroup> + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'"> + <ClCompile> + <Optimization>Disabled</Optimization> + <AdditionalIncludeDirectories>.;..\..\..\Wasabi;..\..\..\replicant;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + <PreprocessorDefinitions>_DEBUG;AAC_SUPPORT;WIN64;_WINDOWS;_USRDLL;UNICODE_INPUT_PLUGIN;DO_LAYER12;_CRT_SECURE_NO_WARNINGS;WIN32_LEAN_AND_MEAN;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <MinimalRebuild>false</MinimalRebuild> + <MultiProcessorCompilation>true</MultiProcessorCompilation> + <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks> + <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary> + <WarningLevel>Level3</WarningLevel> + <DebugInformationFormat>ProgramDatabase</DebugInformationFormat> + <CompileAs>Default</CompileAs> + <DisableSpecificWarnings>4996;%(DisableSpecificWarnings)</DisableSpecificWarnings> + <ProgramDataBaseFileName>$(IntDir)$(TargetName).pdb</ProgramDataBaseFileName> + </ClCompile> + <ResourceCompile> + <PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <Culture>0x0409</Culture> + </ResourceCompile> + <Link> + <AdditionalDependencies>wsock32.lib;comctl32.lib;shlwapi.lib;%(AdditionalDependencies)</AdditionalDependencies> + <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories> + <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile> + <DelayLoadDLLs>comctl32.dll;%(DelayLoadDLLs)</DelayLoadDLLs> + <GenerateDebugInformation>true</GenerateDebugInformation> + <ProgramDatabaseFile>$(IntDir)$(TargetName).pdb</ProgramDatabaseFile> + <RandomizedBaseAddress>false</RandomizedBaseAddress> + <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary> + <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers> + <MapFileName>$(IntDir)$(TargetName).map</MapFileName> + <SubSystem>Windows</SubSystem> + </Link> + <PostBuildEvent> + <Command>xcopy /Y /D $(OutDir)$(TargetName)$(TargetExt) ..\..\..\..\Build\Winamp_$(PlatformShortName)_$(Configuration)\Plugins\ +xcopy /Y /D $(IntDir)$(TargetName).pdb ..\..\..\..\Build\Winamp_$(PlatformShortName)_$(Configuration)\Plugins\ </Command> + <Message>Post build event: 'xcopy /Y /D $(OutDir)$(TargetName)$(TargetExt) ..\..\..\..\Build\Winamp_$(PlatformShortName)_$(Configuration)\Plugins\'</Message> + </PostBuildEvent> + </ItemDefinitionGroup> + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> + <ClCompile> + <Optimization>MinSpace</Optimization> + <IntrinsicFunctions>true</IntrinsicFunctions> + <FavorSizeOrSpeed>Size</FavorSizeOrSpeed> + <AdditionalIncludeDirectories>.;..\..\..\Wasabi;..\..\..\replicant;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + <PreprocessorDefinitions>NDEBUG;AAC_SUPPORT;WIN32;_WINDOWS;_USRDLL;UNICODE_INPUT_PLUGIN;DO_LAYER12;_CRT_SECURE_NO_WARNINGS;WIN32_LEAN_AND_MEAN;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <StringPooling>true</StringPooling> + <MultiProcessorCompilation>true</MultiProcessorCompilation> + <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary> + <BufferSecurityCheck>true</BufferSecurityCheck> + <TreatWChar_tAsBuiltInType>true</TreatWChar_tAsBuiltInType> + <ForceConformanceInForLoopScope>true</ForceConformanceInForLoopScope> + <WarningLevel>Level3</WarningLevel> + <DebugInformationFormat>None</DebugInformationFormat> + <CompileAs>Default</CompileAs> + <DisableSpecificWarnings>4996;4244;%(DisableSpecificWarnings)</DisableSpecificWarnings> + <ProgramDataBaseFileName>$(IntDir)$(TargetName).pdb</ProgramDataBaseFileName> + </ClCompile> + <ResourceCompile> + <PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <Culture>0x0409</Culture> + </ResourceCompile> + <Link> + <AdditionalDependencies>wsock32.lib;comctl32.lib;shlwapi.lib;%(AdditionalDependencies)</AdditionalDependencies> + <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories> + <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile> + <DelayLoadDLLs>comctl32.dll;%(DelayLoadDLLs)</DelayLoadDLLs> + <GenerateDebugInformation>false</GenerateDebugInformation> + <ProgramDatabaseFile>$(IntDir)$(TargetName).pdb</ProgramDatabaseFile> + <GenerateMapFile>false</GenerateMapFile> + <MapFileName>$(IntDir)$(TargetName).map</MapFileName> + <OptimizeReferences>true</OptimizeReferences> + <EnableCOMDATFolding>true</EnableCOMDATFolding> + <RandomizedBaseAddress>true</RandomizedBaseAddress> + <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary> + <TargetMachine>MachineX86</TargetMachine> + <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers> + <SubSystem>Windows</SubSystem> + </Link> + <PostBuildEvent> + <Command>xcopy /Y /D $(OutDir)$(TargetName)$(TargetExt) ..\..\..\..\Build\Winamp_$(PlatformShortName)_$(Configuration)\Plugins\ </Command> + <Message>Post build event: 'xcopy /Y /D $(OutDir)$(TargetName)$(TargetExt) ..\..\..\..\Build\Winamp_$(PlatformShortName)_$(Configuration)\Plugins\'</Message> + </PostBuildEvent> + </ItemDefinitionGroup> + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'"> + <ClCompile> + <Optimization>MinSpace</Optimization> + <IntrinsicFunctions>true</IntrinsicFunctions> + <FavorSizeOrSpeed>Size</FavorSizeOrSpeed> + <AdditionalIncludeDirectories>.;..\..\..\Wasabi;..\..\..\replicant;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + <PreprocessorDefinitions>NDEBUG;AAC_SUPPORT;WIN64;_WINDOWS;_USRDLL;UNICODE_INPUT_PLUGIN;DO_LAYER12;_CRT_SECURE_NO_WARNINGS;WIN32_LEAN_AND_MEAN;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <StringPooling>true</StringPooling> + <MultiProcessorCompilation>true</MultiProcessorCompilation> + <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary> + <BufferSecurityCheck>true</BufferSecurityCheck> + <TreatWChar_tAsBuiltInType>true</TreatWChar_tAsBuiltInType> + <ForceConformanceInForLoopScope>true</ForceConformanceInForLoopScope> + <WarningLevel>Level3</WarningLevel> + <DebugInformationFormat>None</DebugInformationFormat> + <CompileAs>Default</CompileAs> + <DisableSpecificWarnings>4996;4244;%(DisableSpecificWarnings)</DisableSpecificWarnings> + <ProgramDataBaseFileName>$(IntDir)$(TargetName).pdb</ProgramDataBaseFileName> + </ClCompile> + <ResourceCompile> + <PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <Culture>0x0409</Culture> + </ResourceCompile> + <Link> + <AdditionalDependencies>wsock32.lib;comctl32.lib;shlwapi.lib;%(AdditionalDependencies)</AdditionalDependencies> + <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories> + <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile> + <DelayLoadDLLs>comctl32.dll;%(DelayLoadDLLs)</DelayLoadDLLs> + <GenerateDebugInformation>false</GenerateDebugInformation> + <ProgramDatabaseFile>$(IntDir)$(TargetName).pdb</ProgramDatabaseFile> + <GenerateMapFile>false</GenerateMapFile> + <MapFileName>$(IntDir)$(TargetName).map</MapFileName> + <OptimizeReferences>true</OptimizeReferences> + <EnableCOMDATFolding>true</EnableCOMDATFolding> + <RandomizedBaseAddress>true</RandomizedBaseAddress> + <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary> + <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers> + <SubSystem>Windows</SubSystem> + </Link> + <PostBuildEvent> + <Command>xcopy /Y /D $(OutDir)$(TargetName)$(TargetExt) ..\..\..\..\Build\Winamp_$(PlatformShortName)_$(Configuration)\Plugins\ </Command> + <Message>Post build event: 'xcopy /Y /D $(OutDir)$(TargetName)$(TargetExt) ..\..\..\..\Build\Winamp_$(PlatformShortName)_$(Configuration)\Plugins\'</Message> + </PostBuildEvent> + </ItemDefinitionGroup> + <ItemGroup> + <ProjectReference Include="..\..\..\apev2\apev2.vcxproj"> + <Project>{48387e27-2666-4acf-9c21-ae508c529580}</Project> + <CopyLocalSatelliteAssemblies>true</CopyLocalSatelliteAssemblies> + <ReferenceOutputAssembly>true</ReferenceOutputAssembly> + </ProjectReference> + <ProjectReference Include="..\..\..\id3v2\id3v2.vcxproj"> + <Project>{0c7aafa2-2a78-4b91-99a3-3e866b484adb}</Project> + <CopyLocalSatelliteAssemblies>true</CopyLocalSatelliteAssemblies> + <ReferenceOutputAssembly>true</ReferenceOutputAssembly> + </ProjectReference> + <ProjectReference Include="..\..\..\nsutil\nsutil.vcxproj"> + <Project>{dabe6307-f8dd-416d-9dac-673e2decb73f}</Project> + </ProjectReference> + <ProjectReference Include="..\..\..\replicant\jnetlib\jnetlib.vcxproj"> + <Project>{e105a0a2-7391-47c5-86ac-718003524c3d}</Project> + </ProjectReference> + <ProjectReference Include="..\..\..\replicant\nu\nu.vcxproj"> + <Project>{f1f5cd60-0d5b-4cea-9eeb-2f87ff9aa915}</Project> + </ProjectReference> + <ProjectReference Include="..\..\..\Wasabi\Wasabi.vcxproj"> + <Project>{3e0bfa8a-b86a-42e9-a33f-ec294f823f7f}</Project> + </ProjectReference> + </ItemGroup> + <ItemGroup> + <ClInclude Include="..\..\..\nu\RingBuffer.h" /> + <ClInclude Include="..\..\..\Wasabi\api\service\svcs\svc_metatag.h" /> + <ClInclude Include="AACFrame.h" /> + <ClInclude Include="adts.h" /> + <ClInclude Include="adts_vlb.h" /> + <ClInclude Include="AlbumArt.h" /> + <ClInclude Include="apev2.h" /> + <ClInclude Include="api__in_mp3.h" /> + <ClInclude Include="config.h" /> + <ClInclude Include="CVbriHeader.h" /> + <ClInclude Include="DecodeThread.h" /> + <ClInclude Include="DXHEAD.H" /> + <ClInclude Include="FactoryHelper.h" /> + <ClInclude Include="giofile.h" /> + <ClInclude Include="id3.h" /> + <ClInclude Include="ID3v1.h" /> + <ClInclude Include="ID3v2.h" /> + <ClInclude Include="ifc_mpeg_stream_reader.h" /> + <ClInclude Include="LAMEinfo.h" /> + <ClInclude Include="Lyrics3.h" /> + <ClInclude Include="main.h" /> + <ClInclude Include="Metadata.h" /> + <ClInclude Include="MetadataFactory.h" /> + <ClInclude Include="MP3Info.h" /> + <ClInclude Include="mpegutil.h" /> + <ClInclude Include="OFL.h" /> + <ClInclude Include="pdtimer.h" /> + <ClInclude Include="RawMediaReader.h" /> + <ClInclude Include="resource.h" /> + <ClInclude Include="Stopper.h" /> + <ClInclude Include="uvox_3901.h" /> + <ClInclude Include="uvox_3902.h" /> + <ClInclude Include="WasabiMetadata.h" /> + </ItemGroup> + <ItemGroup> + <ClCompile Include="..\..\..\nu\listview.cpp" /> + <ClCompile Include="..\..\..\nu\RingBuffer.cpp" /> + <ClCompile Include="AACFrame.cpp" /> + <ClCompile Include="adts_vlb.cpp" /> + <ClCompile Include="AlbumArt.cpp" /> + <ClCompile Include="apev2.cpp" /> + <ClCompile Include="config.cpp" /> + <ClCompile Include="CVbriHeader.cpp" /> + <ClCompile Include="DecodeThread.cpp" /> + <ClCompile Include="DXHEAD.C"> + <CompileAs Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">CompileAsCpp</CompileAs> + <CompileAs Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">CompileAsCpp</CompileAs> + <CompileAs Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">CompileAsCpp</CompileAs> + <CompileAs Condition="'$(Configuration)|$(Platform)'=='Release|x64'">CompileAsCpp</CompileAs> + </ClCompile> + <ClCompile Include="ExtendedInfo.cpp" /> + <ClCompile Include="ExtendedRead.cpp" /> + <ClCompile Include="giofile.cpp" /> + <ClCompile Include="id3.cpp" /> + <ClCompile Include="id3dlg.cpp" /> + <ClCompile Include="ID3v1.cpp" /> + <ClCompile Include="ID3v2.cpp" /> + <ClCompile Include="LAMEinfo.cpp" /> + <ClCompile Include="Lyrics3.cpp" /> + <ClCompile Include="main.cpp" /> + <ClCompile Include="Metadata.cpp" /> + <ClCompile Include="MetadataFactory.cpp" /> + <ClCompile Include="MP3Info.cpp" /> + <ClCompile Include="mpegutil.cpp" /> + <ClCompile Include="OFL.cpp" /> + <ClCompile Include="pdtimer.cpp" /> + <ClCompile Include="RawMediaReader.cpp" /> + <ClCompile Include="Stopper.cpp" /> + <ClCompile Include="titlelist.cpp" /> + <ClCompile Include="uvox_3901.cpp" /> + <ClCompile Include="uvox_3902.cpp" /> + <ClCompile Include="WasabiMetadata.cpp" /> + </ItemGroup> + <ItemGroup> + <ResourceCompile Include="in_mp3.rc" /> + </ItemGroup> + <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" /> + <ImportGroup Label="ExtensionTargets"> + </ImportGroup> +</Project>
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mp3/in_mp3.vcxproj.filters b/Src/Plugins/Input/in_mp3/in_mp3.vcxproj.filters new file mode 100644 index 00000000..91c28cd8 --- /dev/null +++ b/Src/Plugins/Input/in_mp3/in_mp3.vcxproj.filters @@ -0,0 +1,218 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <ItemGroup> + <ClCompile Include="AACFrame.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="adts_vlb.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="AlbumArt.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="apev2.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="config.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="CVbriHeader.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="DecodeThread.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="DXHEAD.C"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="ExtendedInfo.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="ExtendedRead.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="giofile.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="id3.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="id3dlg.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="ID3v1.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="ID3v2.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="LAMEinfo.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="Lyrics3.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="main.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="Metadata.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="MetadataFactory.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="MP3Info.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="mpegutil.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="OFL.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="pdtimer.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="RawMediaReader.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="Stopper.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="titlelist.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="uvox_3902.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="uvox_3901.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="WasabiMetadata.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="..\..\..\nu\listview.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="..\..\..\nu\RingBuffer.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + </ItemGroup> + <ItemGroup> + <ClInclude Include="AACFrame.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="adts.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="adts_vlb.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="apev2.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="AlbumArt.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="api__in_mp3.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="config.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="CVbriHeader.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="DecodeThread.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="DXHEAD.H"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="FactoryHelper.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="giofile.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="id3.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="ID3v1.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="ID3v2.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="ifc_mpeg_stream_reader.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="LAMEinfo.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="Lyrics3.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="main.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="Metadata.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="MetadataFactory.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="MP3Info.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="mpegutil.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="OFL.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="pdtimer.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="RawMediaReader.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="resource.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="Stopper.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="uvox_3901.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="uvox_3902.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="WasabiMetadata.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="..\..\..\nu\RingBuffer.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="..\..\..\Wasabi\api\service\svcs\svc_metatag.h"> + <Filter>Header Files</Filter> + </ClInclude> + </ItemGroup> + <ItemGroup> + <Filter Include="Header Files"> + <UniqueIdentifier>{21897fc1-e5e6-4baf-8768-1f94870c3daf}</UniqueIdentifier> + </Filter> + <Filter Include="Ressource Files"> + <UniqueIdentifier>{cf21f14f-48fa-49b3-bd4e-121aea3d1c61}</UniqueIdentifier> + </Filter> + <Filter Include="Source Files"> + <UniqueIdentifier>{d4950784-f6d6-4b2a-8b50-077f3edbee40}</UniqueIdentifier> + </Filter> + </ItemGroup> + <ItemGroup> + <ResourceCompile Include="in_mp3.rc"> + <Filter>Ressource Files</Filter> + </ResourceCompile> + </ItemGroup> +</Project>
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mp3/main.cpp b/Src/Plugins/Input/in_mp3/main.cpp new file mode 100644 index 00000000..e0fb5df2 --- /dev/null +++ b/Src/Plugins/Input/in_mp3/main.cpp @@ -0,0 +1,342 @@ +//#define PLUGIN_NAME "Nullsoft MPEG Audio Decoder" +#include "main.h" +#include <time.h> +#include "DecodeThread.h" +#include "api__in_mp3.h" +#include "../Winamp/wa_ipc.h" +#include "../nu/ServiceBuilder.h" +#include "config.h" +#include "AlbumArt.h" +#include "MetadataFactory.h" +#include "../nu/Singleton.h" +#include "RawMediaReader.h" + +char lastfn_status[256] = {0}; +int lastfn_status_err = 0; +CRITICAL_SECTION g_lfnscs; +CRITICAL_SECTION streamInfoLock; + +int lastfn_data_ready; + +int config_fastvis=0; +unsigned char config_miscopts=0; +unsigned char allow_sctitles=1; +unsigned char sctitle_format=1; +unsigned char config_eqmode=4,config_http_proxynonport80=1; +unsigned int winampVersion=0x00005010; // default version # to use if winamp version is 5.1 or less (and therefore doesn't have a valid HWND during Init) +char config_http_save_dir[MAX_PATH] = "C:\\"; +int config_http_buffersize=64, config_http_prebuffer=40, config_http_prebuffer_underrun=10; +unsigned char config_downmix=0, config_downsample=0, allow_scartwork=1; + +int config_max_bufsize_k=128; +int config_gapless=1; +char INI_FILE[MAX_PATH] = {0}; + +wchar_t lastfn[8192] = {0}; // currently playing file (used for getting info on the current file) + +// Used for correcting DSP plug-in pitch changes +int paused = 0; // are we paused? + +int m_is_stream = 0; +bool m_is_stream_seekable = true; + +volatile int killDecodeThread=0; // the kill switch for the decode thread +HANDLE thread_handle=INVALID_HANDLE_VALUE; // the handle to the decode thread + +DWORD WINAPI DecodeThread(LPVOID b); // the decode thread procedure + +extern char *getfileextensions(); + + +#include "api/service/waservicefactory.h" + +#include "FactoryHelper.h" + +// wasabi based services for localisation support +HINSTANCE WASABI_API_LNG_HINST = 0; +HINSTANCE WASABI_API_ORIG_HINST = 0; + +api_language *WASABI_API_LNG = 0; +api_application *WASABI_API_APP = 0; +api_config *AGAVE_API_CONFIG = 0; +api_memmgr *WASABI_API_MEMMGR = 0; + +AlbumArtFactory albumArtFactory; +MetadataFactory metadataFactory; + +static RawMediaReaderService raw_media_reader_service; +static SingletonServiceFactory<svc_raw_media_reader, RawMediaReaderService> raw_factory; + +int init() +{ + if (!IsWindow(mod.hMainWindow)) + return IN_INIT_FAILURE; + + winampVersion = (unsigned int)SendMessage(mod.hMainWindow, WM_WA_IPC, 0, IPC_GETVERSION); + mod.service->service_register(&metadataFactory); + mod.service->service_register(&albumArtFactory); + raw_factory.Register(mod.service, &raw_media_reader_service); + + ServiceBuild(AGAVE_API_CONFIG, AgaveConfigGUID); + ServiceBuild(WASABI_API_LNG, languageApiGUID); + ServiceBuild(WASABI_API_APP, applicationApiServiceGuid); + ServiceBuild(WASABI_API_MEMMGR, memMgrApiServiceGuid); + + // need to have this initialised before we try to do anything with localisation features + WASABI_API_START_LANG(mod.hDllInstance,InMp3LangGUID); + + static wchar_t szDescription[256]; + swprintf(szDescription, 256, WASABI_API_LNGSTRINGW(IDS_NULLSOFT_MPEG_AUDIO_DECODER), PLUGIN_VERSION); + mod.description = (char*)szDescription; + + InitializeCriticalSection(&g_lfnscs); + InitializeCriticalSection(&streamInfoLock); + mod.UsesOutputPlug|=2; + config_read(); + mod.FileExtensions=getfileextensions(); + return IN_INIT_SUCCESS; +} + +void quit() +{ + DeleteCriticalSection(&g_lfnscs); + DeleteCriticalSection(&streamInfoLock); + ServiceRelease(mod.service, AGAVE_API_CONFIG, AgaveConfigGUID); + ServiceRelease(mod.service, WASABI_API_APP, applicationApiServiceGuid); + ServiceRelease(mod.service, WASABI_API_MEMMGR, memMgrApiServiceGuid); + mod.service->service_deregister(&albumArtFactory); + raw_factory.Deregister(mod.service); +} + +int g_eq_ok; + +int isourfile(const wchar_t *fn) +{ + if (!_wcsnicmp(fn,L"uvox://",7)) return 1; + if (!_wcsnicmp(fn,L"icy://",6)) return 1; + if (!_wcsnicmp(fn,L"sc://",5)) return 1; + if (!_wcsnicmp(fn,L"shoutcast://",12)) return 1; + return 0; +} + + +int m_force_seek=-1; + +// called when winamp wants to play a file +int play(const in_char *fn) +{ + DWORD thread_id; + lastfn_status_err=0; + paused=0; + g_length=-1000; + decode_pos_ms=0; + seek_needed=m_force_seek; + m_force_seek=-1; + m_is_stream = 0; + m_is_stream_seekable = false; + killDecodeThread=0; + g_sndopened=0; + lastfn_data_ready=0; + lastfn_status[0]=0; + g_bufferstat=0; + g_closeaudio=0; + lstrcpynW(lastfn,fn, 8192); + mod.is_seekable = 0; + mod.SetInfo(0,0,0,0); + + g_ds=config_downsample; + + g_eq_ok=1; + // launch decode thread + thread_handle = (HANDLE)CreateThread(NULL,0,(LPTHREAD_START_ROUTINE) DecodeThread,NULL,0,&thread_id); + SetThreadPriority(thread_handle, (int)AGAVE_API_CONFIG->GetInt(playbackConfigGroupGUID, L"priority", THREAD_PRIORITY_HIGHEST)); + + return 0; +} + +// standard pause implementation +void pause() +{ + paused=1; + if (g_sndopened) + mod.outMod->Pause(1); +} + +void unpause() +{ + paused=0; + if (g_sndopened) + mod.outMod->Pause(0); +} + +int ispaused() +{ + return paused; +} + +// stop playing. +void stop() +{ + killDecodeThread=1; + WaitForSingleObject(thread_handle,INFINITE); + CloseHandle(thread_handle); + g_eq_ok=0; + thread_handle = INVALID_HANDLE_VALUE; + g_length=-1000; + lastfn[0]=0; + if (g_closeaudio) + { + g_closeaudio=0; + mod.outMod->Close(); + mod.SAVSADeInit(); + } + g_sndopened=0; + m_force_seek=-1; +} + + +// returns length of playing track +int getlength() +{ + return g_length; +} + + +// returns current output position, in ms. +// you could just use return mod.outMod->GetOutputTime(), +// but the dsp plug-ins that do tempo changing tend to make +// that wrong. +int getoutputtime() +{ + if (g_bufferstat) + return g_bufferstat; + + if (!lastfn_data_ready||!g_sndopened) + return 0; + + if (seek_needed!=-1) + return seek_needed; + + return decode_pos_ms + + (mod.outMod->GetOutputTime()-mod.outMod->GetWrittenTime()); +} + + +// called when the user releases the seek scroll bar. +// usually we use it to set seek_needed to the seek +// point (seek_needed is -1 when no seek is needed) +// and the decode thread checks seek_needed. +void setoutputtime(int time_in_ms) +{ + + + if (m_is_stream == 0 || (m_is_stream !=0 && m_is_stream_seekable)) + { + seek_needed=time_in_ms; + m_force_seek=-1; + } +} + + +// standard volume/pan functions +void setvolume(int volume) +{ + mod.outMod->SetVolume(volume); +} +void setpan(int pan) +{ + mod.outMod->SetPan(pan); +} + + +// this is an odd function. it is used to get the title and/or +// length of a track. +// if filename is either NULL or of length 0, it means you should +// return the info of lastfn. Otherwise, return the information +// for the file in filename. +// if title is NULL, no title is copied into it. +// if length_in_ms is NULL, no length is copied into it. + +static int memcmpv(char *d, char v, int l) +{ + while (l--) + if (*d++ != v) return 1; + return 0; +} + +void eq_set(int on, char data[10], int preamp) +{ + int x; + eq_preamp = preamp; + eq_enabled = on; + for (x = 0; x < 10; x ++) + eq_tab[x] = data[x]; + + // if eq zeroed out, dont use eq + if (eq_enabled && preamp==31 && !memcmpv(data,31,10)) + eq_enabled=0; +} + + + +// render 576 samples into buf. +// this function is only used by DecodeThread. + +// note that if you adjust the size of sample_buffer, for say, 1024 +// sample blocks, it will still work, but some of the visualization +// might not look as good as it could. Stick with 576 sample blocks +// if you can, and have an additional auxiliary (overflow) buffer if +// necessary.. + + +// module definition. + +extern In_Module mod = +{ + IN_VER_RET, // defined in IN2.H + "nullsoft(in_mp3.dll)", + 0, // hMainWindow (filled in by winamp) + 0, // hDllInstance (filled in by winamp) + 0, + // this is a double-null limited list. "EXT\0Description\0EXT\0Description\0" etc. + 0, // is_seekable + 1, // uses output plug-in system + config, + about, + init, + quit, + getfileinfo, + id3Dlg, + isourfile, + play, + pause, + unpause, + ispaused, + stop, + + getlength, + getoutputtime, + setoutputtime, + + setvolume, + setpan, + + 0,0,0,0,0,0,0,0,0, // visualization calls filled in by winamp + + 0,0, // dsp calls filled in by winamp + + eq_set, + + NULL, // setinfo call filled in by winamp + + 0, // out_mod filled in by winamp +}; + +// exported symbol. Returns output module. +extern "C" +{ + __declspec(dllexport) In_Module * winampGetInModule2() + { + return &mod; + } +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mp3/main.h b/Src/Plugins/Input/in_mp3/main.h new file mode 100644 index 00000000..01febf7d --- /dev/null +++ b/Src/Plugins/Input/in_mp3/main.h @@ -0,0 +1,123 @@ +#pragma once +#ifndef WIN32_LEAN_AND_MEAN + #define WIN32_LEAN_AND_MEAN +#endif +#include <windows.h> +#include "giofile.h" +#include "dxhead.h" +#include "CVbriHeader.h" +#include "resource.h" +#include "in2.h" + +#define PLUGIN_VERSION L"4.6" + +extern In_Module mod; + +extern char INI_FILE[MAX_PATH]; +extern int g_length; +extern int lastfn_data_ready; +extern int id3Dlg(const wchar_t *fn, HWND hwnd); +extern int getlength(); +extern void getfileinfo(const wchar_t *filename, wchar_t *title, int *length_in_ms); + +extern int config_max_bufsize_k; +extern int config_gapless; +extern int config_fastvis; +extern unsigned char config_miscopts; +extern unsigned char config_downmix, config_downsample; +extern int config_http_buffersize, config_http_prebuffer, config_http_prebuffer_underrun; +extern unsigned char allow_sctitles,sctitle_format, allow_scartwork; +extern char config_http_save_dir[MAX_PATH]; + +extern wchar_t lastfn[8192]; // currently playing file (used for getting info on the current file) +extern char g_stream_title[256]; +extern char lastfn_status[256]; +extern int lastfn_status_err; +extern int paused; // are we paused? +extern void config_read(); +extern void about(HWND hwndParent); + +extern void strmBuf_Quit(); +extern int strmBuf_Start(char *streamurl, int num_bytes, int pre_buffer_top, int pre_buffer_bottom); +extern int strmBuf_Read(void *data, int bytes_requested); + +extern void config(HWND hwndParent); +extern volatile int killDecodeThread; + +extern unsigned char eq_preamp; +extern unsigned char eq_enabled; +extern unsigned char eq_tab[10]; +extern unsigned char config_eqmode; +extern unsigned int winampVersion; +extern int g_eq_ok; + +extern CRITICAL_SECTION g_lfnscs; +extern CRITICAL_SECTION streamInfoLock; + +#if !defined(__alpha) && !defined(_WIN64) +static __inline long float_to_long(double t) +{ + long r; + __asm fld t + __asm fistp r + return r; +} +#else +#define float_to_long(x) ((long)( x )) +#endif + +extern void processMetaDataC(char *data, int len, int msgId ); + +enum +{ + UVOX_METADATA_STYLE_AOLRADIO = 0, + UVOX_METADATA_STYLE_SHOUTCAST = 1, + UVOX_METADATA_STYLE_SHOUTCAST2 = 2, + UVOX_METADATA_STYLE_SHOUTCAST2_ARTWORK = 3, + UVOX_METADATA_STYLE_SHOUTCAST2_ARTWORK_PLAYING = 4, +}; + +typedef struct { + void *Next; + int style; + long timer; + char title[16384]; + int part; + int total_parts; + int part_len; + int type; + } TitleType; +#define TITLELISTTYPE TitleType + +extern TITLELISTTYPE *TitleLinkedList; +extern TITLELISTTYPE TitleListTerminator; + +extern void initTitleList(void); +extern TITLELISTTYPE *newTitleListEntry(void); +extern void removeTitleListEntry(TITLELISTTYPE *Entry); +extern void clearTitleList(void); + +char *GetUltravoxUserAgent(); +char *GetUserAgent(); +void w9x_lstrcpynW(wchar_t *dest, const wchar_t *src, int maxLen); + +// maximum acceptable deviance between LAME header bytesize and real filesize (minus id3 tag) +// has to be large enough to accomodate unknown tags (APE, lyrics3) +const int MAX_ACCEPTABLE_DEVIANCE = 16384; +void get_extended_info(const wchar_t *fn, int *len); +#define UVOX_3901_LEN 32768 + +void ConvertTryUTF8(const char *in, wchar_t *out, size_t outlen); + +#ifdef AAC_SUPPORT +extern char config_extlist_aac[129]; +#define config_extlist config_extlist_aac +#else +extern char config_extlist[129]; +#endif + +extern int m_force_seek; +extern CGioFile *g_playing_file; + +int _r_i(char *name, int def); +void _w_i(char *name, int d);
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mp3/mp4.cpp b/Src/Plugins/Input/in_mp3/mp4.cpp new file mode 100644 index 00000000..b1f1c63b --- /dev/null +++ b/Src/Plugins/Input/in_mp3/mp4.cpp @@ -0,0 +1,44 @@ +#include "vlb_sub/aacdecoder.h" +#include "vlbout.h" + +void CStreamInfo::setSampleRate() +{ + SetSamplingRate(CChannelInfo::SamplingRateFromIndex(GetSamplingRateIndex ())); +} + +#ifndef ACTIVEX_CONTROL +//methods used by in_mp4 +extern "C" +{ + __declspec( dllexport ) int aacGetBitBuffer() + { + return (int) new CBitBuffer; + } + + __declspec( dllexport ) int aacGetDecoderInterfaces(CAacDecoder **decoder, CBitBuffer *buf, CStreamInfo **info, VLBOut **dataout) + { + *decoder=new CAacDecoder(*buf); + *info=new CStreamInfo; + *dataout=new VLBOut(); + return 1; + } + + __declspec( dllexport ) void aacDeleteBitBuffer(CBitBuffer *bitBuffer) + { + if (bitBuffer) + delete bitBuffer; + } + + __declspec( dllexport ) void aacDeleteDecoderInterfaces(CAacDecoder *decoder, CStreamInfo *info, VLBOut *dataout) + { + if (decoder) + delete decoder; + if (info) + delete info; + if (dataout) + delete dataout; + } + + +} +#endif
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mp3/mpegutil.cpp b/Src/Plugins/Input/in_mp3/mpegutil.cpp new file mode 100644 index 00000000..27d76488 --- /dev/null +++ b/Src/Plugins/Input/in_mp3/mpegutil.cpp @@ -0,0 +1,372 @@ +#include "DecodeThread.h" +#include "main.h" +// benski> cut some old shit +// this code would take the filterbank coefficients to get an approximate spectrum +// and modify the coefficients to do a quick&easy EQ +// it's been off by default for quite a few versions (i think starting with 5.12) +// but not I just yanked it out. cause none of us have 486SX's any more + +static float eq_lookup1[64]={ + 4.000000f,3.610166f,3.320019f,3.088821f,2.896617f, + 2.732131f,2.588368f,2.460685f,2.345845f,2.241498f, + 2.145887f,2.057660f,1.975760f,1.899338f,1.827707f, + 1.760303f,1.696653f,1.636363f,1.579094f,1.524558f, + 1.472507f,1.422724f,1.375019f,1.329225f,1.285197f, + 1.242801f,1.201923f,1.162456f,1.124306f,1.087389f, + 1.051628f,1.016951f,0.983296f,0.950604f,0.918821f, + 0.887898f,0.857789f,0.828454f,0.799853f,0.771950f, + 0.744712f,0.718108f,0.692110f,0.666689f,0.641822f, + 0.617485f,0.593655f,0.570311f,0.547435f,0.525008f, + 0.503013f,0.481433f,0.460253f,0.439458f,0.419035f, + 0.398970f,0.379252f,0.359868f,0.340807f,0.322060f, + 0.303614f,0.285462f,0.267593f,0.250000 +}; + +static float eq_lookup2[64] = { +2.00000000f, 1.96825397f, 1.93650794f, 1.90476191f, 1.87301588f, +1.84126985f, 1.80952382f, 1.77777779f, 1.74603176f, 1.71428573f, +1.68253970f, 1.65079367f, 1.61904764f, 1.58730161f, 1.55555558f, +1.52380955f, 1.49206352f, 1.46031749f, 1.42857146f, 1.39682543f, +1.36507940f, 1.33333337f, 1.30158734f, 1.26984131f, 1.23809528f, +1.20634925f, 1.17460322f, 1.14285719f, 1.11111116f, 1.07936513f, +1.04761910f, 1.01587307f, 0.98412699f, 0.95238096f, 0.92063493f, +0.88888890f, 0.85714287f, 0.82539684f, 0.79365081f, 0.76190478f, +0.73015875f, 0.69841272f, 0.66666669f, 0.63492066f, 0.60317463f, +0.57142860f, 0.53968257f, 0.50793654f, 0.47619048f, 0.44444445f, +0.41269842f, 0.38095239f, 0.34920636f, 0.31746033f, 0.28571430f, +0.25396827f, 0.22222222f, 0.19047619f, 0.15873016f, 0.12698413f, +0.09523810f, 0.06349207f, 0.03174603f, 0.00000000 +}; + +unsigned char eq_preamp = 0; +unsigned char eq_enabled = 0; +unsigned char eq_tab[10] = {0}; + +float g_vis_table[2][2][32][18] = {0}; + +void mp3GiveVisData(float vistable[2][32][18],int gr, int nch) +{ + if (g_vis_enabled) + { + memcpy(&g_vis_table[gr][0][0][0],&vistable[0][0][0],sizeof(float)*32*18*nch); + } +} + +void mp2Equalize(float *xr, int nch, int srate, int nparts) +{ + if (!g_eq_ok || !(mod.UsesOutputPlug & 2)) return; + if (!eq_enabled) return; + float *eq_lookup = (config_eqmode&1)?eq_lookup2:eq_lookup1; + float preamp = eq_lookup[eq_preamp]; + int offs[11] = { 0,1,2,3,4,5,6,9,15,18,32}; + int scale_offs[11] = {0}; + int x; + unsigned char eqt[12] = {0}; + memcpy(eqt+1,eq_tab,10); + eqt[0]=eqt[1]; + eqt[11]=eqt[10]; + for (x = 0; x < 11; x ++) + { + scale_offs[x] = float_to_long( ((float) offs[x] / (float) srate * 44100.0)); + if (scale_offs[x] > 32) scale_offs[x] = 32; + } + { + if (nch == 1) + { + register int i; + for (i = 0; i < 10; i ++) + { + register int x=scale_offs[i]; + register int t=scale_offs[i+1]; + float d = eq_lookup[(int)eqt[i]]*preamp; + float dd = (eq_lookup[(int)eqt[i+1]]*preamp-d)/(float)(t-x); + if (dd < 0.000000001f && dd > -0.000000001f) dd = 0.000000001f; + while (x < t) + { + register float *p = xr+x; + int e=nparts; + while (e--) + { + *(p) *= d; + p+=32; + } + p += 32*(18-nparts); + d += dd; + x++; + } + } + } + else + { + register int i; + for (i = 0; i < 10; i ++) + { + register int x=scale_offs[i]; + register int t=scale_offs[i+1]; + float d = eq_lookup[(int)eqt[i]]*preamp; + float dd = (eq_lookup[(int)eqt[i+1]]*preamp-d)/(float)(t-x); + if (dd < 0.000000001f && dd > -0.000000001f) dd = 0.000000001f; + while (x < t) + { + register float *p = xr+x; + int e=nparts; + while (e--) + { + *(p+32*18) *= d; + *(p) *= d; + p+=32; + } + p += 32*(18-nparts); + d += dd; + x++; + } + } + } + } +} + + +void mp3Equalize(float *xr, int nch, int srate) +{ + if (!g_eq_ok || !(mod.UsesOutputPlug & 2)) return; + if (!eq_enabled) return; + float *eq_lookup = (config_eqmode&1)?eq_lookup2:eq_lookup1; + float preamp = eq_lookup[eq_preamp]; + static int scale_offs[11]; + static int lrate; + unsigned char eqt[12] = {0}; + memcpy(eqt+1,eq_tab,10); + eqt[0]=eqt[1]; + eqt[11]=eqt[10]; + if (lrate!=srate) + { + lrate=srate; + for (int x = 0; x < 11; x ++) + { + int offs[11] = { 0,2,4, 9,16, 30, 63, 95,153,308, 576}; + scale_offs[x] = float_to_long( ((float) offs[x] / (float) srate * 44100.0)); + if (scale_offs[x] > 576) scale_offs[x] = 576; + } + } + { + if (nch == 1) + { + register float *p = xr; + register float d = eq_lookup[eqt[0]]*preamp; + register int i = 0; + for (i = 0; i < 10; i ++) + { + register int x=scale_offs[i]; + register int t=scale_offs[i+1]; + register float dd = (eq_lookup[eqt[i+1]]*preamp-d)/(float)(t-x); + while (x++ < t) + { + *(p++) *= d; + d += dd; + } + } + } + else + { + register float *p = xr; + register int i; + for (i = 0; i < 10; i ++) + { + register int x=scale_offs[i]; + register int t=scale_offs[i+1]; + float d = eq_lookup[(int)eqt[i]]*preamp; + float dd = (eq_lookup[(int)eqt[i+1]]*preamp-d)/(float)(t-x); + if (dd < 0.000000001f && dd > -0.000000001f) dd = 0.000000001f; + while (x++ < t) + { + *(p+32*18) *= d; + *(p++) *= d; + d += dd; + } + } + } + } +} + + +void genOsc(char *tempdata, short *samples, int len) +{ + float d = 0.0f, dd = len/(75.0f*2.0f); + short int *sbuf = samples; + int x,y=0; + signed char *c = (signed char *) tempdata; + for (x = 0; x < 75; x ++) + { + float q=0; + int td = float_to_long((d+=dd)); + for (; y < td; y ++) + q += *sbuf++; + q *= (32.0f/(dd*65536.0f)); + *c++ = (signed char) float_to_long(q); + } +} +void genSpec(char *tempdata, float *xr, int nch) +{ + static int offsets[76] = + { + 0,1,2,3,4,5,7,8,9,10,12,13,15,16,18,20,21,23,25,27,29,31,33,36,38,41,43,46,48,51, + 54,57,60,64,67,71,74,78,82,86,91,95,100,105,109,115,120,126,131,137,144,150,157, + 164,171,178,186,194,203,211,220,230,239,250,260,271,282,294,306,319,332,345,360, + 374,389,576 + }; + { + for (int x = 0; x < 75; x++) + { + float sum = 0.0; + int z; + int sx = offsets[x]; + int ex = offsets[x+1]; + if (nch == 2) + { + float *p = &xr[0]; + int w=32*18; + for (z = sx; z < ex; z ++) + { + register float t1=p[z], t2=p[w+z]; + if (t1 <0.0) t1=-t1; + if (t2<0.0f) t2=-t2; + sum += (t1+t2) * 0.5f; + } + } + else + { + float *p = &xr[0]; + for (z = sx; z < ex; z ++) + { + register float t=p[z]; + if (t < 0.0) t=-t; + sum += t; + } + } + sum *= 1.0f + (x) * 12.0f / (ex-sx) ; + sum *= 1.8f/24000.0f; + if (sum < 0.0) sum = 0.0; + if (sum > 255.0) sum = 255.0; + tempdata[x] = (unsigned char) float_to_long(sum); + } + } +} + +void do_layer3_vis(short *samples, float *xr, int nch, int ts) +{ + int vis_waveNch; + int vis_specNch; + int csa = mod.SAGetMode(); + int is_vis_running = mod.VSAGetMode(&vis_specNch,&vis_waveNch); + static char tempdata[75*2]; + int len=32*18*nch; + + if (is_vis_running) + { + char data[576*4] = {0}; + int data_offs=0; + int x; + + if (nch == 1 && vis_specNch > 0) + { + float *specdata = xr; + int y; + for (y=0; y < 576; y++) + { + float p = *specdata++ / 24.0f; + if (p < 0.0) p = -p; + if (p > 255.0) p = 255.0; + data[data_offs++] = (unsigned char) float_to_long(p); + } + if (vis_specNch == 2) + { + memcpy(data+data_offs,data+data_offs-576,576); + data_offs += 576; + } + } + else if (vis_specNch == 2) + { + for (x = 0; x < 2; x ++) + { + float *specdata = &xr[x*32*18]; + for (int y=0; y < 576; y++) + { + float p = *specdata++ / 24.0f; + if (p < 0.0) p = -p; + if (p > 255.0) p = 255.0; + data[data_offs++] = (unsigned char) float_to_long(p); + } + } + } + else if (vis_specNch == 1) + { + float *specdata = &xr[0]; + int y; + for (y = 0; y < 576; y++) + { + register float p1=specdata[0],p2=specdata[32*18],p; + if (p1 < 0.0) p1=-p1; + if (p2 < 0.0) p2=-p2; + p = (p1+p2)/ 48.0f; + specdata++; + if (p > 255.0) p = 255.0; + data[data_offs++] = (unsigned char) float_to_long(p); + } + } // end of spectrum code + + if (nch == 1 && vis_waveNch > 0) + { + for (x = 0; x < 576; x++) + { + data[data_offs++] = ((samples[x])>>8); + } + if (vis_waveNch == 2) + { + memcpy(data+data_offs,data+data_offs-576,576); + data_offs += 576; + } + } + else if (vis_waveNch == 2) + { + for (x = 0; x < 2; x ++) + { + int y; + for (y = 0; y < 576; y ++ ) + { + data[data_offs++] = ((samples[y*2+x])>>8); + } + } + } + else if (vis_waveNch == 1) + { + int x; + for (x = 0; x < 576; x ++) + { + data[data_offs++] = ((int)samples[x*2]+(int)samples[x*2+1])>>9; + } + } + mod.VSAAdd(data,ts); + } + if (csa==4) + { + tempdata[0] = 0; + tempdata[1] = 0; + mod.SAAdd(tempdata,ts,4); + } + else if (csa == 3) + { + genSpec(tempdata,xr,nch); + genOsc(tempdata+75,samples,len); + mod.SAAdd(tempdata,ts,0x80000003); + } + else if (csa == 2) + { + genOsc(tempdata,samples,len); + mod.SAAdd(tempdata,ts,2); + } + else if (csa == 1) { + genSpec(tempdata,xr,nch); + mod.SAAdd(tempdata,ts,1); + } +} + diff --git a/Src/Plugins/Input/in_mp3/mpegutil.h b/Src/Plugins/Input/in_mp3/mpegutil.h new file mode 100644 index 00000000..578f37b6 --- /dev/null +++ b/Src/Plugins/Input/in_mp3/mpegutil.h @@ -0,0 +1,9 @@ +#ifndef NULLSOFT_MPEGUTILH +#define NULLSOFT_MPEGUTILH + +extern float g_vis_table[2][2][32][18]; +void do_layer3_vis(short *samples, float *xr, int nch, int ts); +void mp3Equalize(float *xr, int nch, int srate); +void mp2Equalize(float *xr, int nch, int srate, int nparts); +void mp3GiveVisData(float vistable[2][32][18],int gr, int nch); +#endif
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mp3/pdtimer.cpp b/Src/Plugins/Input/in_mp3/pdtimer.cpp new file mode 100644 index 00000000..4b6b5b95 --- /dev/null +++ b/Src/Plugins/Input/in_mp3/pdtimer.cpp @@ -0,0 +1,24 @@ +#include "pdtimer.h" + +__int64 pdReadResolution(void) +{ + __int64 myfeq; + LARGE_INTEGER feq; + + QueryPerformanceFrequency( &feq); + myfeq = feq.QuadPart; + + return myfeq; +} + +__int64 pdReadTimer(void) +{ + __int64 mynow; + + LARGE_INTEGER now; + + QueryPerformanceCounter( &now ); + mynow = now.QuadPart; + + return mynow; +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mp3/pdtimer.h b/Src/Plugins/Input/in_mp3/pdtimer.h new file mode 100644 index 00000000..1c0da887 --- /dev/null +++ b/Src/Plugins/Input/in_mp3/pdtimer.h @@ -0,0 +1,9 @@ +#ifndef NULLSOFT_PDTIMERH +#define NULLSOFT_PDTIMERH + +#include <windows.h> + +__int64 pdReadResolution(void); +__int64 pdReadTimer(void); + +#endif
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mp3/proxydt.h b/Src/Plugins/Input/in_mp3/proxydt.h new file mode 100644 index 00000000..8802075d --- /dev/null +++ b/Src/Plugins/Input/in_mp3/proxydt.h @@ -0,0 +1,16 @@ +// proxydt.h +#ifndef PROXYDT_H +#define PROXYDT_H + +#include <string> +#include <stdio.h> +#include <atlbase.h> + +char* detectBrowserProxy(); + +char* DetectIEProxy(); + +char* DetectNS4Proxy(); +char* DetectNS6Proxy(); + +#endif
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mp3/resource.h b/Src/Plugins/Input/in_mp3/resource.h new file mode 100644 index 00000000..14776a1b --- /dev/null +++ b/Src/Plugins/Input/in_mp3/resource.h @@ -0,0 +1,253 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by script1.rc +// +#define IDS_NULLSOFT_MPEG_AUDIO_DECODER_OLD 0 +#define IDS_CANNOT_WRITE_STREAMS_TO_DISK 1 +#define IDS_BUFFER_X 2 +#define IDC_ID3_REMOVE 3 +#define IDC_ID3V1_REMOVE 3 +#define IDC_REVERTTAG 3 +#define IDS_ERROR_SYNCING_TO_STREAM 3 +#define IDC_ID3V2_REMOVE 4 +#define IDS_CONNECTING 4 +#define IDC_ID3V1_SAVE 5 +#define IDS_NO_VALID_MULTICONNECT_URL 5 +#define IDC_ID3_REMOVE_ALL 6 +#define IDS_REDIRECT_LIMIT_EXCEEDED 6 +#define IDC_ID3V1_TO_V2 7 +#define IDS_READING_ID3 7 +#define IDC_ID3V2_SAVE 8 +#define IDS_STREAM_TERMINATED 8 +#define IDC_ID3V2_TO_V1 9 +#define IDS_STREAM_TEMPORARILY_INTERRUPTED 9 +#define IDC_ID3V2_STOP 10 +#define IDS_NETWORK_RECEIVED_X_BYTES 16 +#define IDS_SERVER 17 +#define IDS_CONTENT_TYPE 18 +#define IDS_ULTRAVOX_SYNC 19 +#define IDS_ULTRAVOX_DATA_MESSAGE 20 +#define IDS_ULTRAVOX_SID_AVGBR_MAXBR 21 +#define IDS_METADATA_RECEIVED 22 +#define IDS_METADATA_INTERVAL 23 +#define IDS_ID3v2_TAG 24 +#define IDS_VBR_LEADING_FRAME 25 +#define IDS_STREAM_NAME 26 +#define IDS_CURRENT_TITLE 27 +#define IDS_CONTENT_LENGTH 28 +#define IDS_SAVING_TO 29 +#define IDS_CUSTOM 32 +#define IDS_MONO 33 +#define IDS_STEREO 34 +#define IDS_3_CHANNEL 35 +#define IDS_4_CHANNEL 36 +#define IDS_SURROUND 37 +#define IDS_5_1 38 +#define IDS_7_1 39 +#define IDS_ERROR 40 +#define IDS_NONE 41 +#define IDS_50_15_MICROSEC 42 +#define IDS_INVALID 43 +#define IDS_JOINT_STEREO 44 +#define IDS_2_CHANNEL 45 +#define IDS_PAYLOAD_SIZE 46 +#define IDS_FORMAT_AAC 47 +#define IDS_MPEG2_HE_AAC_IS 48 +#define IDS_MPEG4_HE_AAC_IS 49 +#define IDS_SAMPLE_RATE_OUTPUT 50 +#define IDS_SAMPLE_RATE 51 +#define IDS_SBR_PRESENT 52 +#define IDS_SBR_NOT_PRESENT 53 +#define IDS_CHANNELS_OUTPUT 54 +#define IDS_CHANNELS 55 +#define IDS_MODE_MONO 56 +#define IDS_MODE_STEREO 57 +#define IDS_MODE_PARAMETRIC_STEREO 58 +#define IDS_MODE_DUAL_CHANNEL 59 +#define IDS_MODE_4_CHANNEL_2_CPE 60 +#define IDS_MODE_4_CHANNEL_MPEG 61 +#define IDS_MODE_5_CHANNEL 62 +#define IDS_MODE_5_1 63 +#define IDS_MODE_6_1 64 +#define IDS_MODE_7_1 65 +#define IDS_BITRATE 66 +#define IDS_AVERAGE_BITRATE 67 +#define IDS_HEADER_FOUND_AT_X_BYTES 68 +#define IDS_LENGTH_X_SECONDS 69 +#define IDS_PROFILE 70 +#define IDS_YES 71 +#define IDS_NO 72 +#define IDS_ENC_DELAY_ZERO_PADDING 73 +#define IDS_S_LAYER_X 74 +#define IDS_X_KBIT 75 +#define IDS_X_KBIT_APPROX 76 +#define IDS_X_KBIT_VBR 77 +#define IDS_X_HZ_S 78 +#define IDS_COPYRIGHTED 79 +#define IDS_ORIGINAL 80 +#define IDS_EMPHASIS 81 +#define IDS_EXT_MP3_SURROUND 82 +#define IDS_MP3_HAS_BEEN_MODIFIED_NOT_ALL_MAY_BE_CORRECT 83 +#define IDS_UPDATING_FILE 84 +#define IDS_CANNOT_MODIFY_TAG_READ_ONLY 85 +#define IDS_CANNOT_INSERT_ID3v2_TAG_DRIVE_FULL 86 +#define IDS_CANNOT_INSERT_ID3v2_TAG_STOP_PLAYBACK 87 +#define IDS_CANNOT_INSERT_ID3v2_TAG_READ_ONLY 88 +#define IDS_UPDATING_FILE_REMOVING_TAG 89 +#define IDS_CANNOT_REMOVE_ID3v2_TAG_DRIVE_FULL 90 +#define IDS_CANNOT_REMOVE_ID3v2_TAG_STOP_TRY_AGAIN 91 +#define IDS_CANNOT_REMOVE_ID3v2_TAG_READ_ONLY 92 +#define IDS_CANNOT_WRITE_ID3v1_TAG 93 +#define IDS_CANNOT_REMOVE_ID3v1_TAG 94 +#define IDS_MPEG_AUDIO_FILES 95 +#define IDS_MPEG_AUDIO_DECODER_SETTINGS 96 +#define IDS_LATIN_1 97 +#define IDS_SYSTEM_LANGUAGE 98 +#define IDS_UNICODE_UTF_16 99 +#define IDS_SELECT_DIRECTORY_TO_SAVE_TO 100 +#define ID_YES 101 +#define IDD_PREFS 103 +#define IDD_OUTPUT 104 +#define IDD_STREAM 115 +#define IDD_HTTP 177 +#define IDD_TAGOPTS 179 +#define IDD_HTTPAUTH 182 +#define IDD_INFO_ID3V1 186 +#define IDD_INFO_ID3V2 187 +#define IDD_INFO_LYRICS3 188 +#define IDS_X_KBIT_ABR 189 +#define IDD_INFO_APEV2 189 +#define IDD_ADVANCED_TAGGING 190 +#define IDS_NAME 191 +#define IDS_VALUE 192 +#define IDS_APEV2_RETAIN_HEADER 193 +#define IDS_APEV2_ADD_HEADER 194 +#define IDS_APEV2_REMOVE_HEADER 195 +#define IDS_ERROR_SAVING_METADATA 196 +#define IDS_METADATA_ERROR_READONLY 197 +#define IDS_METADATA_ERROR_OPENING_FILE 198 +#define IDS_METADATA_ERROR_APEV2 199 +#define IDS_METADATA_ERROR_LYRICS3 200 +#define IDS_METADATA_ERROR_ID3V1 201 +#define IDS_METADATA_ERROR_ID3V2 202 +#define IDS_METADATA_ERROR_UNSPECIFIED 203 +#define IDS_TIMED_OUT 204 +#define IDS_FAMILY_STRING_MP3 205 +#define IDS_FAMILY_STRING_MP2 206 +#define IDS_FAMILY_STRING_MP1 207 +#define IDS_FAMILY_STRING_MPEG2_AAC 208 +#define IDS_FAMILY_STRING_DOLBY 209 +#define IDS_ABOUT_STRING 210 +#define IDS_ABOUT_TEXT 210 +#define IDS_LENGTH_X_PART_SECONDS 211 +#define IDC_FASTL3EQ 1000 +#define IDC_BUFFERS_NUMBUFS 1001 +#define IDC_FASTL12EQ 1002 +#define IDC_ID3V1 1003 +#define IDC_ID3V2 1004 +#define IDC_16BIT 1005 +#define IDC_PREBUFSLIDER 1011 +#define IDC_PREBUFSLIDER2 1012 +#define IDC_STEREO 1046 +#define IDC_REVSTEREO 1048 +#define IDC_FULLRATE 1049 +#define IDC_HALFRATE 1050 +#define IDC_QRATE 1051 +#define IDC_PREFS_PRIORITY_DECODE 1076 +#define IDC_ID3_TITLE 1078 +#define IDC_ID3_ARTIST 1079 +#define IDC_ID3_ALBUM 1080 +#define IDC_ID3_COMMENT 1081 +#define IDC_ID3_YEAR 1082 +#define IDC_ID3_GENRE 1083 +#define IDC_ID3V2_TITLE 1086 +#define IDC_ID3V2_ARTIST 1087 +#define IDC_ID3V2_ALBUM 1088 +#define IDC_ID3V2_YEAR 1089 +#define IDC_ID3V2_GENRE 1090 +#define IDC_ID3V2_COMMENT 1091 +#define IDC_ID3V2_COMPOSER 1092 +#define IDC_ID3V2_MARTIST 1093 +#define IDC_ID3V2_RECORD 1094 +#define IDC_ID3V2_URL 1095 +#define IDC_ID3V2_ENCODER 1096 +#define IDC_ID3V2_TRACK 1097 +#define IDC_ID3V11_TRACK 1098 +#define IDC_BPM 1098 +#define IDC_ID3V2_BPM 1098 +#define IDC_ID3V2_ALBUM_ARTIST 1099 +#define IDC_ID3V2_ENCODER2 1100 +#define IDC_ID3V2_DISC 1100 +#define IDC_ID3V2_ENCODER3 1101 +#define IDC_ID3V2_PUBLISHER 1101 +#define IDC_RADIO1 1106 +#define IDC_RADIO2 1107 +#define IDC_CHECK1 1108 +#define IDC_STATUS 1109 +#define IDC_SC_ARTWORK 1109 +#define IDC_CHECK2 1110 +#define IDC_URL 1111 +#define IDC_BUTTON2 1112 +#define IDC_ID3FORMAT 1127 +#define IDC_NORMFORMAT 1128 +#define IDC_BUFMAX 1136 +#define IDC_CHECK3 1138 +#define IDC_ID3V2_STATUS_TEXT 1139 +#define IDC_EASTER 1143 +#define IDC_USEID3 1145 +#define IDC_EDIT1 1146 +#define IDC_BUTTON1 1147 +#define IDC_TRACK_GAIN 1147 +#define IDC_REALM 1148 +#define IDC_CHECK5 1150 +#define IDC_READ_LYRICS3 1150 +#define IDC_WRITE_LOCAL 1151 +#define IDC_READ_APEV2 1151 +#define IDC_WRITE_UTF16 1152 +#define IDC_WRITE_LATIN 1153 +#define IDC_READ_LATIN 1154 +#define IDC_READ_LOCAL 1155 +#define IDC_WRITE_ID3V1 1156 +#define IDC_CHECK7 1157 +#define IDC_WRITE_ID3V2 1157 +#define IDC_READ_ID3V1 1158 +#define IDC_READ_ID3V2 1159 +#define IDC_24BIT 1160 +#define IDC_WRITE_APEV2 1160 +#define IDC_CREATE_ID3V1 1160 +#define IDC_SURROUND 1161 +#define IDC_EDIT_REPLAYGAIN 1162 +#define IDC_CREATE_ID3V2 1162 +#define IDC_ALBUM_GAIN 1165 +#define IDC_COMBO1 1168 +#define IDC_COMBO2 1169 +#define IDC_LYRICS3_TITLE 1169 +#define IDC_LYRICS3_ARTIST 1170 +#define IDC_LYRICS3_ALBUM 1171 +#define IDC_LYRICS3V2 1172 +#define IDC_LYRICS3 1172 +#define IDC_APEV2 1173 +#define IDC_APELIST 1177 +#define IDC_APE_LIST 1177 +#define IDC_APE_KEY 1178 +#define IDC_DELETE_ALL 1179 +#define IDC_APE_VALUE 1180 +#define IDC_APE_ADD 1181 +#define IDC_APE_DELETE 1182 +#define IDC_CREATE_APEV2 1183 +#define IDC_STATIC_APEV2_HEADER 1184 +#define IDC_APEV2_HEADER_OPTIONS 1185 +#define IDC_RATING_EMAIL 1186 +#define IDC_RATING_EMAIL_RESET 1187 +#define IDS_NULLSOFT_MPEG_AUDIO_DECODER 65534 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 212 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1188 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/Src/Plugins/Input/in_mp3/resources/aboutMP3_256.bmp b/Src/Plugins/Input/in_mp3/resources/aboutMP3_256.bmp Binary files differnew file mode 100644 index 00000000..bc3629f9 --- /dev/null +++ b/Src/Plugins/Input/in_mp3/resources/aboutMP3_256.bmp diff --git a/Src/Plugins/Input/in_mp3/resources/hand.cur b/Src/Plugins/Input/in_mp3/resources/hand.cur Binary files differnew file mode 100644 index 00000000..614403a0 --- /dev/null +++ b/Src/Plugins/Input/in_mp3/resources/hand.cur diff --git a/Src/Plugins/Input/in_mp3/resources/llama.bmp b/Src/Plugins/Input/in_mp3/resources/llama.bmp Binary files differnew file mode 100644 index 00000000..5f2a6ec1 --- /dev/null +++ b/Src/Plugins/Input/in_mp3/resources/llama.bmp diff --git a/Src/Plugins/Input/in_mp3/resources/logoCodTech.90x42.bmp b/Src/Plugins/Input/in_mp3/resources/logoCodTech.90x42.bmp Binary files differnew file mode 100644 index 00000000..de49ffd5 --- /dev/null +++ b/Src/Plugins/Input/in_mp3/resources/logoCodTech.90x42.bmp diff --git a/Src/Plugins/Input/in_mp3/resources/logoIIS_127x42.bmp b/Src/Plugins/Input/in_mp3/resources/logoIIS_127x42.bmp Binary files differnew file mode 100644 index 00000000..3dd674ca --- /dev/null +++ b/Src/Plugins/Input/in_mp3/resources/logoIIS_127x42.bmp diff --git a/Src/Plugins/Input/in_mp3/resources/logoId3v2_44x42.bmp b/Src/Plugins/Input/in_mp3/resources/logoId3v2_44x42.bmp Binary files differnew file mode 100644 index 00000000..851493ae --- /dev/null +++ b/Src/Plugins/Input/in_mp3/resources/logoId3v2_44x42.bmp diff --git a/Src/Plugins/Input/in_mp3/resources/logoMp3surround_101x42.bmp b/Src/Plugins/Input/in_mp3/resources/logoMp3surround_101x42.bmp Binary files differnew file mode 100644 index 00000000..aba97616 --- /dev/null +++ b/Src/Plugins/Input/in_mp3/resources/logoMp3surround_101x42.bmp diff --git a/Src/Plugins/Input/in_mp3/resources/logoWA.bmp b/Src/Plugins/Input/in_mp3/resources/logoWA.bmp Binary files differnew file mode 100644 index 00000000..41266529 --- /dev/null +++ b/Src/Plugins/Input/in_mp3/resources/logoWA.bmp diff --git a/Src/Plugins/Input/in_mp3/tagz.cpp b/Src/Plugins/Input/in_mp3/tagz.cpp new file mode 100644 index 00000000..0e110b23 --- /dev/null +++ b/Src/Plugins/Input/in_mp3/tagz.cpp @@ -0,0 +1,953 @@ +#include <windows.h> +#include <stdlib.h> +#include <string.h> +#include <ctype.h> +#include <stdio.h> + +#include "tagz.h" + +#ifdef TAGZ_UNICODE +#define _TX(X) L##X +#define t_strdup _wcsdup +#define t_strlen wcslen +#define t_strnicmp _wcsnicmp +#define t_strtoul wcstoul +#define t_stricmp _wcsicmp +#define t_strstr wcsstr + +#define sprintf swprintf +#else +#define _TX(X) X +#define t_strdup _strdup +#define t_strlen strlen +#define t_strnicmp _strnicmp +#define t_strtoul strtoul +#define t_stricmp _stricmp +#define t_strstr strstr + +#endif + +int t_atoi(T_CHAR * c) +{ + if (c[0]=='-') return -(int)t_strtoul(c+1,0,10); + else return (int)t_strtoul(c,0,10); +} + +#define TABSIZE(x) (sizeof(x)/sizeof(x[0])) +/* +char * strndup(char * p1,UINT n) +{ + char * r=(char*)malloc(n+1); + if (r) + { + memcpy(r,p1,n); + r[n]=0; + } + return n; +} +*/ + +class String +{ +private: + T_CHAR * data; + UINT size, used; +public: + String() {data=0;size=0;used=0;} + void AddChar(T_CHAR c) + { + if (!data) + { + data=(T_CHAR*)malloc((size=512)*sizeof(T_CHAR)); + used=0; + } + else if (size==used) + { + UINT old_size = size; + T_CHAR * old_data = data; + size<<=1; + data=(T_CHAR*)realloc((char*)data,size*sizeof(T_CHAR)); + if (!data) + { + size = old_size; + data = old_data; + return; + } + } + if (data) data[used++]=c; + } + void AddInt(int i) + { + T_CHAR foo[16]; + sprintf(foo,_TX("%i"),i); + AddString(foo); + } + void AddString(const T_CHAR * z) + { + while(*z) {AddChar(*z);z++;} + } + void AddString(String & s) + { + AddString(s.Peek()); + } + ~String() + { + if (data) free(data); + } + T_CHAR * GetBuf() + { + if (!data) return t_strdup(_TX("")); + T_CHAR * r=(T_CHAR*)realloc(data,(used+1)*sizeof(T_CHAR)); + r[used]=0; + data=0; + return r; + } + T_CHAR operator[](UINT i) + { + if (!data || i>=used) return 0; + else return data[i]; + } + UINT Len() {return data ? used : 0;} + void Reset() + { + if (data) {free(data);data=0;} + } + const T_CHAR * Peek() + { + AddChar(0); + used--; + return data; + } + T_CHAR * _strdup() + { + return ::t_strdup(Peek()); + } +}; + + + + +static int separator(T_CHAR x) +{ + if (!x || x==' ') return 1; + if (x=='\'' || x=='_') return 0; +#ifdef TAGZ_UNICODE + return !iswalnum(x); +#else + return !isalnum(x); +#endif +} + +static int sepcmp(T_CHAR* src,T_CHAR* val) +{ + UINT l=t_strlen(val); + return !t_strnicmp(src,val,l) && separator(src[l]); +} + +static char roman_num[]= +{ + 'I','V','X','L','C','D','M' +}; + + +static int is_roman(T_CHAR * ptr)//could be more smart i think +{ + if (ptr[0]==']' && ptr[1]=='[' && separator(ptr[2])) return 1; + while(!separator(*ptr)) + { + UINT n; + bool found=0; + for(n=0;n<TABSIZE(roman_num);n++) + { + if (*ptr==roman_num[n]) {found=1;break;} + } + if (!found) return 0; + ptr++; + } + return 1; +} + +static int need_full(T_CHAR* ptr) +{ + if (is_roman(ptr)) return 1; + if (sepcmp(ptr,_TX("RPG"))) return 1; + while(!separator(*ptr)) + { + if (*ptr<='0' || *ptr>='9') return 0; + ptr++; + } + return 1; +} + +class VarList +{ +private: + typedef struct tagVAR + { + tagVAR * next; + T_CHAR * name; + T_CHAR * value; + } VAR; + VAR * vars; +public: + VarList() {vars=0;} + ~VarList() + { + VAR * p=vars; + while(p) + { + VAR *n=p->next; + free(p->name); + free(p->value); + delete p; + p=n; + } + } + T_CHAR * Get(T_CHAR * name) + { + VAR * p=vars; + while(p) + { + if (!t_stricmp(name,p->name)) return p->value; + p=p->next; + } + return 0; + } + void Put(T_CHAR * name,T_CHAR* value) + { + VAR * p=vars; + while(p) + { + if (!t_stricmp(name,p->name)) + { + free(p->value); + p->value=t_strdup(value); + return; + } + p=p->next; + } + p=new VAR; + p->next=vars; + vars=p; + p->value=t_strdup(value); + p->name=t_strdup(name); + } +}; + +typedef void (*TEXTFUNC)(UINT n_src,T_CHAR **src,UINT*,String &out,VarList & vars); + + +#define MAKEFUNC(X) static void X(UINT n_src,T_CHAR ** src,UINT *found_src,String &out,VarList &vars) + +/* +void Blah(UINT n_src,T_CHAR ** src,UINT*,String &out) +{ + out.AddString("blah"); +} + +void Nop(UINT n_src,T_CHAR ** src,UINT *,String &out) +{ + UINT n; + for(n=0;n<n_src;n++) out.AddString(src[n]); +} +*/ + +MAKEFUNC(Get) +{ + if (n_src>=1) + { + T_CHAR * p=vars.Get(src[0]); + if (p) out.AddString(p); + } +} + +MAKEFUNC(Put) +{ + if (n_src>=2) + { + vars.Put(src[0],src[1]); + out.AddString(src[1]); + } +} + +MAKEFUNC(PutQ) +{ + if (n_src>=2) + { + vars.Put(src[0],src[1]); + } +} + +MAKEFUNC(If) +{ + if (n_src!=3) out.AddString(_TX("[INVALID $IF SYNTAX]")); + else + { + out.AddString(src[found_src[0] ? 1 : 2]); + } +} + +MAKEFUNC(Iflonger) +{ + if (n_src!=4) out.AddString(_TX("[INVALID $IFLONGER SYNTAX]")); + else + { + out.AddString(src[(int)t_strlen(src[0])>t_atoi(src[1]) ? 2 : 3]); + } +} + +MAKEFUNC(Ifgreater) +{ + if (n_src!=4) out.AddString(_TX("[INVALID $IFGREATER SYNTAX]")); + else + { + out.AddString(src[t_atoi(src[0])>t_atoi(src[1]) ? 2 : 3]); + } +} + +MAKEFUNC(Upper) +{ + if (n_src>=1) + { + T_CHAR * s=src[0]; + while(*s) + { + out.AddChar(toupper(*(s++))); + } + } +} + +MAKEFUNC(Lower) +{ + if (n_src>=1) + { + T_CHAR * s=src[0]; + while(*s) + { + out.AddChar(tolower(*(s++))); + } + } +} + +MAKEFUNC(Pad) +{ + if (n_src>=2) + { + T_CHAR *fill=_TX(" "); + if (n_src>=3 && src[2][0]) fill=src[2]; + int num=t_atoi(src[1]); + T_CHAR *p=src[0]; + while(*p) {out.AddChar(*(p++));num--;} + UINT fl=t_strlen(fill); + while(num>0) {out.AddChar(fill[(--num)%fl]);} + } +} + +MAKEFUNC(Cut) +{ + if (n_src>=2) + { + UINT num=t_atoi(src[1]); + T_CHAR *p=src[0]; + while(*p && num) {out.AddChar(*(p++));num--;} + } +} + +MAKEFUNC(PadCut) +{ + if (n_src>=2) + { + T_CHAR *fill=_TX(" "); + if (n_src>=3 && src[2][0]) fill=src[3]; + int num=t_atoi(src[1]); + T_CHAR *p=src[0]; + while(*p && num>0) {out.AddChar(*(p++));num--;} + UINT fl=t_strlen(fill); + while(num>0) {out.AddChar(fill[(--num)%fl]);} + } +} + +MAKEFUNC(Abbr) +{//abbr(string,len) + if (n_src==0 || (n_src>=2 && (int)t_strlen(src[0])<t_atoi(src[1]))) return; + T_CHAR * meta=src[0]; + bool w=0,r=0; + while(*meta) + { + bool an=!separator(*meta) || *meta==']' || *meta=='['; + if (w && !an) + { + w=0; + } + else if (!w && an) + { + w=1; + if (!sepcmp(meta,_TX("a")) && !sepcmp(meta,_TX("the"))) + { + r=need_full(meta)?1:0; + out.AddChar(*meta); + } + else r=0; + } + else if (w && r) + { + out.AddChar(*meta); + } + meta++; + } +} + + + +MAKEFUNC(Caps) +{ + if (n_src<1) return; + T_CHAR* sp=src[0]; + int sep=1; + while(*sp) + { + T_CHAR c=*(sp++); + int s = separator(c); + if (!s && sep) + c=toupper(c); + else if (!sep) c=tolower(c); + sep=s; + out.AddChar(c); + } +} + +MAKEFUNC(Caps2) +{ + if (n_src<1) return; + T_CHAR* sp=src[0]; + int sep=1; + while(*sp) + { + T_CHAR c=*(sp++); + int s = separator(c); + if (!s && sep) + c=toupper(c); + sep=s; + out.AddChar(c); + } +} + +MAKEFUNC(Longest) +{ + T_CHAR * ptr=0; + UINT n,m=0; + for(n=0;n<n_src;n++) + { + UINT l=t_strlen(src[n]); + if (l>m) {m=l;ptr=src[n];} + } + if (ptr) out.AddString(ptr); +} + +MAKEFUNC(Shortest) +{ + T_CHAR * ptr=0; + UINT n,m=(UINT)(-1); + for(n=0;n<n_src;n++) + { + UINT l=t_strlen(src[n]); + if (l<m) {m=l;ptr=src[n];} + } + if (ptr) out.AddString(ptr); +} + +MAKEFUNC(Num) +{ + if (n_src==2) + { + T_CHAR tmp[16]; + T_CHAR tmp1[16]; + sprintf(tmp1,_TX("%%0%uu"),t_atoi(src[1])); + sprintf(tmp,tmp1,t_atoi(src[0])); + out.AddString(tmp); + } +} + +MAKEFUNC(Hex) +{ + if (n_src==2) + { + T_CHAR tmp[16]; + T_CHAR tmp1[16]; + sprintf(tmp1,_TX("%%0%ux"),t_atoi(src[1])); + sprintf(tmp,tmp1,t_atoi(src[0])); + out.AddString(tmp); + } +} + +MAKEFUNC(StrChr) +{ + if (n_src==2) + { + T_CHAR * p=src[0]; + T_CHAR s=src[1][0]; + while(*p && *p!=s) p++; + if (*p==s) out.AddInt(1+p-src[0]); + else out.AddChar('0'); + } +} + +MAKEFUNC(StrRChr) +{ + if (n_src==2) + { + T_CHAR * p=src[0],*p1=0; + T_CHAR s=src[1][0]; + while(*p) + { + if (*p==s) p1=p; + p++; + } + if (p1) out.AddInt(1+p1-src[0]); + else out.AddChar('0'); + + } +} + +MAKEFUNC(StrStr) +{ + if (n_src==2) + { + T_CHAR * p=t_strstr(src[0],src[1]); + if (p) out.AddInt(1+p-src[0]); + else out.AddChar('0'); + } +} + +MAKEFUNC(SubStr) +{ + int n1,n2; + if (n_src<2) return; + n1=t_atoi(src[1]); + if (n_src>=3) + { + n2=t_atoi(src[2]); + } + else n2=n1; + if (n1<1) n1=1; + if (n2>=n1) + { + n1--; + n2--; + while(n1<=n2 && src[0][n1]) + { + out.AddChar(src[0][n1++]); + } + } +} + +MAKEFUNC(Len) +{ + if (n_src>=1) out.AddInt(t_strlen(src[0])); +} + +MAKEFUNC(FileName) +{ + if (n_src<1) return; + T_CHAR * p=src[0]; + T_CHAR * p1=0; + while(*p) + { + if (*p=='\\' || *p=='/') p1=p; + p++; + } + if (p1) p1++; + else p1=p; + while(*p1 && *p1!='.') out.AddChar(*(p1++)); +} + +MAKEFUNC(Add) +{ + UINT n; + int s=0; + for(n=0;n<n_src;n++) + { + s+=t_atoi(src[n]); + } + out.AddInt(s); +} + +MAKEFUNC(Sub) +{ + if (n_src>=1) + { + UINT n; + int s=t_atoi(src[0]); + for(n=1;n<n_src;n++) + { + s-=t_atoi(src[n]); + } + out.AddInt(s); + } +} + +MAKEFUNC(Mul) +{ + UINT n; + int s=1; + for(n=0;n<n_src;n++) + { + s*=t_atoi(src[n]); + } + out.AddInt(s); +} + +MAKEFUNC(Div) +{ + if (n_src>=1) + { + UINT n; + int s=t_atoi(src[0]); + for(n=1;n<n_src;n++) + { + int t=t_atoi(src[n]); + if (t) s/=t; + else t=0; + } + out.AddInt(s); + } +} + +MAKEFUNC(Mod) +{ + if (n_src>=1) + { + UINT n; + int s=t_atoi(src[0]); + for(n=1;n<n_src;n++) + { + int t=t_atoi(src[n]); + if (t) s%=t; + else t=0; + } + out.AddInt(s); + } +} + +MAKEFUNC(Max) +{ + if (!n_src) return; + int m=t_atoi(src[0]); + UINT n; + for(n=1;n<n_src;n++) + { + int t=t_atoi(src[n]); + if (t>m) m=t; + } + out.AddInt(m); +} + +MAKEFUNC(Min) +{ + if (!n_src) return; + int m=t_atoi(src[0]); + UINT n; + for(n=1;n<n_src;n++) + { + int t=t_atoi(src[n]); + if (t<m) m=t; + } + out.AddInt(m); +} + +MAKEFUNC(Null) +{ +} + +struct +{ + TEXTFUNC func; + const T_CHAR * name; +} FUNCS[]={ +// Blah,"blah", +// Nop,"nop", + If,_TX("if"), + Upper,_TX("upper"), + Lower,_TX("lower"), + Pad,_TX("pad"), + Cut,_TX("cut"), + PadCut,_TX("padcut"), + Abbr,_TX("abbr"), + Caps,_TX("caps"), + Caps2,_TX("caps2"), + Longest,_TX("longest"), + Shortest,_TX("shortest"), + Iflonger,_TX("iflonger"), + Ifgreater,_TX("ifgreater"), + Num,_TX("num"),Num,_TX("dec"), + Hex,_TX("hex"), + StrChr,_TX("strchr"), + StrChr,_TX("strlchr"), + StrRChr,_TX("strrchr"), + StrStr,_TX("strstr"), + SubStr,_TX("substr"), + Len,_TX("len"), + Add,_TX("add"), + Sub,_TX("sub"), + Mul,_TX("mul"), + Div,_TX("div"), + Mod,_TX("mod"), + FileName,_TX("filename"), + Min,_TX("min"), + Max,_TX("max"), + Get,_TX("get"), + Put,_TX("put"), + PutQ,_TX("puts"), + Null,_TX("null"), +}; + + +class FMT +{ +private: + String str; + VarList *vars; + T_CHAR * spec; + TAGFUNC f; + TAGFREEFUNC ff; + void * fp; + T_CHAR * org_spec; + int found; + + void Error(T_CHAR *e=0) {str.Reset();str.AddString(e ? e : _TX("[SYNTAX ERROR IN FORMATTING STRING]"));} + + T_CHAR * _FMT(T_CHAR * s,UINT *f=0) + { + FMT fmt(this,s); + T_CHAR * c=(T_CHAR*)fmt; + if (f) *f=fmt.found; + found+=fmt.found; + return c; + } + + static bool skipshit(T_CHAR** _p,T_CHAR *bl) + { + T_CHAR * p=*_p; + int bc1=0,bc2=0; + while(*p) + { + if (!bc1 && !bc2 && bl) + { + T_CHAR *z=bl; + while(*z) + { + if (*z==*p) break; + z++; + } + if (*z) break; + } + if (*p=='\'') + { + p++; + while(*p && *p!='\'') p++; + if (!*p) return 0; + } + else if (*p=='(') bc1++; + else if (*p==')') + { + if (--bc1<0) return 0; + } + else if (*p=='[') bc2++; + else if (*p==']') + { + if (--bc2<0) return 0; + } + p++; + } + *_p=p; + return *p && !bc1 && !bc2; + } + + void run() + { + if (!spec) {Error();return;} + while(*spec) + { + if (*spec=='%') + { + spec++; + if (*spec=='%') {str.AddChar('%');spec++;continue;} + T_CHAR* s1=spec+1; + while(*s1 && *s1!='%') s1++; + if (!*s1) {Error();break;} + *s1=0; + T_CHAR * tag=f(spec,fp); + *s1='%'; + //if (!tag) tag=tag_unknown; + if (tag && tag[0]) + { + found++; + str.AddString(tag); + } + else + { + str.AddString(_TX("?")); + } + if (tag && ff) ff(tag,fp); + spec=s1+1; + } + else if (*spec=='$') + { + spec++; + if (*spec=='$') {str.AddChar('$');spec++;continue;} + T_CHAR * s1=spec+1; + while(*s1 && *s1!='(') s1++; + if (!*s1) {Error();break;} + T_CHAR * s2=s1+1; + if (!skipshit(&s2,_TX(")"))) {Error();break;} + if (!*s2) {Error();break;}; + T_CHAR * p=s1+1; + T_CHAR* temp[64]; + UINT temp_f[64]; + UINT nt=0; + T_CHAR * p1=s1+1; + while(p<=s2 && nt<64) + { + if (!skipshit(&p,_TX(",)"))) {Error();return;} + if (p>s2 || (*p!=',' && *p!=')')) {Error(_TX("internal error"));return;} + T_CHAR bk=*p; + *p=0; + temp[nt]=_FMT(p1,&temp_f[nt]); + nt++; + *p=bk;; + p1=p+1; + p++; + } + *s1=0; + UINT n; + TEXTFUNC fn=0; + for(n=0;n<TABSIZE(FUNCS);n++) + { + if (!t_stricmp(spec,FUNCS[n].name)) {fn=FUNCS[n].func;break;} + } + *s1='('; + if (fn) + { + fn(nt,temp,temp_f,str,*vars); + } + else + { + str.AddString(_TX("[UNKNOWN FUNCTION]")); + } + for(n=0;n<nt;n++) free(temp[n]); + spec=s2+1; + } + else if (*spec=='\'') + { + spec++; + if (*spec=='\'') {str.AddChar('\'');spec++;continue;} + T_CHAR * s1=spec+1; + while(*s1 && *s1!='\'') s1++; + if (!*s1) {Error();break;} + *s1=0; + str.AddString(spec); + *s1='\''; + spec=s1+1; + } + else if (*spec=='[') + { + spec++; + T_CHAR * s1=spec; + if (!skipshit(&s1,_TX("]"))) {Error();break;} + T_CHAR bk=*s1; + *s1=0; + FMT fmt(this,spec); + fmt.run(); + if (fmt.found) + { + str.AddString(fmt); + found+=fmt.found; + } + *s1=bk; + spec=s1+1; + } + else if (*spec==']' || *spec=='(' || *spec==')') {Error();break;} + else + { + str.AddChar(*spec); + spec++; + } + } + } + + FMT(FMT* base,T_CHAR * _spec) + { + vars=base->vars; + found=0; + org_spec=0; + f=base->f; + ff=base->ff; + fp=base->fp; + spec=_spec; + } +public: + FMT(T_CHAR * p_spec,TAGFUNC _f,TAGFREEFUNC _ff,void * _fp,VarList * _vars) + { + vars=_vars; + found=0; + org_spec=spec=t_strdup(p_spec); + f=_f; + ff=_ff; + fp=_fp; + } + operator T_CHAR*() + { + run(); + return str.GetBuf(); + } + ~FMT() + { + if (org_spec) free(org_spec); + } +}; + +extern "C" +{ + +UINT tagz_format(T_CHAR * spec,TAGFUNC f,TAGFREEFUNC ff,void *fp,T_CHAR* out,UINT max) +{ + T_CHAR * zz=tagz_format_r(spec,f,ff,fp); + UINT r=0; + while(r<max-1 && zz[r]) + { + out[r]=zz[r]; + r++; + } + out[r]=0; + free(zz); + return r; +} + +T_CHAR * tagz_format_r(T_CHAR* spec,TAGFUNC f,TAGFREEFUNC ff,void * fp) +{ + VarList vars; + return FMT(spec,f,ff,fp,&vars); +} + +//char tagz_manual[]="TODO: WTFM"; + +char tagz_manual[]="Syntax reference: \n" + "\n" + "* %tagname% - inserts field named <tagname>, eg. \"%artist%\"\n" + "* $abbr(x) - inserts abbreviation of x, eg. \"$abbr(%album%)\" - will convert album name of \"Final Fantasy VI\" to \"FFVI\"\n" + "* $abbr(x,y) - inserts abbreviation of x if x is longer than y characters; otherwise inserts full value of x, eg. \"$abbr(%album%,10)\"\n" + "* $lower(x), $upper(x) - converts x to in lower/uppercase, eg. \"$upper(%title%)\"\n" + "* $num(x,y) - displays x number and pads with zeros up to y characters (useful for track numbers), eg. $num(%tracknumber%,2)\n" + "* $caps(x) - converts first letter in every word of x to uppercase, and all other letters to lowercase, eg. \"blah BLAH\" -> \"Blah Blah\"\n" + "* $caps2(x) - similar to $caps, but leaves uppercase letters as they are, eg. \"blah BLAH\" -> \"Blah BLAH\"\n" + "* $if(A,B,C) - if A contains at least one valid tag, displays B, otherwise displays C; eg. \"$if(%artist%,%artist%,unknown artist)\" will display artist name if present; otherwise will display \"unknown artist\"; note that \"$if(A,A,)\" is equivalent to \"[A]\" (see below)\n" + "* $longest(A,B,C,....) - compares lengths of output strings produced by A,B,C... and displays the longest one, eg. \"$longest(%title%,%comment%)\" will display either title if it's longer than comment; otherwise it will display comment\n" + "* $pad(x,y) - pads x with spaces up to y characters\n" + "* $cut(x,y) - truncates x to y characters\n" + "* $padcut(x,y) - pads x to y characters and truncates to y if longer\n" + "* [ .... ] - displays contents of brackets only if at least one of fields referenced inside has been found, eg. \"%artist% - [%album% / ]%title%\" will hide [] block if album field is not present\n" + "* \' (single quotation mark) - outputs raw text without parsing, eg, \'blah$blah%blah[][]\' will output the contained string and ignore all reserved characters (%,$,[,]) in it; you can use this feature to insert square brackets for an example.\n" + "\n" + "eg. \"[%artist% - ][$abbr(%album%,10)[ %tracknumber%] / ]%title%[ %streamtitle%]\"\n"; + +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mp3/tagz.h b/Src/Plugins/Input/in_mp3/tagz.h new file mode 100644 index 00000000..4699ca98 --- /dev/null +++ b/Src/Plugins/Input/in_mp3/tagz.h @@ -0,0 +1,26 @@ +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef UINT +typedef unsigned int UINT; +#endif + +#ifdef TAGZ_UNICODE +typedef unsigned short T_CHAR; +#else +#define T_CHAR char +#endif + +typedef T_CHAR* (*TAGFUNC)(T_CHAR * tag,void * p); //return 0 if not found +typedef void (*TAGFREEFUNC)(T_CHAR * tag,void * p); + + +UINT tagz_format(T_CHAR * spec,TAGFUNC f,TAGFREEFUNC ff,void *fp,T_CHAR * out,UINT max); +T_CHAR * tagz_format_r(T_CHAR * spec,TAGFUNC f,TAGFREEFUNC ff,void * fp); + +extern char tagz_manual[]; + +#ifdef __cplusplus +} +#endif
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mp3/titlelist.cpp b/Src/Plugins/Input/in_mp3/titlelist.cpp new file mode 100644 index 00000000..65767e49 --- /dev/null +++ b/Src/Plugins/Input/in_mp3/titlelist.cpp @@ -0,0 +1,62 @@ +#include "main.h" + +TITLELISTTYPE TitleListTerminator; +TITLELISTTYPE *TitleLinkedList = &TitleListTerminator; + +void initTitleList(void) +{ + TitleListTerminator.Next = NULL; + TitleListTerminator.timer = 0; +} + + +/* ----------------------------------------------------------------------------------------------- + Adds an entry in the list + -----------------------------------------------------------------------------------------------*/ +TITLELISTTYPE *newTitleListEntry(void) +{ +TITLELISTTYPE *TitleObject = (TITLELISTTYPE *)calloc(1,sizeof(TITLELISTTYPE)); /* Allocate new entry */ +TitleObject->Next = (void *)TitleLinkedList; /* New entry's next is old list _head */ +TitleLinkedList = TitleObject; /* new _head is new entry */ +return TitleObject; /* return pointer to new entry */ +} + +/* ----------------------------------------------------------------------------------------------- + Removes an entry from the list + -----------------------------------------------------------------------------------------------*/ +void removeTitleListEntry(TITLELISTTYPE *Entry) +{ +TITLELISTTYPE *TitleObject = TitleLinkedList; + + if (TitleObject == &TitleListTerminator) return; /* List is empty */ + + if (TitleObject == (void *)Entry) + { + TitleLinkedList = (TITLELISTTYPE *)TitleObject->Next; + free(TitleObject); + } + else + while (TitleObject->Next) /* While not terminator */ + { + if ((TITLELISTTYPE *)(TitleObject->Next) == (void *)Entry) /* If next entry is what we're looking for */ + { + TitleObject->Next = ((TITLELISTTYPE *)(TitleObject->Next))->Next; /* Skip one entry */ + free(Entry); /* free the entry we dont' want anymore */ + } + TitleObject = (TITLELISTTYPE *)TitleObject->Next; /* Get next entry */ + } +} + +void clearTitleList() +{ + TITLELISTTYPE *TitleObject = TitleLinkedList; + if (TitleObject == &TitleListTerminator) return; /* List is empty */ + + while (TitleObject->Next) /* While not terminator */ + { + TITLELISTTYPE *KillMe=TitleObject; + TitleObject = (TITLELISTTYPE *)KillMe->Next; /* Get next entry */ + free(KillMe); + } + TitleLinkedList = &TitleListTerminator; +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mp3/todo.txt b/Src/Plugins/Input/in_mp3/todo.txt new file mode 100644 index 00000000..4cbe62e6 --- /dev/null +++ b/Src/Plugins/Input/in_mp3/todo.txt @@ -0,0 +1,97 @@ +Changes:
+ * [a7] made seeking work (slightly) better on realshitbox encoded mp3s (with broken
+ VBR headers)
+ * [a7] made save http file location persistent when turned off
+ * [a7] fixed id3v2 bug (1 character strings not being displayed correctly)
+ * [a6] fixed stupid file association bug (oops)
+ * [a5] fixed crash when repeating a non-existing file bug (added a Sleep())
+ * [a5] fixed shoutcast disk writer issue
+ * [a5] fixed SendMessage() potential issues (using postmessage and SendMessageTimeout() now)
+ * [a5] added new format-for-non-id3 files, added 'use id3 tag' option, which lets you disable them
+ completely
+ * [a5] return of the file association list
+ * [a5] made temp file handling slightly better-- checks for read only, better error messages.
+ * [a4] fixed lots of potential (and a few serious) bugs in id3lib.
+ * [a4] fixed pause right after playback starts bug
+ * fixed crash/hang/freeze when reading some mp3 files with a weird id3v2 tag (as found in some
+ real jukebox generated mp3s, etc...)
+ * this one will break a few things (i.e. windowshade vis), because justin is updating it to go
+ with winamp 2.7
+ * all code is now win32 file io
+ * %a will now display id3v1.1/v2 track #
+ * fixed id3v1 reading bug that added year field in album field
+ * why does it ask me to stop the currently playing file when updating an id3v2 tag ?
+ answer:
+ whenever you strip or update an id3v2 tag, it creates a tmp file (FILENAME.new),
+ writes it out, and if it wrote it out correctly, then it renames the original
+ to FILENAME.bak, and renames the new one to FILENAME, and if that was successful,
+ then deletes FILENAME.bak). this is required because of the implementation of the
+ id3v2 protocol.
+
+ * [2.666b] fixed crash when using crossfading output plugin
+ * [2.666b] fixed the issue that files with large id3v2 tags don't seek correctly
+ * [2.666b] added id3v1.1 track field editing
+ * [2.666b] simplified id3 edit box (removed all save/remove buttons, all is done via
+ update button now)
+ * [2.666b] fixed some more stuff in id3 edit box... should be more reliable
+ * [2.666b] contains devil easter egg
+
+ * winamp 2.666 release
+
+ * [a18] dll is smaller
+ * [a18] fixed vbr header reading on some musicmatch/crap generated files
+ * [a18] id3v1.1 track # reading support (who cares about id3v1.1 writing?)
+ * [a18] crc checking is now activable in prefs box
+ * [a18] "show average on VBR files" is now activable in prefs box
+ * [a17] "update tags" button only saves selected tags now
+ * [a17] vbr-division-by-0-bug-on-edit fixed
+ * [a17] long id3v1 tags reading corrected
+ * [a17] id3v2 url tag will now interact with the minibrowser
+ * [a17] added id3v2 variables to id3 title formatting
+ * [a16] corrected crash/bug in id3v2 genre reading
+ * [a16] corrected id3v2 comment editor to support multiple lines :)
+ * [a16] new "stop track" button in id3v2 editor so you don't have to retype everything
+ when id3v2 can't be updated because file is locked
+ * [a16] added track number id3v2 field
+ * [a16] id3v2 warnings no more appear under id3 tag editor
+ * [a10] streaming info improvements/fixes
+ * [a10] made more options for streaming title formatting (for you brennan)
+ * [a10] still needs better id3v2 reading writing. THIS IS ON THE WAY, CHILL.
+ * [a9] improved streaming error notification (i.e. on can't connect, can't resolve, timeout)
+ * [a9] made streaming detect id3v2 tag and skip it (todo: make it look at the id3v2 tag and use it)
+ * [a9] updated id3v2 support to detect invalid id3v2 tags, and autodetect their actual
+ size
+ * [a9] info box now tells you where the first mpeg header was found (useful)
+ * [a8] fixed live365 streaming (they need a space between User-Agent:
+ and the agent string. those assclowns.)
+ * [a8] rescheduled some of the polyphase for a few cycles
+ * [a7] bugfix: vbr headers read when id3v2 tag is present now
+ * [a7] downsampling modes have better vis support
+ * [a7] id3v2 writing support
+ * [a7] stream info box
+ * [a6] mmm.
+ * [a6] return of working id3 code
+ * [a5] optimized bitgetting.
+ * [a5] keen streaming buffer indicators in mini-vis display
+ * [a5] made fast eq modes optional (can use slow pcm eq like wav files)
+ * [a5] fixed fastly-changing-tracks bug
+ * [a4] tuned decode loop more
+ * [a4] optimized huffman decoding
+ * [a3] improved network code. updates status in title area.
+ * [a3] layer 1/2 eq code
+ * [a3] optimized decoder some more. we can still make it a bit faster me thinks.
+ * [a3] moved more code into decode thread.. should act much more asynchronously
+ * [a2] Improved skip robustness
+ * [a2] Optimized decoder for ppro. changed 8 bit mode for speed.
+ * [a2] partial ID3V2 support
+ * Fully ISO compliant decoder (based on FHG's implementation)
+ * Also fully supports MPEG 2.5 low bitrates.
+ * Full MPEG Layer 1 and Layer 2 support
+ * Improved equalizer code
+ * Optimized visualization data generation code
+ * Improved network code (single threaded)
+ * Lots of other cleanups
+
+todo:
+ make more blip resistant (see pvd.mp3)
+ remove seek-blip
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mp3/uvox_3901.cpp b/Src/Plugins/Input/in_mp3/uvox_3901.cpp new file mode 100644 index 00000000..d385a1aa --- /dev/null +++ b/Src/Plugins/Input/in_mp3/uvox_3901.cpp @@ -0,0 +1,79 @@ +#include "uvox_3901.h" +#include "api__in_mp3.h" +#include "api/service/waservicefactory.h" +#include <strsafe.h> +#include "in2.h" +extern In_Module mod; + +#include "FactoryHelper.h" + + +Ultravox3901::Ultravox3901() : parser(0) +{ + title[0]=album[0]=artist[0]=album_art_url[0]=0; + ServiceBuild(parser, obj_xmlGUID); + if (parser) + { + parser->xmlreader_registerCallback(L"metadata\fsong\f*", this); + parser->xmlreader_open(); + } +} + +Ultravox3901::~Ultravox3901() +{ + ServiceRelease(parser, obj_xmlGUID); +} + +int Ultravox3901::Parse(const char *xml_data) +{ + if (parser) + { + int ret = parser->xmlreader_feed((void *)xml_data, strlen(xml_data)); + if (ret != API_XML_SUCCESS) + return ret; + return parser->xmlreader_feed(0, 0); + } + return API_XML_FAILURE; +} + +void Ultravox3901::TextHandler(const wchar_t *xmlpath, const wchar_t *xmltag, const wchar_t *str) +{ + if (!_wcsicmp(xmltag, L"name")) + { + StringCbCatW(title, sizeof(title), str); + } + else if (!_wcsicmp(xmltag, L"album")) + { + StringCbCatW(album, sizeof(album), str); + } + else if (!_wcsicmp(xmltag, L"artist")) + { + StringCbCatW(artist, sizeof(artist), str); + } + else if (!_wcsicmp(xmltag, L"album_art")) + { + StringCbCatW(album_art_url, sizeof(album_art_url), str); + } +} + +int Ultravox3901::GetExtendedData(const char *tag, wchar_t *data, int dataLen) +{ + if (!_stricmp(tag, "uvox/title")) + StringCchCopy(data, dataLen, title); + else if (!_stricmp(tag, "uvox/album")) + StringCchCopy(data, dataLen, album); + else if (!_stricmp(tag, "uvox/artist")) + StringCchCopy(data, dataLen, artist); + else if (!_stricmp(tag, "uvox/albumart")) + StringCchCopy(data, dataLen, album_art_url); + else + return 0; + + return 1; +} + +#define CBCLASS Ultravox3901 +START_DISPATCH; +VCB(ONCHARDATA, TextHandler) +END_DISPATCH; +#undef CBCLASS
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mp3/uvox_3901.h b/Src/Plugins/Input/in_mp3/uvox_3901.h new file mode 100644 index 00000000..69d0a8fe --- /dev/null +++ b/Src/Plugins/Input/in_mp3/uvox_3901.h @@ -0,0 +1,20 @@ +#pragma once +#include "../xml/obj_xml.h" +#include "../xml/ifc_xmlreadercallback.h" + + +class Ultravox3901 : public ifc_xmlreadercallback +{ +public: + Ultravox3901(); + ~Ultravox3901(); + int Parse(const char *xml_data); + int GetExtendedData(const char *tag, wchar_t *data, int dataLen); +private: + /* XML callbacks */ + void TextHandler(const wchar_t *xmlpath, const wchar_t *xmltag, const wchar_t *str); + + obj_xml *parser; + wchar_t title[256],artist[256],album[256],album_art_url[4096]; + RECVS_DISPATCH; +};
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mp3/uvox_3902.cpp b/Src/Plugins/Input/in_mp3/uvox_3902.cpp new file mode 100644 index 00000000..95521e58 --- /dev/null +++ b/Src/Plugins/Input/in_mp3/uvox_3902.cpp @@ -0,0 +1,77 @@ +#include "uvox_3902.h" +#include "api__in_mp3.h" +#include "api/service/waservicefactory.h" +#include <strsafe.h> +#include "in2.h" +extern In_Module mod; + +#include "FactoryHelper.h" + +Ultravox3902::Ultravox3902() : parser(0) +{ + title[0]=album[0]=artist[0]=0; + ServiceBuild(parser, obj_xmlGUID); + if (parser) + { + parser->xmlreader_setCaseSensitive(); + parser->xmlreader_registerCallback(L"metadata\f*", this); + parser->xmlreader_open(); + } +} + +Ultravox3902::~Ultravox3902() +{ + if (parser) + { + parser->xmlreader_unregisterCallback(this); + parser->xmlreader_close(); + } + ServiceRelease(parser, obj_xmlGUID); +} + +int Ultravox3902::Parse(const char *xml_data) +{ + if (parser) + { + int ret = parser->xmlreader_feed((void *)xml_data, strlen(xml_data)); + if (ret != API_XML_SUCCESS) + return ret; + return parser->xmlreader_feed(0, 0); + } + return API_XML_FAILURE; +} + +void Ultravox3902::TextHandler(const wchar_t *xmlpath, const wchar_t *xmltag, const wchar_t *str) +{ + if (!_wcsicmp(xmlpath, L"metadata\fTIT2")) + { + StringCbCatW(title, sizeof(title), str); + } + else if (!_wcsicmp(xmlpath, L"metadata\fTALB")) + { + StringCbCatW(album, sizeof(album), str); + } + else if (!_wcsicmp(xmlpath, L"metadata\fTPE1")) + { + StringCbCatW(artist, sizeof(artist), str); + } +} + +int Ultravox3902::GetExtendedData(const char *tag, wchar_t *data, int dataLen) +{ + if (!_stricmp(tag, "title")) + StringCchCopy(data, dataLen, title); + else if (!_stricmp(tag, "album")) + StringCchCopy(data, dataLen, album); + else if (!_stricmp(tag, "artist")) + StringCchCopy(data, dataLen, artist); + else + return 0; + return 1; +} + +#define CBCLASS Ultravox3902 +START_DISPATCH; +VCB(ONCHARDATA, TextHandler) +END_DISPATCH; +#undef CBCLASS
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mp3/uvox_3902.h b/Src/Plugins/Input/in_mp3/uvox_3902.h new file mode 100644 index 00000000..5314719d --- /dev/null +++ b/Src/Plugins/Input/in_mp3/uvox_3902.h @@ -0,0 +1,20 @@ +#pragma once +#include "../xml/obj_xml.h" +#include "../xml/ifc_xmlreadercallback.h" + + +class Ultravox3902 : public ifc_xmlreadercallback +{ +public: + Ultravox3902(); + ~Ultravox3902(); + int Parse(const char *xml_data); + int GetExtendedData(const char *tag, wchar_t *data, int dataLen); +private: + /* XML callbacks */ + void TextHandler(const wchar_t *xmlpath, const wchar_t *xmltag, const wchar_t *str); + + obj_xml *parser; + wchar_t title[256],artist[256],album[256]; + RECVS_DISPATCH; +};
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mp3/version.rc2 b/Src/Plugins/Input/in_mp3/version.rc2 new file mode 100644 index 00000000..2a761aed --- /dev/null +++ b/Src/Plugins/Input/in_mp3/version.rc2 @@ -0,0 +1,39 @@ + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// +#include "../../../Winamp/buildType.h" +VS_VERSION_INFO VERSIONINFO + FILEVERSION 4,6,0,0 + PRODUCTVERSION WINAMP_PRODUCTVER + FILEFLAGSMASK 0x17L +#ifdef _DEBUG + FILEFLAGS 0x1L +#else + FILEFLAGS 0x0L +#endif + FILEOS 0x4L + FILETYPE 0x2L + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" + BEGIN + VALUE "CompanyName", "Winamp SA" + VALUE "FileDescription", "Winamp Input Plug-in" + VALUE "FileVersion", "4,6,0,0" + VALUE "InternalName", "Nullsoft MPEG Audio Decoder" + VALUE "LegalCopyright", "Copyright © 1998-2023 Winamp SA" + VALUE "LegalTrademarks", "Nullsoft and Winamp are trademarks of Winamp SA" + VALUE "OriginalFilename", "in_mp3.dll" + VALUE "ProductName", "Winamp" + VALUE "ProductVersion", STR_WINAMP_PRODUCTVER + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 + END +END |