424 lines
12 KiB
C++
424 lines
12 KiB
C++
/*
|
|
** 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;
|
|
} |