windows-nt/Source/XPSP1/NT/shell/ext/media/wmpss.cpp
2020-09-26 16:20:57 +08:00

1903 lines
61 KiB
C++

#include "pch.h"
#include "thisdll.h"
#include "wmwrap.h"
#include "MediaProp.h"
#include <streams.h> // For VIDEOINFOHEADER, etc..
#include <drmexternals.h>
#include "ids.h"
#define TRACK_ONE_BASED L"WM/TrackNumber"
#define TRACK_ZERO_BASED L"WM/Track"
// Struct used when collecting information about a file.
// This is used when populating sl ow files, and the information within is retrieved by several
// different methods.
typedef struct
{
// DRM info
LPWSTR pszLicenseInformation;
DWORD dwPlayCount;
FILETIME ftPlayStarts;
FILETIME ftPlayExpires;
// Audio properties
LPWSTR pszStreamNameAudio;
WORD wStreamNumberAudio;
WORD nChannels;
DWORD dwBitrateAudio;
LPWSTR pszCompressionAudio;
DWORD dwSampleRate;
ULONG lSampleSizeAudio;
// Video properties
LPWSTR pszStreamNameVideo;
WORD wStreamNumberVideo;
WORD wBitDepth;
DWORD dwBitrateVideo;
LONG cx;
LONG cy;
LPWSTR pszCompressionVideo;
DWORD dwFrames;
DWORD dwFrameRate;
} SHMEDIA_AUDIOVIDEOPROPS;
// Helpers for putting information in SHMEDIA_AUDIOVIDEOPROPS
void GetVideoProperties(IWMStreamConfig *pConfig, SHMEDIA_AUDIOVIDEOPROPS *pVideoProps);
void GetVideoPropertiesFromHeader(VIDEOINFOHEADER *pvih, SHMEDIA_AUDIOVIDEOPROPS *pVideoProps);
void GetVideoPropertiesFromBitmapHeader(BITMAPINFOHEADER *bmi, SHMEDIA_AUDIOVIDEOPROPS *pVideoProps);
void InitializeAudioVideoProperties(SHMEDIA_AUDIOVIDEOPROPS *pAVProps);
void FreeAudioVideoProperties(SHMEDIA_AUDIOVIDEOPROPS *pAVProps);
void GetAudioProperties(IWMStreamConfig *pConfig, SHMEDIA_AUDIOVIDEOPROPS *pAudioProps);
void AcquireLicenseInformation(IWMDRMReader *pReader, SHMEDIA_AUDIOVIDEOPROPS *pAVProps);
HRESULT GetSlowProperty(REFFMTID fmtid, PROPID pid, SHMEDIA_AUDIOVIDEOPROPS *pAVProps, PROPVARIANT *pvar);
void _AssertValidDRMStrings();
// Window media audio supported formats
// Applies to wma, mp3,...
const COLMAP* c_rgWMADocSummaryProps[] =
{
{&g_CM_Category},
};
const COLMAP* c_rgWMASummaryProps[] =
{
{&g_CM_Author},
{&g_CM_Title},
{&g_CM_Comment},
};
const COLMAP* c_rgWMAMusicProps[] =
{
{&g_CM_Artist},
{&g_CM_Album},
{&g_CM_Year},
{&g_CM_Track},
{&g_CM_Genre},
{&g_CM_Lyrics},
};
const COLMAP* c_rgWMADRMProps[] =
{
{&g_CM_Protected},
{&g_CM_DRMDescription},
{&g_CM_PlayCount},
{&g_CM_PlayStarts},
{&g_CM_PlayExpires},
};
const COLMAP* c_rgWMAAudioProps[] =
{
{&g_CM_Duration},
{&g_CM_Bitrate},
{&g_CM_ChannelCount},
{&g_CM_SampleSize},
{&g_CM_SampleRate},
};
const PROPSET_INFO g_rgWMAPropStgs[] =
{
{ PSGUID_MUSIC, c_rgWMAMusicProps, ARRAYSIZE(c_rgWMAMusicProps) },
{ PSGUID_SUMMARYINFORMATION, c_rgWMASummaryProps, ARRAYSIZE(c_rgWMASummaryProps) },
{ PSGUID_DOCUMENTSUMMARYINFORMATION, c_rgWMADocSummaryProps, ARRAYSIZE(c_rgWMADocSummaryProps)},
{ PSGUID_AUDIO, c_rgWMAAudioProps, ARRAYSIZE(c_rgWMAAudioProps)},
{ PSGUID_DRM, c_rgWMADRMProps, ARRAYSIZE(c_rgWMADRMProps)},
};
// Windows media audio
// Window media video supported formats
// applies to wmv, asf, ...
const COLMAP* c_rgWMVSummaryProps[] =
{
{&g_CM_Author},
{&g_CM_Title},
{&g_CM_Comment},
};
const COLMAP* c_rgWMVDRMProps[] =
{
{&g_CM_Protected},
{&g_CM_DRMDescription},
{&g_CM_PlayCount},
{&g_CM_PlayStarts},
{&g_CM_PlayExpires},
};
const COLMAP* c_rgWMVAudioProps[] =
{
{&g_CM_Duration},
{&g_CM_Bitrate},
{&g_CM_ChannelCount},
{&g_CM_SampleSize},
{&g_CM_SampleRate},
};
const COLMAP* c_rgWMVVideoProps[] =
{
{&g_CM_StreamName},
{&g_CM_FrameRate},
{&g_CM_SampleSizeV},
{&g_CM_BitrateV},
{&g_CM_Compression},
};
const COLMAP* c_rgWMVImageProps[] =
{
{&g_CM_Width},
{&g_CM_Height},
{&g_CM_Dimensions},
{&g_CM_FrameCount},
};
const PROPSET_INFO g_rgWMVPropStgs[] =
{
{ PSGUID_DRM, c_rgWMVDRMProps, ARRAYSIZE(c_rgWMVDRMProps) },
{ PSGUID_SUMMARYINFORMATION, c_rgWMVSummaryProps, ARRAYSIZE(c_rgWMVSummaryProps) },
{ PSGUID_AUDIO, c_rgWMVAudioProps, ARRAYSIZE(c_rgWMVAudioProps)},
{ PSGUID_VIDEO, c_rgWMVVideoProps, ARRAYSIZE(c_rgWMVVideoProps)},
{ PSGUID_IMAGESUMMARYINFORMATION, c_rgWMVImageProps, ARRAYSIZE(c_rgWMVImageProps)},
};
// Windows media video
// Map from scids to corresponding WMSDK attributes, for some of the "fast" properties
// retrieved via IWMHeaderInfo. Two of these properties may also be slow (if the values aren't available
// via IWMHeaderInfo).
typedef struct
{
const SHCOLUMNID *pscid;
LPCWSTR pszSDKName;
} SCIDTOSDK;
const SCIDTOSDK g_rgSCIDToSDKName[] =
{
// SCID sdk name
{&SCID_Author, L"Author"},
{&SCID_Title, L"Title"},
{&SCID_Comment, L"Description"},
{&SCID_Category, L"WM/Genre"},
{&SCID_MUSIC_Artist, L"Author"},
{&SCID_MUSIC_Album, L"WM/AlbumTitle"},
{&SCID_MUSIC_Year, L"WM/Year"},
{&SCID_MUSIC_Genre, L"WM/Genre"},
{&SCID_MUSIC_Track, NULL}, // Track is a special property, as evidenced by
{&SCID_DRM_Protected, L"Is_Protected"}, // the fact that it doesn't have an SDK Name.
{&SCID_AUDIO_Duration, L"Duration"}, // Duration is slow, but may also be fast, depending on the file
{&SCID_AUDIO_Bitrate, L"Bitrate"}, // Bitrate is slow, but may also be fast, depending on the file
{&SCID_MUSIC_Lyrics, L"WM/Lyrics"}, // Lyrics
};
// impl
class CWMPropSetStg : public CMediaPropSetStg
{
public:
HRESULT FlushChanges(REFFMTID fmtid, LONG cNumProps, const COLMAP **ppcmapInfo, PROPVARIANT *pVarProps, BOOL *pbDirtyFlags);
BOOL _IsSlowProperty(const COLMAP *pPInfo);
private:
HRESULT _FlushProperty(IWMHeaderInfo *phi, const COLMAP *pPInfo, PROPVARIANT *pvar);
HRESULT _PopulateSpecialProperty(IWMHeaderInfo *phi, const COLMAP *pPInfo);
HRESULT _SetPropertyFromWMT(const COLMAP *pPInfo, WMT_ATTR_DATATYPE attrDatatype, UCHAR *pData, WORD cbSize);
HRESULT _PopulatePropertySet();
HRESULT _PopulateSlowProperties();
HRESULT _GetSlowPropertyInfo(SHMEDIA_AUDIOVIDEOPROPS *pAVProps);
LPCWSTR _GetSDKName(const COLMAP *pPInfo);
BOOL _IsHeaderProperty(const COLMAP *pPInfo);
HRESULT _OpenHeaderInfo(IWMHeaderInfo **pHeaderInfo, BOOL fReadingOnly);
HRESULT _PreCheck();
HRESULT _QuickLookup(const COLMAP *pPInfo, PROPVARIANT **ppvar);
void _PostProcess();
BOOL _fProtectedContent;
BOOL _fDurationSlow;
BOOL _fBitrateSlow;
};
#define HI_READONLY TRUE
#define HI_READWRITE FALSE
// The only difference between CWMA and CWMV is which properties they're initialized with.
class CWMAPropSetStg : public CWMPropSetStg
{
public:
CWMAPropSetStg() { _pPropStgInfo = g_rgWMAPropStgs; _cPropertyStorages = ARRAYSIZE(g_rgWMAPropStgs);};
// IPersist
STDMETHODIMP GetClassID(CLSID *pclsid) {*pclsid = CLSID_AudioMediaProperties; return S_OK;};
};
class CWMVPropSetStg : public CWMPropSetStg
{
public:
CWMVPropSetStg() { _pPropStgInfo = g_rgWMVPropStgs; _cPropertyStorages = ARRAYSIZE(g_rgWMVPropStgs);};
// IPersist
STDMETHODIMP GetClassID(CLSID *pclsid) {*pclsid = CLSID_VideoMediaProperties; return S_OK;};
};
HRESULT CreateReader(REFIID riid, void **ppv)
{
IWMReader *pReader;
HRESULT hr = WMCreateReader(NULL, 0, &pReader);
if (SUCCEEDED(hr))
{
hr = pReader->QueryInterface(riid, ppv);
pReader->Release();
}
return hr;
}
HRESULT CWMPropSetStg::_PopulateSlowProperties()
{
if (!_bSlowPropertiesExtracted)
{
_bSlowPropertiesExtracted = TRUE;
SHMEDIA_AUDIOVIDEOPROPS avProps = {0};
InitializeAudioVideoProperties(&avProps);
HRESULT hr = _GetSlowPropertyInfo(&avProps);
if (SUCCEEDED(hr))
{
// Iterate through all fmtid/pid pairs we want, and call GetSlowProperty
CEnumAllProps enumAllProps(_pPropStgInfo, _cPropertyStorages);
const COLMAP *pPInfo = enumAllProps.Next();
while (pPInfo)
{
if (_IsSlowProperty(pPInfo))
{
PROPVARIANT var = {0};
if (SUCCEEDED(GetSlowProperty(pPInfo->pscid->fmtid,
pPInfo->pscid->pid,
&avProps,
&var)))
{
_PopulateProperty(pPInfo, &var);
PropVariantClear(&var);
}
}
pPInfo = enumAllProps.Next();
}
// Free info in structure
FreeAudioVideoProperties(&avProps);
hr = S_OK;
}
_hrSlowProps = hr;
}
return _hrSlowProps;
}
BOOL CWMPropSetStg::_IsSlowProperty(const COLMAP *pPInfo)
{
// Some properties can be slow or "fast", depending on the file.
if (pPInfo == &g_CM_Bitrate)
return _fBitrateSlow;
if (pPInfo == &g_CM_Duration)
return _fDurationSlow;
// Other than that - if it had a name used for IWMHeaderInfo->GetAttributeXXX, then it's a fast property.
for (int i = 0; i < ARRAYSIZE(g_rgSCIDToSDKName); i++)
{
if (IsEqualSCID(*pPInfo->pscid, *g_rgSCIDToSDKName[i].pscid))
{
// Definitely a fast property.
return FALSE;
}
}
// If it's not one of the IWMHeaderInfo properties, then it's definitely slow.
return TRUE;
}
STDAPI_(BOOL) IsNullTime(const FILETIME *pft)
{
FILETIME ftNull = {0, 0};
return CompareFileTime(&ftNull, pft) == 0;
}
HRESULT GetSlowProperty(REFFMTID fmtid, PROPID pid, SHMEDIA_AUDIOVIDEOPROPS *pAVProps, PROPVARIANT *pvar)
{
HRESULT hr = E_FAIL;
if (IsEqualGUID(fmtid, FMTID_DRM))
{
switch (pid)
{
case PIDDRSI_PROTECTED:
ASSERTMSG(FALSE, "WMPSS: Asking for PIDDRSI_PROTECTED as a slow property");
break;
case PIDDRSI_DESCRIPTION:
if (pAVProps->pszLicenseInformation)
{
hr = SHStrDupW(pAVProps->pszLicenseInformation, &pvar->pwszVal);
if (SUCCEEDED(hr))
pvar->vt = VT_LPWSTR;
}
break;
case PIDDRSI_PLAYCOUNT:
if (pAVProps->dwPlayCount != -1)
{
pvar->vt = VT_UI4;
pvar->ulVal = pAVProps->dwPlayCount;
hr = S_OK;
}
break;
case PIDDRSI_PLAYSTARTS:
if (!IsNullTime(&pAVProps->ftPlayStarts))
{
pvar->vt = VT_FILETIME;
pvar->filetime = pAVProps->ftPlayStarts;
hr = S_OK;
}
break;
case PIDDRSI_PLAYEXPIRES:
if (!IsNullTime(&pAVProps->ftPlayExpires))
{
pvar->vt = VT_FILETIME;
pvar->filetime = pAVProps->ftPlayExpires;
hr = S_OK;
}
break;
}
}
else if (IsEqualGUID(fmtid, FMTID_AudioSummaryInformation))
{
switch (pid)
{
// case PIDASI_FORMAT: Don't know how to get this yet.
// case PIDASI_DURATION: Don't know how to get this yet, but it's usually available through IWMHeaderInfo
case PIDASI_STREAM_NAME:
if (pAVProps->pszStreamNameAudio != NULL)
{
hr = SHStrDupW(pAVProps->pszStreamNameAudio, &pvar->pwszVal);
if (SUCCEEDED(hr))
pvar->vt = VT_LPWSTR;
}
break;
case PIDASI_STREAM_NUMBER:
if (pAVProps->wStreamNumberAudio > 0)
{
pvar->vt = VT_UI2;
pvar->uiVal = pAVProps->wStreamNumberAudio;
hr = S_OK;
}
break;
case PIDASI_AVG_DATA_RATE:
if (pAVProps->dwBitrateAudio > 0)
{
pvar->vt = VT_UI4;
pvar->ulVal = pAVProps->dwBitrateAudio;
hr = S_OK;
}
break;
case PIDASI_SAMPLE_RATE:
if (pAVProps->dwSampleRate > 0)
{
pvar->vt = VT_UI4;
pvar->ulVal = pAVProps->dwSampleRate;
hr = S_OK;
}
break;
case PIDASI_SAMPLE_SIZE:
if (pAVProps->lSampleSizeAudio > 0)
{
pvar->vt = VT_UI4;
pvar->ulVal = pAVProps->lSampleSizeAudio;
hr = S_OK;
}
break;
case PIDASI_CHANNEL_COUNT:
if (pAVProps->nChannels > 0)
{
pvar->vt = VT_UI4;
pvar->ulVal = pAVProps->nChannels;
hr = S_OK;
}
break;
// Not supported yet - don't know how to get this.
case PIDASI_COMPRESSION:
if (pAVProps->pszCompressionAudio != NULL)
{
hr = SHStrDupW(pAVProps->pszCompressionAudio, &pvar->pwszVal);
if (SUCCEEDED(hr))
pvar->vt = VT_LPWSTR;
}
break;
}
}
else if (IsEqualGUID(fmtid, FMTID_VideoSummaryInformation))
{
switch (pid)
{
case PIDVSI_STREAM_NAME:
if (pAVProps->pszStreamNameVideo != NULL)
{
hr = SHStrDupW(pAVProps->pszStreamNameVideo, &pvar->pwszVal);
if (SUCCEEDED(hr))
pvar->vt = VT_LPWSTR;
}
break;
case PIDVSI_STREAM_NUMBER:
if (pAVProps->wStreamNumberVideo > 0)
{
pvar->vt = VT_UI2;
pvar->uiVal = pAVProps->wStreamNumberVideo;
hr = S_OK;
}
break;
// Not supported yet - don't know how to get this.
case PIDVSI_FRAME_RATE:
if (pAVProps->dwFrameRate > 0)
{
pvar->vt = VT_UI4;
pvar->ulVal = pAVProps->dwFrameRate;
hr = S_OK;
}
break;
case PIDVSI_DATA_RATE:
if (pAVProps->dwBitrateVideo > 0)
{
pvar->vt = VT_UI4;
pvar->ulVal = pAVProps->dwBitrateVideo;
hr = S_OK;
}
break;
case PIDVSI_SAMPLE_SIZE:
//This is bitdepth.
if (pAVProps->wBitDepth > 0)
{
pvar->vt = VT_UI4;
pvar->ulVal = (ULONG)pAVProps->wBitDepth;
hr = S_OK;
}
break;
// Not supported yet - don't know how to get this.
case PIDVSI_COMPRESSION:
if (pAVProps->pszCompressionVideo != NULL)
{
hr = SHStrDupW(pAVProps->pszCompressionVideo, &pvar->pwszVal);
if (SUCCEEDED(hr))
pvar->vt = VT_LPWSTR;
}
break;
}
}
else if (IsEqualGUID(fmtid, FMTID_ImageSummaryInformation))
{
switch(pid)
{
case PIDISI_CX:
if (pAVProps->cx > 0)
{
pvar->vt = VT_UI4;
pvar->ulVal = pAVProps->cx;
hr = S_OK;
}
break;
case PIDISI_CY:
if (pAVProps->cy > 0)
{
pvar->vt = VT_UI4;
pvar->ulVal = pAVProps->cy;
hr = S_OK;
}
break;
case PIDISI_FRAME_COUNT:
if (pAVProps->dwFrames > 0)
{
pvar->vt = VT_UI4;
pvar->ulVal = pAVProps->dwFrames;
hr = S_OK;
}
break;
case PIDISI_DIMENSIONS:
if ((pAVProps->cy > 0) && (pAVProps->cx > 0))
{
WCHAR szFmt[64];
if (LoadString(m_hInst, IDS_DIMENSIONS_FMT, szFmt, ARRAYSIZE(szFmt)))
{
DWORD_PTR args[2];
args[0] = (DWORD_PTR)pAVProps->cx;
args[1] = (DWORD_PTR)pAVProps->cy;
WCHAR szBuffer[64];
FormatMessage(FORMAT_MESSAGE_FROM_STRING|FORMAT_MESSAGE_ARGUMENT_ARRAY,
szFmt, 0, 0, szBuffer, ARRAYSIZE(szBuffer), (va_list*)args);
hr = SHStrDup(szBuffer, &pvar->pwszVal);
if (SUCCEEDED(hr))
pvar->vt = VT_LPWSTR;
}
}
break;
}
}
return hr;
}
typedef struct
{
UINT ridDays;
UINT ridWeeks;
UINT ridMonths;
} TIMEDRMRIDS;
// These are sentinel values.
#define DRMRIDS_TYPE_NONE -1
#define DRMRIDS_TYPE_NORIGHT -2
// These are indices into the ridTimes array in the DRMRIDS structure.
#define DRMRIDS_TYPE_BEFORE 0
#define DRMRIDS_TYPE_NOTUNTIL 1
#define DRMRIDS_TYPE_COUNTBEFORE 2
#define DRMRIDS_TYPE_COUNTNOTUNTIL 3
typedef struct
{
UINT ridNoRights;
TIMEDRMRIDS ridTimes[4];
UINT ridCountRemaining;
} DRMRIDS;
//*****************************************************************************
// NOTE: wszCount parameter is optional... can populate just a date string.
//*****************************************************************************
HRESULT ChooseAndPopulateDateCountString(
FILETIME ftCurrent, // current time
FILETIME ftLicense, // license UTC time
WCHAR *wszCount, // optional count string
const TIMEDRMRIDS *pridTimes,
WCHAR *wszOutValue, // returned formatted string
DWORD cchOutValue ) // num chars in 'wszOutValue'
{
HRESULT hr = S_OK;
// 'ftLicense' (the license time) is greater than the current time.
// Determine how much greater, and use the appropriate string.
ULARGE_INTEGER ulCurrent, ulLicense;
WCHAR wszDiff[ 34 ];
QWORD qwDiff;
DWORD dwDiffDays;
DWORD rid = 0;
// Laborious conversion to I64 type.
ulCurrent.LowPart = ftCurrent.dwLowDateTime;
ulCurrent.HighPart = ftCurrent.dwHighDateTime;
ulLicense.LowPart = ftLicense.dwLowDateTime;
ulLicense.HighPart = ftLicense.dwHighDateTime;
if ((QWORD)ulLicense.QuadPart > (QWORD)ulCurrent.QuadPart)
qwDiff = (QWORD)ulLicense.QuadPart - (QWORD)ulCurrent.QuadPart;
else
qwDiff = (QWORD)ulCurrent.QuadPart - (QWORD)ulLicense.QuadPart;
dwDiffDays = ( DWORD )( qwDiff / ( QWORD )864000000000); // number of 100-ns units in a day.
// We'll count the partial day as 1, so increment.
// NOTE: this means we will never show a string that says
// "expires in 0 day(s)".
dwDiffDays++;
if ( 31 >= dwDiffDays )
{
rid = pridTimes->ridDays;
}
else if ( 61 >= dwDiffDays )
{
rid = pridTimes->ridWeeks;
dwDiffDays /= 7; // derive # weeks
}
else
{
rid = pridTimes->ridMonths;
dwDiffDays /= 30; // derive # months
}
_ltow(( long )dwDiffDays, wszDiff, 10 );
WCHAR szDRMMsg[MAX_PATH];
WCHAR* rgchArgList[2];
rgchArgList[0] = wszDiff;
rgchArgList[1] = wszCount; // may be NULL
// Can't get FORMAT_MESSAGE_FROM_HMODULE to work with FormatMessage....
LoadString(m_hInst, rid, szDRMMsg, ARRAYSIZE(szDRMMsg));
FormatMessage(FORMAT_MESSAGE_FROM_STRING | FORMAT_MESSAGE_ARGUMENT_ARRAY, szDRMMsg, 0, 0, wszOutValue, cchOutValue, reinterpret_cast<char**>(rgchArgList));
return( hr );
}
// Return S_FALSE indicates no information found in the state data struct.
//*****************************************************************************
HRESULT ParseDRMStateData(const WM_LICENSE_STATE_DATA *sdValue, // data from DRM
const DRMRIDS *prids, // array of resource ID's
WCHAR *wszOutValue, // ptr to output buffer
DWORD cchOutValue, // number of chars in 'wszOutValue' buffer
DWORD *pdwCount, // Extra non-string info: counts remaining.
FILETIME *pftStarts, // Extra non string info: when it starts.
FILETIME *pftExpires) // Extra non string info: when it expires.
{
HRESULT hr = S_OK;
*pdwCount = -1;
pftExpires->dwLowDateTime = 0;
pftExpires->dwHighDateTime = 0;
pftStarts->dwLowDateTime = 0;
pftStarts->dwHighDateTime = 0;
WCHAR wszCount[34];
WCHAR wszTemp[MAX_PATH];
DWORD dwNumCounts = sdValue->stateData[0].dwNumCounts;
if (dwNumCounts != 0)
{
// We have a valid play count.
ASSERTMSG(1 == dwNumCounts, "Invalid number of playcounts in DRM_LICENSE_STATE_DATA");
(void)_ltow(( long )sdValue->stateData[ 0 ].dwCount[ 0 ], wszCount, 10 );
// ** Bonus information to store off.
*pdwCount = sdValue->stateData[0].dwCount[0];
}
// Now deal with dates.
UINT dwNumDates = sdValue->stateData[ 0 ].dwNumDates;
// Most licenses have at most one date... an expiration.
// There should be at most 2 dates!!
if (dwNumDates == 0)
{
// No dates.. if there is also no playcount, then it's unlimited play.
if (*pdwCount == -1)
{
// We're done.
hr = S_FALSE;
}
else
{
// No dates.. just a count. Fill it into proper string.
LoadString(m_hInst, prids->ridCountRemaining, wszTemp, ARRAYSIZE(wszTemp));
wnsprintf(wszOutValue, cchOutValue, wszTemp, wszCount);
// We're done.
}
}
else
{
DWORD dwCategory = sdValue->stateData[0].dwCategory;
// There are dates.
if (dwNumDates == 1)
{
// Is it start or end?
if ((dwCategory == WM_DRM_LICENSE_STATE_FROM) || (dwCategory == WM_DRM_LICENSE_STATE_COUNT_FROM))
{
// Start.
*pftStarts = sdValue->stateData[0].datetime[0];
}
else if ((dwCategory == WM_DRM_LICENSE_STATE_UNTIL) || (dwCategory == WM_DRM_LICENSE_STATE_COUNT_UNTIL))
{
// Expires.
*pftExpires = sdValue->stateData[0].datetime[0];
}
else
{
ASSERTMSG(FALSE, "Unexpected dwCategory for 1 date in DRM_LICENSE_STATE_DATA");
hr = E_FAIL;
}
}
else if (dwNumDates == 2)
{
// A start and end date.
ASSERTMSG((dwCategory == WM_DRM_LICENSE_STATE_FROM_UNTIL) || (dwCategory == WM_DRM_LICENSE_STATE_COUNT_FROM_UNTIL), "Unexpected dwCategory for 2 dates in DRM_LICENSE_STATE_DATA");
*pftStarts = sdValue->stateData[0].datetime[0];
*pftExpires = sdValue->stateData[0].datetime[1];
}
else
{
ASSERTMSG(FALSE, "Too many dates in DRM_LICENSE_STATE_DATA");
hr = E_FAIL;
}
if (SUCCEEDED(hr))
{
// 7 cases here. * = license date. T = current time.
// --------------------------
// BEGIN | END
// --------------------------
// 1 T * "...not allowed for xxx"
// 2 * T --- don't show anything - action is allowed ---
// 3 T * "...expires in xxx"
// 4 * T "...not allowed"
// 5 T * * "...not allowed for xxx"
// 6 * T * "...expires in xxx"
// 7 * * T "...not allowed"
DWORD dwType; // This can be an array index into prids->ridTimes[]
FILETIME ftLicense;
FILETIME ftCurrent;
GetSystemTimeAsFileTime(&ftCurrent); // UTC time
if (!IsNullTime(pftStarts))
{
// We have a start time.
if (CompareFileTime(&ftCurrent, pftStarts) == -1)
{
// CASE 1,5. We're before the start time.
dwType = (*pdwCount == -1) ? DRMRIDS_TYPE_NOTUNTIL : DRMRIDS_TYPE_COUNTNOTUNTIL;
ftLicense = *pftStarts;
}
else
{
// We're after the start time
if (!IsNullTime(pftExpires))
{
// We have an expire time, and we're after the start time.
if (CompareFileTime(&ftCurrent, pftExpires) == -1)
{
// CASE 6. We're before the expire time. Use "expires in" strings.
dwType = (*pdwCount == -1) ? DRMRIDS_TYPE_BEFORE : DRMRIDS_TYPE_COUNTBEFORE;
ftLicense = *pftExpires;
}
else
{
// CASE 7. After the the expire time. Action is not allowed.
dwType = DRMRIDS_TYPE_NORIGHT;
}
}
else
{
// CASE 2. Nothing to show. Action is allowed, since we're after the start date, with no expiry.
dwType = DRMRIDS_TYPE_NONE;
}
}
}
else
{
// No start time.
ASSERT(!IsNullTime(pftExpires));
// We have an expire time
if (CompareFileTime(&ftCurrent, pftExpires) == -1)
{
// CASE 3. We're before the expire time. Use "expires in" strings.
dwType = (*pdwCount == -1) ? DRMRIDS_TYPE_BEFORE : DRMRIDS_TYPE_COUNTBEFORE;
ftLicense = *pftExpires;
}
else
{
// CASE 4. After the the expire time. Action is not allowed.
dwType = DRMRIDS_TYPE_NORIGHT;
}
}
if (dwType == DRMRIDS_TYPE_NORIGHT)
{
// Current time is >= 'ftLicense'. Just return the "no rights" string.
LoadString(m_hInst, prids->ridNoRights, wszOutValue, cchOutValue );
}
else if (dwType != DRMRIDS_TYPE_NONE)
{
hr = ChooseAndPopulateDateCountString(
ftCurrent,
ftLicense,
(*pdwCount != -1) ? wszCount : NULL,
&prids->ridTimes[dwType],
wszOutValue,
cchOutValue);
}
else
{
// Nothing to display. Action is allowed.
ASSERT(dwType == DRMRIDS_TYPE_NONE);
}
}
}
return hr;
}
void AppendLicenseInfo(SHMEDIA_AUDIOVIDEOPROPS *pAVProps, WCHAR *pszLicenseInfo)
{
WCHAR *pszLI = pAVProps->pszLicenseInformation;
BOOL fFirstOne = (pszLI == NULL);
// (fFirstOne ? 1 : 3) --> 2 extra char for '\r\n' (except first time), 1 extra char for terminating NULL
pszLI = (WCHAR*)CoTaskMemRealloc(pszLI, (lstrlen(pszLicenseInfo) + lstrlen(pszLI) + (fFirstOne ? 1 : 3)) * sizeof(WCHAR));
if (pszLI)
{
if (fFirstOne)
{
// Make sure we have something to StrCat to.
pszLI[0] = 0;
}
else
{
StrCat(pszLI, L"\r\n");
}
StrCat(pszLI, pszLicenseInfo);
pAVProps->pszLicenseInformation = pszLI; // in case it moved.
}
}
const DRMRIDS g_drmridsPlay =
{
IDS_DRM_PLAYNORIGHTS,
{
{IDS_DRM_PLAYBEFOREDAYS, IDS_DRM_PLAYBEFOREWEEKS, IDS_DRM_PLAYBEFOREMONTHS},
{IDS_DRM_PLAYNOTUNTILDAYS, IDS_DRM_PLAYNOTUNTILWEEKS, IDS_DRM_PLAYNOTUNTILMONTHS},
{IDS_DRM_PLAYCOUNTBEFOREDAYS, IDS_DRM_PLAYCOUNTBEFOREWEEKS, IDS_DRM_PLAYCOUNTBEFOREMONTHS},
{IDS_DRM_PLAYCOUNTNOTUNTILDAYS, IDS_DRM_PLAYCOUNTNOTUNTILWEEKS, IDS_DRM_PLAYCOUNTNOTUNTILMONTHS}
},
IDS_DRM_PLAYCOUNTREMAINING,
};
const DRMRIDS g_drmridsCopyToCD =
{
IDS_DRM_COPYCDNORIGHTS,
{
{IDS_DRM_COPYCDBEFOREDAYS, IDS_DRM_COPYCDBEFOREWEEKS, IDS_DRM_COPYCDBEFOREMONTHS},
{IDS_DRM_COPYCDNOTUNTILDAYS, IDS_DRM_COPYCDNOTUNTILWEEKS, IDS_DRM_COPYCDNOTUNTILMONTHS},
{IDS_DRM_COPYCDCOUNTBEFOREDAYS, IDS_DRM_COPYCDCOUNTBEFOREWEEKS, IDS_DRM_COPYCDCOUNTBEFOREMONTHS},
{IDS_DRM_COPYCDCOUNTNOTUNTILDAYS, IDS_DRM_COPYCDCOUNTNOTUNTILWEEKS, IDS_DRM_COPYCDCOUNTNOTUNTILMONTHS}
},
IDS_DRM_COPYCDCOUNTREMAINING,
};
const DRMRIDS g_drmridsCopyToNonSDMIDevice =
{
IDS_DRM_COPYNONSDMINORIGHTS,
{
{IDS_DRM_COPYNONSDMIBEFOREDAYS, IDS_DRM_COPYNONSDMIBEFOREWEEKS, IDS_DRM_COPYNONSDMIBEFOREMONTHS},
{IDS_DRM_COPYNONSDMINOTUNTILDAYS, IDS_DRM_COPYNONSDMINOTUNTILWEEKS, IDS_DRM_COPYNONSDMINOTUNTILMONTHS},
{IDS_DRM_COPYNONSDMICOUNTBEFOREDAYS, IDS_DRM_COPYNONSDMICOUNTBEFOREWEEKS, IDS_DRM_COPYNONSDMICOUNTBEFOREMONTHS},
{IDS_DRM_COPYNONSDMICOUNTNOTUNTILDAYS, IDS_DRM_COPYNONSDMICOUNTNOTUNTILWEEKS, IDS_DRM_COPYNONSDMICOUNTNOTUNTILMONTHS}
},
IDS_DRM_COPYNONSDMICOUNTREMAINING,
};
const DRMRIDS g_drmridsCopyToSDMIDevice =
{
IDS_DRM_COPYSDMINORIGHTS,
{
{IDS_DRM_COPYSDMIBEFOREDAYS, IDS_DRM_COPYSDMIBEFOREWEEKS, IDS_DRM_COPYSDMIBEFOREMONTHS},
{IDS_DRM_COPYSDMINOTUNTILDAYS, IDS_DRM_COPYSDMINOTUNTILWEEKS, IDS_DRM_COPYSDMINOTUNTILMONTHS},
{IDS_DRM_COPYSDMICOUNTBEFOREDAYS, IDS_DRM_COPYSDMICOUNTBEFOREWEEKS, IDS_DRM_COPYSDMICOUNTBEFOREMONTHS},
{IDS_DRM_COPYSDMICOUNTNOTUNTILDAYS, IDS_DRM_COPYSDMICOUNTNOTUNTILWEEKS, IDS_DRM_COPYSDMICOUNTNOTUNTILMONTHS}
},
IDS_DRM_COPYSDMICOUNTREMAINING,
};
#define ACTIONALLOWED_PLAY L"ActionAllowed.Play"
#define ACTIONALLOWED_COPYTOCD L"ActionAllowed.Print.redbook"
#define ACTIONALLOWED_COPYTONONSMDI L"ActionAllowed.Transfer.NONSDMI"
#define ACTIONALLOWED_COPYTOSMDI L"ActionAllowed.Transfer.SDMI"
#define LICENSESTATE_PLAY L"LicenseStateData.Play"
#define LICENSESTATE_COPYTOCD L"LicenseStateData.Print.redbook"
#define LICENSESTATE_COPYTONONSMDI L"LicenseStateData.Transfer.NONSDMI"
#define LICENSESTATE_COPYTOSMDI L"LicenseStateData.Transfer.SDMI"
typedef struct
{
LPCWSTR pszAction;
LPCWSTR pszLicenseState;
const DRMRIDS *pdrmrids; // Resource ID's
} LICENSE_INFO;
const LICENSE_INFO g_rgLicenseInfo[] =
{
{ ACTIONALLOWED_PLAY, LICENSESTATE_PLAY, &g_drmridsPlay },
{ ACTIONALLOWED_COPYTOCD, LICENSESTATE_COPYTOCD, &g_drmridsCopyToCD },
{ ACTIONALLOWED_COPYTONONSMDI, LICENSESTATE_COPYTONONSMDI, &g_drmridsCopyToNonSDMIDevice },
{ ACTIONALLOWED_COPYTOSMDI, LICENSESTATE_COPYTOSMDI, &g_drmridsCopyToSDMIDevice },
};
// We can't use the drm string constants in our const array above (they aren't initialized until after
// our struct is initialized, so they're null), so we redefined the strings
// as #define's ourselves. This function asserts that none of the strings have changed.
void _AssertValidDRMStrings()
{
ASSERT(StrCmp(ACTIONALLOWED_PLAY, g_wszWMDRM_ActionAllowed_Playback) == 0);
ASSERT(StrCmp(ACTIONALLOWED_COPYTOCD, g_wszWMDRM_ActionAllowed_CopyToCD) == 0);
ASSERT(StrCmp(ACTIONALLOWED_COPYTONONSMDI, g_wszWMDRM_ActionAllowed_CopyToNonSDMIDevice) == 0);
ASSERT(StrCmp(ACTIONALLOWED_COPYTOSMDI, g_wszWMDRM_ActionAllowed_CopyToSDMIDevice) == 0);
ASSERT(StrCmp(LICENSESTATE_PLAY, g_wszWMDRM_LicenseState_Playback) == 0);
ASSERT(StrCmp(LICENSESTATE_COPYTOCD, g_wszWMDRM_LicenseState_CopyToCD) == 0);
ASSERT(StrCmp(LICENSESTATE_COPYTONONSMDI, g_wszWMDRM_LicenseState_CopyToNonSDMIDevice) == 0);
ASSERT(StrCmp(LICENSESTATE_COPYTOSMDI, g_wszWMDRM_LicenseState_CopyToSDMIDevice) == 0);
}
BOOL _IsActionPlayback(LPCWSTR pszAction)
{
return (StrCmp(pszAction, ACTIONALLOWED_PLAY) == 0);
}
void AcquireLicenseInformation(IWMDRMReader *pReader, SHMEDIA_AUDIOVIDEOPROPS *pAVProps)
{
WMT_ATTR_DATATYPE dwType;
DWORD dwValue = 0;
WORD cbLength;
WCHAR szValue[MAX_PATH];
_AssertValidDRMStrings();
// For each of the "actions":
for (int i = 0; i < ARRAYSIZE(g_rgLicenseInfo); i++)
{
cbLength = sizeof(dwValue);
// Request the license info.
WM_LICENSE_STATE_DATA licenseState;
cbLength = sizeof(licenseState);
if (SUCCEEDED(pReader->GetDRMProperty(g_rgLicenseInfo[i].pszLicenseState, &dwType, (BYTE*)&licenseState, &cbLength)))
{
DWORD dwCount;
FILETIME ftExpires, ftStarts;
// We should always get at least one DRM_LICENSE_STATE_DATA. This is what ParseDRMStateData assumes.
ASSERTMSG(licenseState.dwNumStates >= 1, "Received WM_LICENSE_STATE_DATA with no states");
// Parse easy special cases first.
if (licenseState.stateData[0].dwCategory == WM_DRM_LICENSE_STATE_NORIGHT)
{
// Not allowed ever. Indicate this.
// Special case for playback action:
if (_IsActionPlayback(g_rgLicenseInfo[i].pszAction))
{
// Not allowed playback. Determine why. Is it because we can never play it, or can we
// just not play it on this computer?
cbLength = sizeof(dwValue);
if (SUCCEEDED(pReader->GetDRMProperty(g_wszWMDRM_IsDRMCached, &dwType, (BYTE*)&dwValue, &cbLength)))
{
UINT uID = (dwValue == 0) ? IDS_DRM_PLAYNORIGHTS : IDS_DRM_PLAYNOPLAYHERE;
LoadString(m_hInst, IDS_DRM_PLAYNOPLAYHERE, szValue, ARRAYSIZE(szValue));
AppendLicenseInfo(pAVProps, szValue);
}
}
else
{
// Regular case:
LoadString(m_hInst, g_rgLicenseInfo[i].pdrmrids->ridNoRights, szValue, ARRAYSIZE(szValue));
AppendLicenseInfo(pAVProps, szValue);
}
}
// Now parse the more complex stuff.
else if (ParseDRMStateData(&licenseState, g_rgLicenseInfo[i].pdrmrids, szValue, ARRAYSIZE(szValue), &dwCount, &ftStarts, &ftExpires) == S_OK)
{
AppendLicenseInfo(pAVProps, szValue);
// Special case for playback action - assign these values:
if (_IsActionPlayback(g_rgLicenseInfo[i].pszAction))
{
pAVProps->ftPlayExpires = ftExpires;
pAVProps->ftPlayStarts = ftStarts;
pAVProps->dwPlayCount = dwCount;
}
}
}
}
}
/**
* Extracts all the "slow" information at once from the file, and places it in the
* SHMEDIA_AUDIOVIDEOPROPS struct.
*/
HRESULT CWMPropSetStg::_GetSlowPropertyInfo(SHMEDIA_AUDIOVIDEOPROPS *pAVProps)
{
IWMReader *pReader;
HRESULT hr = CreateReader(IID_PPV_ARG(IWMReader, &pReader));
if (SUCCEEDED(hr))
{
ResetEvent(_hFileOpenEvent);
IWMReaderCallback *pReaderCB;
hr = QueryInterface(IID_PPV_ARG(IWMReaderCallback, &pReaderCB));
if (SUCCEEDED(hr))
{
hr = pReader->Open(_wszFile, pReaderCB, NULL);
pReaderCB->Release();
if (SUCCEEDED(hr))
{
// Wait until file is ready.
WaitForSingleObject(_hFileOpenEvent, INFINITE);
// Indicate whether the content is protected under DRM or not.
WCHAR szValue[128];
LoadString(m_hInst, (_fProtectedContent ? IDS_DRM_ISPROTECTED : IDS_DRM_UNPROTECTED), szValue, ARRAYSIZE(szValue));
AppendLicenseInfo(pAVProps, szValue);
// Try to get license information, if this is protected content
if (_fProtectedContent)
{
IWMDRMReader *pDRMReader;
if (SUCCEEDED(pReader->QueryInterface(IID_PPV_ARG(IWMDRMReader, &pDRMReader))))
{
AcquireLicenseInformation(pDRMReader, pAVProps);
pDRMReader->Release();
}
}
// Let's interate through the streams,
IWMProfile *pProfile;
hr = pReader->QueryInterface(IID_PPV_ARG(IWMProfile, &pProfile));
if (SUCCEEDED(hr))
{
DWORD cStreams;
hr = pProfile->GetStreamCount(&cStreams);
if (SUCCEEDED(hr))
{
BOOL bFoundVideo = FALSE;
BOOL bFoundAudio = FALSE;
for (DWORD dw = 0; dw < cStreams; dw++)
{
IWMStreamConfig *pConfig;
hr = pProfile->GetStream(dw, &pConfig);
if (FAILED(hr))
break;
GUID guidStreamType;
if (SUCCEEDED(pConfig->GetStreamType(&guidStreamType)))
{
if (guidStreamType == MEDIATYPE_Audio)
{
GetAudioProperties(pConfig, pAVProps);
bFoundAudio = TRUE;
}
else if (guidStreamType == MEDIATYPE_Video)
{
GetVideoProperties(pConfig, pAVProps);
bFoundVideo = TRUE;
}
}
pConfig->Release();
if (bFoundVideo && bFoundAudio)
break;
}
}
pProfile->Release();
}
pReader->Close();
}
}
pReader->Release();
}
return hr;
}
void GetVideoPropertiesFromBitmapHeader(BITMAPINFOHEADER *bmi, SHMEDIA_AUDIOVIDEOPROPS *pVideoProps)
{
// bit depth
pVideoProps->wBitDepth = bmi->biBitCount;
// compression.
// Is there an easy way to get this?
// Maybe something with the codec info?
// pVideoProps->pszCompression = new WCHAR[cch];
}
void GetVideoPropertiesFromHeader(VIDEOINFOHEADER *pvih, SHMEDIA_AUDIOVIDEOPROPS *pVideoProps)
{
pVideoProps->cx = pvih->rcSource.right - pvih->rcSource.left;
pVideoProps->cy = pvih->rcSource.bottom - pvih->rcSource.top;
// Obtain frame rate
// AvgTimePerFrame is in 100ns units.
// ISSUE: This value is always zero.
GetVideoPropertiesFromBitmapHeader(&pvih->bmiHeader, pVideoProps);
}
// Can't find def'n for VIDEOINFOHEADER2
/*
void GetVideoPropertiesFromHeader2(VIDEOINFOHEADER2 *pvih, SHMEDIA_AUDIOVIDEOPROPS *pVideoProps)
{
pVideoProps->cx = pvih->rcSource.right - pvih->rcSource.left;
pVideoProps->cy = pvih->rcSource.bottom - pvih->rcSource.top;
GetVideoPropertiesFromBitmapHeader(&pvih->bmiHeader, pVideoProps);
}
*/
/**
* assumes pConfig is a video stream. assumes pVideoProps is zero-inited.
*/
void GetVideoProperties(IWMStreamConfig *pConfig, SHMEDIA_AUDIOVIDEOPROPS *pVideoProps)
{
// bitrate
pConfig->GetBitrate(&pVideoProps->dwBitrateVideo); // ignore result
// stream name
WORD cchStreamName;
if (SUCCEEDED(pConfig->GetStreamName(NULL, &cchStreamName)))
{
pVideoProps->pszStreamNameVideo = new WCHAR[cchStreamName];
if (pVideoProps->pszStreamNameVideo)
{
pConfig->GetStreamName(pVideoProps->pszStreamNameVideo, &cchStreamName); // ignore result
}
}
// stream number
pConfig->GetStreamNumber(&pVideoProps->wStreamNumberVideo); // ignore result
// Try to get an IWMMediaProps interface.
IWMMediaProps *pMediaProps;
if (SUCCEEDED(pConfig->QueryInterface(IID_PPV_ARG(IWMMediaProps, &pMediaProps))))
{
DWORD cbType;
// Make the first call to establish the size of buffer needed.
if (SUCCEEDED(pMediaProps->GetMediaType(NULL, &cbType)))
{
// Now create a buffer of the appropriate size
BYTE *pBuf = new BYTE[cbType];
if (pBuf)
{
// Create an appropriate structure pointer to the buffer.
WM_MEDIA_TYPE *pType = (WM_MEDIA_TYPE*) pBuf;
// Call the method again to extract the information.
if (SUCCEEDED(pMediaProps->GetMediaType(pType, &cbType)))
{
// Pick up other more obscure information.
if (IsEqualGUID(pType->formattype, FORMAT_MPEGVideo))
{
GetVideoPropertiesFromHeader((VIDEOINFOHEADER*)&((MPEG1VIDEOINFO*)pType->pbFormat)->hdr, pVideoProps);
}
else if (IsEqualGUID(pType->formattype, FORMAT_VideoInfo))
{
GetVideoPropertiesFromHeader((VIDEOINFOHEADER*)pType->pbFormat, pVideoProps);
}
// No def'n available for VIDEOINFOHEADER2
// else if (IsEqualGUID(pType->formattype, Format_MPEG2Video))
// {
// GetVideoPropertiesFromHeader2((VIDEOINFOHEADER2*)&((MPEG1VIDEOINFO2*)&pType->pbFormat)->hdr);
// }
// else if (IsEqualGUID(pType->formattype, Format_VideoInfo2))
// {
// GetVideoPropertiesFromHeader2((VIDEOINFOHEADER2*)&pType->pbFormat);
// }
}
delete[] pBuf;
}
}
pMediaProps->Release();
}
}
void InitializeAudioVideoProperties(SHMEDIA_AUDIOVIDEOPROPS *pAVProps)
{
pAVProps->dwPlayCount = -1; // Indicating no playcount.
ASSERT(pAVProps->pszLicenseInformation == NULL);
ASSERT(IsNullTime(&pAVProps->ftPlayStarts));
ASSERT(IsNullTime(&pAVProps->ftPlayExpires));
// Audio properties
ASSERT(pAVProps->pszStreamNameAudio == NULL);
ASSERT(pAVProps->wStreamNumberAudio == 0);
ASSERT(pAVProps->nChannels == 0);
ASSERT(pAVProps->dwBitrateAudio == 0);
ASSERT(pAVProps->pszCompressionAudio == NULL);
ASSERT(pAVProps->dwSampleRate == 0);
ASSERT(pAVProps->lSampleSizeAudio == 0);
// Video properties
ASSERT(pAVProps->pszStreamNameVideo == NULL);
ASSERT(pAVProps->wStreamNumberVideo == 0);
ASSERT(pAVProps->wBitDepth == 0);
ASSERT(pAVProps->dwBitrateVideo == 0);
ASSERT(pAVProps->cx == 0);
ASSERT(pAVProps->cy == 0);
ASSERT(pAVProps->pszCompressionVideo == NULL);
ASSERT(pAVProps->dwFrames == 0);
ASSERT(pAVProps->dwFrameRate == 0);
}
void FreeAudioVideoProperties(SHMEDIA_AUDIOVIDEOPROPS *pAVProps)
{
if (pAVProps->pszStreamNameVideo)
{
delete[] pAVProps->pszStreamNameVideo;
}
if (pAVProps->pszCompressionVideo)
{
delete[] pAVProps->pszCompressionVideo;
}
if (pAVProps->pszStreamNameAudio)
{
delete[] pAVProps->pszStreamNameAudio;
}
if (pAVProps->pszCompressionAudio)
{
delete[] pAVProps->pszCompressionAudio;
}
if (pAVProps->pszLicenseInformation)
{
CoTaskMemFree(pAVProps->pszLicenseInformation);
}
}
/**
* assumes pConfig is an audio stream. assumes pAudioProps is zero-inited.
*/
void GetAudioProperties(IWMStreamConfig *pConfig, SHMEDIA_AUDIOVIDEOPROPS *pAudioProps)
{
// bitrate
pConfig->GetBitrate(&pAudioProps->dwBitrateAudio); // ignore result
// stream name
WORD cchStreamName;
if (SUCCEEDED(pConfig->GetStreamName(NULL, &cchStreamName)))
{
pAudioProps->pszStreamNameAudio = new WCHAR[cchStreamName];
if (pAudioProps->pszStreamNameAudio)
{
pConfig->GetStreamName(pAudioProps->pszStreamNameAudio, &cchStreamName); // ignore result
}
}
// stream number
pConfig->GetStreamNumber(&pAudioProps->wStreamNumberAudio); // ignore result
// Try to get an IWMMediaProps interface.
IWMMediaProps *pMediaProps;
if (SUCCEEDED(pConfig->QueryInterface(IID_PPV_ARG(IWMMediaProps, &pMediaProps))))
{
DWORD cbType;
// Make the first call to establish the size of buffer needed.
if (SUCCEEDED(pMediaProps->GetMediaType(NULL, &cbType)))
{
// Now create a buffer of the appropriate size
BYTE *pBuf = new BYTE[cbType];
if (pBuf)
{
// Create an appropriate structure pointer to the buffer.
WM_MEDIA_TYPE *pType = (WM_MEDIA_TYPE*)pBuf;
// Call the method again to extract the information.
if (SUCCEEDED(pMediaProps->GetMediaType(pType, &cbType)))
{
if (pType->bFixedSizeSamples) // Assuming lSampleSize only valid if fixed sample sizes
{
pAudioProps->lSampleSizeAudio = pType->lSampleSize;
}
// Pick up other more obscure information.
if (IsEqualGUID(pType->formattype, FORMAT_WaveFormatEx))
{
WAVEFORMATEX *pWaveFmt = (WAVEFORMATEX*)pType->pbFormat;
pAudioProps->nChannels = pWaveFmt->nChannels;
pAudioProps->dwSampleRate = pWaveFmt->nSamplesPerSec;
// Setting this again if we got in here.
// For mp3s and wmas at least, this number is accurate, while pType->lSampleSize is bogus.
pAudioProps->lSampleSizeAudio = pWaveFmt->wBitsPerSample;
}
// How do we get compression?
}
delete[] pBuf;
}
}
pMediaProps->Release();
}
}
// Returns a *pHeaderInfo and success if it opened an editor and obtained a IWMHeaderInfo.
HRESULT CWMPropSetStg::_OpenHeaderInfo(IWMHeaderInfo **ppHeaderInfo, BOOL fReadingOnly)
{
IWMMetadataEditor *pEditor;
*ppHeaderInfo = NULL;
// use the "editor" object as it is much MUCH faster than the reader
HRESULT hr = WMCreateEditor(&pEditor);
if (SUCCEEDED(hr))
{
IWMMetadataEditor2 *pmde2;
if (fReadingOnly && SUCCEEDED(pEditor->QueryInterface(IID_PPV_ARG(IWMMetadataEditor2, &pmde2))))
{
hr = pmde2->OpenEx(_wszFile, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_DELETE);
pmde2->Release();
}
else
{
hr = pEditor->Open(_wszFile);
}
if (SUCCEEDED(hr))
{
hr = pEditor->QueryInterface(IID_PPV_ARG(IWMHeaderInfo, ppHeaderInfo));
if (FAILED(hr))
{
pEditor->Close();
}
}
// Always release this particular ref to the editor. Don't need it anymore.
pEditor->Release();
// If we got here with SUCCEESS, it means we have an open editor, and a ref to the metadata editor.
ASSERT((FAILED(hr) && (*ppHeaderInfo == NULL)) || (SUCCEEDED(hr) && (*ppHeaderInfo != NULL)));
}
return hr;
}
// Cleans up after _OpenHeaderInfo (closes the editor, etc...)
// fFlush Flush the header?
HRESULT _CloseHeaderInfo(IWMHeaderInfo *pHeaderInfo, BOOL fFlush)
{
HRESULT hr = S_OK;
if (pHeaderInfo)
{
// Close the editor.
IWMMetadataEditor *pEditor;
hr = pHeaderInfo->QueryInterface(IID_PPV_ARG(IWMMetadataEditor, &pEditor));
ASSERT(SUCCEEDED(hr)); // QI is symmetric, so this always succeeds.
if (SUCCEEDED(hr))
{
if (fFlush)
{
hr = pEditor->Flush();
}
pEditor->Close();
pEditor->Release();
}
pHeaderInfo->Release();
}
return hr;
}
HRESULT CWMPropSetStg::FlushChanges(REFFMTID fmtid, LONG cNumProps, const COLMAP **pcmapInfo, PROPVARIANT *pVarProps, BOOL *pbDirtyFlags)
{
if (!_bIsWritable)
return STG_E_ACCESSDENIED;
IWMHeaderInfo *phi;
HRESULT hr = _OpenHeaderInfo(&phi, HI_READWRITE);
if (SUCCEEDED(hr))
{
BOOL bFlush = FALSE;
for (LONG i = 0; i < cNumProps; i++)
{
if (pbDirtyFlags[i])
{
HRESULT hrFlush = E_FAIL;
if ((pcmapInfo[i]->vt == pVarProps[i].vt) || (VT_EMPTY == pVarProps[i].vt) || (VT_NULL == pVarProps[i].vt)) // VT_EMPTY/VT_NULL means remove property
{
hrFlush = _FlushProperty(phi, pcmapInfo[i], &pVarProps[i]);
}
else
{
PROPVARIANT var;
// Don't need to call PropVariantInit
hrFlush = PropVariantCopy(&var, &pVarProps[i]);
if (SUCCEEDED(hrFlush))
{
hrFlush = CoerceProperty(&var, pcmapInfo[i]->vt);
if (SUCCEEDED(hrFlush))
{
hrFlush = _FlushProperty(phi, pcmapInfo[i], &var);
}
PropVariantClear(&var);
}
}
if (FAILED(hrFlush))
{
// Take note of any failure case, so we have something to return.
hr = hrFlush;
}
}
}
// Specify the flush bit if we succeeded in writing all the properties.
HRESULT hrClose = _CloseHeaderInfo(phi, SUCCEEDED(hr));
// If for some reason the Flush failed (in _CloseHeaderInfo), we'll fail.
if (FAILED(hrClose))
{
hr = hrClose;
}
}
return hr;
}
#define MAX_PROP_LENGTH 4096 // big enough for large props like lyrics.
HRESULT CWMPropSetStg::_FlushProperty(IWMHeaderInfo *phi, const COLMAP *pPInfo, PROPVARIANT *pvar)
{
WMT_ATTR_DATATYPE datatype;
BYTE buffer[MAX_PROP_LENGTH];
WORD cbLen = ARRAYSIZE(buffer);
HRESULT hr = E_FAIL;
// Handle special properties first:
// The Track property can exist as both the newer 1-based WM/TrackNumber, or the old
// 0-based WM/Track
if (IsEqualSCID(SCID_MUSIC_Track, *pPInfo->pscid))
{
if ((pvar->vt != VT_EMPTY) && (pvar->vt != VT_NULL))
{
ASSERT(pvar->vt = VT_UI4);
if (pvar->ulVal > 0) // Track number must be greater than zero - don't want to overflow 0-based buffer
{
// Decrement the track number for writing to the old zero-based property
pvar->ulVal--;
HRESULT hr1 = WMTFromPropVariant(buffer, &cbLen, &datatype, pvar);
if (SUCCEEDED(hr1))
{
hr1 = phi->SetAttribute(0, TRACK_ZERO_BASED, datatype, buffer, cbLen);
}
pvar->ulVal++; // back to 1-based
HRESULT hr2 = WMTFromPropVariant(buffer, &cbLen, &datatype, pvar);
if (SUCCEEDED(hr2))
{
hr2 = phi->SetAttribute(0, TRACK_ONE_BASED, datatype, buffer, cbLen);
}
// Return success if one of them worked.
hr = (SUCCEEDED(hr1) || SUCCEEDED(hr2)) ? S_OK : hr1;
}
}
else
{
hr = S_OK; // Someone tried to remove the track property, but we'll just fail silently, since we can't return a good error.
}
}
else if (IsEqualSCID(SCID_DRM_Protected, *pPInfo->pscid))
{
// We should never get here. Protected is read only.
hr = E_INVALIDARG;
}
else
{
// Regular properties.
if ((pvar->vt == VT_EMPTY) || (pvar->vt == VT_NULL))
{
// Try to remove this property.
// Note: Doesn't matter what we pass in for datatype, since we're providing NULL as the value.
hr = phi->SetAttribute(0, _GetSDKName(pPInfo), WMT_TYPE_STRING, NULL, 0);
// This is weak.
// The WMSDK has a bug where if you try to remove a property that has already been removed, it will return
// an error (ASF_E_NOTFOUND for wma files, E_FAIL for mp3s). So for any errors, we'll return success.
if (FAILED(hr))
{
hr = S_OK;
}
}
else
{
hr = WMTFromPropVariant(buffer, &cbLen, &datatype, pvar);
if (SUCCEEDED(hr))
{
hr = phi->SetAttribute(0, _GetSDKName(pPInfo), datatype, buffer, cbLen);
}
}
}
return hr;
}
// We need to check for protected content ahead of time.
HRESULT CWMPropSetStg::_PreCheck()
{
HRESULT hr = _PopulatePropertySet();
if (SUCCEEDED(hr))
{
if (_fProtectedContent && _bIsWritable)
{
_bIsWritable = FALSE;
hr = STG_E_STATUS_COPY_PROTECTION_FAILURE;
}
}
return hr;
}
HRESULT CWMPropSetStg::_PopulatePropertySet()
{
HRESULT hr = E_FAIL;
if (_wszFile[0] == 0)
{
hr = STG_E_INVALIDNAME;
}
else if (!_bHasBeenPopulated)
{
IWMHeaderInfo *phi;
hr = _OpenHeaderInfo(&phi, HI_READONLY);
if (SUCCEEDED(hr))
{
CEnumAllProps enumAllProps(_pPropStgInfo, _cPropertyStorages);
const COLMAP *pPInfo = enumAllProps.Next();
while (pPInfo)
{
LPCWSTR pszPropName = _GetSDKName(pPInfo);
// Skip it if this is not one of the properties available quickly through
// IWMHeaderInfo
if (_IsHeaderProperty(pPInfo))
{
// Get length of buffer needed for property value
WMT_ATTR_DATATYPE proptype;
UCHAR buf[MAX_PROP_LENGTH];
WORD cbData = sizeof(buf);
WORD wStreamNum = 0;
if (_PopulateSpecialProperty(phi, pPInfo) == S_FALSE)
{
// Not a special property
ASSERT(_GetSDKName(pPInfo)); // If not def'd as a special prop, must have an SDK name
// Note: this call will fail if the buffer is not big enough. This means that
// we will not get values for potentially really large properties like lyrics.
hr = phi->GetAttributeByName(&wStreamNum, pszPropName, &proptype, buf, &cbData);
if (SUCCEEDED(hr))
{
hr = _SetPropertyFromWMT(pPInfo, proptype, cbData ? buf : NULL, cbData);
}
else
{
// Is it supposed to be a string property? If so, provide a NULL string.
// ISSUE: we may want to revisit this policy, because of changes in docprop
if ((pPInfo->vt == VT_LPSTR) || (pPInfo->vt == VT_LPWSTR))
{
hr = _SetPropertyFromWMT(pPInfo, WMT_TYPE_STRING, NULL, 0);
}
}
}
}
pPInfo = enumAllProps.Next();
}
_PostProcess();
_CloseHeaderInfo(phi, FALSE);
}
_bHasBeenPopulated = TRUE;
// even if we couldn't create the metadata editor, we might be able to open a reader (which we'll do later)
// So we can return S_OK here. However, it would be nice to know ahead of time if opening a Reader
// will work. Oh well.
_hrPopulated = S_OK;
}
return _hrPopulated;
}
/**
* Takes a quick peek at what the current value of this property is (does not
* force a slow property to be populated), returning a reference to the actual
* value (so no PropVariantClear is necessary)
*/
HRESULT CWMPropSetStg::_QuickLookup(const COLMAP *pPInfo, PROPVARIANT **ppvar)
{
CMediaPropStorage *pps;
HRESULT hr = _ResolveFMTID(pPInfo->pscid->fmtid, &pps);
if (SUCCEEDED(hr))
{
PROPSPEC spec;
spec.ulKind = PRSPEC_PROPID;
spec.propid = pPInfo->pscid->pid;
hr = pps->QuickLookup(&spec, ppvar);
}
return hr;
}
/**
* Any special actions to take after initial property population.
*/
void CWMPropSetStg::_PostProcess()
{
PROPVARIANT *pvar;
// 1) If this file is protected, mark this. (we don't allow writes to protected files)
if (SUCCEEDED(_QuickLookup(&g_CM_Protected, &pvar)))
{
if (pvar->vt == VT_BOOL)
{
_fProtectedContent = pvar->boolVal;
}
}
// 2) Mark if Duration or Bitrate were retrieved. If they weren't, then we'll consider them
// "slow" properties for this file.
if (SUCCEEDED(_QuickLookup(&g_CM_Duration, &pvar)))
{
_fDurationSlow = (pvar->vt == VT_EMPTY);
}
if (SUCCEEDED(_QuickLookup(&g_CM_Bitrate, &pvar)))
{
_fBitrateSlow = (pvar->vt == VT_EMPTY);
}
}
/**
* Special properties need some additional action taken.
*
* Track: Use 1-based track # if available, 0-based otherwise.
*/
HRESULT CWMPropSetStg::_PopulateSpecialProperty(IWMHeaderInfo *phi, const COLMAP *pPInfo)
{
WMT_ATTR_DATATYPE proptype;
UCHAR buf[1024]; // big enough
WORD cbData = sizeof(buf);
WORD wStreamNum = 0;
HRESULT hr;
if (IsEqualSCID(SCID_MUSIC_Track, *pPInfo->pscid))
{
// Try to get 1-based track.
hr = phi->GetAttributeByName(&wStreamNum, TRACK_ONE_BASED, &proptype, buf, &cbData);
if (FAILED(hr))
{
// Nope, so try to get 0-based track and increment by one.
hr = phi->GetAttributeByName(&wStreamNum, TRACK_ZERO_BASED, &proptype, buf, &cbData);
if (SUCCEEDED(hr))
{
// We can't just increment the value so easily, because the value could be of type
// WMT_TYPE_STRING or WMT_TYPE_DWORD (track # is string for some mp3's)
// So we'll go through the same conversion process as happens when we call _SetPropertyFromWMT
PROPVARIANT varTemp = {0};
hr = PropVariantFromWMT(buf, cbData, proptype, &varTemp, VT_UI4);
if (SUCCEEDED(hr))
{
// Got a VT_UI4, we know how to increment that.
varTemp.ulVal++;
// Now convert back to a WMT_ATTR that we can provide to _SetPropertyFromWMT
hr = WMTFromPropVariant(buf, &cbData, &proptype, &varTemp);
PropVariantClear(&varTemp);
}
}
}
if (SUCCEEDED(hr))
{
_SetPropertyFromWMT(pPInfo, proptype, cbData ? buf : NULL, cbData);
}
}
else
{
hr = S_FALSE;
}
return hr;
}
HRESULT CWMPropSetStg::_SetPropertyFromWMT(const COLMAP *pPInfo, WMT_ATTR_DATATYPE attrDatatype, UCHAR *pData, WORD cbSize)
{
PROPSPEC spec;
spec.ulKind = PRSPEC_PROPID;
spec.propid = pPInfo->pscid->pid;
PROPVARIANT var = {0};
if (SUCCEEDED(PropVariantFromWMT(pData, cbSize, attrDatatype, &var, pPInfo->vt)))
{
_PopulateProperty(pPInfo, &var);
PropVariantClear(&var);
}
return S_OK;
}
// Retrieves the name used by the WMSDK in IWMHeaderInfo->Get/SetAttribute
LPCWSTR CWMPropSetStg::_GetSDKName(const COLMAP *pPInfo)
{
for (int i = 0; i < ARRAYSIZE(g_rgSCIDToSDKName); i++)
{
if (IsEqualSCID(*pPInfo->pscid, *g_rgSCIDToSDKName[i].pscid))
return g_rgSCIDToSDKName[i].pszSDKName;
}
return NULL;
}
// Is it one of the properties that can be accessed via IWMHeaderInfo?
BOOL CWMPropSetStg::_IsHeaderProperty(const COLMAP *pPInfo)
{
for (int i = 0; i < ARRAYSIZE(g_rgSCIDToSDKName); i++)
{
if (IsEqualSCID(*pPInfo->pscid, *g_rgSCIDToSDKName[i].pscid))
return TRUE;
}
return FALSE;
}
// Creates
// For audio files (mp3, wma, ....)
STDAPI CWMAPropSetStg_CreateInstance(IUnknown *punkOuter, IUnknown **ppunk, LPCOBJECTINFO poi)
{
HRESULT hr;
CWMAPropSetStg *pPropSetStg = new CWMAPropSetStg();
if (pPropSetStg)
{
hr = pPropSetStg->Init();
if (SUCCEEDED(hr))
{
hr = pPropSetStg->QueryInterface(IID_PPV_ARG(IUnknown, ppunk));
}
pPropSetStg->Release();
}
else
{
*ppunk = NULL;
hr = E_OUTOFMEMORY;
}
return hr;
}
// For video/audio files (wmv, wma, ... )
STDAPI CWMVPropSetStg_CreateInstance(IUnknown *punkOuter, IUnknown **ppunk, LPCOBJECTINFO poi)
{
HRESULT hr;
CWMVPropSetStg *pPropSetStg = new CWMVPropSetStg();
if (pPropSetStg)
{
hr = pPropSetStg->Init();
if (SUCCEEDED(hr))
{
hr = pPropSetStg->QueryInterface(IID_PPV_ARG(IUnknown, ppunk));
}
pPropSetStg->Release();
}
else
{
*ppunk = NULL;
hr = E_OUTOFMEMORY;
}
return hr;
}