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