ExtendedInfo.cpp 29 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022
  1. #include "main.h"
  2. #include "VirtualIO.h"
  3. #include "../nu/ns_wc.h"
  4. #include "../nu/AutoChar.h"
  5. #include <vector>
  6. #include "../Winamp/wa_ipc.h"
  7. #include "api__in_mp4.h"
  8. #include "Stopper.h"
  9. #include <shlwapi.h>
  10. #include <strsafe.h>
  11. #include "resource.h"
  12. static inline wchar_t *IncSafe(wchar_t *val, int x)
  13. {
  14. while (x--)
  15. {
  16. if (*val)
  17. val++;
  18. }
  19. return val;
  20. }
  21. bool KeywordMatch(const char *mainString, const char *keyword)
  22. {
  23. return !_stricmp(mainString, keyword);
  24. }
  25. bool GetCustomMetadata(MP4FileHandle mp4, char *metadata, wchar_t *dest, int destlen, const char *owner)
  26. {
  27. u_int8_t *value;
  28. u_int32_t size;
  29. bool success = MP4GetMetadataFreeForm(mp4, metadata, &value, &size, owner);
  30. if (success)
  31. {
  32. int cnt = MultiByteToWideChar(CP_UTF8, 0, (LPCSTR)value, size, dest, destlen - 1);
  33. dest[cnt] = 0;
  34. MP4Free(value);
  35. }
  36. else
  37. dest[0] = 0;
  38. return success;
  39. }
  40. bool HasVideo(MP4FileHandle infile)
  41. {
  42. /* find Video track */
  43. int numTracks = MP4GetNumberOfTracks(infile, NULL, /* subType */ 0);
  44. for (int i = 0; i < numTracks; i++)
  45. {
  46. MP4TrackId trackId = MP4FindTrackId(infile, i, NULL, /* subType */ 0);
  47. const char* trackType = MP4GetTrackType(infile, trackId);
  48. if (!lstrcmpA(trackType, MP4_VIDEO_TRACK_TYPE))
  49. return true;
  50. }
  51. /* can't decode this */
  52. return false;
  53. }
  54. typedef struct __INFOKEYWORD
  55. {
  56. LPCWSTR buffer;
  57. INT len;
  58. } INFOKEYWORD;
  59. typedef std::vector<INFOKEYWORD> KeywordList;
  60. /*static void KeywordList_PushBack(KeywordList *list, LPCWSTR buffer, INT len)
  61. {
  62. if (NULL == list) return;
  63. INFOKEYWORD kw = { buffer, len };
  64. list->push_back(kw);
  65. }
  66. static LPCWSTR ParseFormatInfoLine(LPCWSTR line, KeywordList *list)
  67. {
  68. if (NULL == line || L'\0' == *line) return line;
  69. LPCWSTR cursor = line;
  70. for(;;)
  71. {
  72. switch(*cursor)
  73. {
  74. case L'\r':
  75. if (L'\n' == *(cursor + 1))
  76. {
  77. KeywordList_PushBack(list, line, (INT)(cursor - line));
  78. cursor += 2;
  79. return cursor;
  80. }
  81. case L'\0':
  82. KeywordList_PushBack(list, line, (INT)(cursor - line));
  83. return NULL;
  84. case L'\t':
  85. KeywordList_PushBack(list, line, (INT)(cursor - line));
  86. line = ++cursor;
  87. break;
  88. default:
  89. cursor++;
  90. break;
  91. }
  92. }
  93. }
  94. static HRESULT FormatInformationString(LPWSTR pszBuffer, INT cchBufferMax, LPCSTR formatInfo)
  95. {
  96. if (NULL == pszBuffer) return E_POINTER;
  97. *pszBuffer = L'\0';
  98. if (NULL == formatInfo)
  99. return S_OK;
  100. INT cchFormat = lstrlenA(formatInfo);
  101. if (0 == cchFormat) return S_OK;
  102. LPWSTR pszFormat = (LPWSTR)malloc(sizeof(WCHAR) * (cchFormat + 1));
  103. if (NULL == pszFormat) return E_OUTOFMEMORY;
  104. INT result = MultiByteToWideCharSZ(CP_UTF8, 0, formatInfo, cchFormat, pszFormat, cchFormat + 1);
  105. if (0 == result)
  106. {
  107. DWORD errorCode = GetLastError();
  108. return HRESULT_FROM_WIN32(errorCode);
  109. }
  110. HRESULT hr(S_OK);
  111. KeywordList keys;
  112. KeywordList values;
  113. LPCWSTR line = pszFormat;
  114. line = ParseFormatInfoLine(line, &keys);
  115. line = ParseFormatInfoLine(line, &values);
  116. size_t count = keys.size();
  117. if (0 != count && count == values.size())
  118. {
  119. LPWSTR cursor = pszBuffer;
  120. size_t remaining = cchBufferMax;
  121. for (size_t index = 0; index < count; index++)
  122. {
  123. if (cursor != pszBuffer)
  124. hr = StringCchCopyEx(cursor, remaining, L"\r\n", &cursor, &remaining, 0);
  125. if (SUCCEEDED(hr) && 0 != keys[index].len)
  126. {
  127. hr = StringCchCopyNEx(cursor, remaining, keys[index].buffer, keys[index].len, &cursor, &remaining, 0);
  128. if (SUCCEEDED(hr))
  129. hr = StringCchCopyEx(cursor, remaining, L":", &cursor, &remaining, 0);
  130. }
  131. if (SUCCEEDED(hr) && 0 != values[index].len)
  132. {
  133. if (SUCCEEDED(hr) && 0 != keys[index].len)
  134. hr = StringCchCopyEx(cursor, remaining, L" ", &cursor, &remaining, 0);
  135. hr = StringCchCopyNEx(cursor, remaining, values[index].buffer, values[index].len, &cursor, &remaining, 0);
  136. }
  137. if (FAILED(hr))
  138. break;
  139. }
  140. }
  141. else
  142. {
  143. hr = StringCchCopy(pszBuffer, cchBufferMax, pszFormat);
  144. }
  145. if (NULL != pszFormat)
  146. free(pszFormat);
  147. return hr;
  148. }*/
  149. static MP4FileHandle getFileInfoMP4 = 0;
  150. static wchar_t getFileInfoFn[MAX_PATH]=L"";
  151. static void *getFileInfoReader=0;
  152. static FILETIME ftLastWriteTime;
  153. // is used to determine if the last write time of the file has changed when
  154. // asked to get the metadata for the same cached file so we can update things
  155. BOOL HasFileTimeChanged(const wchar_t *fn)
  156. {
  157. WIN32_FILE_ATTRIBUTE_DATA fileData = {0};
  158. if (GetFileAttributesExW(fn, GetFileExInfoStandard, &fileData) == TRUE)
  159. {
  160. if(CompareFileTime(&ftLastWriteTime, &fileData.ftLastWriteTime))
  161. {
  162. ftLastWriteTime = fileData.ftLastWriteTime;
  163. return TRUE;
  164. }
  165. }
  166. return FALSE;
  167. }
  168. void UpdateFileTimeChanged(const wchar_t *fn)
  169. {
  170. WIN32_FILE_ATTRIBUTE_DATA fileData;
  171. if (GetFileAttributesExW(fn, GetFileExInfoStandard, &fileData) == TRUE)
  172. {
  173. ftLastWriteTime = fileData.ftLastWriteTime;
  174. }
  175. }
  176. extern "C"
  177. {
  178. __declspec( dllexport ) int winampGetExtendedFileInfoW(const wchar_t *fn, const char *data, wchar_t *dest, size_t destlen)
  179. {
  180. if (KeywordMatch(data, "type"))
  181. {
  182. if (!fn || (fn && !fn[0]) || !PathFileExistsW(fn))
  183. {
  184. const wchar_t *e = PathFindExtensionW(fn);
  185. DWORD lcid = MAKELCID(MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), SORT_DEFAULT);
  186. // check known video extensions
  187. if ((CSTR_EQUAL == CompareStringW(lcid, NORM_IGNORECASE, e, -1, L".M4V", -1))
  188. || (CSTR_EQUAL == CompareStringW(lcid, NORM_IGNORECASE, e, -1, L".MOV", -1))
  189. || (CSTR_EQUAL == CompareStringW(lcid, NORM_IGNORECASE, e, -1, L".F4V", -1))
  190. || (CSTR_EQUAL == CompareStringW(lcid, NORM_IGNORECASE, e, -1, L".MP4", -1)))
  191. dest[0] = '1';
  192. else
  193. dest[0] = '0';
  194. dest[1] = 0;
  195. return 1;
  196. }
  197. }
  198. else if (KeywordMatch(data, "rateable"))
  199. {
  200. dest[0] = '1';
  201. dest[1] = 0;
  202. return 1;
  203. }
  204. if (!fn || (fn && !fn[0])) return 0;
  205. if (KeywordMatch(data, "family"))
  206. {
  207. LPCWSTR e;
  208. int pID = -1;
  209. e = PathFindExtensionW(fn);
  210. if (L'.' != *e) return 0;
  211. e++;
  212. DWORD lcid = MAKELCID(MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), SORT_DEFAULT);
  213. if (CSTR_EQUAL == CompareStringW(lcid, NORM_IGNORECASE, e, -1, L"M4A", -1)) pID = IDS_FAMILY_STRING_M4A;
  214. else if (CSTR_EQUAL == CompareStringW(lcid, NORM_IGNORECASE, e, -1, L"MP4", -1)) pID = IDS_FAMILY_STRING_MPEG4;
  215. else if (CSTR_EQUAL == CompareStringW(lcid, NORM_IGNORECASE, e, -1, L"M4V", -1)) pID = IDS_FAMILY_STRING_M4V;
  216. else if (CSTR_EQUAL == CompareStringW(lcid, NORM_IGNORECASE, e, -1, L"MOV", -1)) pID = IDS_FAMILY_STRING_QUICKTIME;
  217. else if (CSTR_EQUAL == CompareStringW(lcid, NORM_IGNORECASE, e, -1, L"3GP", -1)) pID = IDS_FAMILY_STRING_3GPP;
  218. else if (CSTR_EQUAL == CompareStringW(lcid, NORM_IGNORECASE, e, -1, L"F4V", -1)) pID = IDS_FAMILY_STRING_FLV;
  219. if (pID != -1 && S_OK == StringCchCopyW(dest, destlen, WASABI_API_LNGSTRINGW(pID))) return 1;
  220. return 0;
  221. }
  222. if (KeywordMatch(data, "mime"))
  223. {
  224. LPCWSTR e;
  225. e = PathFindExtensionW(fn);
  226. if (L'.' != *e) return 0;
  227. e++;
  228. DWORD lcid = MAKELCID(MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), SORT_DEFAULT);
  229. if (CSTR_EQUAL == CompareStringW(lcid, NORM_IGNORECASE, e, -1, L"M4A", -1)) { StringCchCopyW(dest, destlen, L"audio/mp4"); return 1; }
  230. else if (CSTR_EQUAL == CompareStringW(lcid, NORM_IGNORECASE, e, -1, L"MP4", -1)) { StringCchCopyW(dest, destlen, L"audio/mp4"); return 1; }
  231. else if (CSTR_EQUAL == CompareStringW(lcid, NORM_IGNORECASE, e, -1, L"M4V", -1)) { StringCchCopyW(dest, destlen, L"video/mp4"); return 1; }
  232. else if (CSTR_EQUAL == CompareStringW(lcid, NORM_IGNORECASE, e, -1, L"MOV", -1)) { StringCchCopyW(dest, destlen, L"video/quicktime"); return 1; }
  233. else if (CSTR_EQUAL == CompareStringW(lcid, NORM_IGNORECASE, e, -1, L"3GP", -1)) { StringCchCopyW(dest, destlen, L"video/3gp"); return 1; }
  234. else if (CSTR_EQUAL == CompareStringW(lcid, NORM_IGNORECASE, e, -1, L"F4V", -1)) { StringCchCopyW(dest, destlen, L"video/f4v"); return 1; }
  235. return 0;
  236. }
  237. if (!getFileInfoMP4 || lstrcmpi(getFileInfoFn, fn) || HasFileTimeChanged(fn)) // different file than last time?
  238. {
  239. lstrcpyn(getFileInfoFn, fn, MAX_PATH);
  240. if (getFileInfoMP4)
  241. MP4Close(getFileInfoMP4);
  242. getFileInfoMP4=0;
  243. if (getFileInfoReader)
  244. DestroyUnicodeReader(getFileInfoReader);
  245. getFileInfoReader = CreateUnicodeReader(fn);
  246. if (!getFileInfoReader)
  247. {
  248. getFileInfoFn[0]=0;
  249. return 0;
  250. }
  251. getFileInfoMP4 = MP4ReadEx(fn, getFileInfoReader, &UnicodeIO);
  252. if (!getFileInfoMP4)
  253. {
  254. DestroyUnicodeReader(getFileInfoReader);
  255. getFileInfoReader=0;
  256. getFileInfoFn[0]=0;
  257. return 0;
  258. }
  259. else
  260. {
  261. UnicodeClose(getFileInfoReader); // go ahead and close the file so we don't lock it
  262. }
  263. }
  264. bool success = false;
  265. char *pVal = NULL;
  266. uint16_t *pVal_utf16 = NULL;
  267. if (KeywordMatch(data, "type"))
  268. {
  269. if (GetVideoTrack(getFileInfoMP4) != MP4_INVALID_TRACK_ID) // check for a video track
  270. dest[0] = '1';
  271. else // assume no video mean audio (not necessarily true, for weird mp4 stuff like systems & text)
  272. dest[0] = '0';
  273. dest[1] = 0;
  274. success = true;
  275. }
  276. else if (KeywordMatch(data, "lossless"))
  277. {
  278. dest[0]='0';
  279. dest[1]=0;
  280. MP4TrackId track = GetAudioTrack(getFileInfoMP4);
  281. if (track != MP4_INVALID_TRACK_ID)
  282. {
  283. // TODO: benski> We should check more than just ALAC
  284. // potentially asking the mpeg4audio services
  285. // but for now this should suffice.
  286. const char *media_data_name = MP4GetTrackMediaDataName(getFileInfoMP4, track);
  287. if (media_data_name && KeywordMatch (media_data_name, "alac"))
  288. dest[0]='1';
  289. }
  290. success=true;
  291. }
  292. else if (KeywordMatch(data, "title"))
  293. {
  294. success = MP4GetMetadataName(getFileInfoMP4, &pVal);
  295. if (!success)
  296. success = MP4Get3GPMetadata(getFileInfoMP4, "titl", &pVal_utf16);
  297. }
  298. else if (KeywordMatch(data, "album"))
  299. {
  300. success = MP4GetMetadataAlbum(getFileInfoMP4, &pVal);
  301. if (!success)
  302. success = MP4Get3GPMetadata(getFileInfoMP4, "albm", &pVal_utf16);
  303. }
  304. else if (KeywordMatch(data, "artist"))
  305. {
  306. success = MP4GetMetadataArtist(getFileInfoMP4, &pVal);
  307. if (!success)
  308. success = MP4Get3GPMetadata(getFileInfoMP4, "perf", &pVal_utf16);
  309. }
  310. else if (KeywordMatch(data, "rating"))
  311. {
  312. success = MP4GetMetadataRating(getFileInfoMP4, &pVal);
  313. // add a /* to enable reading from 3GP metadata, otherwise is only used on normal MP4
  314. if (success)/*/
  315. if (!success)
  316. {
  317. if ((success = MP4Get3GPMetadata(getFileInfoMP4, "rate", &pVal_utf16)))
  318. {
  319. wchar_t *value = (wchar_t*)pVal_utf16;
  320. if(value && *value) {
  321. int rating = _wtoi(value);
  322. // keeps things limited to our range of 0-100
  323. if (rating >= 100) {
  324. rating = 5;
  325. }
  326. // 1-100 case
  327. else if (rating > 0 && rating < 100) {
  328. rating = (rating /= 20);
  329. // shift up by one rating when in next band
  330. // 1-20 = 1, 21-40 = 2, 41-60 = 3, 61-80 = 4, 81-100 = 5
  331. rating += ((_wtoi(value) - (rating * 20)) > 0);
  332. }
  333. // otherwise just make sure and set zero
  334. else {
  335. rating = 0;
  336. }
  337. StringCchPrintfW(dest, destlen, L"%u", rating);
  338. MP4Free(pVal_utf16);
  339. return 1;
  340. }
  341. }
  342. }
  343. else/**/
  344. {
  345. char *value = (char*)pVal;
  346. if(value && *value) {
  347. int rating = atoi(value);
  348. // keeps things limited to our range of 0-100
  349. if (rating >= 100) {
  350. rating = 5;
  351. }
  352. // 1-100 case
  353. else if (rating > 0 && rating < 100) {
  354. rating = (rating /= 20);
  355. // shift up by one rating when in next band
  356. // 1-20 = 1, 21-40 = 2, 41-60 = 3, 61-80 = 4, 81-100 = 5
  357. rating += ((atoi(value) - (rating * 20)) > 0);
  358. }
  359. // otherwise just make sure and set zero
  360. else {
  361. rating = 0;
  362. }
  363. StringCchPrintfW(dest, destlen, L"%u", rating);
  364. MP4Free(pVal);
  365. return 1;
  366. }
  367. }
  368. }
  369. else if (KeywordMatch(data, "comment"))
  370. success = MP4GetMetadataComment(getFileInfoMP4, &pVal);
  371. else if (KeywordMatch(data, "albumartist"))
  372. success = MP4GetMetadataAlbumArtist(getFileInfoMP4, &pVal);
  373. else if (KeywordMatch(data, "gain"))
  374. {
  375. StringCchPrintfW(dest, destlen, L"%+-.2f dB", (float)log10f(GetGain(getFileInfoMP4, false))*20.0f);
  376. success = true;
  377. }
  378. else if (KeywordMatch(data, "replaygain_track_gain"))
  379. {
  380. GetCustomMetadata(getFileInfoMP4, "replaygain_track_gain", dest, destlen);
  381. success = true;
  382. }
  383. else if (KeywordMatch(data, "replaygain_album_gain"))
  384. {
  385. GetCustomMetadata(getFileInfoMP4, "replaygain_album_gain", dest, destlen);
  386. success = true;
  387. }
  388. else if (KeywordMatch(data, "replaygain_track_peak"))
  389. {
  390. GetCustomMetadata(getFileInfoMP4, "replaygain_track_peak", dest, destlen);
  391. success = true;
  392. }
  393. else if (KeywordMatch(data, "replaygain_album_peak"))
  394. {
  395. GetCustomMetadata(getFileInfoMP4, "replaygain_album_peak", dest, destlen);
  396. success = true;
  397. }
  398. else if (KeywordMatch(data, "bpm"))
  399. {
  400. unsigned __int16 tempo = 0;
  401. success = MP4GetMetadataTempo(getFileInfoMP4, &tempo);
  402. if (success && tempo)
  403. StringCchPrintf(dest, destlen, L"%u", tempo);
  404. }
  405. else if (KeywordMatch(data, "year"))
  406. {
  407. success = MP4GetMetadataYear(getFileInfoMP4, &pVal);
  408. if (!success)
  409. {
  410. uint64_t val = 0;
  411. success = MP4Get3GPMetadataInteger(getFileInfoMP4, "yrrc", &val);
  412. if (success)
  413. {
  414. StringCchPrintf(dest, destlen, L"%I64u", val);
  415. }
  416. }
  417. }
  418. else if (KeywordMatch(data, "bitrate"))
  419. {
  420. uint32_t audio_bitrate = 0, video_bitrate = 0;
  421. MP4TrackId track = GetAudioTrack(getFileInfoMP4);
  422. if (track != MP4_INVALID_TRACK_ID)
  423. audio_bitrate = MP4GetTrackBitRate(getFileInfoMP4, track) / 1000;
  424. track = GetVideoTrack(getFileInfoMP4);
  425. if (track != MP4_INVALID_TRACK_ID)
  426. video_bitrate = MP4GetTrackBitRate(getFileInfoMP4, track) / 1000;
  427. if (audio_bitrate || video_bitrate)
  428. StringCchPrintf(dest, destlen, L"%u", audio_bitrate+video_bitrate);
  429. else
  430. dest[0] = 0;
  431. success = true;
  432. }
  433. else if (KeywordMatch(data, "height"))
  434. {
  435. MP4TrackId track = GetVideoTrack(getFileInfoMP4);
  436. if (track != MP4_INVALID_TRACK_ID)
  437. {
  438. uint16_t height = MP4GetTrackVideoHeight(getFileInfoMP4, track);
  439. if (height)
  440. {
  441. StringCchPrintf(dest, destlen, L"%u", height);
  442. }
  443. else
  444. dest[0]=0;
  445. success=true;
  446. }
  447. }
  448. else if (KeywordMatch(data, "width"))
  449. {
  450. MP4TrackId track = GetVideoTrack(getFileInfoMP4);
  451. if (track != MP4_INVALID_TRACK_ID)
  452. {
  453. uint16_t width = MP4GetTrackVideoWidth(getFileInfoMP4, track);
  454. if (width)
  455. {
  456. StringCchPrintf(dest, destlen, L"%u", width);
  457. }
  458. else
  459. dest[0]=0;
  460. success=true;
  461. }
  462. }
  463. //else if(KeywordMatch(data,"srate")) wsprintf(dest,"%d",srate);
  464. //else if(KeywordMatch(data,"stereo")) wsprintf(dest,"%d",is_stereo);
  465. //else if(KeywordMatch(data,"vbr")) wsprintf(dest,"%d",is_vbr);
  466. else if (KeywordMatch(data, "genre"))
  467. {
  468. success = MP4GetMetadataGenre(getFileInfoMP4, &pVal);
  469. if (!success)
  470. success = MP4Get3GPMetadata(getFileInfoMP4, "gnre", &pVal_utf16);
  471. }
  472. else if (KeywordMatch(data, "disc"))
  473. {
  474. unsigned __int16 dummy = 0, dummy2 = 0;
  475. success = MP4GetMetadataDisk(getFileInfoMP4, &dummy, &dummy2);
  476. if (success && dummy)
  477. {
  478. if (dummy2)
  479. StringCchPrintf(dest, destlen, L"%u/%u", dummy, dummy2);
  480. else
  481. StringCchPrintf(dest, destlen, L"%u", dummy);
  482. }
  483. else
  484. dest[0] = 0;
  485. }
  486. else if (KeywordMatch(data, "track"))
  487. {
  488. unsigned __int16 dummy = 0, dummy2 = 0;
  489. success = MP4GetMetadataTrack(getFileInfoMP4, &dummy, &dummy2);
  490. if (success && dummy)
  491. {
  492. if (dummy2)
  493. StringCchPrintf(dest, destlen, L"%u/%u", dummy, dummy2);
  494. else
  495. StringCchPrintf(dest, destlen, L"%u", dummy);
  496. }
  497. else
  498. dest[0] = 0;
  499. }
  500. else if (KeywordMatch(data, "length"))
  501. {
  502. /* TODO: use sample rate and number of samples from iTunSMPB to get a more exact length */
  503. //MP4TrackId track = GetAudioTrack(getFileInfoMP4);
  504. //if (track != MP4_INVALID_TRACK_ID)
  505. //{
  506. // int m_timescale = MP4GetTrackTimeScale(getFileInfoMP4, track);
  507. // unsigned __int64 trackDuration = MP4GetTrackDuration(getFileInfoMP4, track);
  508. // double m_length = (double)(__int64)trackDuration / (double)m_timescale;
  509. // StringCchPrintf(dest, destlen, L"%d", (int)(m_length*1000));
  510. // success = true;
  511. //}
  512. double lengthAudio = 0;
  513. double lengthVideo = 0;
  514. double m_length = 0;
  515. MP4TrackId audio_track = GetAudioTrack(getFileInfoMP4);
  516. if (audio_track != -1)
  517. {
  518. int timescale = MP4GetTrackTimeScale(getFileInfoMP4, audio_track);
  519. unsigned __int64 trackDuration = MP4GetTrackDuration(getFileInfoMP4, audio_track);
  520. lengthAudio = (double)(__int64)trackDuration / (double)timescale;
  521. }
  522. MP4TrackId video_track = GetVideoTrack(getFileInfoMP4);
  523. if (video_track != -1)
  524. {
  525. int timescale = MP4GetTrackTimeScale(getFileInfoMP4, video_track);
  526. unsigned __int64 trackDuration = MP4GetTrackDuration(getFileInfoMP4, video_track);
  527. lengthVideo = (double)(__int64)trackDuration / (double)timescale;
  528. }
  529. m_length = (max(lengthAudio, lengthVideo));
  530. StringCchPrintf(dest, destlen, L"%d", (int)(m_length * 1000));
  531. success = true;
  532. }
  533. else if (KeywordMatch(data, "tool"))
  534. success = MP4GetMetadataTool(getFileInfoMP4, &pVal);
  535. else if (KeywordMatch(data, "composer"))
  536. success = MP4GetMetadataWriter(getFileInfoMP4, &pVal);
  537. else if (KeywordMatch(data, "category"))
  538. success = MP4GetMetadataGrouping(getFileInfoMP4, &pVal);
  539. else if (KeywordMatch(data, "GracenoteFileID"))
  540. {
  541. GetCustomMetadata(getFileInfoMP4, "gnid", dest, destlen);
  542. success = true;
  543. }
  544. else if (KeywordMatch(data, "GracenoteExtData"))
  545. {
  546. GetCustomMetadata(getFileInfoMP4, "gnxd", dest, destlen);
  547. success = true;
  548. }
  549. else if (KeywordMatch(data, "publisher"))
  550. {
  551. GetCustomMetadata(getFileInfoMP4, "publisher", dest, destlen, "com.nullsoft.winamp");
  552. success = true;
  553. }
  554. else if (KeywordMatch(data, "pregap"))
  555. {
  556. wchar_t gap_data[128] = {0};
  557. if (GetCustomMetadata(getFileInfoMP4, "iTunSMPB", gap_data, 128) && gap_data[0])
  558. {
  559. wchar_t *itr = IncSafe(gap_data, 9);
  560. unsigned int pregap = wcstoul(itr, 0, 16);
  561. StringCchPrintfW(dest, destlen, L"%u",pregap);
  562. success=true;
  563. }
  564. }
  565. else if (KeywordMatch(data, "postgap"))
  566. {
  567. wchar_t gap_data[128] = {0};
  568. if (GetCustomMetadata(getFileInfoMP4, "iTunSMPB", gap_data, 128) && gap_data[0])
  569. {
  570. wchar_t *itr = IncSafe(gap_data, 18);
  571. unsigned int postgap = wcstoul(itr, 0, 16);
  572. StringCchPrintfW(dest, destlen, L"%u",postgap);
  573. success=true;
  574. }
  575. }
  576. else if (KeywordMatch(data, "numsamples"))
  577. {
  578. wchar_t gap_data[128] = {0};
  579. if (GetCustomMetadata(getFileInfoMP4, "iTunSMPB", gap_data, 128) && gap_data[0])
  580. {
  581. wchar_t *itr = IncSafe(gap_data,27);
  582. unsigned __int64 numsamples = _wcstoui64(itr, 0, 16);
  583. StringCchPrintfW(dest, destlen, L"%I64u", numsamples);
  584. success=true;
  585. }
  586. }
  587. else if (KeywordMatch(data, "formatinformation"))
  588. {
  589. MP4TrackId track = GetAudioTrack(getFileInfoMP4);
  590. if (track != MP4_INVALID_TRACK_ID)
  591. {
  592. char *track_type = MP4PrintAudioInfo(getFileInfoMP4, track);
  593. if (track_type)
  594. {
  595. uint32_t bitrate = MP4GetTrackBitRate(getFileInfoMP4, track);
  596. StringCchPrintfEx(dest, destlen, &dest, &destlen, 0, WASABI_API_LNGSTRINGW(IDS_AUDIO_INFO), track_type, (bitrate + 500) / 1000);
  597. MP4Free(track_type);
  598. }
  599. }
  600. track = GetVideoTrack(getFileInfoMP4);
  601. if (track != MP4_INVALID_TRACK_ID)
  602. {
  603. char *track_type = MP4PrintVideoInfo(getFileInfoMP4, track);
  604. if (track_type)
  605. {
  606. uint32_t bitrate = MP4GetTrackBitRate(getFileInfoMP4, track);
  607. StringCchPrintfEx(dest, destlen, &dest, &destlen, 0, WASABI_API_LNGSTRINGW(IDS_VIDEO_INFO), track_type, (bitrate + 500) / 1000);
  608. u_int16_t width = MP4GetTrackVideoWidth(getFileInfoMP4, track);
  609. u_int16_t height = MP4GetTrackVideoHeight(getFileInfoMP4, track);
  610. if (width && height)
  611. {
  612. // TODO: framerate, but the MP4GetTrackVideoFrameRate method just guesses based on duration and samples
  613. StringCchPrintfEx(dest, destlen, &dest, &destlen, 0, L"\t%ux%u\n", width, height);
  614. }
  615. MP4Free(track_type);
  616. }
  617. }
  618. success=true;
  619. }
  620. else // don't understand the name
  621. {
  622. // MP4Close(getFileInfoMP4);
  623. return 0;
  624. }
  625. if (pVal)
  626. {
  627. MultiByteToWideCharSZ(CP_UTF8, 0, pVal, -1, dest, destlen);
  628. MP4Free(pVal);
  629. }
  630. else if (pVal_utf16)
  631. {
  632. StringCchCopyW(dest, destlen, (const wchar_t *)pVal_utf16);
  633. MP4Free(pVal_utf16);
  634. }
  635. if (!success)
  636. dest[0] = 0;
  637. //MP4Close(getFileInfoMP4);
  638. return 1;
  639. }
  640. static MP4FileHandle setFileInfoMP4 = 0;
  641. static int m_last_err = 0;
  642. static wchar_t m_last_ext_fn[MAX_PATH] = L"";
  643. static void *setFileInfoReader = 0;
  644. static bool setFile3GP=false;
  645. /*static int SetExtendedInfo3GP(const char *data, const wchar_t *val)
  646. {
  647. if (KeywordMatch(data, "title"))
  648. {
  649. if (val && *val) MP4Set3GPMetadata(setFileInfoMP4, "titl", (const uint16_t *)val);
  650. else MP4Delete3GPMetadata(setFileInfoMP4, "titl");
  651. }
  652. else if (KeywordMatch(data, "album"))
  653. {
  654. if (val && *val) MP4Set3GPMetadata(setFileInfoMP4, "albm", (const uint16_t *)val);
  655. else MP4Delete3GPMetadata(setFileInfoMP4, "albm");
  656. }
  657. else if (KeywordMatch(data, "genre"))
  658. {
  659. if (val && *val) MP4Set3GPMetadata(setFileInfoMP4, "gnre", (const uint16_t *)val);
  660. else MP4Delete3GPMetadata(setFileInfoMP4, "gnre");
  661. }
  662. else if (KeywordMatch(data, "artist"))
  663. {
  664. if (val && *val) MP4Set3GPMetadata(setFileInfoMP4, "perf", (const uint16_t *)val);
  665. else MP4Delete3GPMetadata(setFileInfoMP4, "perf");
  666. }
  667. else if (KeywordMatch(data, "year"))
  668. {
  669. if (val && *val) MP4Set3GPMetadataInteger(setFileInfoMP4, "yrrc", _wtoi64(val));
  670. else MP4Delete3GPMetadata(setFileInfoMP4, "yrrc");
  671. }
  672. else
  673. return 0;
  674. return 1;
  675. }*/
  676. static int SetExtendedInfoMP4(const char *data, const wchar_t *val)
  677. {
  678. if (KeywordMatch(data, "title"))
  679. {
  680. if (val && *val) MP4SetMetadataName(setFileInfoMP4, AutoChar(val, CP_UTF8));
  681. else MP4DeleteMetadataName(setFileInfoMP4);
  682. }
  683. else if (KeywordMatch(data, "album"))
  684. {
  685. if (val && *val)
  686. MP4SetMetadataAlbum(setFileInfoMP4, AutoChar(val, CP_UTF8));
  687. else MP4DeleteMetadataAlbum(setFileInfoMP4);
  688. }
  689. else if (KeywordMatch(data, "artist"))
  690. {
  691. if (val && *val)
  692. MP4SetMetadataArtist(setFileInfoMP4, AutoChar(val, CP_UTF8));
  693. else MP4DeleteMetadataArtist(setFileInfoMP4);
  694. }
  695. else if (KeywordMatch(data, "rating"))
  696. {
  697. if (val && *val)
  698. {
  699. char temp[128] = {0};
  700. StringCchPrintfA(temp, 128, "%u", _wtoi(val) * 20);
  701. MP4SetMetadataRating(setFileInfoMP4, temp);
  702. }
  703. else MP4DeleteMetadataRating(setFileInfoMP4);
  704. }
  705. else if (KeywordMatch(data, "comment"))
  706. {
  707. if (val && *val)
  708. MP4SetMetadataComment(setFileInfoMP4, AutoChar(val, CP_UTF8));
  709. else MP4DeleteMetadataComment(setFileInfoMP4);
  710. }
  711. else if (KeywordMatch(data, "year"))
  712. {
  713. if (val && *val)
  714. MP4SetMetadataYear(setFileInfoMP4, AutoChar(val, CP_UTF8));
  715. else MP4DeleteMetadataYear(setFileInfoMP4);
  716. }
  717. else if (KeywordMatch(data, "genre"))
  718. {
  719. if (val && *val)
  720. MP4SetMetadataGenre(setFileInfoMP4, AutoChar(val, CP_UTF8));
  721. else MP4DeleteMetadataGenre(setFileInfoMP4);
  722. }
  723. else if (KeywordMatch(data, "albumartist"))
  724. {
  725. if (val && *val)
  726. MP4SetMetadataAlbumArtist(setFileInfoMP4, AutoChar(val, CP_UTF8));
  727. else MP4DeleteMetadataAlbumArtist(setFileInfoMP4);
  728. }
  729. else if (KeywordMatch(data, "replaygain_track_gain"))
  730. {
  731. if (val && *val)
  732. {
  733. MP4DeleteMetadataFreeForm(setFileInfoMP4, "replaygain_track_gain", "com.apple.iTunes");
  734. AutoChar utf8(val, CP_UTF8);
  735. MP4SetMetadataFreeForm(setFileInfoMP4, "replaygain_track_gain", (u_int8_t *)(char *)utf8, lstrlenA(utf8), "org.hydrogenaudio.replaygain");
  736. }
  737. else
  738. {
  739. MP4DeleteMetadataFreeForm(setFileInfoMP4, "replaygain_track_gain", "com.apple.iTunes");
  740. MP4DeleteMetadataFreeForm(setFileInfoMP4, "replaygain_track_gain", "org.hydrogenaudio.replaygain");
  741. }
  742. }
  743. else if (KeywordMatch(data, "replaygain_track_peak"))
  744. {
  745. if (val && *val)
  746. {
  747. MP4DeleteMetadataFreeForm(setFileInfoMP4, "replaygain_track_peak", "com.apple.iTunes");
  748. AutoChar utf8(val, CP_UTF8);
  749. MP4SetMetadataFreeForm(setFileInfoMP4, "replaygain_track_peak", (u_int8_t *)(char *)utf8, lstrlenA(utf8), "org.hydrogenaudio.replaygain");
  750. }
  751. else
  752. {
  753. MP4DeleteMetadataFreeForm(setFileInfoMP4, "replaygain_track_peak", "com.apple.iTunes");
  754. MP4DeleteMetadataFreeForm(setFileInfoMP4, "replaygain_track_peak", "org.hydrogenaudio.replaygain");
  755. }
  756. }
  757. else if (KeywordMatch(data, "replaygain_album_gain"))
  758. {
  759. if (val && *val)
  760. {
  761. MP4DeleteMetadataFreeForm(setFileInfoMP4, "replaygain_album_gain", "com.apple.iTunes");
  762. AutoChar utf8(val, CP_UTF8);
  763. MP4SetMetadataFreeForm(setFileInfoMP4, "replaygain_album_gain", (u_int8_t *)(char *)utf8, lstrlenA(utf8), "org.hydrogenaudio.replaygain");
  764. }
  765. else
  766. {
  767. MP4DeleteMetadataFreeForm(setFileInfoMP4, "replaygain_album_gain", "com.apple.iTunes");
  768. MP4DeleteMetadataFreeForm(setFileInfoMP4, "replaygain_album_gain", "org.hydrogenaudio.replaygain");
  769. }
  770. }
  771. else if (KeywordMatch(data, "replaygain_album_peak"))
  772. {
  773. if (val && *val)
  774. {
  775. MP4DeleteMetadataFreeForm(setFileInfoMP4, "replaygain_album_peak", "com.apple.iTunes");
  776. AutoChar utf8(val, CP_UTF8);
  777. MP4SetMetadataFreeForm(setFileInfoMP4, "replaygain_album_peak", (u_int8_t *)(char *)utf8, lstrlenA(utf8), "org.hydrogenaudio.replaygain");
  778. }
  779. else
  780. {
  781. MP4DeleteMetadataFreeForm(setFileInfoMP4, "replaygain_album_peak", "com.apple.iTunes");
  782. MP4DeleteMetadataFreeForm(setFileInfoMP4, "replaygain_album_peak", "org.hydrogenaudio.replaygain");
  783. }
  784. }
  785. else if (KeywordMatch(data, "track"))
  786. {
  787. int track = _wtoi(val);
  788. if (track)
  789. {
  790. int tracks = 0;
  791. const wchar_t *_tracks = wcschr(val, L'/');
  792. if (_tracks) tracks = _wtoi(_tracks + 1);
  793. MP4SetMetadataTrack(setFileInfoMP4, track, tracks);
  794. }
  795. else
  796. MP4DeleteMetadataTrack(setFileInfoMP4);
  797. }
  798. else if (KeywordMatch(data, "disc"))
  799. {
  800. int disc = _wtoi(val);
  801. if (disc)
  802. {
  803. int discs = 0;
  804. const wchar_t *_discs = wcschr(val, L'/');
  805. if (_discs) discs = _wtoi(_discs + 1);
  806. MP4SetMetadataDisk(setFileInfoMP4, disc, discs);
  807. }
  808. else
  809. MP4DeleteMetadataDisk(setFileInfoMP4);
  810. }
  811. else if (KeywordMatch(data, "tool"))
  812. {
  813. if (val && *val)
  814. MP4SetMetadataTool(setFileInfoMP4, AutoChar(val, CP_UTF8));
  815. else MP4DeleteMetadataTool(setFileInfoMP4);
  816. }
  817. else if (KeywordMatch(data, "bpm"))
  818. {
  819. if (val && *val && *val != '0')
  820. MP4SetMetadataTempo(setFileInfoMP4, _wtoi(val));
  821. else
  822. MP4DeleteMetadataTempo(setFileInfoMP4);
  823. }
  824. else if (KeywordMatch(data, "composer"))
  825. {
  826. if (val && *val)
  827. MP4SetMetadataWriter(setFileInfoMP4, AutoChar(val, CP_UTF8));
  828. else MP4DeleteMetadataWriter(setFileInfoMP4);
  829. }
  830. else if (KeywordMatch(data, "GracenoteFileID"))
  831. {
  832. MP4DeleteMetadataFreeForm(setFileInfoMP4, "gnid", "com.apple.iTunes"); // delete obselete metadata storage scheme
  833. if (val && *val)
  834. {
  835. AutoChar utf8(val, CP_UTF8);
  836. MP4SetMetadataFreeForm(setFileInfoMP4, "gnid", (u_int8_t *)(char *)utf8, lstrlenA(utf8), "com.gracenote.cddb");
  837. }
  838. else
  839. {
  840. MP4DeleteMetadataFreeForm(setFileInfoMP4, "gnid", "com.gracenote.cddb");
  841. }
  842. }
  843. else if (KeywordMatch(data, "GracenoteExtData"))
  844. {
  845. MP4DeleteMetadataFreeForm(setFileInfoMP4, "gnxd", "com.apple.iTunes");// delete obselete metadata storage scheme
  846. if (val && *val)
  847. {
  848. AutoChar utf8(val, CP_UTF8);
  849. MP4SetMetadataFreeForm(setFileInfoMP4, "gnxd", (u_int8_t *)(char *)utf8, lstrlenA(utf8), "com.gracenote.cddb");
  850. }
  851. else
  852. {
  853. MP4DeleteMetadataFreeForm(setFileInfoMP4, "gnxd", "com.gracenote.cddb");
  854. }
  855. }
  856. else if (KeywordMatch(data, "publisher"))
  857. {
  858. if (val && *val)
  859. {
  860. AutoChar utf8(val, CP_UTF8);
  861. MP4SetMetadataFreeForm(setFileInfoMP4, "publisher", (u_int8_t *)(char *)utf8, lstrlenA(utf8), "com.nullsoft.winamp");
  862. }
  863. else
  864. {
  865. MP4DeleteMetadataFreeForm(setFileInfoMP4, "publisher", "com.nullsoft.winamp");
  866. }
  867. }
  868. else if (KeywordMatch(data, "category"))
  869. {
  870. if (val && *val)
  871. MP4SetMetadataGrouping(setFileInfoMP4, AutoChar(val, CP_UTF8));
  872. else MP4DeleteMetadataGrouping(setFileInfoMP4);
  873. }
  874. else
  875. return 0;
  876. return 1;
  877. }
  878. static Stopper stopper;
  879. __declspec( dllexport ) int winampSetExtendedFileInfoW(const wchar_t *fn, const char *data, const wchar_t *val)
  880. {
  881. if (!setFileInfoMP4 || lstrcmpi(m_last_ext_fn, fn)) // different file than last time?
  882. {
  883. m_last_err = 0;
  884. lstrcpyn(m_last_ext_fn, fn, MAX_PATH);
  885. if (setFileInfoMP4)
  886. MP4Close(setFileInfoMP4);
  887. /* TODO: make MP4ModifyEx so we can use this
  888. if (setFileInfoReader)
  889. DestroyUnicodeReader(setFileInfoReader);
  890. setFileInfoReader = CreateUnicodeReader(fn);
  891. if (!setFileInfoReader)
  892. return 0;
  893. */
  894. if (!_wcsicmp(m_last_ext_fn, lastfn))
  895. stopper.Stop();
  896. setFileInfoMP4 = MP4Modify(m_last_ext_fn, 0, 0);
  897. if (!setFileInfoMP4)
  898. {
  899. stopper.ChangeTracking(1); // enable stats updating
  900. //DestroyUnicodeReader(setFileInfoReader);
  901. m_last_err = 1;
  902. return 0;
  903. }
  904. setFile3GP = false;
  905. const char *brand=0;
  906. if (MP4GetStringProperty(setFileInfoMP4, "ftyp.majorBrand", &brand))
  907. {
  908. if (!strncmp(brand, "3gp6", 4))
  909. setFile3GP=true;
  910. }
  911. }
  912. /*
  913. if (setFile3GP)
  914. return SetExtendedInfo3GP(data, val);
  915. else*/
  916. return SetExtendedInfoMP4(data, val);
  917. }
  918. __declspec(dllexport) int winampWriteExtendedFileInfo()
  919. {
  920. int err = m_last_err;
  921. m_last_err = 0;
  922. // clear this as well, so dirty info isn't read back
  923. if (getFileInfoMP4)
  924. MP4Close(getFileInfoMP4);
  925. getFileInfoMP4=0;
  926. if (getFileInfoReader)
  927. DestroyUnicodeReader(getFileInfoReader);
  928. getFileInfoReader=0;
  929. if (setFileInfoMP4)
  930. {
  931. MP4Close(setFileInfoMP4);
  932. MP4Optimize(m_last_ext_fn, NULL, 0); //put the fields at the beginning of the file
  933. setFileInfoMP4 = 0;
  934. m_last_ext_fn[0] = 0;
  935. stopper.Play();
  936. }
  937. // update last modified so we're not re-queried on our own updates
  938. UpdateFileTimeChanged(m_last_ext_fn);
  939. return !err;
  940. }
  941. }