#include "main.h" #include "../nu/AutoWide.h" #include "WMPlaylist.h" #include "resource.h" #include #include WMInformation *setFileInfo = 0; static wchar_t *setFileInfoName=0; static bool forcedStop = false; static int outTime = 0; float GetGain(WMInformation *info, bool allowDefault); static const wchar_t *extension(const wchar_t *fn) { const wchar_t *x = PathFindExtension(fn); if (*x) return CharNext(x); else return x; } static bool KeywordMatch(const char *mainString, const char *keyword) { return !_stricmp(mainString, keyword); } static bool KeywordMatch(const wchar_t *mainString, const wchar_t *keyword) { return !lstrcmpiW(mainString, keyword); } static bool StartsWith(const wchar_t *mainString, const wchar_t *substring) { return !_wcsnicmp(mainString, substring, lstrlenW(substring)); } static int Width(int dec) { // there's probably a better way int width=3; while (width && (dec % 10) == 0) { dec/=10; width--; } return width; } int GetExtendedInformation(WMInformation *getExtendedFileInfo, const wchar_t *fn, const char *data, wchar_t *dest, int destlen) { AutoWide tagNameW(data); const wchar_t *tagName = GetAlias(tagNameW); if (KeywordMatch(tagName, L"streammetadata")) { if (config_http_metadata) { lstrcpyn(dest, L"1", destlen); return 1; } return 0; } else if (KeywordMatch(tagName, L"type")) { if (!fn || !fn[0]) lstrcpyn(dest, (config_no_video ? L"0" : L"1"), destlen); else if (getExtendedFileInfo->IsAttribute(g_wszWMHasVideo)) lstrcpyn(dest, L"1", destlen); else if (getExtendedFileInfo->IsAttribute(g_wszWMHasAudio)) lstrcpyn(dest, L"0", destlen); else { switch (fileTypes.GetAVType(extension(fn))) { case FileType::AUDIO: lstrcpyn(dest, L"0", destlen); break; case FileType::VIDEO: lstrcpyn(dest, L"1", destlen); break; default: return 0; } } return 1; } else if (KeywordMatch(tagName, L"rateable")) { dest[0] = '1'; dest[1] = 0; return 1; } else if (StartsWith(tagName, L"WM/")) { getExtendedFileInfo->GetAttribute(tagName, dest, destlen); return 1; } /*else if (KeywordMatch(data, "burnable")) // note: this isn't supposed to be any kind of protection - just keeps the burner from misbehaving on protected tracks { if (getExtendedFileInfo->IsAttribute(g_wszWMProtected)) lstrcpyn(dest, L"0", destlen); else lstrcpyn(dest, L"1", destlen); return 1; }*/ else if (KeywordMatch(data, "noburnreason")) // note: this isn't supposed to be any kind of protection - just keeps the burner from misbehaving on protected tracks { if (getExtendedFileInfo->IsAttribute(g_wszWMProtected)) { lstrcpyn(dest, L"DRM (copy protected) file", destlen); return 1; } } else if ((const wchar_t *)tagNameW != tagName) // if the tag was in the tag list { getExtendedFileInfo->GetAttribute(tagName, dest, destlen); return 1; } else if (KeywordMatch(data, "bitrate")) { StringCchPrintfW(dest, destlen, L"%u", getExtendedFileInfo->GetBitrate() / 1000); return 1; } else if (KeywordMatch(data, "vbr")) { if (getExtendedFileInfo->IsAttribute(g_wszWMIsVBR)) StringCchCopyW(dest, destlen, L"1"); else if (getExtendedFileInfo->IsNotAttribute(g_wszWMIsVBR)) StringCchCopyW(dest, destlen, L"0"); return 1; } //else if (KeywordMatch(data, "srate")) else if (KeywordMatch(data, "length")) { long length = getExtendedFileInfo->GetLengthMilliseconds(); if (length == -1000) return 0; _itow(length, dest, 10); return 1; } else if (KeywordMatch(data, "rating")) { wchar_t rating_string[128] = {0}; getExtendedFileInfo->GetAttribute(L"WM/SharedUserRating", rating_string, 128); int rating = _wtoi(rating_string); if (rating == 0) dest[0]=0; else if (rating >= 1 && rating <= 12) dest[0]=L'1'; else if (rating >= 13 && rating <= 37) dest[0]=L'2'; else if (rating >= 38 && rating <= 62) dest[0]=L'3'; else if (rating >= 63 && rating <= 86) dest[0]=L'4'; else dest[0]=L'5'; dest[1]=0; return 1; } else if (KeywordMatch(data, "replaygain_track_gain") || KeywordMatch(data, "replaygain_track_peak") || KeywordMatch(data, "replaygain_album_gain") || KeywordMatch(data, "replaygain_album_peak")) { getExtendedFileInfo->GetAttribute(tagName, dest, destlen); return 1; } else if (KeywordMatch(data, "gain")) { StringCchPrintfW(dest, destlen, L"%-+.2f dB", (float)log10f(GetGain(getExtendedFileInfo, false))*20.0f); return 1; } else if (KeywordMatch(data, "audiocodec")) { if (!getExtendedFileInfo->GetCodecName(dest, destlen)) dest[0]=0; return 1; } else if (KeywordMatch(data, "lossless")) { wchar_t codecname[1024] = {0}; if (!getExtendedFileInfo->GetCodecName(codecname, 1024)) dest[0]=0; else { dest[0] = wcsstr(codecname, L"Lossless")?'1':'0'; dest[1]=0; } return 1; } else if (KeywordMatch(data, "GracenoteFileID")) { getExtendedFileInfo->GetAttribute_BinString(L"GN/UniqueFileIdentifier", dest, destlen); return 1; } else if (KeywordMatch(data, "GracenoteExtData")) { getExtendedFileInfo->GetAttribute_BinString(L"GN/ExtData", dest, destlen); return 1; } else if (KeywordMatch(data, "formatinformation")) { // this is a bit of a clusterfuck, but it's safe and (hopefully) logically laid out. wchar_t codec[128]=L"", duration[64]=L"", bitrate[64]=L"", filesize[64]=L"", wmver[64]=L"", seekable[64]=L""; wchar_t stridable[64]=L"", broadcast[64]=L"", protect[64]=L"", trusted[64]=L"", contents[64]=L""; wchar_t buf[128]=L""; // temporary buffer // get the codec name string if (getExtendedFileInfo->GetCodecName(buf, 128) && buf[0]) StringCchPrintf(codec,128,L"%s: %s\n",WASABI_API_LNGSTRINGW(IDS_CODEC),buf); // get the length string formatted h:mm:ss.tttt long t = getExtendedFileInfo->GetLengthMilliseconds(); if (t) { long h = t/36000000; long m = (t/60000)%60; long s = (t/1000)%60; long ms = t%1000; if (h) StringCchPrintf(duration,64,L"%s: %u:%02u:%02u.%03u\n",WASABI_API_LNGSTRINGW(IDS_DURATION),h,m,s,ms); else if (m) StringCchPrintf(duration,64,L"%s: %u:%02u.%03u\n",WASABI_API_LNGSTRINGW(IDS_DURATION),m,s,ms); else StringCchPrintf(duration,64,L"%s: %u.%03u\n",WASABI_API_LNGSTRINGW(IDS_DURATION),s,ms); } // get the bitrate string formatted 128.235 kbps long br = getExtendedFileInfo->GetBitrate(); wchar_t kbps[16] = {0}; StringCchPrintf(bitrate,64,L"%s: %.*f %s\n", WASABI_API_LNGSTRINGW(IDS_BITRATE), Width(br%1000), br/1000.0, WASABI_API_LNGSTRINGW_BUF(IDS_KBPS,kbps,16)); // get the filesize string, with commas grouping in threes buf[0]=0; getExtendedFileInfo->GetAttribute(L"FileSize",buf,64); uint64_t fs = _wcstoui64(buf, 0, 10); if (fs) { uint64_t fsgb = (fs/1000000000LL); uint64_t fsmb = (fs/1000000LL)%1000LL; uint64_t fskb = (fs/1000LL)%1000LL; uint64_t fsb = fs%1000LL; if (fsgb) StringCchPrintf(filesize,64,L"%s: %I64u,%03I64u,%03I64u,%03I64u\n",WASABI_API_LNGSTRINGW(IDS_FILESIZE),fsgb,fsmb,fskb,fsb); else if (fsmb) StringCchPrintf(filesize,64,L"%s: %I64u,%03I64u,%03I64u\n",WASABI_API_LNGSTRINGW(IDS_FILESIZE),fsmb,fskb,fsb); else if (fskb) StringCchPrintf(filesize,64,L"%s: %I64u,%03I64u\n",WASABI_API_LNGSTRINGW(IDS_FILESIZE),fskb,fsb); else StringCchPrintf(filesize,64,L"%s: %I64u\n",WASABI_API_LNGSTRINGW(IDS_FILESIZE),fsb); } // 4 boolean flags, compose their strings wchar_t yes[64] = {0}, no[64] = {0}; WASABI_API_LNGSTRINGW_BUF(IDS_YES,yes,64); WASABI_API_LNGSTRINGW_BUF(IDS_NO,no,64); buf[0]=0; getExtendedFileInfo->GetAttribute(L"WMFSDKVersion",buf,128); if (buf[0]) StringCchPrintf(wmver,64,L"%s: %s\n",WASABI_API_LNGSTRINGW(IDS_WMVER),buf); StringCchPrintf(seekable,64,L"%s: %s\n",WASABI_API_LNGSTRINGW(IDS_SEEKABLE),getExtendedFileInfo->IsAttribute(L"Seekable")?yes:no); StringCchPrintf(stridable,64,L"%s: %s\n",WASABI_API_LNGSTRINGW(IDS_STRIDABLE),getExtendedFileInfo->IsAttribute(L"Stridable")?yes:no); StringCchPrintf(broadcast,64,L"%s: %s\n",WASABI_API_LNGSTRINGW(IDS_BROADCAST),getExtendedFileInfo->IsAttribute(L"Broadcast")?yes:no); StringCchPrintf(protect,64,L"%s: %s\n",WASABI_API_LNGSTRINGW(IDS_PROTECTED),getExtendedFileInfo->IsAttribute(L"Is_Protected")?yes:no); StringCchPrintf(trusted,64,L"%s: %s\n",WASABI_API_LNGSTRINGW(IDS_TRUSTED),getExtendedFileInfo->IsAttribute(L"Is_Trusted")?yes:no); // file contents. bit gross i know. wchar_t cont[4][16]={L"",L"",L"",L""}; int i=0; if (getExtendedFileInfo->IsAttribute(L"HasAudio")) WASABI_API_LNGSTRINGW_BUF(IDS_AUDIO,cont[i++],16); if (getExtendedFileInfo->IsAttribute(L"HasVideo")) WASABI_API_LNGSTRINGW_BUF(IDS_VIDEO,cont[i++],16); if (getExtendedFileInfo->IsAttribute(L"HasImage")) WASABI_API_LNGSTRINGW_BUF(IDS_IMAGE,cont[i++],16); if (getExtendedFileInfo->IsAttribute(L"HasScript")) WASABI_API_LNGSTRINGW_BUF(IDS_SCRIPT,cont[i++],16); WASABI_API_LNGSTRINGW_BUF(IDS_NONE,buf,64); if (i == 0) StringCchPrintf(contents,64,L"%s: %s",WASABI_API_LNGSTRINGW(IDS_CONTAINS), buf); else if (i == 1) StringCchPrintf(contents,64,L"%s: %s",WASABI_API_LNGSTRINGW(IDS_CONTAINS), cont[0]); else if (i == 2) StringCchPrintf(contents,64,L"%s: %s, %s",WASABI_API_LNGSTRINGW(IDS_CONTAINS), cont[0], cont[1]); else if (i == 3) StringCchPrintf(contents,64,L"%s: %s, %s, %s",WASABI_API_LNGSTRINGW(IDS_CONTAINS), cont[0], cont[1], cont[2]); else if (i == 4) StringCchPrintf(contents,64,L"%s: %s, %s, %s, %s",WASABI_API_LNGSTRINGW(IDS_CONTAINS), cont[0], cont[1], cont[2], cont[3]); // compose our string together! StringCchPrintf(dest,destlen,L"%s%s%s%s%s%s%s%s%s%s%s",codec, duration, bitrate, filesize, wmver, seekable, stridable, broadcast, protect, trusted, contents); } else return 0; return 1; } #if 0 // had to disable this because it was locking the file from being deleted WMInformation *lastGetInfo = 0; wchar_t *lastGetInfoFn; #endif extern "C" __declspec(dllexport) int winampGetExtendedFileInfoW(const wchar_t *fn, const char *data, wchar_t *dest, int destlen) { /* Check if there's a status message for this filename doing this forces Winamp to hit plugin.getfileinfo, which gives us better control over adding things like [Individualizing] to the playlist title for local files */ if (winamp.HasStatus(fn)) return 0; if ((!fn || !*fn) && KeywordMatch(data, "type")) { if (config_no_video) lstrcpyn(dest, L"0", destlen); else lstrcpyn(dest, L"1", destlen); return 1; } if (KeywordMatch(data, "mime")) { int len; const wchar_t *p; if (!fn || !fn[0]) return 0; len = lstrlenW(fn); if (len < 4 || L'.' != fn[len - 4]) return 0; p = &fn[len - 3]; if (!_wcsicmp(p, L"WMA")) { StringCchCopyW(dest, destlen, L"audio/x-ms-wma"); return 1; } if (!_wcsicmp(p, L"WMV")) { StringCchCopyW(dest, destlen, L"video/x-ms-wmv"); return 1; } if (!_wcsicmp(p, L"ASF")) { StringCchCopyW(dest, destlen, L"video/x-ms-asf"); return 1; } return 0; } if (KeywordMatch(data, "family")) { LPCWSTR e, p(NULL); DWORD lcid; size_t i; int len2(0); if (!fn || !*fn) return 0; e = PathFindExtensionW(fn); if (L'.' != *e) return 0; e++; if (!*e) return 0; lcid = MAKELCID(MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), SORT_DEFAULT); for (i = 0; i < fileTypes.types.size() && !p; i++) { if (CSTR_EQUAL == CompareStringW(lcid, NORM_IGNORECASE, e, -1, fileTypes.types.at(i).wtype, -1)) p = fileTypes.types.at(i).description; } if (p) { wchar_t szTest[16]; if (S_OK == StringCchPrintfW(szTest, sizeof(szTest)/sizeof(wchar_t), L" (*.%s)", e)) { int len1 = lstrlenW(szTest); len2 = lstrlenW(p); if (len2 > len1 && CSTR_EQUAL == CompareStringW(lcid, NORM_IGNORECASE, szTest, -1, (p + len2 - len1), -1)) len2 -= len1; } } return (p && S_OK == StringCchCopyNW(dest, destlen, p, len2)); } if (!config_http_metadata && PathIsURL(fn)) return 0; if (KeywordMatch(data, "type") && !PathFileExistsW(fn)) { switch (fileTypes.GetAVType(extension(fn))) { case FileType::AUDIO: lstrcpyn(dest, L"0", destlen); return 1; case FileType::VIDEO: lstrcpyn(dest, L"1", destlen); return 1; default: return 0; } } if (activePlaylist.IsMe(fn)) { WMInformation getExtendedFileInfo(activePlaylist.GetFileName()); return GetExtendedInformation(&getExtendedFileInfo, fn, data, dest, destlen); } else { WMInformation getExtendedFileInfo(fn); return GetExtendedInformation(&getExtendedFileInfo, fn, data, dest, destlen); } #if 0 // had to disable this because it was locking the file from being deleted if (lastGetInfo && lastGetInfoFn && !_wcsicmp(fn, lastGetInfoFn)) { return GetExtendedInformation(lastGetInfo, fn, data, dest, destlen); } delete lastGetInfo; lastGetInfo=0; free(lastGetInfoFn); lastGetInfoFn=0; if (activePlaylist.IsMe(fn)) lastGetInfoFn = _wcsdup(activePlaylist.GetFileName()); else lastGetInfoFn = _wcsdup(fn); lastGetInfo = new WMInformation(lastGetInfoFn); if (lastGetInfo->ErrorOpening()) { if (KeywordMatch(data, "type")) { switch (fileTypes.GetAVType(extension(fn))) { case FileType::AUDIO: lstrcpyn(dest, L"0", destlen); return 1; case FileType::VIDEO: lstrcpyn(dest, L"1", destlen); return 1; default: return 0; } } delete lastGetInfo; lastGetInfo=0; free(lastGetInfoFn); lastGetInfoFn=0; return 0; } return GetExtendedInformation(lastGetInfo, fn, data, dest, destlen); #endif } extern "C" __declspec(dllexport) int winampClearExtendedFileInfoW(const wchar_t *fn) { // TODO: press stop if it's the currently playing file WMInformation wmInfo(fn); wmInfo.ClearAllAttributes(); wmInfo.Flush(); return 1; } extern "C" __declspec(dllexport) int winampSetExtendedFileInfoW(const wchar_t *fn, const char *data, wchar_t *val) { // if (!lastSetInfoFilename.empty() && lastSetInfoFilename != fn) // dosomething(); #if 0 // had to disable this because it was locking the file from being deleted if (lastGetInfoFn && !_wcsicmp(lastGetInfoFn,fn)) { delete lastGetInfo; lastGetInfo=0; free(lastGetInfoFn); lastGetInfoFn=0; } #endif if (!setFileInfo) { if (activePlaylist.IsMe(fn) && mod.playing) { forcedStop = true; outTime = mod.GetOutputTime(); winamp.PressStop(); } free(setFileInfoName); setFileInfoName = _wcsdup(fn); setFileInfo = new WMInformation(fn); if (!setFileInfo->MakeWritable(fn)) return 0; // can't write } AutoWide tagNameW(data); const wchar_t *tagName = GetAlias(tagNameW); if (StartsWith(tagName, L"WM/")) { setFileInfo->SetAttribute(tagName, val); return 1; } else if ((const wchar_t *)tagNameW != tagName) // if the tag was in the tag list { setFileInfo->SetAttribute(tagName, val); return 1; } else if (KeywordMatch(data, "rating")) { int rating = _wtoi(val); if (rating == 0) setFileInfo->SetAttribute(L"WM/SharedUserRating", L"",WMT_TYPE_DWORD); else { switch(rating) { case 1: setFileInfo->SetAttribute(L"WM/SharedUserRating", L"1",WMT_TYPE_DWORD); break; case 2: setFileInfo->SetAttribute(L"WM/SharedUserRating", L"25",WMT_TYPE_DWORD); break; case 3: setFileInfo->SetAttribute(L"WM/SharedUserRating", L"50",WMT_TYPE_DWORD); break; case 4: setFileInfo->SetAttribute(L"WM/SharedUserRating", L"75",WMT_TYPE_DWORD); break; default: setFileInfo->SetAttribute(L"WM/SharedUserRating", L"99",WMT_TYPE_DWORD); break; } } } else if (KeywordMatch(data, "replaygain_track_gain") || KeywordMatch(data, "replaygain_track_peak") || KeywordMatch(data, "replaygain_album_gain") || KeywordMatch(data, "replaygain_album_peak")) { setFileInfo->SetAttribute(tagName, val); return 1; } else if (KeywordMatch(data, "GracenoteFileID")) { setFileInfo->SetAttribute_BinString(L"GN/UniqueFileIdentifier", val); return 1; } else if (KeywordMatch(data, "GracenoteExtData")) { setFileInfo->SetAttribute_BinString(L"GN/ExtData", val); return 1; } // else if (KeywordMatch(data, "bitrate")) //else if (KeywordMatch(data, "disc")) // else if (KeywordMatch(data, "vbr")) //else if (KeywordMatch(data, "srate")) // else if (KeywordMatch(data, "length")) return 0; } extern "C" __declspec(dllexport) int winampWriteExtendedFileInfo() { if (setFileInfo) { bool flushOK = setFileInfo->Flush(); delete setFileInfo; setFileInfo = 0; if (forcedStop) { mod.startAtMilliseconds = outTime; winamp.PressPlay(); } forcedStop=false; if (flushOK) return 1; else return 0; } return 0; }