aboutsummaryrefslogtreecommitdiff
path: root/Src/Plugins/Input/in_mp3
diff options
context:
space:
mode:
Diffstat (limited to 'Src/Plugins/Input/in_mp3')
-rw-r--r--Src/Plugins/Input/in_mp3/AACFrame.cpp94
-rw-r--r--Src/Plugins/Input/in_mp3/AACFrame.h43
-rw-r--r--Src/Plugins/Input/in_mp3/AlbumArt.cpp283
-rw-r--r--Src/Plugins/Input/in_mp3/AlbumArt.h39
-rw-r--r--Src/Plugins/Input/in_mp3/CVbriHeader.cpp327
-rw-r--r--Src/Plugins/Input/in_mp3/CVbriHeader.h49
-rw-r--r--Src/Plugins/Input/in_mp3/CreateFile.cpp0
-rw-r--r--Src/Plugins/Input/in_mp3/CreateFile.h6
-rw-r--r--Src/Plugins/Input/in_mp3/DXHEAD.C54
-rw-r--r--Src/Plugins/Input/in_mp3/DXHEAD.H43
-rw-r--r--Src/Plugins/Input/in_mp3/DecodeThread.cpp810
-rw-r--r--Src/Plugins/Input/in_mp3/DecodeThread.h19
-rw-r--r--Src/Plugins/Input/in_mp3/ExtendedInfo.cpp315
-rw-r--r--Src/Plugins/Input/in_mp3/ExtendedRead.cpp323
-rw-r--r--Src/Plugins/Input/in_mp3/FactoryHelper.h25
-rw-r--r--Src/Plugins/Input/in_mp3/ID3Info.cpp10
-rw-r--r--Src/Plugins/Input/in_mp3/ID3v1.cpp211
-rw-r--r--Src/Plugins/Input/in_mp3/ID3v1.h29
-rw-r--r--Src/Plugins/Input/in_mp3/ID3v2.cpp654
-rw-r--r--Src/Plugins/Input/in_mp3/ID3v2.h33
-rw-r--r--Src/Plugins/Input/in_mp3/IN2.H4
-rw-r--r--Src/Plugins/Input/in_mp3/LAMEinfo.cpp398
-rw-r--r--Src/Plugins/Input/in_mp3/LAMEinfo.h118
-rw-r--r--Src/Plugins/Input/in_mp3/Lyrics3.cpp193
-rw-r--r--Src/Plugins/Input/in_mp3/Lyrics3.h25
-rw-r--r--Src/Plugins/Input/in_mp3/MP3Info.cpp612
-rw-r--r--Src/Plugins/Input/in_mp3/MP3Info.h47
-rw-r--r--Src/Plugins/Input/in_mp3/Metadata.cpp616
-rw-r--r--Src/Plugins/Input/in_mp3/Metadata.h64
-rw-r--r--Src/Plugins/Input/in_mp3/MetadataFactory.cpp62
-rw-r--r--Src/Plugins/Input/in_mp3/MetadataFactory.h24
-rw-r--r--Src/Plugins/Input/in_mp3/OFL.cpp165
-rw-r--r--Src/Plugins/Input/in_mp3/OFL.h21
-rw-r--r--Src/Plugins/Input/in_mp3/RawMediaReader.cpp84
-rw-r--r--Src/Plugins/Input/in_mp3/RawMediaReader.h30
-rw-r--r--Src/Plugins/Input/in_mp3/Stopper.cpp44
-rw-r--r--Src/Plugins/Input/in_mp3/Stopper.h10
-rw-r--r--Src/Plugins/Input/in_mp3/StreamInfo.cpp9
-rw-r--r--Src/Plugins/Input/in_mp3/WasabiMetadata.cpp67
-rw-r--r--Src/Plugins/Input/in_mp3/WasabiMetadata.h30
-rw-r--r--Src/Plugins/Input/in_mp3/about.cpp576
-rw-r--r--Src/Plugins/Input/in_mp3/adts.h37
-rw-r--r--Src/Plugins/Input/in_mp3/adts_mp2.cpp400
-rw-r--r--Src/Plugins/Input/in_mp3/adts_mp2.h58
-rw-r--r--Src/Plugins/Input/in_mp3/adts_vlb.cpp153
-rw-r--r--Src/Plugins/Input/in_mp3/adts_vlb.h37
-rw-r--r--Src/Plugins/Input/in_mp3/apev2.cpp169
-rw-r--r--Src/Plugins/Input/in_mp3/apev2.h46
-rw-r--r--Src/Plugins/Input/in_mp3/api__in_mp3.h16
-rw-r--r--Src/Plugins/Input/in_mp3/config.cpp685
-rw-r--r--Src/Plugins/Input/in_mp3/config.h51
-rw-r--r--Src/Plugins/Input/in_mp3/giofile.cpp2475
-rw-r--r--Src/Plugins/Input/in_mp3/giofile.h306
-rw-r--r--Src/Plugins/Input/in_mp3/graphics/filterWater.cpp452
-rw-r--r--Src/Plugins/Input/in_mp3/graphics/filterWater.h47
-rw-r--r--Src/Plugins/Input/in_mp3/graphics/image.cpp209
-rw-r--r--Src/Plugins/Input/in_mp3/graphics/image.h61
-rw-r--r--Src/Plugins/Input/in_mp3/graphics/imageFilters.cpp91
-rw-r--r--Src/Plugins/Input/in_mp3/graphics/imageFilters.h17
-rw-r--r--Src/Plugins/Input/in_mp3/id3.cpp556
-rw-r--r--Src/Plugins/Input/in_mp3/id3.h43
-rw-r--r--Src/Plugins/Input/in_mp3/id3dlg.cpp1071
-rw-r--r--Src/Plugins/Input/in_mp3/ifc_mpeg_stream_reader.h45
-rw-r--r--Src/Plugins/Input/in_mp3/in_mp3.rc503
-rw-r--r--Src/Plugins/Input/in_mp3/in_mp3.sln102
-rw-r--r--Src/Plugins/Input/in_mp3/in_mp3.vcxproj386
-rw-r--r--Src/Plugins/Input/in_mp3/in_mp3.vcxproj.filters218
-rw-r--r--Src/Plugins/Input/in_mp3/main.cpp342
-rw-r--r--Src/Plugins/Input/in_mp3/main.h123
-rw-r--r--Src/Plugins/Input/in_mp3/mp4.cpp44
-rw-r--r--Src/Plugins/Input/in_mp3/mpegutil.cpp372
-rw-r--r--Src/Plugins/Input/in_mp3/mpegutil.h9
-rw-r--r--Src/Plugins/Input/in_mp3/pdtimer.cpp24
-rw-r--r--Src/Plugins/Input/in_mp3/pdtimer.h9
-rw-r--r--Src/Plugins/Input/in_mp3/proxydt.h16
-rw-r--r--Src/Plugins/Input/in_mp3/resource.h253
-rw-r--r--Src/Plugins/Input/in_mp3/resources/aboutMP3_256.bmpbin0 -> 24650 bytes
-rw-r--r--Src/Plugins/Input/in_mp3/resources/hand.curbin0 -> 326 bytes
-rw-r--r--Src/Plugins/Input/in_mp3/resources/llama.bmpbin0 -> 35118 bytes
-rw-r--r--Src/Plugins/Input/in_mp3/resources/logoCodTech.90x42.bmpbin0 -> 4942 bytes
-rw-r--r--Src/Plugins/Input/in_mp3/resources/logoIIS_127x42.bmpbin0 -> 6454 bytes
-rw-r--r--Src/Plugins/Input/in_mp3/resources/logoId3v2_44x42.bmpbin0 -> 2926 bytes
-rw-r--r--Src/Plugins/Input/in_mp3/resources/logoMp3surround_101x42.bmpbin0 -> 12654 bytes
-rw-r--r--Src/Plugins/Input/in_mp3/resources/logoWA.bmpbin0 -> 3474 bytes
-rw-r--r--Src/Plugins/Input/in_mp3/tagz.cpp953
-rw-r--r--Src/Plugins/Input/in_mp3/tagz.h26
-rw-r--r--Src/Plugins/Input/in_mp3/titlelist.cpp62
-rw-r--r--Src/Plugins/Input/in_mp3/todo.txt97
-rw-r--r--Src/Plugins/Input/in_mp3/uvox_3901.cpp79
-rw-r--r--Src/Plugins/Input/in_mp3/uvox_3901.h20
-rw-r--r--Src/Plugins/Input/in_mp3/uvox_3902.cpp77
-rw-r--r--Src/Plugins/Input/in_mp3/uvox_3902.h20
-rw-r--r--Src/Plugins/Input/in_mp3/version.rc239
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(&params);
+ 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(&params);
+ if (!status)
+ {
+ needsync = 0;
+ }
+ else if (file->MPEGStream_EOF())
+ {
+ return adts::ENDOFFILE;
+ }
+ }
+
+ if (!needsync)
+ {
+ int status = decoder->DecodeFrame(&params);
+
+ 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
new file mode 100644
index 00000000..bc3629f9
--- /dev/null
+++ b/Src/Plugins/Input/in_mp3/resources/aboutMP3_256.bmp
Binary files differ
diff --git a/Src/Plugins/Input/in_mp3/resources/hand.cur b/Src/Plugins/Input/in_mp3/resources/hand.cur
new file mode 100644
index 00000000..614403a0
--- /dev/null
+++ b/Src/Plugins/Input/in_mp3/resources/hand.cur
Binary files differ
diff --git a/Src/Plugins/Input/in_mp3/resources/llama.bmp b/Src/Plugins/Input/in_mp3/resources/llama.bmp
new file mode 100644
index 00000000..5f2a6ec1
--- /dev/null
+++ b/Src/Plugins/Input/in_mp3/resources/llama.bmp
Binary files differ
diff --git a/Src/Plugins/Input/in_mp3/resources/logoCodTech.90x42.bmp b/Src/Plugins/Input/in_mp3/resources/logoCodTech.90x42.bmp
new file mode 100644
index 00000000..de49ffd5
--- /dev/null
+++ b/Src/Plugins/Input/in_mp3/resources/logoCodTech.90x42.bmp
Binary files differ
diff --git a/Src/Plugins/Input/in_mp3/resources/logoIIS_127x42.bmp b/Src/Plugins/Input/in_mp3/resources/logoIIS_127x42.bmp
new file mode 100644
index 00000000..3dd674ca
--- /dev/null
+++ b/Src/Plugins/Input/in_mp3/resources/logoIIS_127x42.bmp
Binary files differ
diff --git a/Src/Plugins/Input/in_mp3/resources/logoId3v2_44x42.bmp b/Src/Plugins/Input/in_mp3/resources/logoId3v2_44x42.bmp
new file mode 100644
index 00000000..851493ae
--- /dev/null
+++ b/Src/Plugins/Input/in_mp3/resources/logoId3v2_44x42.bmp
Binary files differ
diff --git a/Src/Plugins/Input/in_mp3/resources/logoMp3surround_101x42.bmp b/Src/Plugins/Input/in_mp3/resources/logoMp3surround_101x42.bmp
new file mode 100644
index 00000000..aba97616
--- /dev/null
+++ b/Src/Plugins/Input/in_mp3/resources/logoMp3surround_101x42.bmp
Binary files differ
diff --git a/Src/Plugins/Input/in_mp3/resources/logoWA.bmp b/Src/Plugins/Input/in_mp3/resources/logoWA.bmp
new file mode 100644
index 00000000..41266529
--- /dev/null
+++ b/Src/Plugins/Input/in_mp3/resources/logoWA.bmp
Binary files differ
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