diff options
Diffstat (limited to 'Src/Plugins/Input/in_flac')
30 files changed, 4789 insertions, 0 deletions
diff --git a/Src/Plugins/Input/in_flac/About.cpp b/Src/Plugins/Input/in_flac/About.cpp new file mode 100644 index 00000000..ff5f4ccf --- /dev/null +++ b/Src/Plugins/Input/in_flac/About.cpp @@ -0,0 +1,54 @@ +/* +** Copyright © 2007-2014 Winamp SA +** +** This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held +** liable for any damages arising from the use of this software. +** +** Permission is granted to anyone to use this software for any purpose, including commercial applications, and to +** alter it and redistribute it freely, subject to the following restrictions: +** +** 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. +** If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +** +** 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +** +** 3. This notice may not be removed or altered from any source distribution. +** +** Author: Ben Allison benski@winamp.com +** Created: March 1, 2007 +** +*/ + +#include "main.h" +#include "../Agave/Language/api_language.h" +#include "resource.h" +#include <FLAC/all.h> +#include <strsafe.h> + +static HMODULE libflac=0; +static char defaultVersionString[64]; +static const char **versionString=0; +static const char *GetFLACVersion() +{ + return "1.4.2"; +} + +int DoAboutMessageBox(HWND parent, wchar_t* title, wchar_t* message) +{ + MSGBOXPARAMSW msgbx = {sizeof(MSGBOXPARAMSW),0}; + msgbx.lpszText = message; + msgbx.lpszCaption = title; + msgbx.lpszIcon = MAKEINTRESOURCEW(102); + msgbx.hInstance = GetModuleHandle(0); + msgbx.dwStyle = MB_USERICON; + msgbx.hwndOwner = parent; + return MessageBoxIndirectW(&msgbx); +} + +void About(HWND hwndParent) +{ + wchar_t message[1024] = {0}; + StringCchPrintfW(message, 1024, WASABI_API_LNGSTRINGW(IDS_ABOUT_TEXT), + plugin.description, __DATE__, GetFLACVersion()); + DoAboutMessageBox(hwndParent,(wchar_t*)plugin.description,message); +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_flac/AlbumArt.cpp b/Src/Plugins/Input/in_flac/AlbumArt.cpp new file mode 100644 index 00000000..22944db3 --- /dev/null +++ b/Src/Plugins/Input/in_flac/AlbumArt.cpp @@ -0,0 +1,270 @@ +/* +** Copyright © 2007-2014 Winamp SA +** +** This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held +** liable for any damages arising from the use of this software. +** +** Permission is granted to anyone to use this software for any purpose, including commercial applications, and to +** alter it and redistribute it freely, subject to the following restrictions: +** +** 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. +** If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +** +** 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +** +** 3. This notice may not be removed or altered from any source distribution. +** +** Author: Ben Allison benski@winamp.com +** Created: July 25, 2007 +** +*/ + +#include "main.h" +#include "api__in_flv.h" +#include "Metadata.h" +#include "../nu/AutoWide.h" +#include "AlbumArt.h" +#include <shlwapi.h> +#include <strsafe.h> + +bool FLAC_AlbumArtProvider::IsMine(const wchar_t *filename) +{ + const wchar_t *extension = PathFindExtensionW(filename); + if (extension && *extension) + { + wchar_t exts[128] = {0}; + GetPrivateProfileStringW(L"in_flac", L"extensions", DEFAULT_EXTENSIONSW, exts, 128, winampINI); + + extension++; + wchar_t *b = exts; + wchar_t *c; + 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; +} + +int FLAC_AlbumArtProvider::ProviderType() +{ + return ALBUMARTPROVIDER_TYPE_EMBEDDED; +} + +bool NameToAPICType(const wchar_t *name, FLAC__StreamMetadata_Picture_Type &num) +{ + if (!name || !*name) // default to cover + num=FLAC__STREAM_METADATA_PICTURE_TYPE_FRONT_COVER; + else if (!_wcsicmp(name, L"fileicon")) // 32x32 pixels 'file icon' (PNG only) + num=FLAC__STREAM_METADATA_PICTURE_TYPE_FILE_ICON_STANDARD; + else if (!_wcsicmp(name, L"icon")) // Other file icon + num=FLAC__STREAM_METADATA_PICTURE_TYPE_FILE_ICON; + else if (!_wcsicmp(name, L"cover")) // Cover (front) + num=FLAC__STREAM_METADATA_PICTURE_TYPE_FRONT_COVER; + else if (!_wcsicmp(name, L"back")) // Cover (back) + num=FLAC__STREAM_METADATA_PICTURE_TYPE_BACK_COVER; + else if (!_wcsicmp(name, L"leaflet")) // Leaflet page + num=FLAC__STREAM_METADATA_PICTURE_TYPE_LEAFLET_PAGE; + else if (!_wcsicmp(name, L"media")) // Media (e.g. lable side of CD) + num=FLAC__STREAM_METADATA_PICTURE_TYPE_MEDIA; + else if (!_wcsicmp(name, L"leadartist")) //Lead artist/lead performer/soloist + num=FLAC__STREAM_METADATA_PICTURE_TYPE_LEAD_ARTIST; + else if (!_wcsicmp(name, L"artist")) // Artist/performer + num=FLAC__STREAM_METADATA_PICTURE_TYPE_ARTIST; + else if (!_wcsicmp(name, L"conductor")) // Conductor + num=FLAC__STREAM_METADATA_PICTURE_TYPE_CONDUCTOR; + else if (!_wcsicmp(name, L"band")) // Band/Orchestra + num=FLAC__STREAM_METADATA_PICTURE_TYPE_BAND; + else if (!_wcsicmp(name, L"composer")) // Composer + num=FLAC__STREAM_METADATA_PICTURE_TYPE_COMPOSER; + else if (!_wcsicmp(name, L"lyricist")) // Lyricist/text writer + num=FLAC__STREAM_METADATA_PICTURE_TYPE_LYRICIST; + else if (!_wcsicmp(name, L"location")) // Recording Location + num=FLAC__STREAM_METADATA_PICTURE_TYPE_RECORDING_LOCATION; + else if (!_wcsicmp(name, L"recording")) // During recording + num=FLAC__STREAM_METADATA_PICTURE_TYPE_DURING_RECORDING; + else if (!_wcsicmp(name, L"performance")) // During performance + num=FLAC__STREAM_METADATA_PICTURE_TYPE_DURING_PERFORMANCE; + else if (!_wcsicmp(name, L"preview")) // Movie/video screen capture + num=FLAC__STREAM_METADATA_PICTURE_TYPE_VIDEO_SCREEN_CAPTURE; + else if (!_wcsicmp(name, L"fish")) // A bright coloured fish + num=FLAC__STREAM_METADATA_PICTURE_TYPE_FISH; + else if (!_wcsicmp(name, L"illustration")) // Illustration + num=FLAC__STREAM_METADATA_PICTURE_TYPE_ILLUSTRATION; + else if (!_wcsicmp(name, L"artistlogo")) // Band/artist logotype + num=FLAC__STREAM_METADATA_PICTURE_TYPE_BAND_LOGOTYPE; + else if (!_wcsicmp(name, L"publisherlogo")) // Publisher/Studio logotype + num=FLAC__STREAM_METADATA_PICTURE_TYPE_PUBLISHER_LOGOTYPE; + else + return false; + return true; +} + +int FLAC_AlbumArtProvider::GetAlbumArtData(const wchar_t *filename, const wchar_t *type, void **bits, size_t *len, wchar_t **mimeType) +{ + FLACMetadata metadata; + + if (metadata.Open(filename)) + { + FLAC__StreamMetadata_Picture_Type pictype; + if (NameToAPICType(type, pictype)) + { + if (metadata.GetPicture(pictype, bits, len, mimeType)) + return ALBUMARTPROVIDER_SUCCESS; + } + } + + return ALBUMARTPROVIDER_FAILURE; +} + +int FLAC_AlbumArtProvider::SetAlbumArtData(const wchar_t *filename, const wchar_t *type, void *bits, size_t len, const wchar_t *mimeType) +{ + if (!info || info && _wcsicmp(filename, info->filename)) + { + FLACMetadata metadata; + if (metadata.Open(filename)) + { + FLAC__StreamMetadata_Picture_Type pictype; + if (NameToAPICType(type, pictype)) + { + if (metadata.SetPicture(pictype, bits, len, mimeType, 0/*TODO*/, 0/*TODO*/)) + { + if (metadata.Save(filename)) + return ALBUMARTPROVIDER_SUCCESS; + else + return ALBUMARTPROVIDER_FAILURE; + } + } + } + } + else + { + FLAC__StreamMetadata_Picture_Type pictype; + if (NameToAPICType(type, pictype)) + { + if (info->metadata.SetPicture(pictype, bits, len, mimeType, 0/*TODO*/, 0/*TODO*/)) + { + return ALBUMARTPROVIDER_SUCCESS; + } + } + } + + return ALBUMARTPROVIDER_FAILURE; +} + +int FLAC_AlbumArtProvider::DeleteAlbumArt(const wchar_t *filename, const wchar_t *type) +{ + if (!info || info && _wcsicmp(filename, info->filename)) + { + FLACMetadata metadata; + if (metadata.Open(filename)) + { + FLAC__StreamMetadata_Picture_Type pictype; + if (NameToAPICType(type, pictype)) + { + if (info->metadata.RemovePicture(pictype)) + { + if (info->metadata.Save(filename)) + return ALBUMARTPROVIDER_SUCCESS; + else + return ALBUMARTPROVIDER_FAILURE; + } + } + } + } + else + { + FLAC__StreamMetadata_Picture_Type pictype; + if (NameToAPICType(type, pictype)) + { + if (info->metadata.RemovePicture(pictype)) + { + return ALBUMARTPROVIDER_SUCCESS; + } + } + } + + return ALBUMARTPROVIDER_FAILURE; +} + +#define CBCLASS FLAC_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 FLAC_AlbumArtProvider albumArtProvider; + +// {622C3B42-866E-4935-AA52-3B456AE8B036} +static const GUID flac_albumartproviderGUID = + { 0x622c3b42, 0x866e, 0x4935, { 0xaa, 0x52, 0x3b, 0x45, 0x6a, 0xe8, 0xb0, 0x36 } }; + +FOURCC AlbumArtFactory::GetServiceType() +{ + return svc_albumArtProvider::SERVICETYPE; +} + +const char *AlbumArtFactory::GetServiceName() +{ + return "FLAC Album Art Provider"; +} + +GUID AlbumArtFactory::GetGUID() +{ + return flac_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; +} + +#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; +#undef CBCLASS
\ No newline at end of file diff --git a/Src/Plugins/Input/in_flac/AlbumArt.h b/Src/Plugins/Input/in_flac/AlbumArt.h new file mode 100644 index 00000000..11975e02 --- /dev/null +++ b/Src/Plugins/Input/in_flac/AlbumArt.h @@ -0,0 +1,38 @@ +#ifndef NULLSOFT_IN_FLAC_ALBUMART_H +#define NULLSOFT_IN_FLAC_ALBUMART_H + +#include "../Agave/AlbumArt/svc_albumArtProvider.h" + +class FLAC_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> + +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_flac/DecodeThread.cpp b/Src/Plugins/Input/in_flac/DecodeThread.cpp new file mode 100644 index 00000000..38c7fcb6 --- /dev/null +++ b/Src/Plugins/Input/in_flac/DecodeThread.cpp @@ -0,0 +1,719 @@ +/* +** Copyright (C) 2007-2011 Nullsoft, Inc. +** +** This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held +** liable for any damages arising from the use of this software. +** +** Permission is granted to anyone to use this software for any purpose, including commercial applications, and to +** alter it and redistribute it freely, subject to the following restrictions: +** +** 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. +** If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +** +** 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +** +** 3. This notice may not be removed or altered from any source distribution. +** +** Author: Ben Allison benski@winamp.com +** Created: March 1, 2007 +** +*/ +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#include "main.h" +#include <windows.h> +#include <math.h> +#include <assert.h> +#include <locale.h> +#include <FLAC/all.h> +#include "StreamFileWin32.h" +#include "../Winamp/wa_ipc.h" +#include "QuickBuf.h" +#include "api__in_flv.h" +#include "../nu/AudioOutput.h" +#include "../Agave/Language/api_language.h" +#include "FLACFileCallbacks.h" +#include "nx/nxpath.h" + +int m_force_seek = -1; // el hacko grande +const DWORD PAUSE_TIMEOUT = 100; // number of milliseconds to sleep for when paused + +static bool paused = false; + +// could probably move this inside client_data +int realbits; +int bps; +int samplerate; +int channels; +volatile int currentSongLength=-1000; +int averageBitrate; + +// if input plugins weren't single-instance only, we could put this in thread-local-storage +static FLAC__StreamDecoder *decoder = 0; +static uint64_t fileSize = 0; +static FLAC__uint64 decodePosition = 0; + +double gain = 1.0; +static double buffer_scale=1.0; + +class FLACWait +{ +public: + int WaitOrAbort(int time_in_ms) + { + if (WaitForSingleObject(killswitch, time_in_ms) == WAIT_OBJECT_0) + return 1; + return 0; + } +}; + + +volatile int bufferCount=0; +static void Buffering(int bufStatus, const wchar_t *displayString) +{ + if (bufStatus < 0 || bufStatus > 100) + return; + + char tempdata[75*2] = {0, }; + + int csa = plugin.SAGetMode(); + if (csa & 1) + { + for (int x = 0; x < bufStatus*75 / 100; x ++) + tempdata[x] = x * 16 / 75; + } + else if (csa&2) + { + int offs = (csa & 1) ? 75 : 0; + int x = 0; + while (x < bufStatus*75 / 100) + { + tempdata[offs + x++] = -6 + x * 14 / 75; + } + while (x < 75) + { + tempdata[offs + x++] = 0; + } + } + else if (csa == 4) + { + tempdata[0] = tempdata[1] = (bufStatus * 127 / 100); + } + if (csa) plugin.SAAdd(tempdata, ++bufferCount, (csa == 3) ? 0x80000003 : csa); + + /* + TODO + wchar_t temp[64] = {0}; + StringCchPrintf(temp, 64, L"%s: %d%%",displayString, bufStatus); + SetStatus(temp); + */ + //SetVideoStatusText(temp); // TODO: find a way to set the old status back + // videoOutput->notifyBufferState(static_cast<int>(bufStatus*2.55f)); +} + +static nu::AudioOutput<FLACWait> audio_output(&plugin); + +#pragma region Truncaters +inline static void clip(double &x, double a, double b) +{ + double x1 = fabs(x - a); + double x2 = fabs(x - b); + x = x1 + (a + b); + x -= x2; + x *= 0.5; +} + +static void InterleaveAndTruncate32(const FLAC__int32 *const buffer[], void *_output, int bps, int channels, int blocksize, double gain) +{ + // TODO: this can be sped up significantly + FLAC__int32 *output = (FLAC__int32 *)_output; + + for (int b = 0;b < blocksize;b++) + { + for (int c = 0;c < channels;c++) + { + double appliedGain = gain * (double)buffer[c][b]; + // TODO: add dither + clip(appliedGain, -2147483648., 2147483647.); + *output = (FLAC__int32)appliedGain; + output++; + } + } +} + +static void InterleaveAndTruncate24(const FLAC__int32 *const buffer[], void *_output, int bps, int channels, int blocksize, double gain) +{ + char *output = (char *)_output; + + for (int b = 0;b < blocksize;b++) + { + for (int c = 0;c < channels;c++) + { + double appliedGain = gain * (double)buffer[c][b]; + // TODO: add dither + clip(appliedGain, -8388608., 8388607.); + FLAC__int32 sample = (FLAC__int32)appliedGain; + + // little endian specific code + output[0] = (unsigned char)(sample); + output[1] = (unsigned char)(sample >> 8); + output[2] = (unsigned char)(sample >> 16); + output += 3; + } + } +} + +static void InterleaveAndTruncate16(const FLAC__int32 *const buffer[], void *_output, int bps, int channels, int blocksize, double gain) +{ + short *output = (short *)_output; + + for (int b = 0;b < blocksize;b++) + { + for (int c = 0;c < channels;c++) + { + double appliedGain = gain * (double)buffer[c][b]; + // TODO: add dither + clip(appliedGain, -32768., 32767.); + FLAC__int32 sample = (FLAC__int32)appliedGain; + + *output = (short) sample ; + output ++; + } + } +} + +static void InterleaveAndTruncate8(const FLAC__int32 *const buffer[], void *_output, int bps, int channels, int blocksize, double gain) +{ + unsigned char *output = (unsigned char *)_output; + + for (int b = 0;b < blocksize;b++) + { + for (int c = 0;c < channels;c++) + { + double appliedGain = gain * (double)buffer[c][b]; + // TODO: add dither + clip(appliedGain, -128., 127.); + FLAC__int32 sample = (FLAC__int32)appliedGain; + + *output = (unsigned char)(sample + 128); + output++; + } + } +} + +/* --- Versions without gain adjustment --- */ + +static void InterleaveAndTruncate32(const FLAC__int32 *const buffer[], void *_output, int bps, int channels, int blocksize) +{ + FLAC__int32 *output = (FLAC__int32 *)_output; + + for (int b = 0;b < blocksize;b++) + { + for (int c = 0;c < channels;c++) + { + *output = buffer[c][b]; + output++; + } + } +} + +static void InterleaveAndTruncate24(const FLAC__int32 *const buffer[], void *_output, int bps, int channels, int blocksize) +{ + char *output = (char *)_output; + + for (int b = 0;b < blocksize;b++) + { + for (int c = 0;c < channels;c++) + { + FLAC__int32 sample = buffer[c][b]; + + // little endian specific code + output[0] = (unsigned char)(sample); + output[1] = (unsigned char)(sample >> 8); + output[2] = (unsigned char)(sample >> 16); + output += 3; + } + } +} + +static void InterleaveAndTruncate16(const FLAC__int32 *const buffer[], void *_output, int bps, int channels, int blocksize) +{ + short *output = (short *)_output; + + for (int b = 0;b < blocksize;b++) + { + for (int c = 0;c < channels;c++) + { + *output = (short) buffer[c][b]; + output ++; + } + } +} + +static void InterleaveAndTruncate8(const FLAC__int32 *const buffer[], void *_output, int bps, int channels, int blocksize) +{ + unsigned char *output = (unsigned char *)_output; + + for (int b = 0;b < blocksize;b++) + { + for (int c = 0;c < channels;c++) + { + *output = (unsigned char)(buffer[c][b] + 128); + output++; + } + } +} + +void InterleaveAndTruncate(const FLAC__int32 *const buffer[], void *_output, int bps, int channels, int blocksize, double gain) +{ + if (gain == 1.0) // if it's EXACTLY 1.0, i.e. from a hardcoded value. not meant to be a "RG happens to be 1.0" check + { + switch(bps) + { + case 8: + InterleaveAndTruncate8(buffer, _output, bps, channels, blocksize); + break; + case 16: + InterleaveAndTruncate16(buffer, _output, bps, channels, blocksize); + break; + case 24: + InterleaveAndTruncate24(buffer, _output, bps, channels, blocksize); + break; + case 32: + InterleaveAndTruncate32(buffer, _output, bps, channels, blocksize); + break; + } + } + else // apply replay gain + { + switch(bps) + { + case 8: + InterleaveAndTruncate8(buffer, _output, bps, channels, blocksize, gain); + break; + case 16: + InterleaveAndTruncate16(buffer, _output, bps, channels, blocksize, gain); + break; + case 24: + InterleaveAndTruncate24(buffer, _output, bps, channels, blocksize, gain); + break; + case 32: + InterleaveAndTruncate32(buffer, _output, bps, channels, blocksize, gain); + break; + } + } +} +#pragma endregion + +QuickBuf output; +FLAC__uint64 lastoutputtime; +static FLAC__StreamDecoderWriteStatus OnAudio(const FLAC__StreamDecoder *decoder, const FLAC__Frame *frame, const FLAC__int32 *const buffer[], void *client_data) +{ + // TODO: if frame bps/samplerate/channels doesn't equal our own, we'll probably have to close & re-open the audio output + // mux buffer into interleaved samples + FLAC__uint64 newPosition; + FLAC__stream_decoder_get_decode_position(decoder, &newPosition); + FLAC__uint64 delta = newPosition - decodePosition; + decodePosition = newPosition; + if (!config_average_bitrate) + plugin.SetInfo((int)(delta / (125.*frame->header.blocksize / samplerate)), -1, -1, -1); + else if (fixBitrate) + { + fixBitrate = false; + plugin.SetInfo(averageBitrate, -1, -1, -1); + } + + if (frame->header.number_type == FLAC__FRAME_NUMBER_TYPE_SAMPLE_NUMBER) + lastoutputtime = 1000ULL*frame->header.number.sample_number / (FLAC__uint64)samplerate; + else + lastoutputtime = 0; + + int byteLength = (bps / 8) * channels * frame->header.blocksize; + output.Reserve(byteLength*2); + InterleaveAndTruncate(buffer, output, bps, channels, frame->header.blocksize, gain); + + audio_output.Write(output, byteLength); + return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE; +} + +static int GetBits(int incoming) +{ + int bits = AGAVE_API_CONFIG->GetUnsigned(playbackConfigGroupGUID, L"bits", 16); + if (bits > 16 && AGAVE_API_CONFIG->GetBool(playbackConfigGroupGUID, L"replaygain", false)) + return bits; + + return min(bits, incoming); +} + +static double GetGain(const FLAC__StreamMetadata *metadata) +{ + if (AGAVE_API_CONFIG->GetBool(playbackConfigGroupGUID, L"replaygain", false)) + { + if (metadata) + { + int gainPos = -1, peakPos = -1; + switch (AGAVE_API_CONFIG->GetUnsigned(playbackConfigGroupGUID, L"replaygain_source", 0)) + { + case 0: // track + gainPos = FLAC__metadata_object_vorbiscomment_find_entry_from(metadata, 0, "REPLAYGAIN_TRACK_GAIN"); + if (gainPos < 0 && !AGAVE_API_CONFIG->GetBool(playbackConfigGroupGUID, L"replaygain_preferred_only", false)) + gainPos = FLAC__metadata_object_vorbiscomment_find_entry_from(metadata, 0, "REPLAYGAIN_ALBUM_GAIN"); + + peakPos = FLAC__metadata_object_vorbiscomment_find_entry_from(metadata, 0, "REPLAYGAIN_TRACK_PEAK"); + if (peakPos < 0 && !AGAVE_API_CONFIG->GetBool(playbackConfigGroupGUID, L"replaygain_preferred_only", false)) + peakPos = FLAC__metadata_object_vorbiscomment_find_entry_from(metadata, 0, "REPLAYGAIN_ALBUM_PEAK"); + + break; + case 1: + gainPos = FLAC__metadata_object_vorbiscomment_find_entry_from(metadata, 0, "REPLAYGAIN_ALBUM_GAIN"); + if (gainPos < 0 && !AGAVE_API_CONFIG->GetBool(playbackConfigGroupGUID, L"replaygain_preferred_only", false)) + gainPos = FLAC__metadata_object_vorbiscomment_find_entry_from(metadata, 0, "REPLAYGAIN_TRACK_GAIN"); + + peakPos = FLAC__metadata_object_vorbiscomment_find_entry_from(metadata, 0, "REPLAYGAIN_ALBUM_PEAK"); + if (peakPos < 0 && !AGAVE_API_CONFIG->GetBool(playbackConfigGroupGUID, L"replaygain_preferred_only", false)) + peakPos = FLAC__metadata_object_vorbiscomment_find_entry_from(metadata, 0, "REPLAYGAIN_TRACK_PEAK"); + break; + } + + double dB = 0, peak = 1.0; + _locale_t C_locale = WASABI_API_LNG->Get_C_NumericLocale(); + if (gainPos >= 0) + { + const char *entry = (const char *)metadata->data.vorbis_comment.comments[gainPos].entry; + const char *value = strchr(entry, '='); // find the first equal + if (value++) + { + if (value[0] == '+') + dB = _atof_l(&value[1], C_locale); + else + dB = _atof_l(value, C_locale); + } + } + else + { + dB = AGAVE_API_CONFIG->GetFloat(playbackConfigGroupGUID, L"non_replaygain", -6.0); + return pow(10.0, dB / 20.0); + } + + if (peakPos >= 0) + { + const char *entry = (const char *)metadata->data.vorbis_comment.comments[peakPos].entry; + const char *value = strchr(entry, '='); // find the first equal + if (value++) + { + peak = _atof_l(value, C_locale); + } + } + + switch (AGAVE_API_CONFIG->GetUnsigned(playbackConfigGroupGUID, L"replaygain_mode", 1)) + { + case 0: // apply gain + return pow(10.0, dB / 20.0); + break; + case 1: // apply gain, but don't clip + return min(pow(10.0, dB / 20.0), 1.0 / peak); + break; + case 2: // normalize + return 1.0 / peak; + break; + case 3: // prevent clipping + if (peak > 1.0) + return 1.0 / peak; + else + return 1.0; + } + } + else + { + double dB = AGAVE_API_CONFIG->GetFloat(playbackConfigGroupGUID, L"non_replaygain", -6.0); + return pow(10.0, dB / 20.0); + } + } + + return 1.0; +} + +static void OnMetadata(const FLAC__StreamDecoder *decoder, const FLAC__StreamMetadata *metadata, void *client_data) +{ + switch (metadata->type) + { + case FLAC__METADATA_TYPE_STREAMINFO: + { + realbits = metadata->data.stream_info.bits_per_sample; + channels = metadata->data.stream_info.channels; + samplerate = metadata->data.stream_info.sample_rate; + bps = GetBits(metadata->data.stream_info.bits_per_sample); + gain = GetGain(0) * pow(2., (double)(bps - realbits)); + if (metadata->data.stream_info.total_samples) + { + currentSongLength = (int)((metadata->data.stream_info.total_samples*1000ULL)/(uint64_t)samplerate); + averageBitrate = (int)(fileSize / (125 * metadata->data.stream_info.total_samples / (__int64)samplerate)); + } + else + { + currentSongLength=-1000; + averageBitrate=0; + } + } + break; + case FLAC__METADATA_TYPE_VORBIS_COMMENT: + gain = GetGain(metadata) * pow(2., (double)(bps - realbits)); + break; + } +} + +static void OnError(const FLAC__StreamDecoder *decoder, FLAC__StreamDecoderErrorStatus status, void *client_data) +{ + //client_data = client_data; // dummy line so i can set a breakpoint +} + +void CALLBACK APCPause(ULONG_PTR data) +{ + paused = !!data; + plugin.outMod->Pause(!!paused); +} + +void CALLBACK APCSeek(ULONG_PTR data) +{ + // TODO: break out of end-of-file handling if necessary + buffer_scale=1.0; + int time_in_ms = (int)data; + lastoutputtime=time_in_ms; // cheating a bit here :) + audio_output.Flush(time_in_ms); + __int64 frames = Int32x32To64(time_in_ms, samplerate) / 1000; + FLAC__StreamDecoderState state = FLAC__stream_decoder_get_state(decoder); + plugin.outMod->Pause(0); + FLAC__stream_decoder_seek_absolute(decoder, frames); + plugin.outMod->Pause(paused); + if (FLAC__stream_decoder_get_state(decoder) == FLAC__STREAM_DECODER_SEEK_ERROR) + { + FLAC__stream_decoder_flush(decoder); + } +} + +static const double prebuffer_seconds = 0.5; +static bool DoBuffering(nx_file_t file) +{ + // TODO: check for disconnect, etc. + + // anything less than half-a-second and we'll rebuffer. but when we rebuffer, we'll get 2 seconds worth of audio + // also, every time we're forced to re-buffer, we'll double the buffer amount + + uint64_t required; + if (averageBitrate > 0) + { + required = (uint64_t)((double)averageBitrate * 1000.0 * buffer_scale * prebuffer_seconds / 8.0); + } + else /* no bitrate specified, let's assume 1000kbps */ + { + required = (uint64_t)(1000.0 * 1000.0 * buffer_scale * prebuffer_seconds / 8.0); + } + + uint64_t available; + if (NXFileProgressiveDownloaderAvailable(file, required, &available) == NErr_True) + return true; + + Buffering(0, 0); + bufferCount=lastoutputtime; + required *= 4; + buffer_scale *= 2.0; + + while (NXFileProgressiveDownloaderAvailable(file, required, &available) == NErr_False) + { + int percent = (int)((double)available * 100.0 / (double)required); + if (percent <= 0) + percent=1; + if (percent > 99) + percent=99; + + Buffering(percent, 0); + if (WaitForSingleObject(killswitch, 10) == WAIT_OBJECT_0) + { + return false; + } + } + Buffering(100, 0); + bufferCount=0; + return true; +} + +extern HANDLE threadStarted; +DWORD CALLBACK FLACThread(LPVOID param) +{ + buffer_scale=1.0; + bool streaming=false; + nx_file_t file; + FLACClientData state; + audio_output.Init(plugin.outMod); + paused=false; + SetEvent(threadStarted); + nx_uri_t filename = (nx_uri_t)param; + decodePosition = 0; + gain = 1.0; + bufferCount = 0; + decoder = FLAC__stream_decoder_new(); + if (decoder == 0) + { + if (WaitForSingleObject(killswitch, 200) != WAIT_OBJECT_0) + PostMessage(plugin.hMainWindow, WM_WA_MPEG_EOF, 0, 0); + + free(filename); + return 0; + } + + FLAC__stream_decoder_set_md5_checking(decoder, false); + FLAC__stream_decoder_set_metadata_respond(decoder, FLAC__METADATA_TYPE_VORBIS_COMMENT); + + plugin.is_seekable = 1; + + + int ret; + + if (NXPathIsURL(filename) == NErr_True) + { + // Display a window to request to download + // + MessageBox(NULL, L"Cannot stream flac file, please download it", NULL, 0); + /* + char agent[256] = { 0 }; + snprintf(agent, 256, "User-Agent: %S/%S", "Winamp", "5.9.1"); + ret = NXFileOpenProgressiveDownloader(&file, filename, nx_file_FILE_read_binary, 0, agent); // TODO: calculate real user agent + */ + return NErr_Disabled; + } + else + { + ret = NXFileOpenFile(&file, filename, nx_file_FILE_read_binary); + } + + if (ret != NErr_Success) + { + FLAC__stream_decoder_delete(decoder); + if (WaitForSingleObject(killswitch, 200) != WAIT_OBJECT_0) + PostMessage(plugin.hMainWindow, WM_WA_MPEG_EOF, 0, 0); + + NXURIRelease(filename); + return 0; + } + + state.SetFile(file); + + NXFileLength(file, &fileSize); + + if (FLAC__stream_decoder_init_stream( + decoder, + FLAC_NXFile_Read, + FLAC_NXFile_Seek, + FLAC_NXFile_Tell, + FLAC_NXFile_Length, + FLAC_NXFile_EOF, + OnAudio, + OnMetadata, // or NULL + OnError, + &state + ) != FLAC__STREAM_DECODER_INIT_STATUS_OK) + { + FLAC__stream_decoder_delete(decoder); + NXFileRelease(file); + if (WaitForSingleObject(killswitch, 200) != WAIT_OBJECT_0) + PostMessage(plugin.hMainWindow, WM_WA_MPEG_EOF, 0, 0); + + NXURIRelease(filename); + return 0; + } + + FLAC__stream_decoder_process_until_end_of_metadata(decoder); + + + + if (!audio_output.Open(0, channels, samplerate, bps)) + { + FLAC__stream_decoder_finish(decoder); + FLAC__stream_decoder_delete(decoder); + NXFileRelease(file); + if (WaitForSingleObject(killswitch, 200) != WAIT_OBJECT_0) + PostMessage(plugin.hMainWindow, WM_WA_MPEG_EOF, 0, 0); + + NXURIRelease(filename); + return 0; + } + + plugin.SetInfo(averageBitrate, -1, -1, -1); + plugin.outMod->SetVolume(volume); + plugin.outMod->SetPan(pan); + + if (streaming && !DoBuffering(file)) + { + FLAC__stream_decoder_finish(decoder); + FLAC__stream_decoder_delete(decoder); + NXFileRelease(file); + NXURIRelease(filename); + + return 0; + } + + if (m_force_seek != -1) + APCSeek((ULONG_PTR)m_force_seek); + m_force_seek = -1; + + HANDLE events[] = {killswitch}; + DWORD timeout = 0; + while (true) + { + DWORD result = WaitForMultipleObjectsEx(sizeof(events) / sizeof(*events), events, FALSE, timeout, TRUE); + switch (result) + { + case WAIT_OBJECT_0:// kill thread + FLAC__stream_decoder_finish(decoder); + FLAC__stream_decoder_delete(decoder); + NXFileRelease(file); + NXURIRelease(filename); + return 0; + + case WAIT_TIMEOUT: + timeout = paused ? PAUSE_TIMEOUT : 0; + if (streaming && !DoBuffering(file)) + { + FLAC__stream_decoder_finish(decoder); + FLAC__stream_decoder_delete(decoder); + NXFileRelease(file); + NXURIRelease(filename); + + return 0; + } + + if (!paused) + { + FLAC__bool decode_successful = FLAC__stream_decoder_process_single(decoder); + + FLAC__StreamDecoderState FLACstate = FLAC__stream_decoder_get_state(decoder); + + if (FLACstate == FLAC__STREAM_DECODER_END_OF_STREAM) + { + audio_output.Write(0, 0); + if (audio_output.WaitWhilePlaying()) + { + FLAC__stream_decoder_finish(decoder); + FLAC__stream_decoder_delete(decoder); + NXFileRelease(file); + NXURIRelease(filename); + return 0; + } + PostMessage(plugin.hMainWindow, WM_WA_MPEG_EOF, 0, 0); + timeout = INFINITE; // sit and wait for killswitch + } + else if (!decode_successful) + { + // some other error - abort playback + // if we can find FLAC files with errors, we might be able to gracefully handle some errors + PostMessage(plugin.hMainWindow, WM_WA_MPEG_EOF, 0, 0); + timeout = INFINITE; // sit and wait for killswitch + } + } + break; + } + } + + return 0; +} diff --git a/Src/Plugins/Input/in_flac/ExtendedFileInfo.cpp b/Src/Plugins/Input/in_flac/ExtendedFileInfo.cpp new file mode 100644 index 00000000..39405687 --- /dev/null +++ b/Src/Plugins/Input/in_flac/ExtendedFileInfo.cpp @@ -0,0 +1,424 @@ +/* +** Copyright (C) 2007-2011 Nullsoft, Inc. +** +** This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held +** liable for any damages arising from the use of this software. +** +** Permission is granted to anyone to use this software for any purpose, including commercial applications, and to +** alter it and redistribute it freely, subject to the following restrictions: +** +** 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. +** If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +** +** 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +** +** 3. This notice may not be removed or altered from any source distribution. +** +** Author: Ben Allison benski@winamp.com +** Created: March 1, 2007 +** +*/ + +#include "main.h" +#include "Metadata.h" +#include "../nu/ns_wc.h" +#include "../nu/AutoChar.h" +#include "../Winamp/wa_ipc.h" +#include <shlwapi.h> +#include "resource.h" +#include "../Agave/Language/api_language.h" +#include "Stopper.h" +#include <strsafe.h> + +static int FillFileInfo(wchar_t *infoStr, size_t len, FLACMetadata &metadata) +{ + const FLAC__StreamMetadata_StreamInfo *info = metadata.GetStreamInfo(); + if (info) + { + unsigned __int64 length = info->total_samples / info->sample_rate; + StringCchPrintfExW(infoStr, len, &infoStr, &len, 0, WASABI_API_LNGSTRINGW(IDS_LENGTH_IN_SECONDS), length); + StringCchPrintfExW(infoStr, len, &infoStr, &len, 0, WASABI_API_LNGSTRINGW(IDS_CHANNELS), info->channels); + StringCchPrintfExW(infoStr, len, &infoStr, &len, 0, WASABI_API_LNGSTRINGW(IDS_BITS_PER_SAMPLE), info->bits_per_sample); + StringCchPrintfExW(infoStr, len, &infoStr, &len, 0, WASABI_API_LNGSTRINGW(IDS_SAMPLE_RATE), info->sample_rate); + __int64 filesize = metadata.GetFileSize(); + StringCchPrintfExW(infoStr, len, &infoStr, &len, 0, WASABI_API_LNGSTRINGW(IDS_FILE_SIZE_IN_BYTES), filesize); + if (info->total_samples) + { + StringCchPrintfExW(infoStr, len, &infoStr, &len, 0, WASABI_API_LNGSTRINGW(IDS_AVERAGE_BITRATE), filesize / (125*info->total_samples / (__int64)info->sample_rate)); // (125 is 1000/8) + int percent = (int)((100*filesize) / (info->total_samples * (info->bits_per_sample/8) * info->channels)); + StringCchPrintfExW(infoStr, len, &infoStr, &len, 0, WASABI_API_LNGSTRINGW(IDS_COMPRESSION_RATIO), percent); + } + return 1; + } + return 0; +} + +bool KeywordMatch(const char *mainString, const char *keyword) +{ + return !_stricmp(mainString, keyword); +} + +Info *info = 0; +FLACMetadata *getMetadata = 0; +wchar_t *getFileInfoFn = 0; +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; + } +} + +void ResetMetadataCache() +{ + // 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".flac"; + reset_info.metadata=L"artist"; + reset_info.ret = d; + reset_info.retlen=10; + SendMessage(plugin.hMainWindow, WM_WA_IPC, (WPARAM)&reset_info, IPC_GET_EXTENDED_FILE_INFOW); +} + +#define START_TAG_ALIAS(name, alias) if (KeywordMatch(data, name)) lookup=alias +#define TAG_ALIAS(name, alias) else if (KeywordMatch(data, name)) lookup=alias +extern "C" __declspec( dllexport ) int winampGetExtendedFileInfoW(const wchar_t *fn, const char *data, wchar_t *dest, int destlen) +{ + if (KeywordMatch(data, "type")) + { + dest[0] = '0'; + dest[1] = 0; + return 1; + } + if (KeywordMatch(data, "rateable")) + { + dest[0] = '1'; + dest[1] = 0; + return 1; + } + else if (KeywordMatch(data, "lossless")) + { + dest[0] = '1'; + dest[1] = 0; + return 1; + } + + if (!fn || (fn && !fn[0])) + return 0; + + if (KeywordMatch(data, "family")) + { + LPCWSTR e; + int pID = -1; + DWORD lcid; + e = PathFindExtensionW(fn); + if (L'.' != *e) return 0; + e++; + lcid = MAKELCID(MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), SORT_DEFAULT); + if (CSTR_EQUAL == CompareStringW(lcid, NORM_IGNORECASE, e, -1, L"FLAC", -1) || + CSTR_EQUAL == CompareStringW(lcid, NORM_IGNORECASE, e, -1, L"FLA", -1)) pID = IDS_FAMILY_STRING; + + if (pID != -1 && S_OK == StringCchCopyW(dest, destlen, WASABI_API_LNGSTRINGW(pID))) return 1; + return 0; + } + + if (KeywordMatch(data, "mime")) + { + StringCchCopyW(dest, destlen, L"audio/flac"); + return 1; + } + + if (!getMetadata || !getFileInfoFn || _wcsicmp(fn, getFileInfoFn) || HasFileTimeChanged(fn)) + { + if (getMetadata) + getMetadata->Reset(); + else + getMetadata = new FLACMetadata; + + if (!getMetadata->Open(fn)) + { + delete getMetadata; + getMetadata = 0; + dest[0]=0; + return 0; + } + free(getFileInfoFn); + getFileInfoFn = _wcsdup(fn); + } + + FLACMetadata &metadata = *getMetadata; + + if(KeywordMatch(data, "formatinformation")) + return FillFileInfo(dest,destlen,metadata); + + const char *lookup=0; + if (KeywordMatch(data, "length")) + { + unsigned __int64 length_in_msec; + if (metadata.GetLengthMilliseconds(&length_in_msec)) + StringCchPrintfW(dest, destlen, L"%d", length_in_msec); + else + dest[0]=0; + return 1; + } + else if (KeywordMatch(data, "bitrate")) + { + // TODO: move this into FLACMetadata + const FLAC__StreamMetadata_StreamInfo *streaminfo = metadata.GetStreamInfo(); + if (streaminfo) + { + if (streaminfo->total_samples == 0 || streaminfo->sample_rate == 0) // prevent divide-by-zero + dest[0]=0; + else + StringCchPrintfW(dest, destlen, L"%I64d", metadata.GetFileSize() / (125*streaminfo->total_samples / (__int64)streaminfo->sample_rate)); // (125 is 1000/8) + } + else + dest[0]=0; + return 1; + } + TAG_ALIAS("title", "TITLE"); + TAG_ALIAS("artist", "ARTIST"); + TAG_ALIAS("album", "ALBUM"); + TAG_ALIAS("genre", "GENRE"); + TAG_ALIAS("comment", "COMMENT"); + TAG_ALIAS("year", "DATE"); + TAG_ALIAS("track", "TRACKNUMBER"); + TAG_ALIAS("albumartist", "ALBUM ARTIST"); + TAG_ALIAS("composer", "COMPOSER"); + TAG_ALIAS("disc", "DISCNUMBER"); + TAG_ALIAS("publisher", "PUBLISHER"); + TAG_ALIAS("conductor", "CONDUCTOR"); + TAG_ALIAS("tool", "ENCODED-BY"); + TAG_ALIAS("replaygain_track_gain", "REPLAYGAIN_TRACK_GAIN"); + TAG_ALIAS("replaygain_track_peak", "REPLAYGAIN_TRACK_PEAK"); + TAG_ALIAS("replaygain_album_gain", "REPLAYGAIN_ALBUM_GAIN"); + TAG_ALIAS("replaygain_album_peak", "REPLAYGAIN_ALBUM_PEAK"); + TAG_ALIAS("GracenoteFileID", "GRACENOTEFILEID"); + TAG_ALIAS("GracenoteExtData", "GRACENOTEEXTDATA"); + TAG_ALIAS("bpm", "BPM"); + TAG_ALIAS("remixing", "REMIXING"); + TAG_ALIAS("subtitle", "VERSION"); + TAG_ALIAS("isrc", "ISRC"); + TAG_ALIAS("category", "CATEGORY"); + TAG_ALIAS("rating", "RATING"); + TAG_ALIAS("producer", "PRODUCER"); + + if (!lookup) + return 0; + + const char *value = metadata.GetMetadata(lookup); + + if(KeywordMatch("comment",data)) { + if(!value || !*value) value = metadata.GetMetadata("DESCRIPTION"); + } + + if(KeywordMatch("year",data)) { + if(!value || !*value) value = metadata.GetMetadata("YEAR"); + } + + if(KeywordMatch("track",data)) { + if(!value || !*value) value = metadata.GetMetadata("TRACK"); + } + + if(KeywordMatch("albumartist",data)) { + if(!value || !*value) value = metadata.GetMetadata("ALBUMARTIST"); + if(!value || !*value) value = metadata.GetMetadata("ENSEMBLE"); + } + + if(KeywordMatch("publisher",data)) { + if(!value || !*value) value = metadata.GetMetadata("ORGANIZATION"); + } + + if(KeywordMatch("category",data)) { + if(!value || !*value) value = metadata.GetMetadata("CONTENTGROUP"); + if(!value || !*value) value = metadata.GetMetadata("GROUPING"); + } + + if(KeywordMatch(data, "rating")) { + if(!value || !*value) value = metadata.GetMetadata("RATING"); + if(value && *value) { + int rating = atoi(value); + + // keeps things limited to our range of 0-100 + if (rating >= 100) { + rating = 5; + } + // 1-100 case + else if (rating > 5 && rating < 100) { + rating = (rating /= 20); + // shift up by one rating when in next band + // 1-20 = 1, 21-40 = 2, 41-60 = 3, 61-80 = 4, 81-100 = 5 + rating += ((atoi(value) - (rating * 20)) > 0); + } + else if (rating > 0 && rating <= 5) { + } + // otherwise just make sure and set zero + else { + rating = 0; + } + + StringCchPrintfW(dest, destlen, L"%u", rating); + return 1; + } + } + + MultiByteToWideCharSZ(CP_UTF8, 0, value, -1, dest, destlen); + return 1; +} + +FLACMetadata *setMetadata=0; +wchar_t *setFn=0; +extern "C" __declspec( dllexport ) int winampSetExtendedFileInfoW(const wchar_t *fn, const char *data, wchar_t *val) +{ + if (!setMetadata || !setFn || lstrcmpiW(fn, setFn)) + { + free(setFn); + setFn=_wcsdup(fn); + if (!setMetadata) + setMetadata = new FLACMetadata; + if (setMetadata->Open(setFn, true) == false) + { + delete setMetadata; + setMetadata=0; + return 0; + } + } + + const char *lookup=0; + START_TAG_ALIAS("artist", "ARTIST"); + TAG_ALIAS("title", "TITLE"); + TAG_ALIAS("album", "ALBUM"); + TAG_ALIAS("genre", "GENRE"); + TAG_ALIAS("comment", "COMMENT"); + TAG_ALIAS("year", "DATE"); + TAG_ALIAS("track", "TRACKNUMBER"); + TAG_ALIAS("albumartist", "ALBUM ARTIST"); + TAG_ALIAS("composer", "COMPOSER"); + TAG_ALIAS("disc", "DISCNUMBER"); + TAG_ALIAS("publisher", "PUBLISHER"); + TAG_ALIAS("conductor", "CONDUCTOR"); + TAG_ALIAS("tool", "ENCODED-BY"); + TAG_ALIAS("replaygain_track_gain", "REPLAYGAIN_TRACK_GAIN"); + TAG_ALIAS("replaygain_track_peak", "REPLAYGAIN_TRACK_PEAK"); + TAG_ALIAS("replaygain_album_gain", "REPLAYGAIN_ALBUM_GAIN"); + TAG_ALIAS("replaygain_album_peak", "REPLAYGAIN_ALBUM_PEAK"); + TAG_ALIAS("GracenoteFileID", "GRACENOTEFILEID"); + TAG_ALIAS("GracenoteExtData", "GRACENOTEEXTDATA"); + TAG_ALIAS("bpm", "BPM"); + TAG_ALIAS("remixing", "REMIXING"); + TAG_ALIAS("subtitle", "VERSION"); + TAG_ALIAS("isrc", "ISRC"); + TAG_ALIAS("category", "CATEGORY"); + TAG_ALIAS("rating", "RATING"); + TAG_ALIAS("producer", "PRODUCER"); + + if (!lookup) + return 0; + + if (val && *val) + { + if(KeywordMatch("rating",data)) + { + char temp[128] = {0}; + StringCchPrintfA(temp, 128, "%u", _wtoi(val)*20); + setMetadata->SetMetadata(lookup, temp); + } + else + { + setMetadata->SetMetadata(lookup, AutoChar(val, CP_UTF8)); + } + } + else + { + setMetadata->RemoveMetadata(lookup); + if(KeywordMatch("comment",data)) + { + // need to remove this one also, or else it's gonna look like delete doesn't work + // if the file was tagged using this alternate field + setMetadata->RemoveMetadata("DESCRIPTION"); + } + else if(KeywordMatch("year",data)) + { + // need to remove this one also, or else it's gonna look like delete doesn't work + // if the file was tagged using this alternate field + setMetadata->RemoveMetadata("YEAR"); + } + else if(KeywordMatch("track",data)) + { + // need to remove this one also, or else it's gonna look like delete doesn't work + // if the file was tagged using this alternate field + setMetadata->RemoveMetadata("TRACK"); + } + else if(KeywordMatch("albumartist",data)) + { + // need to remove these two, also, or else it's gonna look like delete doesn't work + // if the file was tagged using these alternate fields + setMetadata->RemoveMetadata("ALBUMARTIST"); + setMetadata->RemoveMetadata("ENSEMBLE"); + } + else if(KeywordMatch("publisher",data)) + { + // need to remove this one also, or else it's gonna look like delete doesn't work + // if the file was tagged using this alternate field + setMetadata->RemoveMetadata("ORGANIZATION"); + } + else if(KeywordMatch("category",data)) + { + // need to remove these two also, or else it's gonna look like delete doesn't work + // if the file was tagged using these alternate fields + setMetadata->RemoveMetadata("CONTENTGROUP"); + setMetadata->RemoveMetadata("GROUPING"); + } + } + + return 1; +} + +extern "C" __declspec(dllexport) int winampWriteExtendedFileInfo() +{ + if (setFn && setMetadata) + { + Stopper stopper; + if (lastfn && !_wcsicmp(lastfn, setFn)) + stopper.Stop(); + bool success = setMetadata->Save(setFn); + stopper.Play(); + setMetadata->Reset(); + free(setFn); + setFn=0; + + delete getMetadata; + getMetadata=0; + + // update last modified so we're not re-queried on our own updates + UpdateFileTimeChanged(setFn); + + return !!success; + } + return 1; +} + +extern "C" __declspec(dllexport) const wchar_t *winampWriteExtendedGetLastError() +{ + return 0; +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_flac/ExtendedRead.cpp b/Src/Plugins/Input/in_flac/ExtendedRead.cpp new file mode 100644 index 00000000..d3c08fc8 --- /dev/null +++ b/Src/Plugins/Input/in_flac/ExtendedRead.cpp @@ -0,0 +1,166 @@ +/* +** Copyright (C) 2007-2011 Nullsoft, Inc. +** +** This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held +** liable for any damages arising from the use of this software. +** +** Permission is granted to anyone to use this software for any purpose, including commercial applications, and to +** alter it and redistribute it freely, subject to the following restrictions: +** +** 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. +** If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +** +** 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +** +** 3. This notice may not be removed or altered from any source distribution. +** +** Author: Ben Allison benski@winamp.com +** Created: March 1, 2007 +** +*/ + +#include "main.h" +#include <FLAC/all.h> +#include "StreamFileWin32.h" +#include "QuickBuf.h" +#include <bfc/platform/types.h> +#include <assert.h> +#include "FLACFileCallbacks.h" +#include "nswasabi/ReferenceCounted.h" + +struct ExtendedRead +{ + int bps, channels, samplerate, truebps; + uint64_t samples; + QuickBuf output; + FLAC__StreamDecoder *decoder; + size_t used; + FLACClientData client_data; +}; + +static void OnError(const FLAC__StreamDecoder *decoder, FLAC__StreamDecoderErrorStatus status, void *client_data) +{ + //client_data=client_data; // dummy line so i can set a breakpoint +} + +static void OnMetadata(const FLAC__StreamDecoder *decoder, const FLAC__StreamMetadata *metadata, void *client_data) +{ + ExtendedRead *ext = FLAC_GetObject<ExtendedRead>(client_data); + + switch(metadata->type) + { + case FLAC__METADATA_TYPE_STREAMINFO: + { + ext->truebps=metadata->data.stream_info.bits_per_sample; + ext->bps = (ext->truebps +7) & (~7); + ext->channels=metadata->data.stream_info.channels; + ext->samplerate=metadata->data.stream_info.sample_rate; + ext->samples=metadata->data.stream_info.total_samples; + } + break; + } +} + +static FLAC__StreamDecoderWriteStatus OnAudio(const FLAC__StreamDecoder *decoder, const FLAC__Frame *frame, const FLAC__int32 *const buffer[], void *client_data) +{ + ExtendedRead *ext = FLAC_GetObject<ExtendedRead>(client_data); + + int byteLength = (ext->bps/8) * ext->channels * frame->header.blocksize; + ext->output.Reserve(byteLength); + InterleaveAndTruncate(buffer, ext->output, ext->bps, ext->channels, frame->header.blocksize); + ext->used = byteLength; + return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE; +} + +extern "C" +{ + __declspec( dllexport ) intptr_t winampGetExtendedRead_openW(const wchar_t *fn, int *size, int *bps, int *nch, int *srate) + { + nx_file_t file; + ReferenceCountedNXString filename_nx; + ReferenceCountedNXURI filename_uri; + NXStringCreateWithUTF16(&filename_nx, fn); + NXURICreateWithNXString(&filename_uri, filename_nx); + + int ret = NXFileOpenFile(&file, filename_uri, nx_file_FILE_read_binary); + if (ret != NErr_Success) + return 0; + + ExtendedRead * e = (ExtendedRead *)calloc(sizeof(ExtendedRead), 1); + e->decoder = FLAC__stream_decoder_new(); + if (e->decoder == 0) + { + NXFileRelease(file); + free(e); + return 0; + } + + e->client_data.SetFile(file); + e->client_data.SetObject(e); + + FLAC__stream_decoder_set_md5_checking(e->decoder, true); + if(FLAC__stream_decoder_init_stream( + e->decoder, + FLAC_NXFile_Read, + FLAC_NXFile_Seek, + FLAC_NXFile_Tell, + FLAC_NXFile_Length, + FLAC_NXFile_EOF, + OnAudio, + OnMetadata, + OnError, + &e->client_data + ) != FLAC__STREAM_DECODER_INIT_STATUS_OK) + { + FLAC__stream_decoder_delete(e->decoder); + NXFileRelease(file); + free(e); + return 0; + } + + FLAC__stream_decoder_process_until_end_of_metadata(e->decoder); + *bps = e->truebps; + *nch = e->channels; + *srate = e->samplerate; + *size = (int)(e->samples * (e->bps/8) * e->channels); + return (intptr_t) e; + } + + __declspec( dllexport ) intptr_t winampGetExtendedRead_getData(intptr_t handle, char *dest, size_t len, int *killswitch) + { + ExtendedRead *ext = (ExtendedRead *)handle; + + while(!ext->used) // loop until we get some data + { + if (FLAC__stream_decoder_process_single(ext->decoder) == 0) + break; // break out if there's an error + + FLAC__StreamDecoderState FLACstate = FLAC__stream_decoder_get_state(ext->decoder); + if (FLACstate == FLAC__STREAM_DECODER_END_OF_STREAM) // break out if we hit EOF + break; + } + + if (ext->used) + { + size_t toCopy = min(len, ext->used); + memcpy(dest, ext->output, toCopy); + if (toCopy < ext->used) + ext->output.Move(toCopy); + ext->used-=toCopy; + return toCopy; + } + + return 0; + } + + __declspec( dllexport ) void winampGetExtendedRead_close(intptr_t handle) + { + ExtendedRead *ext = (ExtendedRead *)handle; + + ext->output.Free(); + FLAC__stream_decoder_finish(ext->decoder); + FLAC__stream_decoder_delete(ext->decoder); + NXFileRelease(ext->client_data.GetFile()); + free(ext); + } +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_flac/FLACFileCallbacks.cpp b/Src/Plugins/Input/in_flac/FLACFileCallbacks.cpp new file mode 100644 index 00000000..23a53b91 --- /dev/null +++ b/Src/Plugins/Input/in_flac/FLACFileCallbacks.cpp @@ -0,0 +1,57 @@ +#include "FLACFileCallbacks.h" + + +FLAC__StreamDecoderReadStatus FLAC_NXFile_Read(const FLAC__StreamDecoder *decoder, FLAC__byte buffer[], size_t *bytes, void *client_data) +{ + nx_file_t file = FLAC_GetFile(client_data); + size_t bytes_to_read = *bytes; + size_t bytes_read=0; + ns_error_t ret = NXFileRead(file, buffer, bytes_to_read, &bytes_read); + *bytes = bytes_read; + + if (ret == NErr_EndOfFile) + return FLAC__STREAM_DECODER_READ_STATUS_END_OF_STREAM; + + if (ret != NErr_Success) + return FLAC__STREAM_DECODER_READ_STATUS_ABORT; + + return FLAC__STREAM_DECODER_READ_STATUS_CONTINUE; +} + +FLAC__StreamDecoderSeekStatus FLAC_NXFile_Seek(const FLAC__StreamDecoder *decoder, FLAC__uint64 absolute_byte_offset, void *client_data) +{ + nx_file_t file = FLAC_GetFile(client_data); + if (NXFileSeek(file, absolute_byte_offset) == NErr_Success) + return FLAC__STREAM_DECODER_SEEK_STATUS_OK; + else + return FLAC__STREAM_DECODER_SEEK_STATUS_ERROR; +} + +FLAC__StreamDecoderTellStatus FLAC_NXFile_Tell(const FLAC__StreamDecoder *decoder, FLAC__uint64 *absolute_byte_offset, void *client_data) +{ + nx_file_t file = FLAC_GetFile(client_data); + + if (NXFileTell(file, absolute_byte_offset) == NErr_Success) + return FLAC__STREAM_DECODER_TELL_STATUS_OK; + else + return FLAC__STREAM_DECODER_TELL_STATUS_ERROR; +} + +FLAC__StreamDecoderLengthStatus FLAC_NXFile_Length(const FLAC__StreamDecoder *decoder, FLAC__uint64 *stream_length, void *client_data) +{ + nx_file_t file = FLAC_GetFile(client_data); + + if (NXFileLength(file, stream_length) == NErr_Success) + return FLAC__STREAM_DECODER_LENGTH_STATUS_OK; + else + return FLAC__STREAM_DECODER_LENGTH_STATUS_ERROR; +} + +FLAC__bool FLAC_NXFile_EOF(const FLAC__StreamDecoder *decoder, void *client_data) +{ + nx_file_t file = FLAC_GetFile(client_data); + if (NXFileEndOfFile(file) == NErr_False) + return false; + else + return true; +} diff --git a/Src/Plugins/Input/in_flac/FLACFileCallbacks.h b/Src/Plugins/Input/in_flac/FLACFileCallbacks.h new file mode 100644 index 00000000..7bd2538c --- /dev/null +++ b/Src/Plugins/Input/in_flac/FLACFileCallbacks.h @@ -0,0 +1,35 @@ +#pragma once +#include <FLAC/all.h> +#include "nx/nxfile.h" + + +FLAC__StreamDecoderReadStatus FLAC_NXFile_Read(const FLAC__StreamDecoder *decoder, FLAC__byte buffer[], size_t *bytes, void *client_data); +FLAC__StreamDecoderSeekStatus FLAC_NXFile_Seek(const FLAC__StreamDecoder *decoder, FLAC__uint64 absolute_byte_offset, void *client_data); +FLAC__StreamDecoderTellStatus FLAC_NXFile_Tell(const FLAC__StreamDecoder *decoder, FLAC__uint64 *absolute_byte_offset, void *client_data); +FLAC__StreamDecoderLengthStatus FLAC_NXFile_Length(const FLAC__StreamDecoder *decoder, FLAC__uint64 *stream_length, void *client_data); +FLAC__bool FLAC_NXFile_EOF(const FLAC__StreamDecoder *decoder, void *client_data); + + +class FLACClientData +{ +public: + FLACClientData() : object(0) {} + void SetFile(nx_file_t file) { this->file = file; } + void SetObject(void *object) { this->object = object; } + nx_file_t GetFile() { return file; } + void *GetObject() { return object; } +private: + nx_file_t file; + void *object; +}; + +template <typename _t> +static _t *FLAC_GetObject(void *client_data) +{ + return (_t *)((FLACClientData *)client_data)->GetObject(); +} + +static nx_file_t FLAC_GetFile(void *client_data) +{ + return ((FLACClientData *)client_data)->GetFile(); +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_flac/FileInfo.cpp b/Src/Plugins/Input/in_flac/FileInfo.cpp new file mode 100644 index 00000000..d0684c59 --- /dev/null +++ b/Src/Plugins/Input/in_flac/FileInfo.cpp @@ -0,0 +1,400 @@ +/* +** Copyright (C) 2007-2011 Nullsoft, Inc. +** +** This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held +** liable for any damages arising from the use of this software. +** +** Permission is granted to anyone to use this software for any purpose, including commercial applications, and to +** alter it and redistribute it freely, subject to the following restrictions: +** +** 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. +** If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +** +** 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +** +** 3. This notice may not be removed or altered from any source distribution. +** +** Author: Ben Allison benski@winamp.com +** Created: March 1, 2007 +** +*/ + +#include <FLAC/all.h> +#include "main.h" +#include "../nu/ns_wc.h" +#include <windows.h> +#include "resource.h" +#include "Metadata.h" +#include "../nu/AutoWide.h" +#include "../nu/AutoChar.h" +#include "Stopper.h" +#include <strsafe.h> +#include <commctrl.h> +#include "../Agave/Language/api_language.h" + +bool FlacTagToWinampTag(wchar_t * tag, int len) +{ +#define TAG_ALIAS(b,a) if(!_wcsicmp(L ## a, tag)) { lstrcpynW(tag, L ## b, len); return true; } + TAG_ALIAS("title", "TITLE"); + TAG_ALIAS("artist", "ARTIST"); + TAG_ALIAS("album", "ALBUM"); + TAG_ALIAS("genre", "GENRE"); + TAG_ALIAS("comment", "COMMENT"); + TAG_ALIAS("year", "DATE"); + TAG_ALIAS("track", "TRACKNUMBER"); + TAG_ALIAS("albumartist", "ALBUM ARTIST"); + TAG_ALIAS("composer", "COMPOSER"); + TAG_ALIAS("disc", "DISCNUMBER"); + TAG_ALIAS("publisher", "PUBLISHER"); + TAG_ALIAS("conductor", "CONDUCTOR"); + TAG_ALIAS("bpm", "BPM"); + return false; +#undef TAG_ALIAS +} + +bool WinampTagToFlacTag(wchar_t * tag, int len) +{ +#define TAG_ALIAS(a,b) if(!_wcsicmp(L ## a, tag)) { lstrcpynW(tag, L ## b, len); return true; } + TAG_ALIAS("title", "TITLE"); + TAG_ALIAS("artist", "ARTIST"); + TAG_ALIAS("album", "ALBUM"); + TAG_ALIAS("genre", "GENRE"); + TAG_ALIAS("comment", "COMMENT"); + TAG_ALIAS("year", "DATE"); + TAG_ALIAS("track", "TRACKNUMBER"); + TAG_ALIAS("albumartist", "ALBUM ARTIST"); + TAG_ALIAS("composer", "COMPOSER"); + TAG_ALIAS("disc", "DISCNUMBER"); + TAG_ALIAS("publisher", "PUBLISHER"); + TAG_ALIAS("conductor", "CONDUCTOR"); + TAG_ALIAS("bpm", "BPM"); + return false; +#undef TAG_ALIAS +} + +static INT_PTR CALLBACK ChildProc_Advanced(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam) { + static int sel=-1; + static int ismychange=0; + wchar_t key[512]={0}; + wchar_t value[32768]={0}; + + switch(msg) + { + case WM_NOTIFYFORMAT: + return NFR_UNICODE; + case WM_INITDIALOG: + { + #define ListView_InsertColumnW(hwnd, iCol, pcol) \ + (int)SNDMSG((hwnd), LVM_INSERTCOLUMNW, (WPARAM)(int)(iCol), (LPARAM)(const LV_COLUMNW *)(pcol)) + SetWindowLongPtr(hwndDlg,GWLP_USERDATA,lParam); + sel=-1; + HWND hwndlist = GetDlgItem(hwndDlg,IDC_LIST); + ListView_SetExtendedListViewStyle(hwndlist, LVS_EX_FULLROWSELECT | LVS_EX_INFOTIP); + LVCOLUMNW lvc = {0, }; + lvc.mask = LVCF_TEXT|LVCF_WIDTH; + lvc.pszText = WASABI_API_LNGSTRINGW(IDS_NAME); + lvc.cx = 82; + ListView_InsertColumnW(hwndlist, 0, &lvc); + lvc.pszText = WASABI_API_LNGSTRINGW(IDS_VALUE); + lvc.cx = 160; + ListView_InsertColumnW(hwndlist, 1, &lvc); + + Info *info = (Info *)lParam; + int n = info->metadata.GetNumMetadataItems(); + for(int i=0; i<n; i++) { + char key_[512] = {0}; + const char* value_ = info->metadata.EnumMetadata(i,key_,512); + if(value_ && key_[0]) { + AutoWide k(key_, CP_UTF8); + AutoWide v(value_, CP_UTF8); + LVITEMW lvi={LVIF_TEXT,i,0}; + lvi.pszText = k; + SendMessage(hwndlist,LVM_INSERTITEMW,0,(LPARAM)&lvi); + lvi.iSubItem=1; + lvi.pszText = v; + SendMessage(hwndlist,LVM_SETITEMW,0,(LPARAM)&lvi); + } + } + ListView_SetColumnWidth(hwndlist,0,LVSCW_AUTOSIZE); + ListView_SetColumnWidth(hwndlist,1,LVSCW_AUTOSIZE); + + SetDlgItemTextW(hwndDlg,IDC_NAME,L""); + SetDlgItemTextW(hwndDlg,IDC_VALUE,L""); + EnableWindow(GetDlgItem(hwndDlg,IDC_NAME),FALSE); + EnableWindow(GetDlgItem(hwndDlg,IDC_VALUE),FALSE); + EnableWindow(GetDlgItem(hwndDlg,IDC_BUTTON_DEL),FALSE); + } + break; + case WM_DESTROY: + { + HWND hwndlist = GetDlgItem(hwndDlg,IDC_LIST); + ListView_DeleteAllItems(hwndlist); + while(ListView_DeleteColumn(hwndlist,0)); + Info * info = (Info*)GetWindowLongPtr(hwndDlg,GWLP_USERDATA); + delete info; + info = 0; + } + break; + case WM_USER: + if(wParam && lParam && !ismychange) + { + wchar_t * value = (wchar_t*)lParam; + wchar_t tag[100] = {0}; + lstrcpynW(tag,(wchar_t*)wParam,100); + WinampTagToFlacTag(tag,100); + Info *info = (Info *)GetWindowLongPtr(hwndDlg, GWLP_USERDATA); + if(!*value) + { + info->metadata.RemoveMetadata(AutoChar(tag,CP_UTF8)); + if(!_wcsicmp(L"ALBUM ARTIST",tag)) + { + // need to remove these two, also, or else it's gonna look like delete doesn't work + // if the file was tagged using these alternate fields + info->metadata.RemoveMetadata("ALBUMARTIST"); + info->metadata.RemoveMetadata("ENSEMBLE"); + } + if(!_wcsicmp(L"PUBLISHER",tag)) + { + // need to remove this also, or else it's gonna look like delete doesn't work + // if the file was tagged using this alternate field + info->metadata.RemoveMetadata("ORGANIZATION"); + } + if(!_wcsicmp(L"DATE",tag)) + { + // need to remove this also, or else it's gonna look like delete doesn't work + // if the file was tagged using this alternate field + info->metadata.RemoveMetadata("YEAR"); + } + if(!_wcsicmp(L"TRACKNUMBER",tag)) + { + // need to remove this also, or else it's gonna look like delete doesn't work + // if the file was tagged using this alternate field + info->metadata.RemoveMetadata("TRACK"); + } + } + else + { + info->metadata.SetMetadata(AutoChar(tag,CP_UTF8),AutoChar(value,CP_UTF8)); + } + HWND hlist = GetDlgItem(hwndDlg,IDC_LIST); + int n = ListView_GetItemCount(hlist); + for(int i=0; i<n; i++) + { + key[0]=0; + LVITEMW lvi={LVIF_TEXT,i,0}; + lvi.pszText=key; + lvi.cchTextMax=sizeof(key)/sizeof(*key); + SendMessage(hlist,LVM_GETITEMW,0,(LPARAM)&lvi); + if(!_wcsicmp(key,tag)) + { + lvi.iSubItem = 1; + lvi.pszText = value; + SendMessage(hlist,LVM_SETITEMW,0,(LPARAM)&lvi); + if(!*value) + ListView_DeleteItem(hlist,i); + else if(ListView_GetItemState(hlist,i,LVIS_SELECTED)) + SetDlgItemTextW(hwndDlg,IDC_VALUE,value); + return 0; + } + } + // bew hew, not found + LVITEMW lvi={0,0x7FFFFFF0,0}; + n = SendMessage(hlist,LVM_INSERTITEMW,0,(LPARAM)&lvi); + lvi.mask = LVIF_TEXT; + lvi.iItem = n; + lvi.iSubItem = 0; + lvi.pszText = tag; + SendMessage(hlist,LVM_SETITEMW,0,(LPARAM)&lvi); + lvi.iSubItem = 1; + lvi.pszText = value; + SendMessage(hlist,LVM_SETITEMW,0,(LPARAM)&lvi); + } + break; + case WM_NOTIFY: + { + LPNMHDR l=(LPNMHDR)lParam; + if(l->idFrom==IDC_LIST && l->code == LVN_KEYDOWN) { + if((((LPNMLVKEYDOWN)l)->wVKey) == VK_DELETE){ + int selitem = ListView_GetNextItem(l->hwndFrom,-1,LVNI_SELECTED|LVNI_FOCUSED); + if(selitem != -1) + SendMessage(hwndDlg,WM_COMMAND,MAKEWPARAM(IDC_BUTTON_DEL,BN_CLICKED),(LPARAM)GetDlgItem(hwndDlg,IDC_BUTTON_DEL)); + } + } + else if(l->idFrom==IDC_LIST && l->code == LVN_ITEMCHANGED) { + LPNMLISTVIEW lv=(LPNMLISTVIEW)lParam; + if(lv->uNewState & LVIS_SELECTED) { + int n = lv->iItem; + LVITEMW lvi={LVIF_TEXT,lv->iItem,0}; + lvi.pszText=key; + lvi.cchTextMax=sizeof(key)/sizeof(*key); + SendMessage(l->hwndFrom,LVM_GETITEMW,0,(LPARAM)&lvi); + lvi.pszText=value; + lvi.cchTextMax=sizeof(value)/sizeof(*value); + lvi.iSubItem=1; + SendMessage(l->hwndFrom,LVM_GETITEMW,0,(LPARAM)&lvi); + SetDlgItemTextW(hwndDlg,IDC_NAME,key); + SetDlgItemTextW(hwndDlg,IDC_VALUE,value); + sel = n; + EnableWindow(GetDlgItem(hwndDlg,IDC_NAME),TRUE); + EnableWindow(GetDlgItem(hwndDlg,IDC_VALUE),TRUE); + EnableWindow(GetDlgItem(hwndDlg,IDC_BUTTON_DEL),TRUE); + } + if(lv->uOldState & LVIS_SELECTED) { + sel = -1; + SetDlgItemTextW(hwndDlg,IDC_NAME,L""); + SetDlgItemTextW(hwndDlg,IDC_VALUE,L""); + EnableWindow(GetDlgItem(hwndDlg,IDC_NAME),FALSE); + EnableWindow(GetDlgItem(hwndDlg,IDC_VALUE),FALSE); + EnableWindow(GetDlgItem(hwndDlg,IDC_BUTTON_DEL),FALSE); + } + } + } + break; + case WM_COMMAND: + switch(LOWORD(wParam)) { + case IDOK: + { + Info * info = (Info*)GetWindowLongPtr(hwndDlg,GWLP_USERDATA); + Stopper stopper; + if (lastfn && !_wcsicmp(lastfn, info->filename)) + stopper.Stop(); + bool success = info->metadata.Save(info->filename); + stopper.Play(); + if (success) + { + ResetMetadataCache(); + } + else + { + wchar_t title[128] = {0}; + MessageBoxW(hwndDlg,WASABI_API_LNGSTRINGW(IDS_CANNOT_SAVE_METADATA), + WASABI_API_LNGSTRINGW_BUF(IDS_ERROR_SAVING_METADATA,title,128), + MB_OK | MB_ICONWARNING); + } + } + break; + case IDC_NAME: + case IDC_VALUE: + if(HIWORD(wParam) == EN_CHANGE && sel>=0) { + LVITEMW lvi={LVIF_TEXT,sel,0}; + GetDlgItemTextW(hwndDlg,IDC_NAME,key,sizeof(key)/sizeof(*key)); + GetDlgItemTextW(hwndDlg,IDC_VALUE,value,sizeof(value)/sizeof(*value)); + lvi.pszText=key; + lvi.cchTextMax=sizeof(key)/sizeof(*key); + SendMessage(GetDlgItem(hwndDlg,IDC_LIST),LVM_SETITEMW,0,(LPARAM)&lvi); + lvi.pszText=value; + lvi.cchTextMax=sizeof(value)/sizeof(*value); + lvi.iSubItem=1; + SendMessage(GetDlgItem(hwndDlg,IDC_LIST),LVM_SETITEMW,0,(LPARAM)&lvi); + FlacTagToWinampTag(key,sizeof(key)/sizeof(*key)); + ismychange=1; + SendMessage(GetParent(hwndDlg),WM_USER,(WPARAM)key,(WPARAM)value); + ismychange=0; + } + else if(HIWORD(wParam) == EN_KILLFOCUS && sel>=0) { + GetDlgItemTextW(hwndDlg,IDC_NAME,key,sizeof(key)/sizeof(*key)); + GetDlgItemTextW(hwndDlg,IDC_VALUE,value,sizeof(value)/sizeof(*value)); + Info *info = (Info *)GetWindowLongPtr(hwndDlg, GWLP_USERDATA); + char oldkeyA[100]=""; + bool newitem=true; + if(sel < info->metadata.GetNumMetadataItems()) { + info->metadata.EnumMetadata(sel,oldkeyA,100); + newitem=false; + } + AutoWide oldkey(oldkeyA); + if(!newitem && _wcsicmp(oldkey,key)) { // key changed + info->metadata.SetTag(sel,AutoChar(key,CP_UTF8)); + } else { + info->metadata.SetMetadata(AutoChar(key,CP_UTF8),AutoChar(value,CP_UTF8)); + } + FlacTagToWinampTag(key,sizeof(key)/sizeof(*key)); + ismychange=1; + SendMessage(GetParent(hwndDlg),WM_USER,(WPARAM)key,(WPARAM)value); + ismychange=0; + } + break; + case IDC_BUTTON_DEL: + if(sel >= 0){ + GetDlgItemTextW(hwndDlg,IDC_NAME,key,sizeof(key)/sizeof(*key)); + SetDlgItemTextW(hwndDlg,IDC_NAME,L""); + SetDlgItemTextW(hwndDlg,IDC_VALUE,L""); + EnableWindow(GetDlgItem(hwndDlg,IDC_NAME),FALSE); + EnableWindow(GetDlgItem(hwndDlg,IDC_VALUE),FALSE); + EnableWindow(GetDlgItem(hwndDlg,IDC_BUTTON_DEL),FALSE); + Info *info = (Info *)GetWindowLongPtr(hwndDlg, GWLP_USERDATA); + if(sel < info->metadata.GetNumMetadataItems()) + info->metadata.RemoveMetadata(sel); + ListView_DeleteItem(GetDlgItem(hwndDlg,IDC_LIST),sel); + sel=-1; + FlacTagToWinampTag(key,sizeof(key)/sizeof(*key)); + ismychange=1; + SendMessage(GetParent(hwndDlg),WM_USER,(WPARAM)key,(WPARAM)L""); + ismychange=0; + } + break; + case IDC_BUTTON_DELALL: + ListView_DeleteAllItems(GetDlgItem(hwndDlg,IDC_LIST)); + SetDlgItemTextW(hwndDlg,IDC_NAME,L""); + SetDlgItemTextW(hwndDlg,IDC_VALUE,L""); + EnableWindow(GetDlgItem(hwndDlg,IDC_NAME),FALSE); + EnableWindow(GetDlgItem(hwndDlg,IDC_VALUE),FALSE); + EnableWindow(GetDlgItem(hwndDlg,IDC_BUTTON_DEL),FALSE); + sel=-1; + { + Info *info = (Info *)GetWindowLongPtr(hwndDlg, GWLP_USERDATA); + int n = info->metadata.GetNumMetadataItems(); + while(n>0) { + --n; + char tag[100] = {0}; + info->metadata.EnumMetadata(n,tag,100); + MultiByteToWideCharSZ(CP_UTF8, 0, tag, -1, key, sizeof(key)/sizeof(*key)); + FlacTagToWinampTag(key,sizeof(key)/sizeof(*key)); + ismychange=1; + SendMessage(GetParent(hwndDlg),WM_USER,(WPARAM)key,(WPARAM)L""); + ismychange=0; + info->metadata.RemoveMetadata(n); + } + } + break; + case IDC_BUTTON_ADD: + { + HWND hwndlist = GetDlgItem(hwndDlg,IDC_LIST); + LVITEMW lvi={0,0x7FFFFFF0,0}; + int n = SendMessage(hwndlist,LVM_INSERTITEMW,0,(LPARAM)&lvi); + ListView_SetItemState(hwndlist,n,LVIS_SELECTED,LVIS_SELECTED); + } + break; + } + 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) + { + 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) { // add first pane + SetPropW(parent,L"INBUILT_NOWRITEINFO", (HANDLE)1); + info = new Info; + info->filename = filename; + info->metadata.Open(filename, true); + return WASABI_API_CREATEDIALOGPARAMW(IDD_INFOCHILD_ADVANCED,parent,ChildProc_Advanced,(LPARAM)info); + } + return NULL; + } +};
\ No newline at end of file diff --git a/Src/Plugins/Input/in_flac/Metadata.cpp b/Src/Plugins/Input/in_flac/Metadata.cpp new file mode 100644 index 00000000..49815c54 --- /dev/null +++ b/Src/Plugins/Input/in_flac/Metadata.cpp @@ -0,0 +1,577 @@ +/* +** Copyright (C) 2007-2011 Nullsoft, Inc. +** +** This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held +** liable for any damages arising from the use of this software. +** +** Permission is granted to anyone to use this software for any purpose, including commercial applications, and to +** alter it and redistribute it freely, subject to the following restrictions: +** +** 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. +** If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +** +** 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +** +** 3. This notice may not be removed or altered from any source distribution. +** +** Author: Ben Allison benski@winamp.com +** Created: March 1, 2007 +** +*/ +#include "Metadata.h" +#include <FLAC/all.h> +#include "StreamFileWin32.h" // for FileSize64 +#include "api__in_flv.h" +#include <strsafe.h> + +struct MetadataReader +{ + MetadataReader(HANDLE _handle) + { + handle = _handle; + endOfFile=false; + } + HANDLE handle; + bool endOfFile; +}; + +static size_t win32_read(void *ptr, size_t size, size_t nmemb, FLAC__IOHandle handle) +{ + MetadataReader *reader = (MetadataReader *)handle; + DWORD bytesRead=0; + BOOL result = ReadFile(reader->handle, ptr, size*nmemb, &bytesRead, NULL); + if (result == TRUE && bytesRead == 0) + reader->endOfFile=true; + return bytesRead/size; +} + +static size_t win32_write(const void *ptr, size_t size, size_t nmemb, FLAC__IOHandle handle) +{ + MetadataReader *reader = (MetadataReader *)handle; + DWORD bytesWritten=0; + WriteFile(reader->handle, ptr, size*nmemb, &bytesWritten, NULL); + return bytesWritten/size; +} + +static __int64 Seek64(HANDLE hf, __int64 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; +} + +static int win32_seek(FLAC__IOHandle handle, FLAC__int64 offset, int whence) +{ + MetadataReader *reader = (MetadataReader *)handle; + if (Seek64(reader->handle, offset, whence) == -1) + return -1; + else + return 0; + +} + +static FLAC__int64 win32_tell(FLAC__IOHandle handle) +{ + MetadataReader *reader = (MetadataReader *)handle; + return Seek64(reader->handle, 0, FILE_CURRENT); +} + +static int win32_eof(FLAC__IOHandle handle) +{ + MetadataReader *reader = (MetadataReader *)handle; + return !!reader->endOfFile; +} + +static int win32_close(FLAC__IOHandle handle) +{ + MetadataReader *reader = (MetadataReader *)handle; + + CloseHandle(reader->handle); + reader->handle = INVALID_HANDLE_VALUE; + return 0; +} + +static FLAC__IOCallbacks unicodeIO = +{ + win32_read, + win32_write, + win32_seek, + win32_tell, + win32_eof, + win32_close, +}; + +FLACMetadata::FLACMetadata() +{ + chain=FLAC__metadata_chain_new(); + itr = FLAC__metadata_iterator_new(); + block=0; + streamInfo=0; + filesize=0; +} + +FLACMetadata::~FLACMetadata() +{ + if (chain) + FLAC__metadata_chain_delete(chain); + if (itr) + FLAC__metadata_iterator_delete(itr); +} + +void FLACMetadata::Reset() +{ + if (chain) + FLAC__metadata_chain_delete(chain); + if (itr) + FLAC__metadata_iterator_delete(itr); + chain=FLAC__metadata_chain_new(); + itr = FLAC__metadata_iterator_new(); + block=0; + streamInfo=0; + filesize=0; +} + +const FLAC__StreamMetadata_StreamInfo *FLACMetadata::GetStreamInfo() +{ + if (streamInfo) + return &streamInfo->data.stream_info; + else + return 0; +} + +bool FLACMetadata::Open(const wchar_t *filename, bool optimize) +{ + if (!chain || !itr) + return false; + + HANDLE hfile = CreateFileW(filename, GENERIC_READ, FILE_SHARE_READ|FILE_SHARE_WRITE, 0, OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, 0); + MetadataReader reader(hfile); + if (reader.handle == INVALID_HANDLE_VALUE) + return false; + filesize = FileSize64(reader.handle); + + FLAC__bool success = FLAC__metadata_chain_read_with_callbacks(chain, &reader, unicodeIO); + CloseHandle(hfile); + if (!success) + return false; + + if (optimize) + { + FLAC__metadata_chain_sort_padding(chain); + FLAC__metadata_chain_merge_padding(chain); + } + + FLAC__metadata_iterator_init(itr, chain); + while (1) + { + FLAC__MetadataType type=FLAC__metadata_iterator_get_block_type(itr); + switch (type) + { + case FLAC__METADATA_TYPE_VORBIS_COMMENT: + block = FLAC__metadata_iterator_get_block(itr); + break; + case FLAC__METADATA_TYPE_STREAMINFO: + streamInfo = FLAC__metadata_iterator_get_block(itr); + break; + } + if (FLAC__metadata_iterator_next(itr) == false) + break; + } + return true; +} + +const char *FLACMetadata::GetMetadata(const char *tag) +{ + if (!block) + return 0; + + int pos = FLAC__metadata_object_vorbiscomment_find_entry_from(block, 0, tag); + if (pos < 0) + { + // fail + } + else + { + const char *entry = (const char *)block->data.vorbis_comment.comments[pos].entry; + const char *metadata = strchr(entry, '='); // find the first equal + if (metadata) + { + return metadata+1; + } + } + + return 0; +} + +void FLACMetadata::SetMetadata(const char *tag, const char *value) +{ + if (!block) + { + FLAC__metadata_iterator_init(itr, chain); + do + { + if (FLAC__METADATA_TYPE_VORBIS_COMMENT == FLAC__metadata_iterator_get_block_type(itr)) + { + block = FLAC__metadata_iterator_get_block(itr); + break; + } + } + while (FLAC__metadata_iterator_next(itr) != 0); + if (!block) + { + block = FLAC__metadata_object_new(FLAC__METADATA_TYPE_VORBIS_COMMENT); + FLAC__metadata_iterator_insert_block_after(itr, block); + } + } + + if (!block) + return; + + FLAC__StreamMetadata_VorbisComment_Entry entry; + size_t totalLen = strlen(tag) + 1 /* = */ + strlen(value); + entry.entry = (FLAC__byte *)malloc(totalLen + 1); + entry.length = totalLen; + + StringCchPrintfA((char *)entry.entry, totalLen+1, "%s=%s", tag, value); + + int pos = FLAC__metadata_object_vorbiscomment_find_entry_from(block, 0, tag); + if (pos < 0) + { + //new comment + FLAC__metadata_object_vorbiscomment_append_comment(block, entry, true); + // would love to not copy, but we can't guarantee that FLAC links to the same CRT as us + } + else + { + FLAC__metadata_object_vorbiscomment_set_comment(block, pos, entry, true); + // would love to not copy, but we can't guarantee that FLAC links to the same CRT as us + } + free(entry.entry); +} + +void FLACMetadata::RemoveMetadata(const char *tag) +{ + if (!block) + return; + + FLAC__metadata_object_vorbiscomment_remove_entries_matching(block, tag); +} + +void FLACMetadata::RemoveMetadata(int n) +{ + if (!block) + return; + + FLAC__metadata_object_vorbiscomment_delete_comment(block, (unsigned int)n); +} + +static FLAC__StreamMetadata *GetOrMakePadding(FLAC__Metadata_Chain *chain, FLAC__Metadata_Iterator *itr) +{ + FLAC__metadata_iterator_init(itr, chain); + while (1) + { + if (FLAC__METADATA_TYPE_PADDING == FLAC__metadata_iterator_get_block_type(itr)) + { + FLAC__StreamMetadata *block = FLAC__metadata_iterator_get_block(itr); + return block; + } + + if (FLAC__metadata_iterator_next(itr) == false) + break; + } + FLAC__StreamMetadata *padding = FLAC__metadata_object_new(FLAC__METADATA_TYPE_PADDING); + if (padding) + FLAC__metadata_iterator_insert_block_after(itr, padding); + + return padding; +} + +bool FLACMetadata::Save(const wchar_t *filename) +{ + if (FLAC__metadata_chain_check_if_tempfile_needed(chain, true)) + { + // since we needed to write a tempfile, let's add some more padding so it doesn't happen again + FLAC__metadata_chain_sort_padding(chain); + + FLAC__StreamMetadata *padding = GetOrMakePadding(chain, itr); + if (padding && padding->length < 16384) + padding->length = 16384; // TODO: configurable padding size + + HANDLE hfile = CreateFileW(filename, GENERIC_READ, 0, 0, OPEN_EXISTING, 0, 0); + MetadataReader reader(hfile); + if (reader.handle == INVALID_HANDLE_VALUE) + return false; + + wchar_t tempPath[MAX_PATH-14] = {0}, tempFile[MAX_PATH] = {0}; + GetTempPathW(MAX_PATH-14, tempPath); + GetTempFileNameW(tempPath, L"waf", 0, tempFile); + + HANDLE hTempFile = CreateFileW(tempFile, GENERIC_READ|GENERIC_WRITE, 0, 0, CREATE_ALWAYS, 0, 0); + MetadataReader tempReader(hTempFile); + + FLAC__bool res = FLAC__metadata_chain_write_with_callbacks_and_tempfile(chain, false, &reader, unicodeIO, &tempReader, unicodeIO); + + CloseHandle(hfile); + CloseHandle(hTempFile); + if (!MoveFileW(tempFile, filename)) + { + if (CopyFileW(tempFile, filename, FALSE)) + { + DeleteFileW(tempFile); + } + else + { + DeleteFileW(tempFile); + return false; + } + } + return !!res; + } + else + { + HANDLE hfile = CreateFileW(filename, GENERIC_READ|GENERIC_WRITE, 0, 0, OPEN_EXISTING, 0, 0); + MetadataReader reader(hfile); + if (reader.handle == INVALID_HANDLE_VALUE) + return false; + + FLAC__bool res = FLAC__metadata_chain_write_with_callbacks(chain, true, &reader, unicodeIO); + + CloseHandle(hfile); + return !!res; + } +} + +bool FLACMetadata::GetLengthMilliseconds(unsigned __int64 *length) +{ + if (!streamInfo) + return false; + *length = (__int64)((double)(FLAC__int64)streamInfo->data.stream_info.total_samples / (double)streamInfo->data.stream_info.sample_rate * 1000.0 + 0.5); + return true; +} + +int FLACMetadata::GetNumMetadataItems() +{ + if (block) return block->data.vorbis_comment.num_comments; + else return 0; +} + +const char* FLACMetadata::EnumMetadata(int n, char *tag, int taglen) +{ + if (tag) tag[0]=0; + if (!block) return 0; + const char *entry = (const char *)block->data.vorbis_comment.comments[n].entry; + const char *metadata = strchr(entry, '='); // find the first equal + if (metadata) + { + if (tag) lstrcpynA(tag,entry,min(metadata-entry+1,taglen)); + return metadata+1; + } + else return 0; +} + +void FLACMetadata::SetTag(int pos, const char *tag) +{ + char * value = (char*)EnumMetadata(pos,0,0); + value = _strdup(value?value:""); + FLAC__StreamMetadata_VorbisComment_Entry entry; + size_t totalLen = strlen(tag) + 1 /* = */ + strlen(value); + entry.entry = (FLAC__byte *)malloc(totalLen + 1); + entry.length = totalLen; + StringCchPrintfA((char *)entry.entry, totalLen+1, "%s=%s", tag, value); + FLAC__metadata_object_vorbiscomment_set_comment(block, pos, entry, true); + free(value); +} + +bool FLACMetadata::GetPicture(FLAC__StreamMetadata_Picture_Type type, void **data, size_t *len, wchar_t **mimeType) +{ + if (!chain || !itr) + return false; + + FLAC__metadata_iterator_init(itr, chain); + while (1) + { + if (FLAC__METADATA_TYPE_PICTURE == FLAC__metadata_iterator_get_block_type(itr)) + { + FLAC__StreamMetadata *block = FLAC__metadata_iterator_get_block(itr); + FLAC__StreamMetadata_Picture &picture = block->data.picture; + if (picture.type == type) + { + *len = picture.data_length; + *data = WASABI_API_MEMMGR->sysMalloc(picture.data_length); + if (!*data) + return false; + memcpy(*data, picture.data, picture.data_length); + + char *type = 0; + if (picture.mime_type) + type = strchr(picture.mime_type, '/'); + + 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)); + if (*mimeType) + MultiByteToWideChar(CP_ACP, 0, type, -1, *mimeType, typelen); + } + else + *mimeType = 0; // unknown! + + return true; + } + } + + if (FLAC__metadata_iterator_next(itr) == false) + break; + } + + return false; +} + +bool FLACMetadata::RemovePicture(FLAC__StreamMetadata_Picture_Type type) +{ + if (!chain || !itr) + return false; + + FLAC__metadata_iterator_init(itr, chain); + while (1) + { + if (FLAC__METADATA_TYPE_PICTURE == FLAC__metadata_iterator_get_block_type(itr)) + { + FLAC__StreamMetadata *block = FLAC__metadata_iterator_get_block(itr); + FLAC__StreamMetadata_Picture &picture = block->data.picture; + if (picture.type == type) + { + FLAC__metadata_iterator_delete_block(itr, false); + return true; + } + } + + if (FLAC__metadata_iterator_next(itr) == false) + break; + } + + return false; +} + +bool FLACMetadata::SetPicture(FLAC__StreamMetadata_Picture_Type type, void *data, size_t len, const wchar_t *mimeType, int width, int height) +{ + if (!chain || !itr) + return false; + + FLAC__metadata_iterator_init(itr, chain); + while (1) + { + if (FLAC__METADATA_TYPE_PICTURE == FLAC__metadata_iterator_get_block_type(itr)) + { + FLAC__StreamMetadata *block = FLAC__metadata_iterator_get_block(itr); + FLAC__StreamMetadata_Picture &picture = block->data.picture; + if (picture.type == type) + { + FLAC__metadata_object_picture_set_data(block, (FLAC__byte *)data, len, true); + picture.width = width; + picture.height = height; + picture.depth = 32; + picture.colors = 0; + + FLAC__metadata_object_picture_set_description(block, (FLAC__byte *)"", true);// TODO? + + char mime[256] = {0}; + if (wcsstr(mimeType, L"/") != 0) + { + StringCchPrintfA(mime, 256, "%S", mimeType); + } + else + { + StringCchPrintfA(mime, 256, "image/%S", mimeType); + } + FLAC__metadata_object_picture_set_mime_type(block, mime, true); + return true; + } + } + + if (FLAC__metadata_iterator_next(itr) == false) + break; + } + + // not found. let's add it + FLAC__StreamMetadata *newBlock = FLAC__metadata_object_new(FLAC__METADATA_TYPE_PICTURE); + + FLAC__metadata_object_picture_set_data(newBlock, (FLAC__byte *)data, len, true); + FLAC__StreamMetadata_Picture &picture = newBlock->data.picture; + picture.type = type; + picture.width = width; + picture.height = height; + picture.depth = 32; + picture.colors = 0; + + FLAC__metadata_object_picture_set_description(newBlock, (FLAC__byte *)"", true);// TODO? + + char mime[256] = {0}; + StringCchPrintfA(mime, 256, "image/%S", mimeType); + FLAC__metadata_object_picture_set_mime_type(newBlock, mime, true); + + FLAC__metadata_iterator_insert_block_after(itr, newBlock); + return true; +} + +bool FLACMetadata::GetIndexPicture(int index, FLAC__StreamMetadata_Picture_Type *type, void **data, size_t *len, wchar_t **mimeType) +{ + if (!chain || !itr) + return false; + int i=0; + FLAC__metadata_iterator_init(itr, chain); + while (1) + { + if (FLAC__METADATA_TYPE_PICTURE == FLAC__metadata_iterator_get_block_type(itr)) + { + FLAC__StreamMetadata *block = FLAC__metadata_iterator_get_block(itr); + FLAC__StreamMetadata_Picture &picture = block->data.picture; + if (i++ == index) + { + *type = picture.type; + *len = picture.data_length; + *data = WASABI_API_MEMMGR->sysMalloc(picture.data_length); + if (!*data) + return false; + memcpy(*data, picture.data, picture.data_length); + + char *type = 0; + if (picture.mime_type) + type = strchr(picture.mime_type, '/'); + + if (type && *type) + { + type++; + int typelen = MultiByteToWideChar(CP_ACP, 0, type, -1, 0, 0); + *mimeType = (wchar_t *)WASABI_API_MEMMGR->sysMalloc(typelen * sizeof(wchar_t)); + if (*mimeType) + MultiByteToWideChar(CP_ACP, 0, type, -1, *mimeType, typelen); + } + else + *mimeType = 0; // unknown! + + return true; + } + } + + if (FLAC__metadata_iterator_next(itr) == false) + break; + } + + return false; +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_flac/Metadata.h b/Src/Plugins/Input/in_flac/Metadata.h new file mode 100644 index 00000000..90616aeb --- /dev/null +++ b/Src/Plugins/Input/in_flac/Metadata.h @@ -0,0 +1,48 @@ +#ifndef NULLSOFT_IN_FLAC_METADATA_H +#define NULLSOFT_IN_FLAC_METADATA_H + +#include <FLAC/all.h> + +class FLACMetadata +{ +public: + FLACMetadata(); + ~FLACMetadata(); + bool Open(const wchar_t *filename, bool optimize=false); + void Reset(); + const char *GetMetadata(const char *tag); + void SetMetadata(const char *tag, const char *value); + void RemoveMetadata(const char *tag); + void RemoveMetadata(int n); + bool Save(const wchar_t *filename); + const FLAC__StreamMetadata_StreamInfo *GetStreamInfo(); + __int64 GetFileSize() { return filesize; } + bool GetLengthMilliseconds(unsigned __int64 *length); + int GetNumMetadataItems(); + const char* EnumMetadata(int n, char *tag, int len); + void SetTag(int n, const char *tag); + + bool GetPicture(FLAC__StreamMetadata_Picture_Type type, void **data, size_t *len, wchar_t **mimeType); + bool GetIndexPicture(int index, FLAC__StreamMetadata_Picture_Type *type, void **data, size_t *len, wchar_t **mimeType); + bool RemovePicture(FLAC__StreamMetadata_Picture_Type type); + bool SetPicture(FLAC__StreamMetadata_Picture_Type type, void *data, size_t len, const wchar_t *mimeType, int width, int height); +private: + FLAC__Metadata_Chain *chain; + FLAC__Metadata_Iterator *itr; + FLAC__StreamMetadata *block; + FLAC__StreamMetadata *streamInfo; + __int64 filesize; +}; + +class Info +{ +public: + FLACMetadata metadata; + const wchar_t *filename; +}; + +extern FLACMetadata *getMetadata; +extern wchar_t *getFileInfoFn; +extern Info *info; + +#endif
\ No newline at end of file diff --git a/Src/Plugins/Input/in_flac/Preferences.cpp b/Src/Plugins/Input/in_flac/Preferences.cpp new file mode 100644 index 00000000..95500b1c --- /dev/null +++ b/Src/Plugins/Input/in_flac/Preferences.cpp @@ -0,0 +1,110 @@ +/* +** Copyright (C) 2007-2011 Nullsoft, Inc. +** +** This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held +** liable for any damages arising from the use of this software. +** +** Permission is granted to anyone to use this software for any purpose, including commercial applications, and to +** alter it and redistribute it freely, subject to the following restrictions: +** +** 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. +** If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +** +** 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +** +** 3. This notice may not be removed or altered from any source distribution. +** +** Author: Ben Allison benski@winamp.com +** Created: March 1, 2007 +** +*/ +#include "main.h" +#include <FLAC/all.h> +#include "resource.h" +#include "../Agave/Language/api_language.h" +#include "../nu/AutoChar.h" +#include <assert.h> +#include <strsafe.h> + +bool fixBitrate=false; +bool config_average_bitrate=true; + +// the return pointer has been malloc'd. Use free() when you are done. +char *BuildExtensions(const char *extensions) +{ + char name[64] = {0}; + WASABI_API_LNGSTRING_BUF(IDS_FLAC_FILES,name,64); + size_t length = strlen(extensions) + 1 + strlen(name) + 2; + char *newExt = (char *)malloc(length); + char *ret = newExt; // save because we modify newExt + + // copy extensions + StringCchCopyExA(newExt, length, extensions, &newExt, &length, 0); + newExt++; + length--; + + // copy description + StringCchCopyExA(newExt, length, name, &newExt, &length, 0); + newExt++; + length--; + + // double null terminate + assert(length == 1); + *newExt = 0; + + return ret; +} + +static INT_PTR CALLBACK PreferencesProc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam) +{ + switch(msg) + { + case WM_INITDIALOG: + { + wchar_t config_extensions[128] = {0}; + GetPrivateProfileStringW(L"in_flac", L"extensions", DEFAULT_EXTENSIONSW, config_extensions, 128, winampINI); + SetDlgItemTextW(hwndDlg, IDC_EXTENSIONS, config_extensions); + CheckDlgButton(hwndDlg, IDC_AVERAGE_BITRATE, config_average_bitrate?BST_CHECKED:BST_UNCHECKED); + } + return TRUE; + + case WM_DESTROY: + break; + + case WM_COMMAND: + switch(LOWORD(wParam)) + { + case IDOK: + { + wchar_t config_extensions[128] = {0}; + GetDlgItemTextW(hwndDlg, IDC_EXTENSIONS, config_extensions, 128); + if (!lstrcmpiW(config_extensions, DEFAULT_EXTENSIONSW)) + WritePrivateProfileStringW(L"in_flac", L"extensions", 0, winampINI); + else + WritePrivateProfileStringW(L"in_flac", L"extensions", config_extensions, winampINI); + + plugin.FileExtensions = BuildExtensions(AutoChar(config_extensions)); + config_average_bitrate = !!IsDlgButtonChecked(hwndDlg, IDC_AVERAGE_BITRATE); + if (config_average_bitrate) + WritePrivateProfileStringW(L"in_flac", L"average_bitrate", L"1", winampINI); + else + WritePrivateProfileStringW(L"in_flac", L"average_bitrate", L"0", winampINI); + + fixBitrate=true; + EndDialog(hwndDlg, 0); + } + break; + + case IDCANCEL: + EndDialog(hwndDlg, 0); + break; + } + break; + } + return 0; +} + +void Config(HWND hwndParent) +{ + WASABI_API_DIALOGBOXW(IDD_PREFERENCES, hwndParent, PreferencesProc); +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_flac/QuickBuf.h b/Src/Plugins/Input/in_flac/QuickBuf.h new file mode 100644 index 00000000..c615613b --- /dev/null +++ b/Src/Plugins/Input/in_flac/QuickBuf.h @@ -0,0 +1,50 @@ +#ifndef NULLSOFT_IN_FLAC_QUICKBUF_H +#define NULLSOFT_IN_FLAC_QUICKBUF_H + +#include <malloc.h> +class QuickBuf +{ +public: + QuickBuf() : buffer(0), len(0) + { + } + + void Reserve(size_t res) + { + if (res > len) + { + len=res; + free(buffer); + buffer = malloc(len); + } + } + + void Free() + { + free(buffer); + buffer=0; + len=0; + } + + void Move(size_t offset) + { + memmove(buffer, (char *)buffer + offset, len-offset); + } + + + operator void *() + { + return buffer; + } + + operator char *() + { + return (char *)buffer; + } + +private: + void *buffer; + size_t len; +}; + +#endif
\ No newline at end of file diff --git a/Src/Plugins/Input/in_flac/RawReader.cpp b/Src/Plugins/Input/in_flac/RawReader.cpp new file mode 100644 index 00000000..9c6814f5 --- /dev/null +++ b/Src/Plugins/Input/in_flac/RawReader.cpp @@ -0,0 +1,174 @@ +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#include "RawReader.h" +#include "main.h" +#include <limits.h> +#include <shlwapi.h> +#include "../nu/AutoWide.h" +#include <new> +#include <strsafe.h> +#include "nswasabi/ReferenceCounted.h" + +static bool IsMyExtension(const wchar_t *filename) +{ + const wchar_t *extension = PathFindExtensionW(filename); + if (extension && *extension) + { + wchar_t exts[128] = {0}; + GetPrivateProfileStringW(L"in_flac", L"extensions", DEFAULT_EXTENSIONSW, exts, 128, winampINI); + + extension++; + wchar_t *b = exts; + wchar_t *c; + 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; +} + +int RawMediaReaderService::CreateRawMediaReader(const wchar_t *filename, ifc_raw_media_reader **out_reader) +{ + if (IsMyExtension(filename)) + { + nx_file_t file; + ReferenceCountedNXString filename_nx; + ReferenceCountedNXURI filename_uri; + NXStringCreateWithUTF16(&filename_nx, filename); + NXURICreateWithNXString(&filename_uri, filename_nx); + + int ret = NXFileOpenFile(&file, filename_uri, nx_file_FILE_read_binary); + if (ret != NErr_Success) + return ret; + + RawMediaReader *reader = new (std::nothrow) RawMediaReader(); + if (!reader) + { + NXFileRelease(file); + return NErr_OutOfMemory; + } + + ret = reader->Initialize(file); + NXFileRelease(file); + if (ret != NErr_Success) + { + delete reader; + return ret; + } + + *out_reader = reader; + return NErr_Success; + + } + else + { + return NErr_False; + } +} + +#define CBCLASS RawMediaReaderService +START_DISPATCH; +CB(CREATERAWMEDIAREADER, CreateRawMediaReader); +END_DISPATCH; +#undef CBCLASS + +static void OnError(const FLAC__StreamDecoder *decoder, FLAC__StreamDecoderErrorStatus status, void *client_data) +{ + //client_data=client_data; // dummy line so i can set a breakpoint +} + +static void OnMetadata(const FLAC__StreamDecoder *decoder, const FLAC__StreamMetadata *metadata, void *client_data) +{ + +} + +static FLAC__StreamDecoderWriteStatus OnAudio(const FLAC__StreamDecoder *decoder, const FLAC__Frame *frame, const FLAC__int32 *const buffer[], void *client_data) +{ + + return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE; +} + +RawMediaReader::RawMediaReader() +{ + decoder=0; + file=0; +} + +RawMediaReader::~RawMediaReader() +{ + if (decoder) + FLAC__stream_decoder_delete(decoder); + NXFileRelease(file); +} + +int RawMediaReader::Initialize(nx_file_t file) +{ + this->file = NXFileRetain(file); + decoder = FLAC__stream_decoder_new(); + if (!decoder) + return NErr_FailedCreate; + + FLAC__stream_decoder_set_metadata_ignore(decoder, FLAC__METADATA_TYPE_VORBIS_COMMENT); + state.SetFile(file); + state.SetObject(this); + FLAC__stream_decoder_set_md5_checking(decoder, true); + if(FLAC__stream_decoder_init_stream( + decoder, + FLAC_NXFile_Read, + FLAC_NXFile_Seek, + FLAC_NXFile_Tell, + FLAC_NXFile_Length, + FLAC_NXFile_EOF, + OnAudio, + OnMetadata, + OnError, + &state + ) != FLAC__STREAM_DECODER_INIT_STATUS_OK) + { + FLAC__stream_decoder_delete(decoder); + decoder=0; + return NErr_Error; + } + + FLAC__stream_decoder_process_until_end_of_metadata(decoder); + uint64_t position; + FLAC__stream_decoder_get_decode_position(decoder, &position); + FLAC__stream_decoder_delete(decoder); + decoder=0; + NXFileSeek(file, position); + + return NErr_Success; +} + +int RawMediaReader::Read(void *buffer, size_t buffer_size, size_t *bytes_read) +{ + return NXFileRead(file, buffer, buffer_size, bytes_read); +} + +size_t RawMediaReader::Release() +{ + + delete this; + return 0; +} + +#define CBCLASS RawMediaReader +START_DISPATCH; +CB(RELEASE, Release); +CB(RAW_READ, Read); +END_DISPATCH; +#undef CBCLASS
\ No newline at end of file diff --git a/Src/Plugins/Input/in_flac/RawReader.h b/Src/Plugins/Input/in_flac/RawReader.h new file mode 100644 index 00000000..68348e38 --- /dev/null +++ b/Src/Plugins/Input/in_flac/RawReader.h @@ -0,0 +1,36 @@ +#pragma once +#include "../Agave/DecodeFile/svc_raw_media_reader.h" +#include "../Agave/DecodeFile/ifc_raw_media_reader.h" +#include "FLACFileCallbacks.h" +#include <FLAC/all.h> + +// {E906F4DC-3080-4B9B-951F-85950193ACBF} +static const GUID flac_raw_reader_guid = +{ 0xe906f4dc, 0x3080, 0x4b9b, { 0x95, 0x1f, 0x85, 0x95, 0x1, 0x93, 0xac, 0xbf } }; + + +class RawMediaReaderService : public svc_raw_media_reader +{ +public: + static const char *getServiceName() { return "FLAC Raw Reader"; } + static GUID getServiceGuid() { return flac_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(); + ~RawMediaReader(); + int Initialize(nx_file_t ); + int Read(void *buffer, size_t buffer_size, size_t *bytes_read); + size_t Release(); +protected: + RECVS_DISPATCH; +private: + FLAC__StreamDecoder *decoder; + FLACClientData state; + nx_file_t file; +};
\ No newline at end of file diff --git a/Src/Plugins/Input/in_flac/Stopper.cpp b/Src/Plugins/Input/in_flac/Stopper.cpp new file mode 100644 index 00000000..3ff4cad3 --- /dev/null +++ b/Src/Plugins/Input/in_flac/Stopper.cpp @@ -0,0 +1,65 @@ +/* +** Copyright (C) 2008 Nullsoft, Inc. +** +** This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held +** liable for any damages arising from the use of this software. +** +** Permission is granted to anyone to use this software for any purpose, including commercial applications, and to +** alter it and redistribute it freely, subject to the following restrictions: +** +** 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. +** If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +** +** 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +** +** 3. This notice may not be removed or altered from any source distribution. +** +** Author: Ben Allison benski@winamp.com +** Created: January 31, 2008 +** +*/ +#include "Stopper.h" +#include "main.h" +#include "../Winamp/wa_ipc.h" + +Stopper::Stopper() : isplaying(0), timems(0) +{ +} + +void Stopper::ChangeTracking(bool mode) +{ + SendMessage(plugin.hMainWindow, WM_USER, mode, IPC_ALLOW_PLAYTRACKING); // enable / disable stats updating +} + +void Stopper::Stop() +{ + isplaying = SendMessage(plugin.hMainWindow, WM_USER, 0, IPC_ISPLAYING); + if (isplaying) + { + ChangeTracking(0); // disable stats updating + timems = SendMessage(plugin.hMainWindow, WM_USER, 0, IPC_GETOUTPUTTIME); + SendMessage(plugin.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(plugin.hMainWindow, WM_COMMAND, 40045, 0); // Play + //m_force_seek = -1; + if (isplaying & 2) + { + SendMessage(plugin.hMainWindow, WM_COMMAND, 40046, 0); // Pause + } + ChangeTracking(1); // enable stats updating + } +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_flac/Stopper.h b/Src/Plugins/Input/in_flac/Stopper.h new file mode 100644 index 00000000..05d5ac6f --- /dev/null +++ b/Src/Plugins/Input/in_flac/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_flac/StreamFileWin32.cpp b/Src/Plugins/Input/in_flac/StreamFileWin32.cpp new file mode 100644 index 00000000..f34b0863 --- /dev/null +++ b/Src/Plugins/Input/in_flac/StreamFileWin32.cpp @@ -0,0 +1,129 @@ +/* +** Copyright (C) 2007-2012 Nullsoft, Inc. +** +** This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held +** liable for any damages arising from the use of this software. +** +** Permission is granted to anyone to use this software for any purpose, including commercial applications, and to +** alter it and redistribute it freely, subject to the following restrictions: +** +** 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. +** If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +** +** 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +** +** 3. This notice may not be removed or altered from any source distribution. +** +** Author: Ben Allison benski@winamp.com +** Created: March 1, 2007 +** +*/ +#include <windows.h> +#include <FLAC/all.h> +#include <assert.h> +#include "StreamFileWin32.h" + +FLAC__StreamDecoderReadStatus Win32_Read(const FLAC__StreamDecoder *decoder, FLAC__byte buffer[], size_t *bytes, void *client_data) +{ + assert(*bytes <= 4294967295U); + + HANDLE file = ((Win32_State *)client_data)->handle; + if(*bytes > 0) + { + assert(sizeof(FLAC__byte) == 1); + DWORD bytesRead=0, bytesToRead=*bytes; + BOOL result = ReadFile(file, buffer, bytesToRead, &bytesRead, NULL); + *bytes = bytesRead; + + if (!result) + return FLAC__STREAM_DECODER_READ_STATUS_ABORT; + else if(bytesRead == 0) + { + ((Win32_State *)client_data)->endOfFile = true; + return FLAC__STREAM_DECODER_READ_STATUS_END_OF_STREAM; + } + else + return FLAC__STREAM_DECODER_READ_STATUS_CONTINUE; + } + else + return FLAC__STREAM_DECODER_READ_STATUS_ABORT; +} + +__int64 Seek64(HANDLE hf, __int64 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; +} + +FLAC__StreamDecoderSeekStatus Win32_Seek(const FLAC__StreamDecoder *decoder, FLAC__uint64 absolute_byte_offset, void *client_data) +{ + HANDLE file = ((Win32_State *)client_data)->handle; + + __int64 result = Seek64(file, absolute_byte_offset, FILE_BEGIN); + + if (result == INVALID_SET_FILE_POINTER) + return FLAC__STREAM_DECODER_SEEK_STATUS_ERROR; + else + { + ((Win32_State *)client_data)->endOfFile = false; + return FLAC__STREAM_DECODER_SEEK_STATUS_OK; + } +} + +FLAC__StreamDecoderTellStatus Win32_Tell(const FLAC__StreamDecoder *decoder, FLAC__uint64 *absolute_byte_offset, void *client_data) +{ + HANDLE file = ((Win32_State *)client_data)->handle; + + __int64 position = Seek64(file, 0, FILE_CURRENT); + + if (position == INVALID_SET_FILE_POINTER) + return FLAC__STREAM_DECODER_TELL_STATUS_ERROR; + else + { + *absolute_byte_offset=position; + return FLAC__STREAM_DECODER_TELL_STATUS_OK; + } +} +__int64 FileSize64(HANDLE file) +{ + LARGE_INTEGER position; + position.QuadPart=0; + position.LowPart = GetFileSize(file, (LPDWORD)&position.HighPart); + + if (position.LowPart == INVALID_FILE_SIZE && GetLastError() != NO_ERROR) + return INVALID_FILE_SIZE; + else + return position.QuadPart; +} + +FLAC__StreamDecoderLengthStatus Win32_Length(const FLAC__StreamDecoder *decoder, FLAC__uint64 *stream_length, void *client_data) +{ + HANDLE file = ((Win32_State *)client_data)->handle; + + LARGE_INTEGER position; + position.QuadPart=0; + position.LowPart = GetFileSize(file, (LPDWORD)&position.HighPart); + + if (position.LowPart == INVALID_FILE_SIZE && GetLastError() != NO_ERROR) + return FLAC__STREAM_DECODER_LENGTH_STATUS_ERROR; + else + { + *stream_length = position.QuadPart; + return FLAC__STREAM_DECODER_LENGTH_STATUS_OK; + } +} + +FLAC__bool Win32_EOF(const FLAC__StreamDecoder *decoder, void *client_data) +{ + return ((Win32_State *)client_data)->endOfFile; +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_flac/StreamFileWin32.h b/Src/Plugins/Input/in_flac/StreamFileWin32.h new file mode 100644 index 00000000..e3a98d79 --- /dev/null +++ b/Src/Plugins/Input/in_flac/StreamFileWin32.h @@ -0,0 +1,23 @@ +#ifndef NULLSOFT_IN_FLAC_STREAMFILEWIN32_H +#define NULLSOFT_IN_FLAC_STREAMFILEWIN32_H + +#include <FLAC/all.h> +#include <windows.h> + +struct Win32_State +{ + void *userData; + HANDLE handle; + bool endOfFile; +}; + +FLAC__StreamDecoderReadStatus Win32_Read(const FLAC__StreamDecoder *decoder, FLAC__byte buffer[], size_t *bytes, void *client_data); +FLAC__StreamDecoderSeekStatus Win32_Seek(const FLAC__StreamDecoder *decoder, FLAC__uint64 absolute_byte_offset, void *client_data); +FLAC__StreamDecoderTellStatus Win32_Tell(const FLAC__StreamDecoder *decoder, FLAC__uint64 *absolute_byte_offset, void *client_data); +FLAC__StreamDecoderLengthStatus Win32_Length(const FLAC__StreamDecoder *decoder, FLAC__uint64 *stream_length, void *client_data); +FLAC__bool Win32_EOF(const FLAC__StreamDecoder *decoder, void *client_data); + +// helper function extern'd here because DecodeThread needs it +__int64 FileSize64(HANDLE file); + +#endif
\ No newline at end of file diff --git a/Src/Plugins/Input/in_flac/api__in_flv.h b/Src/Plugins/Input/in_flac/api__in_flv.h new file mode 100644 index 00000000..f82c0db4 --- /dev/null +++ b/Src/Plugins/Input/in_flac/api__in_flv.h @@ -0,0 +1,11 @@ +#ifndef NULLSOFT_IN_FLAC_API_H +#define NULLSOFT_IN_FLAC_API_H + +#include "../Agave/Config/api_config.h" +extern api_config *AGAVE_API_CONFIG; + +#include <api/memmgr/api_memmgr.h> +extern api_memmgr *memmgr; +#define WASABI_API_MEMMGR memmgr + +#endif
\ No newline at end of file diff --git a/Src/Plugins/Input/in_flac/in_flac.rc b/Src/Plugins/Input/in_flac/in_flac.rc new file mode 100644 index 00000000..937bcb8b --- /dev/null +++ b/Src/Plugins/Input/in_flac/in_flac.rc @@ -0,0 +1,163 @@ +// 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 + +///////////////////////////////////////////////////////////////////////////// +// +// Dialog +// + +IDD_INFOCHILD_ADVANCED 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 "Advanced",IDC_STATIC,0,0,341,164 + CONTROL "",IDC_LIST,"SysListView32",LVS_REPORT | LVS_SINGLESEL | LVS_SHOWSELALWAYS | LVS_ALIGNLEFT | WS_BORDER | WS_TABSTOP,6,13,164,143 + LTEXT "Name:",IDC_STATIC,175,13,22,8 + EDITTEXT IDC_NAME,175,23,159,14,ES_AUTOHSCROLL + LTEXT "Value:",IDC_STATIC,175,39,21,8 + EDITTEXT IDC_VALUE,175,49,159,90,ES_MULTILINE | ES_AUTOHSCROLL | ES_WANTRETURN | WS_VSCROLL + PUSHBUTTON "Add New",IDC_BUTTON_ADD,176,142,50,13 + PUSHBUTTON "Delete",IDC_BUTTON_DEL,230,142,50,13 + PUSHBUTTON "Delete All",IDC_BUTTON_DELALL,284,142,50,13 +END + +IDD_PREFERENCES DIALOGEX 0, 0, 175, 84 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Nullsoft FLAC Decoder Preferences" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + GROUPBOX "Extensions",IDC_STATIC,5,4,166,43 + EDITTEXT IDC_EXTENSIONS,12,16,153,13,ES_AUTOHSCROLL + LTEXT "Semi-colon separated (e.g. FLAC;FLA)",IDC_STATIC,12,33,124,8 + CONTROL "Show average bitrate",IDC_AVERAGE_BITRATE,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,5,52,110,10 + DEFPUSHBUTTON "OK",IDOK,67,67,50,13 + PUSHBUTTON "Cancel",IDCANCEL,121,67,50,13 +END + + +///////////////////////////////////////////////////////////////////////////// +// +// DESIGNINFO +// + +#ifdef APSTUDIO_INVOKED +GUIDELINES DESIGNINFO +BEGIN + IDD_INFOCHILD_ADVANCED, DIALOG + BEGIN + RIGHTMARGIN, 340 + BOTTOMMARGIN, 163 + END + + IDD_PREFERENCES, DIALOG + BEGIN + LEFTMARGIN, 5 + RIGHTMARGIN, 171 + TOPMARGIN, 4 + BOTTOMMARGIN, 80 + 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 + + +///////////////////////////////////////////////////////////////////////////// +// +// String Table +// + +STRINGTABLE +BEGIN + IDS_NULLSOFT_FLAC_DECODER_OLD "Nullsoft FLAC Decoder" + IDS_TO_ADVANCED_MODE "to advanced mode >>" + IDS_TO_SIMPLE_MODE "<< to simple mode" + IDS_NAME "Name" + IDS_VALUE "Value" + IDS_TRACK_GAIN "Track Gain: %s\n" + IDS_ALBUM_GAIN "Album Gain: %s\n" + IDS_NOT_PRESENT "not present" + IDS_LENGTH_IN_SECONDS "Length: %I64d seconds\n" + IDS_CHANNELS "Channels: %d\n" + IDS_BITS_PER_SAMPLE "Bits per sample: %d\n" + IDS_SAMPLE_RATE "Sample Rate: %d Hz\n" + IDS_FILE_SIZE_IN_BYTES "File Size: %I64d bytes\n" + IDS_AVERAGE_BITRATE "Average bitrate: %I64d kbps\n" + IDS_COMPRESSION_RATIO "Compression Ratio: %u%%\n" +END + +STRINGTABLE +BEGIN + IDS_NULLSOFT_FLAC_DECODER "Nullsoft FLAC Decoder v%s" + 65535 "{9475116B-F8C4-4dff-BC19-9601B238557D}" +END + +STRINGTABLE +BEGIN + IDS_LIBFLAC_DLL_MISSING "[libflac.dll missing]" + IDS_FLAC_FILES "FLAC Files" + IDS_FAMILY_STRING "Free Lossless Audio Codec File" + IDS_ABOUT_TEXT "%s\n© 2007-2023 Winamp SA\nWritten by Ben Allison\nBuild date: %hs\n\nUsing FLAC v%hs" + IDS_CANNOT_SAVE_METADATA "Cannot save metadata: Error writing data." + IDS_ERROR_SAVING_METADATA "Error saving metadata" +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_flac/in_flac.sln b/Src/Plugins/Input/in_flac/in_flac.sln new file mode 100644 index 00000000..192ea80b --- /dev/null +++ b/Src/Plugins/Input/in_flac/in_flac.sln @@ -0,0 +1,105 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.29613.14 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "in_flac", "in_flac.vcxproj", "{2B754A9B-B449-45F2-93B4-49884A7691B6}" + ProjectSection(ProjectDependencies) = postProject + {4FC28B55-2A14-43D5-86F7-201054F338A9} = {4FC28B55-2A14-43D5-86F7-201054F338A9} + {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915} = {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915} + {4CEFBC83-C215-11DB-8314-0800200C9A66} = {4CEFBC83-C215-11DB-8314-0800200C9A66} + {E105A0A2-7391-47C5-86AC-718003524C3D} = {E105A0A2-7391-47C5-86AC-718003524C3D} + {0F9730E4-45DA-4BD2-A50A-403A4BC9751A} = {0F9730E4-45DA-4BD2-A50A-403A4BC9751A} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "nx", "..\replicant\nx\nx.vcxproj", "{57C90706-B25D-4ACA-9B33-95CDB2427C27}" + ProjectSection(ProjectDependencies) = postProject + {0F9730E4-45DA-4BD2-A50A-403A4BC9751A} = {0F9730E4-45DA-4BD2-A50A-403A4BC9751A} + EndProjectSection +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 + {0F9730E4-45DA-4BD2-A50A-403A4BC9751A} = {0F9730E4-45DA-4BD2-A50A-403A4BC9751A} + 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}") = "libFLAC_dynamic", "..\libFLAC\libFLAC_dynamic.vcxproj", "{4CEFBC83-C215-11DB-8314-0800200C9A66}" + ProjectSection(ProjectDependencies) = postProject + {4FC28B55-2A14-43D5-86F7-201054F338A9} = {4FC28B55-2A14-43D5-86F7-201054F338A9} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libogg", "..\libogg\libogg.vcxproj", "{4FC28B55-2A14-43D5-86F7-201054F338A9}" +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 + {2B754A9B-B449-45F2-93B4-49884A7691B6}.Debug|Win32.ActiveCfg = Debug|Win32 + {2B754A9B-B449-45F2-93B4-49884A7691B6}.Debug|Win32.Build.0 = Debug|Win32 + {2B754A9B-B449-45F2-93B4-49884A7691B6}.Debug|x64.ActiveCfg = Debug|Win32 + {2B754A9B-B449-45F2-93B4-49884A7691B6}.Release|Win32.ActiveCfg = Release|Win32 + {2B754A9B-B449-45F2-93B4-49884A7691B6}.Release|Win32.Build.0 = Release|Win32 + {2B754A9B-B449-45F2-93B4-49884A7691B6}.Release|x64.ActiveCfg = Release|Win32 + {57C90706-B25D-4ACA-9B33-95CDB2427C27}.Debug|Win32.ActiveCfg = Debug|Win32 + {57C90706-B25D-4ACA-9B33-95CDB2427C27}.Debug|Win32.Build.0 = Debug|Win32 + {57C90706-B25D-4ACA-9B33-95CDB2427C27}.Debug|x64.ActiveCfg = Debug|x64 + {57C90706-B25D-4ACA-9B33-95CDB2427C27}.Debug|x64.Build.0 = Debug|x64 + {57C90706-B25D-4ACA-9B33-95CDB2427C27}.Release|Win32.ActiveCfg = Release|Win32 + {57C90706-B25D-4ACA-9B33-95CDB2427C27}.Release|Win32.Build.0 = Release|Win32 + {57C90706-B25D-4ACA-9B33-95CDB2427C27}.Release|x64.ActiveCfg = Release|x64 + {57C90706-B25D-4ACA-9B33-95CDB2427C27}.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 + {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 + {4CEFBC83-C215-11DB-8314-0800200C9A66}.Debug|Win32.ActiveCfg = Debug|Win32 + {4CEFBC83-C215-11DB-8314-0800200C9A66}.Debug|Win32.Build.0 = Debug|Win32 + {4CEFBC83-C215-11DB-8314-0800200C9A66}.Debug|x64.ActiveCfg = Debug|x64 + {4CEFBC83-C215-11DB-8314-0800200C9A66}.Debug|x64.Build.0 = Debug|x64 + {4CEFBC83-C215-11DB-8314-0800200C9A66}.Release|Win32.ActiveCfg = Release|Win32 + {4CEFBC83-C215-11DB-8314-0800200C9A66}.Release|Win32.Build.0 = Release|Win32 + {4CEFBC83-C215-11DB-8314-0800200C9A66}.Release|x64.ActiveCfg = Release|x64 + {4CEFBC83-C215-11DB-8314-0800200C9A66}.Release|x64.Build.0 = Release|x64 + {4FC28B55-2A14-43D5-86F7-201054F338A9}.Debug|Win32.ActiveCfg = Debug|Win32 + {4FC28B55-2A14-43D5-86F7-201054F338A9}.Debug|Win32.Build.0 = Debug|Win32 + {4FC28B55-2A14-43D5-86F7-201054F338A9}.Debug|x64.ActiveCfg = Debug|x64 + {4FC28B55-2A14-43D5-86F7-201054F338A9}.Debug|x64.Build.0 = Debug|x64 + {4FC28B55-2A14-43D5-86F7-201054F338A9}.Release|Win32.ActiveCfg = Release|Win32 + {4FC28B55-2A14-43D5-86F7-201054F338A9}.Release|Win32.Build.0 = Release|Win32 + {4FC28B55-2A14-43D5-86F7-201054F338A9}.Release|x64.ActiveCfg = Release|x64 + {4FC28B55-2A14-43D5-86F7-201054F338A9}.Release|x64.Build.0 = Release|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {A0F24E75-21BB-4DDF-A287-A481B554B4EB} + EndGlobalSection +EndGlobal diff --git a/Src/Plugins/Input/in_flac/in_flac.vcxproj b/Src/Plugins/Input/in_flac/in_flac.vcxproj new file mode 100644 index 00000000..cb9250cc --- /dev/null +++ b/Src/Plugins/Input/in_flac/in_flac.vcxproj @@ -0,0 +1,307 @@ +<?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>{2B754A9B-B449-45F2-93B4-49884A7691B6}</ProjectGuid> + <RootNamespace>in_flac</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>WIN32;_DEBUG;_WINDOWS;_USRDLL;IN_FLAC2_EXPORTS;UNICODE_INPUT_PLUGIN;_WIN32_WINNT=0x0601;WINVER=0x0601;_WIN32_IE=0x0A00;WIN32_LEAN_AND_MEAN;_CRT_SECURE_NO_WARNINGS;REPLICANT_INCLUDE;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <MinimalRebuild>false</MinimalRebuild> + <MultiProcessorCompilation>true</MultiProcessorCompilation> + <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks> + <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary> + <WarningLevel>Level3</WarningLevel> + <DebugInformationFormat>ProgramDatabase</DebugInformationFormat> + <DisableSpecificWarnings>4244;4995;%(DisableSpecificWarnings)</DisableSpecificWarnings> + <ProgramDataBaseFileName>$(IntDir)$(TargetName).pdb</ProgramDataBaseFileName> + </ClCompile> + <Link> + <AdditionalDependencies>shlwapi.lib;%(AdditionalDependencies)</AdditionalDependencies> + <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile> + <DelayLoadDLLs>nxlite.dll;%(DelayLoadDLLs)</DelayLoadDLLs> + <GenerateDebugInformation>true</GenerateDebugInformation> + <ProgramDatabaseFile>$(IntDir)$(TargetName).pdb</ProgramDatabaseFile> + <SubSystem>Windows</SubSystem> + <RandomizedBaseAddress>false</RandomizedBaseAddress> + <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary> + <TargetMachine>MachineX86</TargetMachine> + <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories> + <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers> + </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>WIN64;_DEBUG;_WINDOWS;_USRDLL;IN_FLAC2_EXPORTS;UNICODE_INPUT_PLUGIN;_WIN32_WINNT=0x0601;WINVER=0x0601;_WIN32_IE=0x0A00;WIN32_LEAN_AND_MEAN;_CRT_SECURE_NO_WARNINGS;REPLICANT_INCLUDE;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <MinimalRebuild>false</MinimalRebuild> + <MultiProcessorCompilation>true</MultiProcessorCompilation> + <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks> + <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary> + <WarningLevel>Level3</WarningLevel> + <DebugInformationFormat>ProgramDatabase</DebugInformationFormat> + <DisableSpecificWarnings>4244;4267;4995;%(DisableSpecificWarnings)</DisableSpecificWarnings> + <ProgramDataBaseFileName>$(IntDir)$(TargetName).pdb</ProgramDataBaseFileName> + </ClCompile> + <Link> + <AdditionalDependencies>shlwapi.lib;%(AdditionalDependencies)</AdditionalDependencies> + <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile> + <DelayLoadDLLs>nxlite.dll;%(DelayLoadDLLs)</DelayLoadDLLs> + <GenerateDebugInformation>true</GenerateDebugInformation> + <ProgramDatabaseFile>$(IntDir)$(TargetName).pdb</ProgramDatabaseFile> + <SubSystem>Windows</SubSystem> + <RandomizedBaseAddress>false</RandomizedBaseAddress> + <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary> + <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories> + <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers> + </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>WIN32;NDEBUG;_WINDOWS;_USRDLL;IN_FLAC2_EXPORTS;UNICODE_INPUT_PLUGIN;_WIN32_WINNT=0x0601;WINVER=0x0601;_WIN32_IE=0x0A00;WIN32_LEAN_AND_MEAN;_CRT_SECURE_NO_WARNINGS;REPLICANT_INCLUDE;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <StringPooling>true</StringPooling> + <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary> + <BufferSecurityCheck>true</BufferSecurityCheck> + <PrecompiledHeader /> + <WarningLevel>Level3</WarningLevel> + <DebugInformationFormat>None</DebugInformationFormat> + <DisableSpecificWarnings>4995;4244;%(DisableSpecificWarnings)</DisableSpecificWarnings> + <MultiProcessorCompilation>true</MultiProcessorCompilation> + <ProgramDataBaseFileName>$(IntDir)$(TargetName).pdb</ProgramDataBaseFileName> + </ClCompile> + <Link> + <AdditionalDependencies>shlwapi.lib;%(AdditionalDependencies)</AdditionalDependencies> + <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile> + <DelayLoadDLLs>nxlite.dll;%(DelayLoadDLLs)</DelayLoadDLLs> + <GenerateDebugInformation>false</GenerateDebugInformation> + <ProgramDatabaseFile>$(IntDir)$(TargetName).pdb</ProgramDatabaseFile> + <SubSystem>Windows</SubSystem> + <OptimizeReferences>true</OptimizeReferences> + <EnableCOMDATFolding>true</EnableCOMDATFolding> + <RandomizedBaseAddress>false</RandomizedBaseAddress> + <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary> + <TargetMachine>MachineX86</TargetMachine> + <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories> + <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers> + </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>WIN64;NDEBUG;_WINDOWS;_USRDLL;IN_FLAC2_EXPORTS;UNICODE_INPUT_PLUGIN;_WIN32_WINNT=0x0601;WINVER=0x0601;_WIN32_IE=0x0A00;WIN32_LEAN_AND_MEAN;_CRT_SECURE_NO_WARNINGS;REPLICANT_INCLUDE;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <StringPooling>true</StringPooling> + <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary> + <BufferSecurityCheck>true</BufferSecurityCheck> + <PrecompiledHeader /> + <WarningLevel>Level3</WarningLevel> + <DebugInformationFormat>None</DebugInformationFormat> + <DisableSpecificWarnings>4244;4267;4995;%(DisableSpecificWarnings)</DisableSpecificWarnings> + <MultiProcessorCompilation>true</MultiProcessorCompilation> + <ProgramDataBaseFileName>$(IntDir)$(TargetName).pdb</ProgramDataBaseFileName> + </ClCompile> + <Link> + <AdditionalDependencies>shlwapi.lib;%(AdditionalDependencies)</AdditionalDependencies> + <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile> + <DelayLoadDLLs>nxlite.dll;%(DelayLoadDLLs)</DelayLoadDLLs> + <GenerateDebugInformation>false</GenerateDebugInformation> + <ProgramDatabaseFile>$(IntDir)$(TargetName).pdb</ProgramDatabaseFile> + <SubSystem>Windows</SubSystem> + <OptimizeReferences>true</OptimizeReferences> + <EnableCOMDATFolding>true</EnableCOMDATFolding> + <RandomizedBaseAddress>false</RandomizedBaseAddress> + <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary> + <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories> + <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers> + </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="..\..\..\replicant\nx\nx.vcxproj"> + <Project>{57c90706-b25d-4aca-9b33-95cdb2427c27}</Project> + <CopyLocalSatelliteAssemblies>true</CopyLocalSatelliteAssemblies> + <ReferenceOutputAssembly>true</ReferenceOutputAssembly> + </ProjectReference> + <ProjectReference Include="..\..\..\Wasabi\Wasabi.vcxproj"> + <Project>{3e0bfa8a-b86a-42e9-a33f-ec294f823f7f}</Project> + </ProjectReference> + </ItemGroup> + <ItemGroup> + <ClCompile Include="..\..\..\nu\SpillBuffer.cpp" /> + <ClCompile Include="About.cpp" /> + <ClCompile Include="AlbumArt.cpp" /> + <ClCompile Include="DecodeThread.cpp" /> + <ClCompile Include="ExtendedFileInfo.cpp" /> + <ClCompile Include="ExtendedRead.cpp" /> + <ClCompile Include="FileInfo.cpp" /> + <ClCompile Include="FLACFileCallbacks.cpp" /> + <ClCompile Include="main.cpp" /> + <ClCompile Include="Metadata.cpp" /> + <ClCompile Include="mkv_flac_decoder.cpp" /> + <ClCompile Include="Preferences.cpp" /> + <ClCompile Include="RawReader.cpp" /> + <ClCompile Include="Stopper.cpp" /> + <ClCompile Include="StreamFileWin32.cpp" /> + </ItemGroup> + <ItemGroup> + <ClInclude Include="..\..\..\Agave\AlbumArt\svc_albumArtProvider.h" /> + <ClInclude Include="..\..\..\Agave\api\config\api_config.h" /> + <ClInclude Include="..\..\..\Wasabi\api\memmgr\api_memmgr.h" /> + <ClInclude Include="..\..\..\Wasabi\api\service\api_service.h" /> + <ClInclude Include="..\..\..\Winamp\api_language.h" /> + <ClInclude Include="..\..\..\Winamp\IN2.H" /> + <ClInclude Include="..\..\..\Winamp\wa_ipc.h" /> + <ClInclude Include="AlbumArt.h" /> + <ClInclude Include="api__in_flv.h" /> + <ClInclude Include="FLACFileCallbacks.h" /> + <ClInclude Include="main.h" /> + <ClInclude Include="Metadata.h" /> + <ClInclude Include="mkv_flac_decoder.h" /> + <ClInclude Include="QuickBuf.h" /> + <ClInclude Include="RawReader.h" /> + <ClInclude Include="resource.h" /> + <ClInclude Include="Stopper.h" /> + <ClInclude Include="StreamFileWin32.h" /> + </ItemGroup> + <ItemGroup> + <ResourceCompile Include="in_flac.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_flac/in_flac.vcxproj.filters b/Src/Plugins/Input/in_flac/in_flac.vcxproj.filters new file mode 100644 index 00000000..06016ed5 --- /dev/null +++ b/Src/Plugins/Input/in_flac/in_flac.vcxproj.filters @@ -0,0 +1,122 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <ItemGroup> + <ClCompile Include="About.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="AlbumArt.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="DecodeThread.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="ExtendedFileInfo.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="ExtendedRead.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="FileInfo.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="FLACFileCallbacks.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="mkv_flac_decoder.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="Preferences.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="RawReader.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="Stopper.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="StreamFileWin32.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="..\..\..\nu\SpillBuffer.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + </ItemGroup> + <ItemGroup> + <ClInclude Include="StreamFileWin32.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="Stopper.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="resource.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="RawReader.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="QuickBuf.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="mkv_flac_decoder.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="Metadata.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="main.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="FLACFileCallbacks.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="api__in_flv.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="AlbumArt.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="..\..\..\Agave\api\config\api_config.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="..\..\..\Winamp\api_language.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="..\..\..\Wasabi\api\memmgr\api_memmgr.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="..\..\..\Wasabi\api\service\api_service.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="..\..\..\Winamp\IN2.H"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="..\..\..\Agave\AlbumArt\svc_albumArtProvider.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="..\..\..\Winamp\wa_ipc.h"> + <Filter>Header Files</Filter> + </ClInclude> + </ItemGroup> + <ItemGroup> + <Filter Include="Header Files"> + <UniqueIdentifier>{be0e665c-0dc5-41ca-98ab-39750a1644f6}</UniqueIdentifier> + </Filter> + <Filter Include="Ressource Files"> + <UniqueIdentifier>{f00291da-d3eb-4848-bb67-7282ea12bb80}</UniqueIdentifier> + </Filter> + <Filter Include="Source Files"> + <UniqueIdentifier>{f3f6b1c6-d21e-44bd-8b2e-10cdfa6c3e18}</UniqueIdentifier> + </Filter> + </ItemGroup> + <ItemGroup> + <ResourceCompile Include="in_flac.rc"> + <Filter>Ressource Files</Filter> + </ResourceCompile> + </ItemGroup> +</Project>
\ No newline at end of file diff --git a/Src/Plugins/Input/in_flac/main.cpp b/Src/Plugins/Input/in_flac/main.cpp new file mode 100644 index 00000000..047d1529 --- /dev/null +++ b/Src/Plugins/Input/in_flac/main.cpp @@ -0,0 +1,277 @@ +/* +** Copyright © 2007-2014 Winamp SA +** +** This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held +** liable for any damages arising from the use of this software. +** +** Permission is granted to anyone to use this software for any purpose, including commercial applications, and to +** alter it and redistribute it freely, subject to the following restrictions: +** +** 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. +** If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +** +** 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +** +** 3. This notice may not be removed or altered from any source distribution. +** +** Author: Ben Allison benski@winamp.com +** Created: March 1, 2007 +** +*/ +#include "main.h" +#include "api__in_flv.h" +#include "Metadata.h" +#include "../Agave/Language/api_language.h" +#include <api/service/waServiceFactory.h> +#include "../Winamp/wa_ipc.h" +#include "../nu/Singleton.h" +#include "../nu/Autochar.h" +#include <shlwapi.h> +#include "resource.h" +#include "AlbumArt.h" +#include "RawReader.h" +#include <strsafe.h> +#include "nswasabi/ReferenceCounted.h" +#include "mkv_flac_decoder.h" + +api_config *AGAVE_API_CONFIG = 0; +api_memmgr *WASABI_API_MEMMGR=0; +AlbumArtFactory albumArtFactory; +static RawMediaReaderService raw_media_reader_service; +static SingletonServiceFactory<svc_raw_media_reader, RawMediaReaderService> raw_factory; +static MKVDecoder mkv_service; +static SingletonServiceFactory<svc_mkvdecoder, MKVDecoder> mkv_factory; +// wasabi based services for localisation support +api_language *WASABI_API_LNG = 0; +HINSTANCE WASABI_API_LNG_HINST = 0, WASABI_API_ORIG_HINST = 0; + +HANDLE killswitch=0; +HANDLE playThread=0; + +const wchar_t *winampINI=0; +void Config(HWND hwndParent); +void About(HWND hwndParent); +wchar_t pluginName[256] = {0}; + +int Init() +{ + if (!IsWindow(plugin.hMainWindow)) + return IN_INIT_FAILURE; + + killswitch = CreateEvent(0, TRUE, FALSE, 0); + plugin.service->service_register(&albumArtFactory); + raw_factory.Register(plugin.service, &raw_media_reader_service); + mkv_factory.Register(plugin.service, &mkv_service); + + waServiceFactory *sf = (waServiceFactory *)plugin.service->service_getServiceByGuid(AgaveConfigGUID); + if (sf) AGAVE_API_CONFIG= (api_config *)sf->getInterface(); + sf = (waServiceFactory *)plugin.service->service_getServiceByGuid(memMgrApiServiceGuid); + if (sf) WASABI_API_MEMMGR= (api_memmgr *)sf->getInterface(); + + // loader so that we can get the localisation service api for use + sf = plugin.service->service_getServiceByGuid(languageApiGUID); + if (sf) WASABI_API_LNG = reinterpret_cast<api_language*>(sf->getInterface()); + + // need to have this initialised before we try to do anything with localisation features + WASABI_API_START_LANG(plugin.hDllInstance,InFlacLangGUID); + + StringCchPrintfW(pluginName,256,WASABI_API_LNGSTRINGW(IDS_NULLSOFT_FLAC_DECODER),PLUGIN_VER); + plugin.description = (char*)pluginName; + + winampINI = (const wchar_t *)SendMessage(plugin.hMainWindow, WM_WA_IPC, 0, IPC_GETINIFILEW); + + wchar_t exts[1024] = {0}; + GetPrivateProfileStringW(L"in_flac", L"extensions", DEFAULT_EXTENSIONSW, exts, 1024, winampINI); + plugin.FileExtensions = BuildExtensions(AutoChar(exts)); + + config_average_bitrate = !!GetPrivateProfileIntW(L"in_flac", L"average_bitrate", 1, winampINI); + + plugin.UsesOutputPlug|=8; + return IN_INIT_SUCCESS; +} + +void Quit() +{ + CloseHandle(killswitch); + waServiceFactory *sf = (waServiceFactory *)plugin.service->service_getServiceByGuid(AgaveConfigGUID); + if (sf) sf->releaseInterface(AGAVE_API_CONFIG); + sf = (waServiceFactory *)plugin.service->service_getServiceByGuid(memMgrApiServiceGuid); + if (sf) sf->releaseInterface(WASABI_API_MEMMGR); + + plugin.service->service_deregister(&albumArtFactory); + raw_factory.Deregister(plugin.service); +} + +void GetFileInfo(const in_char *file, in_char *title, int *length_in_ms) +{ + if (length_in_ms) + { + if (!file || !*file && currentSongLength != -1000) + *length_in_ms = currentSongLength; + else + { + FLACMetadata metadata; + unsigned __int64 length_in_msec; + if (metadata.Open(file) && metadata.GetLengthMilliseconds(&length_in_msec)) + *length_in_ms = (int)length_in_msec; + else + *length_in_ms=-1000; + } + } + if (title) *title=0; +} + +int InfoBox(const in_char *file, HWND hwndParent) { return 0; } + +int IsOurFile(const in_char *fn) +{ + return 0; +} + +wchar_t *lastfn=0; +HANDLE threadStarted; +extern FLAC__uint64 lastoutputtime; +extern volatile int bufferCount; +int Play(const in_char *fn) +{ + free(lastfn); + lastfn=_wcsdup(fn); + currentSongLength=-1000; + plugin.is_seekable = 0; + plugin.SetInfo(0,0,0,0); + lastoutputtime=0; + bufferCount=0; + ResetEvent(killswitch); + DWORD threadId; + threadStarted = CreateEvent(0, TRUE, FALSE, 0); + + ReferenceCountedNXString filename_nx; + nx_uri_t filename_uri; + NXStringCreateWithUTF16(&filename_nx, fn); + NXURICreateWithNXString(&filename_uri, filename_nx); + + playThread=CreateThread(0, 0, FLACThread, filename_uri, 0, &threadId); + SetThreadPriority(playThread, AGAVE_API_CONFIG->GetInt(playbackConfigGroupGUID, L"priority", THREAD_PRIORITY_HIGHEST)); + WaitForSingleObject(threadStarted, INFINITE); + CloseHandle(threadStarted); + return 0; +} + +int localPause=0; +void Pause() +{ + localPause=1; + QueueUserAPC(APCPause, playThread, (ULONG_PTR)1); +} + +void UnPause() +{ + localPause=0; + QueueUserAPC(APCPause, playThread, (ULONG_PTR)0); +} + +int IsPaused() +{ + return localPause; +} + +void Stop() +{ + SetEvent(killswitch); + WaitForSingleObject(playThread, INFINITE); + currentSongLength=-1000; + plugin.outMod->Close(); + plugin.SAVSADeInit(); + free(lastfn); + lastfn=0; +} + +int GetLength() +{ + return currentSongLength; +} + + +int GetOutputTime() +{ + if (bufferCount) + return bufferCount; + + if (plugin.outMod) + { + return (int)lastoutputtime + (plugin.outMod->GetOutputTime() - plugin.outMod->GetWrittenTime()); + } + else + return 0; +} + +void SetOutputTime(int time_in_ms) +{ + lastoutputtime=time_in_ms; // cheating a bit here :) + QueueUserAPC(APCSeek, playThread, (ULONG_PTR)time_in_ms); +} + +int pan = 0, volume = -666; +void SetVolume(int _volume) +{ + volume = _volume; + if (plugin.outMod) + plugin.outMod->SetVolume(volume); +} + +void SetPan(int _pan) +{ + pan = _pan; + if (plugin.outMod) + plugin.outMod->SetPan(pan); +} + +void EQSet(int on, char data[10], int preamp) +{} + +In_Module plugin = +{ + IN_VER_RET, + "nullsoft(in_flac.dll)", + 0, + 0, + "FLAC\0FLAC Files\0", + 1, + 1, + Config, + About, + Init, + Quit, + GetFileInfo, + InfoBox, + IsOurFile, + Play, + Pause, + UnPause, + IsPaused, + Stop, + GetLength, + GetOutputTime, + SetOutputTime, + SetVolume, + SetPan, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + EQSet, + 0, + 0 +}; + +extern "C" __declspec(dllexport) In_Module * winampGetInModule2() +{ + return &plugin; +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_flac/main.h b/Src/Plugins/Input/in_flac/main.h new file mode 100644 index 00000000..bbe316a4 --- /dev/null +++ b/Src/Plugins/Input/in_flac/main.h @@ -0,0 +1,40 @@ +#ifndef NULLSOFT_IN_FLAC_MAIN_H +#define NULLSOFT_IN_FLAC_MAIN_H + +#define PLUGIN_VER L"3.2" + +#include <windows.h> +extern HANDLE killswitch; + +#include "../Winamp/in2.h" +extern In_Module plugin; + +DWORD CALLBACK FLACThread(LPVOID param); +extern int pan, volume; +extern volatile int currentSongLength; + +void CALLBACK APCPause(ULONG_PTR data); +void CALLBACK APCSeek(ULONG_PTR data); + +void ResetMetadataCache(); + +#include <FLAC/all.h> +void InterleaveAndTruncate(const FLAC__int32 *const buffer[], void *_output, int bps, int channels, int blocksize, double gain=1.0); + +extern const wchar_t *winampINI; +char *BuildExtensions(const char *extensions); +extern bool config_average_bitrate; +extern bool fixBitrate; + +extern int m_force_seek; // set this to something other than -1 to make the file start from the given time (in ms) +extern wchar_t *lastfn; + +// {B6CB4A7C-A8D0-4c55-8E60-9F7A7A23DA0F} +static const GUID playbackConfigGroupGUID = + { + 0xb6cb4a7c, 0xa8d0, 0x4c55, { 0x8e, 0x60, 0x9f, 0x7a, 0x7a, 0x23, 0xda, 0xf } + }; + +#define DEFAULT_EXTENSIONS "FLAC" +#define DEFAULT_EXTENSIONSW L"FLAC" +#endif
\ No newline at end of file diff --git a/Src/Plugins/Input/in_flac/mkv_flac_decoder.cpp b/Src/Plugins/Input/in_flac/mkv_flac_decoder.cpp new file mode 100644 index 00000000..f7ef28da --- /dev/null +++ b/Src/Plugins/Input/in_flac/mkv_flac_decoder.cpp @@ -0,0 +1,204 @@ +#include"mkv_flac_decoder.h" +#include "main.h" + +static FLAC__StreamDecoderReadStatus Packet_Read(const FLAC__StreamDecoder *decoder, FLAC__byte buffer[], size_t *bytes, void *client_data) +{ + packet_client_data_t packet = (packet_client_data_t)client_data; + size_t to_copy = *bytes; + if (to_copy > packet->buffer_length) { + to_copy = packet->buffer_length; + } + memcpy(buffer, packet->buffer, to_copy); + *bytes = to_copy; + packet->buffer += to_copy; + packet->buffer_length -= to_copy; + return FLAC__STREAM_DECODER_READ_STATUS_CONTINUE; +} + +static FLAC__StreamDecoderSeekStatus Packet_Seek(const FLAC__StreamDecoder *decoder, FLAC__uint64 absolute_byte_offset, void *client_data) +{ + return FLAC__STREAM_DECODER_SEEK_STATUS_UNSUPPORTED; +} + +static FLAC__StreamDecoderTellStatus Packet_Tell(const FLAC__StreamDecoder *decoder, FLAC__uint64 *absolute_byte_offset, void *client_data) +{ + return FLAC__STREAM_DECODER_TELL_STATUS_UNSUPPORTED; +} + +static FLAC__StreamDecoderLengthStatus Packet_Length(const FLAC__StreamDecoder *decoder, FLAC__uint64 *stream_length, void *client_data) +{ + return FLAC__STREAM_DECODER_LENGTH_STATUS_UNSUPPORTED; +} + +static FLAC__bool Packet_EOF(const FLAC__StreamDecoder *decoder, void *client_data) +{ + return 0; +} + +static void OnError(const FLAC__StreamDecoder *decoder, FLAC__StreamDecoderErrorStatus status, void *client_data) +{ + //client_data=client_data; // dummy line so i can set a breakpoint +} + +static void OnMetadata(const FLAC__StreamDecoder *decoder, const FLAC__StreamMetadata *metadata, void *client_data) +{ + packet_client_data_t packet = (packet_client_data_t)client_data; + switch(metadata->type) + { + case FLAC__METADATA_TYPE_STREAMINFO: + { + packet->frame_size = metadata->data.stream_info.max_blocksize; + packet->bps=metadata->data.stream_info.bits_per_sample; + packet->bytes_per_sample = (packet->bps + 7) / 8; + packet->channels=metadata->data.stream_info.channels; + packet->sample_rate=metadata->data.stream_info.sample_rate; + packet->samples=metadata->data.stream_info.total_samples; + } + break; + } +} + +static FLAC__StreamDecoderWriteStatus OnAudio(const FLAC__StreamDecoder *decoder, const FLAC__Frame *frame, const FLAC__int32 *const buffer[], void *client_data) +{ + packet_client_data_t packet = (packet_client_data_t)client_data; + + size_t byteLength = packet->bytes_per_sample * packet->channels * frame->header.blocksize; + if (byteLength > packet->outputBufferBytes[0]) { + FLAC__STREAM_DECODER_WRITE_STATUS_ABORT; + } + InterleaveAndTruncate(buffer, packet->outputBuffer, packet->bytes_per_sample * 8, packet->channels, frame->header.blocksize); + packet->outputBufferBytes[0] = byteLength; + + return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE; +} + + +MKVFLACDecoder *MKVFLACDecoder::Create(const nsmkv::TrackEntryData *track_entry_data, const nsmkv::AudioData *audio_data, unsigned int preferred_bits, unsigned int max_channels) +{ + FLAC__StreamDecoder *decoder = FLAC__stream_decoder_new(); + if (!decoder) { + return 0; + } + packet_client_data_t packet = new packet_client_data_s; + packet->buffer = 0; + packet->buffer_length = 0; + + if(FLAC__stream_decoder_init_stream( + decoder, + Packet_Read, + Packet_Seek, + Packet_Tell, + Packet_Length, + Packet_EOF, + OnAudio, + OnMetadata, + OnError, + packet + ) != FLAC__STREAM_DECODER_INIT_STATUS_OK) + { + delete packet; + FLAC__stream_decoder_delete(decoder); + return 0; + } + + packet->buffer = (const uint8_t *)track_entry_data->codec_private; + packet->buffer_length = track_entry_data->codec_private_len; + if (!FLAC__stream_decoder_process_until_end_of_metadata(decoder)) { + delete packet; + FLAC__stream_decoder_delete(decoder); + return 0; + } + + MKVFLACDecoder *mkv_decoder = new MKVFLACDecoder(decoder, packet, preferred_bits); + if (!mkv_decoder) { + delete packet; + FLAC__stream_decoder_delete(decoder); + return 0; + } + return mkv_decoder; +} + +MKVFLACDecoder::MKVFLACDecoder(FLAC__StreamDecoder *decoder, packet_client_data_t packet, unsigned int bps) +: decoder(decoder), packet(packet), bps(bps) +{ +} + +MKVFLACDecoder::~MKVFLACDecoder() +{ + delete packet; + FLAC__stream_decoder_delete(decoder); +} + +int MKVFLACDecoder::OutputFrameSize(size_t *frame_size) +{ + *frame_size = packet->frame_size * packet->bytes_per_sample * packet->channels; + return MKV_SUCCESS; +} + +int MKVFLACDecoder::GetOutputProperties(unsigned int *sampleRate, unsigned int *channels, unsigned int *bitsPerSample, bool *isFloat) +{ + *sampleRate = packet->sample_rate; + *channels = packet->channels; + *bitsPerSample = packet->bps; + *isFloat = false; + + return MKV_SUCCESS; +} + +int MKVFLACDecoder::DecodeBlock(void *inputBuffer, size_t inputBufferBytes, void *outputBuffer, size_t *outputBufferBytes) +{ + packet->buffer = (const uint8_t *)inputBuffer; + packet->buffer_length = inputBufferBytes; + + packet->outputBuffer = outputBuffer; + packet->outputBufferBytes = outputBufferBytes; + + if (FLAC__stream_decoder_process_single(decoder) == 0) { + return MKV_FAILURE; + } + + return MKV_SUCCESS; +} + +void MKVFLACDecoder::Flush() +{ + FLAC__stream_decoder_flush(decoder); +} + +void MKVFLACDecoder::Close() +{ + delete this; +} + +#define CBCLASS MKVFLACDecoder +START_DISPATCH; +CB(OUTPUT_FRAME_SIZE, OutputFrameSize) +CB(GET_OUTPUT_PROPERTIES, GetOutputProperties) +CB(DECODE_BLOCK, DecodeBlock) +VCB(FLUSH, Flush) +VCB(CLOSE, Close) +END_DISPATCH; +#undef CBCLASS + + +int MKVDecoder::CreateAudioDecoder(const char *codec_id, const nsmkv::TrackEntryData *track_entry_data, const nsmkv::AudioData *audio_data, unsigned int preferred_bits, unsigned int max_channels,bool floating_point, ifc_mkvaudiodecoder **decoder) +{ + if (!strcmp(codec_id, "A_FLAC")) + { + MKVFLACDecoder *flac_decoder = MKVFLACDecoder::Create(track_entry_data, audio_data, preferred_bits, max_channels); + if (flac_decoder) + { + *decoder = flac_decoder; + return CREATEDECODER_SUCCESS; + } + return CREATEDECODER_FAILURE; + } + + return CREATEDECODER_NOT_MINE; +} + +#define CBCLASS MKVDecoder +START_DISPATCH; +CB(CREATE_AUDIO_DECODER, CreateAudioDecoder) +END_DISPATCH; +#undef CBCLASS
\ No newline at end of file diff --git a/Src/Plugins/Input/in_flac/mkv_flac_decoder.h b/Src/Plugins/Input/in_flac/mkv_flac_decoder.h new file mode 100644 index 00000000..b4392d11 --- /dev/null +++ b/Src/Plugins/Input/in_flac/mkv_flac_decoder.h @@ -0,0 +1,65 @@ +#pragma once +#include "../in_mkv/svc_mkvdecoder.h" +#include "../in_mkv/ifc_mkvaudiodecoder.h" +#include <FLAC/all.h> + +// {F6AF0AD9-608F-4206-892F-765412574A7D} +static const GUID FLACMKVGUID = +{ 0xf6af0ad9, 0x608f, 0x4206, { 0x89, 0x2f, 0x76, 0x54, 0x12, 0x57, 0x4a, 0x7d } }; + + + +class packet_client_data_s +{ +public: + const uint8_t *buffer; + size_t buffer_length; + + void *outputBuffer; + size_t *outputBufferBytes; + + uint32_t frame_size; + uint32_t bps; + uint32_t bytes_per_sample; + uint32_t channels; + uint32_t sample_rate; + uint64_t samples; +}; + +typedef packet_client_data_s *packet_client_data_t; + +class MKVDecoder : public svc_mkvdecoder +{ +public: + static const char *getServiceName() { return "FLAC MKV Decoder"; } + static GUID getServiceGuid() { return FLACMKVGUID; } + int CreateAudioDecoder(const char *codec_id, const nsmkv::TrackEntryData *track_entry_data, const nsmkv::AudioData *audio_data, unsigned int preferred_bits, unsigned int preferred_channels, bool floating_point, ifc_mkvaudiodecoder **decoder); +protected: + RECVS_DISPATCH; +}; + +class MKVFLACDecoder : public ifc_mkvaudiodecoder +{ +public: + static MKVFLACDecoder *Create(const nsmkv::TrackEntryData *track_entry_data, const nsmkv::AudioData *audio_data, unsigned int preferred_bits, unsigned int max_channels); + +protected: + RECVS_DISPATCH; +private: + /* ifc_mkvaudiodecoder implementation */ + int OutputFrameSize(size_t *frame_size); + int GetOutputProperties(unsigned int *sampleRate, unsigned int *channels, unsigned int *bitsPerSample, bool *isFloat); + int DecodeBlock(void *inputBuffer, size_t inputBufferBytes, void *outputBuffer, size_t *outputBufferBytes); + void Flush(); + void Close(); + +private: + MKVFLACDecoder(FLAC__StreamDecoder *decoder, packet_client_data_t packet, unsigned int bps); + ~MKVFLACDecoder(); + /* internal implementation */ + + /* data */ + FLAC__StreamDecoder *decoder; + unsigned int bps; +packet_client_data_t packet; +};
\ No newline at end of file diff --git a/Src/Plugins/Input/in_flac/resource.h b/Src/Plugins/Input/in_flac/resource.h new file mode 100644 index 00000000..341dd0ab --- /dev/null +++ b/Src/Plugins/Input/in_flac/resource.h @@ -0,0 +1,71 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by in_flac.rc +// +#define IDS_NULLSOFT_FLAC_DECODER_OLD 0 +#define IDS_TO_ADVANCED_MODE 1 +#define IDS_TO_SIMPLE_MODE 2 +#define IDS_NAME 3 +#define IDS_VALUE 4 +#define IDS_TRACK_GAIN 5 +#define IDS_ALBUM_GAIN 6 +#define IDS_NOT_PRESENT 7 +#define IDS_LENGTH_IN_SECONDS 8 +#define IDS_CHANNELS 9 +#define IDS_BITS_PER_SAMPLE 10 +#define IDS_SAMPLE_RATE 11 +#define IDS_FILE_SIZE_IN_BYTES 12 +#define IDS_AVERAGE_BITRATE 13 +#define IDS_COMPRESSION_RATIO 14 +#define IDS_ABOUT_STR 15 +#define IDS_LIBFLAC_DLL_MISSING 16 +#define IDS_FLAC_FILES 17 +#define IDS_FAMILY_STRING 18 +#define IDS_ABOUT_TEXT 19 +#define IDS_CANNOT_SAVE_METADATA 20 +#define IDS_ERROR_SAVING_METADATA 21 +#define IDD_PREFERENCES 102 +#define IDD_INFOCHILD_ADVANCED 105 +#define IDC_FILENAME 1001 +#define IDC_TITLE 1002 +#define IDC_ARTIST 1003 +#define IDC_YEAR 1004 +#define IDC_ALBUM 1005 +#define IDC_GENRE 1006 +#define IDC_COMMENTS 1007 +#define IDC_FILEINFO 1008 +#define IDC_ALBUMARTIST 1009 +#define IDC_UPDATE 1010 +#define IDC_COMPOSER 1011 +#define IDC_EDIT1 1012 +#define IDC_TRACK 1012 +#define IDC_EXTENSIONS 1012 +#define IDC_NAME 1012 +#define IDC_REPLAYGAIN 1013 +#define IDC_DISC 1014 +#define IDC_PUBLISHER 1015 +#define IDC_PUBLISHER2 1017 +#define IDC_TOOL 1017 +#define IDC_CHECK1 1017 +#define IDC_AVERAGE_BITRATE 1017 +#define IDC_COMBO1 1018 +#define IDC_SWITCH 1019 +#define IDC_PLACEHOLDER 1020 +#define IDC_LIST 1022 +#define IDC_VALUE 1024 +#define IDC_BUTTON_ADD 1025 +#define IDC_BUTTON_DEL 1026 +#define IDC_BUTTON5 1028 +#define IDC_BUTTON_DELALL 1028 +#define IDS_NULLSOFT_FLAC_DECODER 65534 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 112 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1029 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/Src/Plugins/Input/in_flac/version.rc2 b/Src/Plugins/Input/in_flac/version.rc2 new file mode 100644 index 00000000..da1d44c9 --- /dev/null +++ b/Src/Plugins/Input/in_flac/version.rc2 @@ -0,0 +1,39 @@ + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// +#include "../../../Winamp/buildType.h" +VS_VERSION_INFO VERSIONINFO + FILEVERSION 3,2,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", "3,2,0,0" + VALUE "InternalName", "Nullsoft FLAC Decoder" + VALUE "LegalCopyright", "Copyright © 2007-2023 Winamp SA" + VALUE "LegalTrademarks", "Nullsoft and Winamp are trademarks of Winamp SA" + VALUE "OriginalFilename", "in_flac.dll" + VALUE "ProductName", "Winamp" + VALUE "ProductVersion", STR_WINAMP_PRODUCTVER + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 + END +END |