|
|
#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; }
|