/* ***** BEGIN LICENSE BLOCK *****
 * Source last modified: $Id: rpfilobj.cpp,v 1.2.24.1 2004/07/09 01:52:03 hubbe Exp $
 * 
 * Portions Copyright (c) 1995-2004 RealNetworks, Inc. All Rights Reserved.
 * 
 * The contents of this file, and the files included with this file,
 * are subject to the current version of the RealNetworks Public
 * Source License (the "RPSL") available at
 * http://www.helixcommunity.org/content/rpsl unless you have licensed
 * the file under the current version of the RealNetworks Community
 * Source License (the "RCSL") available at
 * http://www.helixcommunity.org/content/rcsl, in which case the RCSL
 * will apply. You may also obtain the license terms directly from
 * RealNetworks.  You may not use this file except in compliance with
 * the RPSL or, if you have a valid RCSL with RealNetworks applicable
 * to this file, the RCSL.  Please see the applicable RPSL or RCSL for
 * the rights, obligations and limitations governing use of the
 * contents of the file.
 * 
 * Alternatively, the contents of this file may be used under the
 * terms of the GNU General Public License Version 2 or later (the
 * "GPL") in which case the provisions of the GPL are applicable
 * instead of those above. If you wish to allow use of your version of
 * this file only under the terms of the GPL, and not to allow others
 * to use your version of this file under the terms of either the RPSL
 * or RCSL, indicate your decision by deleting the provisions above
 * and replace them with the notice and other provisions required by
 * the GPL. If you do not delete the provisions above, a recipient may
 * use your version of this file under the terms of any one of the
 * RPSL, the RCSL or the GPL.
 * 
 * This file is part of the Helix DNA Technology. RealNetworks is the
 * developer of the Original Code and owns the copyrights in the
 * portions it created.
 * 
 * This file, and the files included with this file, is distributed
 * and made available on an 'AS IS' basis, WITHOUT WARRANTY OF ANY
 * KIND, EITHER EXPRESS OR IMPLIED, AND REALNETWORKS HEREBY DISCLAIMS
 * ALL SUCH WARRANTIES, INCLUDING WITHOUT LIMITATION, ANY WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, QUIET
 * ENJOYMENT OR NON-INFRINGEMENT.
 * 
 * Technology Compatibility Kit Test Suite(s) Location:
 *    http://www.helixcommunity.org/content/tck
 * 
 * Contributor(s):
 * 
 * ***** END LICENSE BLOCK ***** */

#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>

#include "hxcom.h"
#include "hxtypes.h"
#include "hxwintyp.h"
#include "hxresult.h"
#include "hxcomm.h"
#include "ihxpckts.h"
#include "hxfiles.h"
#include "hxformt.h"
#include "hxplugn.h"
#include "hxprefs.h"
#include "hxrendr.h"
#include "hxformt.h"
#include "hxpends.h"
#include "hxengin.h"
#include "hxmon.h"
#include "hxstring.h"
#include "hxvsrc.h"

// pncont
#include "hxbuffer.h"

// From coreres
#include "pixres.h"

// pnmisc
#include "baseobj.h"
#include "unkimp.h"
#include "hxparse.h"

// pxcomlib
#include "pxcolor.h"
#include "pxrect.h"
#include "pxeffect.h"
#include "rpfile.h"

#include "rpfilobj.h"

#include "hxheap.h"
#ifdef _DEBUG
#undef HX_THIS_FILE		
static char HX_THIS_FILE[] = __FILE__;
#endif

#define BASE_VERSION      HX_ENCODE_PROD_VERSION(0, 0, 0, 0)
#define U2_VERSION        HX_ENCODE_PROD_VERSION(1, 1, 0, 0)
#define REDSTONE_VERSION  HX_ENCODE_PROD_VERSION(1, 1, 0, 0) // XXXMEH - for now, we keep it the same
#define OPACITY_VERSION   HX_ENCODE_PROD_VERSION(1, 4, 0, 0)

char CIMFFileObject::m_pszIMFStartTag[]             = "<imfl>";
char CIMFFileObject::m_pszIMFEndTag[]               = "</imfl>";
char CIMFFileObject::m_pszTagStart[]                = "<";
char CIMFFileObject::m_pszTagEnd[]                  = "/>";
char CIMFFileObject::m_pszHeadTag[]                 = "head";
char CIMFFileObject::m_pszHeadTitleAttribute[]      = "title";
char CIMFFileObject::m_pszHeadAuthorAttribute[]     = "author";
char CIMFFileObject::m_pszHeadCopyrightAttribute[]  = "copyright";
char CIMFFileObject::m_pszHeadStartAttribute[]      = "start";
char CIMFFileObject::m_pszHeadDurationAttribute[]   = "duration";
char CIMFFileObject::m_pszHeadPrerollAttribute[]    = "preroll";
char CIMFFileObject::m_pszHeadBitrateAttribute[]    = "bitrate";
char CIMFFileObject::m_pszHeadWidthAttribute[]      = "width";
char CIMFFileObject::m_pszHeadHeightAttribute[]     = "height";
char CIMFFileObject::m_pszHeadAspectAttribute[]     = "aspect";
char CIMFFileObject::m_pszHeadURLAttribute[]        = "url";
char CIMFFileObject::m_pszHeadMaxFps[]              = "maxfps";
char CIMFFileObject::m_pszHeadTimeFormatAttribute[] = "timeformat";
char CIMFFileObject::m_pszHeadVersionAttribute[]    = "version";
char CIMFFileObject::m_pszHeadBackgroundColorAttr[] = "background-color";
char CIMFFileObject::m_pszHeadOpacityAttr[]         = "backgroundOpacity";
char CIMFFileObject::m_pszWhitespace[]              = " \t\n\r";
char CIMFFileObject::m_pszImageTag[]                = "image";
char CIMFFileObject::m_pszImageHandleAttribute[]    = "handle";
char CIMFFileObject::m_pszImageNameAttribute[]      = "name";
char CIMFFileObject::m_pszFillTag[]                 = "fill";
char CIMFFileObject::m_pszFadeinTag[]               = "fadein";
char CIMFFileObject::m_pszFadeoutTag[]              = "fadeout";
char CIMFFileObject::m_pszCrossfadeTag[]            = "crossfade";
char CIMFFileObject::m_pszWipeTag[]                 = "wipe";
char CIMFFileObject::m_pszViewchangeTag[]           = "viewchange";
char CIMFFileObject::m_pszExternalEffectTag[]       = "effect";
char CIMFFileObject::m_pszAnimateTag[]              = "animate";


CIMFFileObject::CIMFFileObject()
{
    m_cURL              = "";
    m_cTitle            = "";
    m_cAuthor           = "";
    m_cCopyright        = "";
    m_ulStartTime       = 0;
    m_ulDuration        = 0;
    m_ulPreroll         = 0;
    m_ulBitrate         = 0;
    m_ulDisplayWidth    = 0;
    m_ulDisplayHeight   = 0;
    m_ulTimeFormat      = kTimeFormatDHMS;
    m_ulContentVersion  = BASE_VERSION;
    m_ulBackgroundColor = 0; // default to black
    m_ulOpacity         = 255;
    m_bAspectFlag       = TRUE;
    m_ulMaxFps          = 0;
}

CIMFFileObject::~CIMFFileObject()
{
    GListIterator itr;   //CTJ  gcc hated the prior scope
    for (itr = m_cImageList.Begin(); itr != m_cImageList.End(); itr++)
    {
        CIMFImage *pImage = (CIMFImage *) *itr;
        HX_DELETE(pImage);
    }
    for (itr = m_cEffectList.Begin(); itr != m_cEffectList.End(); itr++)
    {
        CIMFEffect *pEffect = (CIMFEffect *) *itr;
        HX_DELETE(pEffect);
    }
};

int CIMFFileObject::GetImageIndexFromHandle(ULONG32 ulHandle)
{
    int idx=0;
    for (GListIterator itr = m_cImageList.Begin(); itr != m_cImageList.End(); itr++)
    {
        CIMFImage *pImage = (CIMFImage *) *itr;
        if (pImage->GetHandle() == ulHandle)
        {
            return idx;
        }
        idx++;
    }

    return 0L;
}
const char * CIMFFileObject::GetNameFromHandle(ULONG32 ulHandle)
{
    for (GListIterator itr = m_cImageList.Begin(); itr != m_cImageList.End(); itr++)
    {
        CIMFImage *pImage = (CIMFImage *) *itr;
        if (pImage->GetHandle() == ulHandle)
        {
            return pImage->GetName().c_str();
        }
    }

    return (const char *) 0;
}

CIMFImage * CIMFFileObject::GetImageFromHandle(ULONG32 ulHandle)
{
    for (GListIterator itr = m_cImageList.Begin(); itr != m_cImageList.End(); itr++)
    {
        CIMFImage *pImage = (CIMFImage *) *itr;
        if (pImage->GetHandle() == ulHandle)
        {
            return pImage;
        }
    }

    return 0L;
}

void CIMFFileObject::RenderAttribute(const char *pszAttrStr, GString &rValue, GString &rText)
{
    rText += pszAttrStr;
    rText += "=\"";
    rText += rValue;
    rText += "\" ";
}

void CIMFFileObject::RenderAttribute(const char *pszAttrStr, ULONG32 ulValue, GString &rText)
{
    char cTmp[32];

    rText += pszAttrStr;
    rText += "=";
    sprintf(cTmp, "%ld", ulValue); /* Flawfinder: ignore */
    rText += cTmp;
    rText += " ";
}

void CIMFFileObject::RenderAttribute(const char *pszAttrStr, BOOL bValue,  GString &rText)
{
    rText += pszAttrStr;
    rText += "=";
    if (bValue == TRUE)
    {
        rText += "true";
    }
    else
    {
        rText += "false";
    }
    rText += " ";
}

void CIMFFileObject::InsertImageIntoList(CIMFImage *pImage)
{
    // If null pointer, don't do anything
    if (!pImage)
    {
        return;
    }

    // Insert in order of ascending start time
    GListIterator itr;
    for (itr = m_cImageList.Begin(); itr != m_cImageList.End(); itr++)
    {
        CIMFImage *pCurImage = (CIMFImage *) *itr;
        if (pCurImage->GetHandle() > pImage->GetHandle())
        {
            break;
        }
    }

    m_cImageList.Insert(itr, (void *) pImage);
}

void CIMFFileObject::InsertEffectIntoList(CIMFEffect *pEffect)
{
    // If null pointer, don't do anything
    if (!pEffect)
    {
        return;
    }

    GListIterator itr;  //CTJ  gcc hated the prior scope
    // Insert in order of ascending start time
    for (itr = m_cEffectList.Begin(); itr != m_cEffectList.End(); itr++)
    {
        CIMFEffect *pCurEffect = (CIMFEffect *) *itr;
        if (pCurEffect->GetStart() > pEffect->GetStart())
        {
            break;
        }
    }

    m_cEffectList.Insert(itr, (void *) pEffect);
}


void CIMFFileObject::RenderText(GString &rText)
{
    // Output <imfl>
    rText  = m_pszIMFStartTag;
    rText += "\r\n  ";

    // Output <head> tag and attributes
    rText += m_pszTagStart;
    rText += m_pszHeadTag;
    rText += " ";

    if (m_cTitle.length() > 0)
    {
        RenderAttribute(m_pszHeadTitleAttribute, m_cTitle, rText);
        rText += "\r\n        ";
    }
    if (m_cAuthor.length() > 0)
    {
        RenderAttribute(m_pszHeadAuthorAttribute, m_cAuthor, rText);
        rText += "\r\n        ";
    }
    if (m_cCopyright.length() > 0)
    {
        RenderAttribute(m_pszHeadCopyrightAttribute, m_cCopyright, rText);
        rText += "\r\n        ";
    }
    if (m_ulStartTime > 0)
    {
        RenderAttribute(m_pszHeadStartAttribute, m_ulStartTime, rText);
        rText += "\r\n        ";
    }
    RenderAttribute(m_pszHeadDurationAttribute, m_ulDuration, rText);
    rText += "\r\n        ";
    if (m_ulPreroll > 0)
    {
        RenderAttribute(m_pszHeadPrerollAttribute, m_ulPreroll, rText);
        rText += "\r\n        ";
    }
    if (m_ulMaxFps > 0)
    {
        RenderAttribute(m_pszHeadMaxFps, m_ulMaxFps, rText);
        rText += "\r\n        ";
    }

    RenderAttribute(m_pszHeadBitrateAttribute, m_ulBitrate,       rText);
    rText += "\r\n        ";
    RenderAttribute(m_pszHeadWidthAttribute,   m_ulDisplayWidth,  rText);
    rText += "\r\n        ";
    RenderAttribute(m_pszHeadHeightAttribute,  m_ulDisplayHeight, rText);
    rText += "\r\n        ";
    if (m_bAspectFlag == FALSE)
    {
        RenderAttribute(m_pszHeadAspectAttribute, m_bAspectFlag, rText);
        rText += "\r\n        ";
    }
    if (m_cURL.length() > 0)
    {
        RenderAttribute(m_pszHeadURLAttribute, m_cURL, rText);
        rText += "\r\n        ";
    }
    rText += m_pszTagEnd;
    rText += "\r\n    ";

    GListIterator itr;   
    // Output images
    for (itr = m_cImageList.Begin(); itr != m_cImageList.End(); itr++)
    {
        CIMFImage *pImage = (CIMFImage *) *itr;
        pImage->RenderText(rText);
        rText += "\r\n  ";
    }
    rText += "\r\n  ";

    // Output effects
    for (itr = m_cEffectList.Begin(); itr != m_cEffectList.End(); itr++)
    {
        CIMFEffect *pEffect = (CIMFEffect *) *itr;
        pEffect->RenderText(rText);
        rText += "\r\n  ";
    }
    rText += "\r\n";

    rText += m_pszIMFEndTag;
    rText += "\r\n";
}

HX_RESULT CIMFFileObject::InitImageUseFlags()
{
    // If no effects, then no work to do
    if (m_cEffectList.Size() == 0)
    {
        return HXR_OK;
    }

    // Loop through effects setting first and last use flags
    GListIterator itr1;
    for (itr1 = m_cEffectList.Begin(); itr1 != m_cEffectList.End(); itr1++)
    {
        // Get the effect
        CIMFEffect *pEffect1 = (CIMFEffect *) *itr1;
        if (pEffect1 == NULL)
        {
            return HXR_FAILED;
        }

        // Does this effect have a target image?
        if (pEffect1->HasTargetImage())
        {
            // This effect DOES have a target image, so first we determine if
            // the target image has been previously used. We look from the
            // beginning of the list up to (but not including, obviously) 
            // the current effect
            GListIterator itr2;
            BOOL          bHandleMatch = FALSE;
            for (itr2 = m_cEffectList.Begin(); itr2 != itr1; itr2++)
            {
                CIMFEffect *pEffect2 = (CIMFEffect *) *itr2;
                if (pEffect2 == NULL)
                {
                    return HXR_FAILED;
                }

                if (pEffect2->HasTargetImage())
                {
                    if (pEffect1->GetTargetImageHandle() == pEffect2->GetTargetImageHandle())
                    {
                        bHandleMatch = TRUE;
                        break;
                    }
                }
            }

            if (bHandleMatch == TRUE)
            {
                // This was NOT the first use of the image
                pEffect1->SetFirstUse(FALSE);
            }
            else
            {
                // This WAS the first use of the image
                pEffect1->SetFirstUse(TRUE);
            }

            // Now we find if this is the last use of this image. We start looking at
            // the last effect in the list and work our way backwards.
            bHandleMatch = FALSE;
            for (itr2 = m_cEffectList.End() - 1; itr2 != itr1; itr2--)
            {
                CIMFEffect *pEffect2 = (CIMFEffect *) *itr2;
                if (pEffect2 == NULL)
                {
                    return HXR_FAILED;
                }

                if (pEffect2->HasTargetImage())
                {
                    if (pEffect1->GetTargetImageHandle() == pEffect2->GetTargetImageHandle())
                    {
                        bHandleMatch = TRUE;
                        break;
                    }
                }
            }

            if (bHandleMatch == TRUE)
            {
                // This was NOT the last use of the image
                pEffect1->SetLastUse(FALSE);
            }
            else
            {
                // This WAS the last use of the image
                pEffect1->SetLastUse(TRUE);
            }
        }
        else
        {
            // This effect does NOT have a target image, so both flags are false
            pEffect1->SetFirstUse(FALSE);
            pEffect1->SetLastUse(FALSE);
        }
    }

    return HXR_OK;
}

BOOL CIMFFileObject::InitFromText(GString &rText, UINT32& rulErrorID, CHXString& rErrText)
{
    UINT32 ulLastTarget = 0;

    // Look for start tag
    LONG32 lStartPos = rText.find(m_pszIMFStartTag);
    if (lStartPos < 0)
    {
        rulErrorID = IDS_ERR_PIX_NOSTART;
        rErrText   = "";
        return FALSE;
    }

    // Look for end tag
    LONG32 lEndPos = rText.find(m_pszIMFEndTag,
                               lStartPos + strlen(m_pszIMFStartTag));
    if (lEndPos < 0)
    {
        rulErrorID = IDS_ERR_PIX_NOEND;
        rErrText   = "";
        return FALSE;
    }

    // Pull out the substring with the contents
    // between <imfl> and </imfl>
    GString cText = rText.substr(lStartPos + strlen(m_pszIMFStartTag), lEndPos - 1);

    // Now go into a loop, looking for tag starts and ends and
    // take the appropriate action based on whether the tag is
    // "head", "image", or one of the effect tags.
    LONG32    lCurPos          = 0;
    BOOL      bValidHead       = FALSE;
    BOOL      bComputeDuration = FALSE;
    BOOL      bRet;
    HX_RESULT retVal;
    while (1)
    {
        // Find tag begin ("<")
        lCurPos = cText.find(m_pszTagStart, lCurPos);
        if (lCurPos < 0) break;
        LONG32 lTagStart = lCurPos;

        // Find first non-whitespace after the tag begin
        lCurPos = cText.find_first_not_of(m_pszWhitespace, lCurPos + strlen(m_pszTagStart));
        if (lCurPos < 0) break;
        LONG32 lTagStrStart = lCurPos;

        // Check to see if it's a comment
        if (cText.substr(lTagStrStart, lTagStrStart + 2) == "!--")
        {
            lCurPos = cText.find('>', lCurPos);
            if (lCurPos < 0) break;
            continue;
        }

        // Find first whitespace after tag name
        lCurPos = cText.find_first_of(m_pszWhitespace, lCurPos);
        if (lCurPos < 0) break;
        LONG32 lTagStrEnd = lCurPos - 1;
        LONG32 lAttrStrStart = lCurPos;

        // Find tag end ("/>")
        lCurPos = cText.find(m_pszTagEnd, lCurPos);
        if (lCurPos < 0) break;
        LONG32 lAttrStrEnd = lCurPos - 1;

        // Update position in order to look for next tag
        lCurPos += strlen(m_pszTagEnd);

        GString cTagStr   = cText.substr(lTagStrStart, lTagStrEnd);
        GString cAttrStr  = cText.substr(lAttrStrStart, lAttrStrEnd);
        GString cWholeTag = cText.substr(lTagStart, lAttrStrEnd + 2);

        // Check to make sure no tag start is found within the attribute string. If
        // we do find this, this would indicate someone forgot the tag end "/>".
        if (cAttrStr.length() > 0)
        {
            LONG32 lEarlyEnd = cAttrStr.find(m_pszTagStart);
            if (lEarlyEnd >= 0)
            {
                rulErrorID = IDS_ERR_PIX_NOXMLEND;
                rErrText   = cWholeTag.c_str();
                return FALSE;
            }
        }

        // Choose action based on tag
        if (cTagStr == "head")
        {
            // Get the version string
            GString cVersion;
            bRet = SetAttributeValue(cAttrStr, m_pszHeadVersionAttribute, cVersion);
            if (bRet == TRUE)
            {
                if (cVersion.length() == 0)
                {
                    rulErrorID = IDS_ERR_PIX_NULLVERSION;
                    rErrText   = "";
                    return FALSE;
                }
                char  *pszToken = strtok((char *) cVersion.c_str(), ".");
                UINT32 ulDotNum = 0;
                INT32  lVer[4]  = {0, 0, 0, 0};
                while(pszToken != NULL && ulDotNum < 4)
                {
                    lVer[ulDotNum++] = atol(pszToken);
                    pszToken = strtok(NULL, ".");
                }

                m_ulContentVersion = (UINT32) ((lVer[0] << 28L) |
                                               (lVer[1] << 20L) |
                                               (lVer[2] << 12L) |
                                                lVer[3]);
            }
            else
            {
                m_ulContentVersion = BASE_VERSION;
            }

            // For optional tags, it's OK if we they are not present. However, if they
            // ARE present in the .imf file, we will check their value
            bRet = SetAttributeValue(cAttrStr, m_pszHeadTitleAttribute, m_cTitle);
            if (bRet == TRUE)
            {
                if (m_cTitle.length() == 0)
                {
                    rulErrorID = IDS_ERR_PIX_NULLTITLE;
                    rErrText   = "";
                    return FALSE;
                }
            }
            bRet = SetAttributeValue(cAttrStr, m_pszHeadAuthorAttribute, m_cAuthor);
            if (bRet == TRUE)
            {
                if (m_cAuthor.length() == 0)
                {
                    rulErrorID = IDS_ERR_PIX_NULLAUTHOR;
                    rErrText   = "";
                    return FALSE;
                }
            }
            bRet = SetAttributeValue(cAttrStr, m_pszHeadCopyrightAttribute, m_cCopyright);
            if (bRet == TRUE)
            {
                if (m_cCopyright.length() == 0)
                {
                    rulErrorID = IDS_ERR_PIX_NULLCOPYRIGHT;
                    rErrText   = "";
                    return FALSE;
                }
            }

            // Get the background color
            GString cColorStr;
            bRet = GetAttributeSubstring(cAttrStr, m_pszHeadBackgroundColorAttr, cColorStr);
            if (bRet)
            {
                PXColor cColor;
                HX_RESULT rv = cColor.InitFromString(cColorStr.c_str());
                if (FAILED(rv))
                {
                    rulErrorID = IDS_ERR_PIX_BADBGCOLOR;
                    rErrText   = "";
                    return FALSE;
                }

                // Assign the background color
                m_ulBackgroundColor = (cColor.GetRed()   << 16) |
                                      (cColor.GetGreen() <<  8) |
                                       cColor.GetBlue();

                // If we see the background color attribute in the <head> tag, we bump
                // the content version to force any older renderers to auto-upgrade.
                if (m_ulContentVersion < U2_VERSION)
                {
                    m_ulContentVersion = U2_VERSION;
                }
            }

            // Get the opacity
            GString cOpacityStr;
            bRet = GetAttributeSubstring(cAttrStr, m_pszHeadOpacityAttr, cOpacityStr);
            if (bRet)
            {
                // Parse the opacity
                HXParseOpacity(cOpacityStr.c_str(), m_ulOpacity);
                // If the opacity is set in the <head> tag, then we
                // need to bump up the content version to force any
                // older renderers to upgrade
                if (m_ulContentVersion < OPACITY_VERSION)
                {
                    m_ulContentVersion = OPACITY_VERSION;
                }
            }

            // We need to determine the timeformat before we get any of the time-related attributes
            GString cTimeFormatStr;
            bRet = SetAttributeValue(cAttrStr, m_pszHeadTimeFormatAttribute, cTimeFormatStr);
            if (bRet == FALSE)
            {
                // No timeformat attribute was present, so we'll assume the default
                m_ulTimeFormat = kTimeFormatMilliseconds;
            }
            else
            {
                if (cTimeFormatStr == "dd:hh:mm:ss.xyz")
                {
                    m_ulTimeFormat   = kTimeFormatDHMS;
                }
                else if (cTimeFormatStr == "milliseconds")
                {
                    m_ulTimeFormat   = kTimeFormatMilliseconds;
                }
                else
                {
                    rulErrorID = IDS_ERR_PIX_BADTIMEFORMAT;
                    rErrText   = cTimeFormatStr.c_str();
                    return FALSE;
                }
            }

            // Get start time
            retVal = SetAttributeTimeValue(cAttrStr, m_pszHeadStartAttribute, m_ulTimeFormat, m_ulStartTime);
            if (retVal != HXR_OK)
            {
                if (retVal == HXR_PROP_NOT_FOUND)
                {
                    // start time not specified, so just assign default
                    m_ulStartTime = 0;
                }
                else
                {
                    rulErrorID = IDS_ERR_PIX_BADSTARTTIME;
                    rErrText   = "";
                    return FALSE;
                }
            }

            // Get preroll
            retVal = SetAttributeTimeValue(cAttrStr, m_pszHeadPrerollAttribute, m_ulTimeFormat, m_ulPreroll);
            if (retVal != HXR_OK)
            {
                if (retVal == HXR_PROP_NOT_FOUND)
                {
                    m_ulPreroll = 0;
                }
                else
                {
                    rulErrorID = IDS_ERR_PIX_BADPREROLL;
                    rErrText   = "";
                    return FALSE;
                }
            }

            bRet = SetAttributeValueBOOL(cAttrStr, m_pszHeadAspectAttribute, m_bAspectFlag);
            if (bRet == FALSE)
            {
                m_bAspectFlag = TRUE;
            }
            bRet = SetAttributeValue(cAttrStr, m_pszHeadURLAttribute, m_cURL);
            if (bRet == TRUE)
            {
                if (m_cURL.length() == 0)
                {
                    rulErrorID = IDS_ERR_PIX_NULLURL;
                    rErrText   = "";
                    return FALSE;
                }
                else
                {
                    char  *pszURL = (char *) m_cURL.c_str();
                    UINT32 ulLen  = strlen(pszURL);
                    if (ulLen > 0)
                    {
                        BOOL bWhitespaceOnly = TRUE;
                        for (UINT32 c = 0; c < ulLen; c++)
                        {
                            if (pszURL[c] != ' '  && pszURL[c] != '\t' &&
                                pszURL[c] != '\n' && pszURL[c] != '\r')
                            {
                                bWhitespaceOnly = FALSE;
                                break;
                            }
                        }
                        if (bWhitespaceOnly)
                        {
                            rulErrorID = IDS_ERR_PIX_URLALLWHITE;
                            rErrText   = "";
                            return FALSE;
                        }
                    }
                    else
                    {
                        rulErrorID = IDS_ERR_PIX_NULLURL;
                        rErrText   = "";
                        return FALSE;
                    }
                }
            }
            bRet = SetAttributeValue(cAttrStr, m_pszHeadMaxFps, m_ulMaxFps);
            if (bRet == FALSE)
            {
                m_ulMaxFps = 0;
            }

            // Duration can be automatically calculated from the effects, so it is not required.
            // If it is not present, we set a flag to have it calculate the duration
            retVal = SetAttributeTimeValue(cAttrStr, m_pszHeadDurationAttribute, m_ulTimeFormat, m_ulDuration);
            if (retVal != HXR_OK)
            {
                if (retVal == HXR_PROP_NOT_FOUND)
                {
                    m_ulDuration       = 0;
                    bComputeDuration = TRUE;
                }
                else
                {
                    rulErrorID = IDS_ERR_PIX_BADDURATION;
                    rErrText   = "";
                    return FALSE;
                }
            }
            else
            {
                bComputeDuration = FALSE;
            }

            if (m_ulDuration == 0 && bComputeDuration == FALSE)
            {
                // Zero duration - this is an error
                rulErrorID = IDS_ERR_PIX_ZERODURATION;
                rErrText   = "";
                return FALSE;
            }

            // The following are required tags, so we make sure they are present AND we check
            // their values. More checking for logical errors is done after everything is parsed.
            INT32 lTmpBitrate;
            bRet = SetAttributeValue(cAttrStr, m_pszHeadBitrateAttribute,  lTmpBitrate);
            if (bRet == FALSE)
            {
                rulErrorID = IDS_ERR_PIX_NOBITRATE;
                rErrText   = "";
                return FALSE;
            }
            // Added this fix to prevent someone from putting in a negative bitrate.
            if (lTmpBitrate <= 0)
            {
                rulErrorID = IDS_ERR_PIX_BADBITRATE;
                rErrText   = "";
                return FALSE;
            }
            else
            {
                m_ulBitrate = (UINT32) lTmpBitrate;
            }
            bRet = SetAttributeValue(cAttrStr, m_pszHeadWidthAttribute,    m_ulDisplayWidth);
            if (bRet == FALSE)
            {
                rulErrorID = IDS_ERR_PIX_NOWIDTH;
                rErrText   = "";
                return FALSE;
            }
            bRet = SetAttributeValue(cAttrStr, m_pszHeadHeightAttribute,   m_ulDisplayHeight);
            if (bRet == FALSE)
            {
                rulErrorID = IDS_ERR_PIX_NOHEIGHT;
                rErrText   = "";
                return FALSE;
            }

            bValidHead = TRUE;
        }
        else if (cTagStr == "image")
        {
            CIMFImage *pImage = new CIMFImage;
            if (!pImage)
            {
                return FALSE;
            }
            bRet = pImage->InitFromText(cAttrStr, rulErrorID, rErrText);
            if (bRet == FALSE)
            {
                delete pImage;
                return FALSE;
            }
            InsertImageIntoList(pImage);
        }
        else if (cTagStr == "fill")
        {
            CIMFFillEffect *pEffect = new CIMFFillEffect(this);
            if (!pEffect)
            {
                return FALSE;
            }
            pEffect->SetDefaultMaxFps(m_ulMaxFps);
            if (!pEffect->InitFromText(cAttrStr))
            {
                rulErrorID = IDS_ERR_PIX_BADEFFECT;
                rErrText   = cWholeTag.c_str();
                delete pEffect;
                return FALSE;
            }
            InsertEffectIntoList(pEffect);
        }
        else if (cTagStr == "fadein")
        {
            CIMFFadeinEffect *pEffect = new CIMFFadeinEffect(this);
            if (!pEffect)
            { 
                return FALSE;
            }
            pEffect->SetAspectDefault(m_bAspectFlag);
            pEffect->SetDefaultMaxFps(m_ulMaxFps);
            if (!pEffect->InitFromText(cAttrStr))
            {
                rulErrorID = IDS_ERR_PIX_BADEFFECT;
                rErrText   = cWholeTag.c_str();
                delete pEffect;
                return FALSE;
            }
            InsertEffectIntoList(pEffect);
            ulLastTarget = pEffect->GetTargetImageHandle();
        }
        else if (cTagStr == "fadeout")
        {
            CIMFFadeoutEffect *pEffect = new CIMFFadeoutEffect(this);
            if (!pEffect)
            {
                return FALSE;
            }
            pEffect->SetDefaultMaxFps(m_ulMaxFps);
            if (!pEffect->InitFromText(cAttrStr))
            {
                rulErrorID = IDS_ERR_PIX_BADEFFECT;
                rErrText   = cWholeTag.c_str();
                delete pEffect;
                return FALSE;
            }
            InsertEffectIntoList(pEffect);
        }
        else if (cTagStr == "crossfade")
        {
            CIMFCrossfadeEffect *pEffect = new CIMFCrossfadeEffect(this);
            if (!pEffect)
            {
                return FALSE;
            }
            pEffect->SetAspectDefault(m_bAspectFlag);
            pEffect->SetDefaultMaxFps(m_ulMaxFps);
            if (!pEffect->InitFromText(cAttrStr))
            {
                rulErrorID = IDS_ERR_PIX_BADEFFECT;
                rErrText   = cWholeTag.c_str();
                delete pEffect;
                return FALSE;
            }
            InsertEffectIntoList(pEffect);
            ulLastTarget = pEffect->GetTargetImageHandle();
        }
        else if (cTagStr == "wipe")
        {
            CIMFWipeEffect *pEffect = new CIMFWipeEffect(this);
            if (!pEffect)
            {
                return FALSE;
            }
            pEffect->SetAspectDefault(m_bAspectFlag);
            pEffect->SetDefaultMaxFps(m_ulMaxFps);
            if (!pEffect->InitFromText(cAttrStr))
            {
                rulErrorID = IDS_ERR_PIX_BADEFFECT;
                rErrText   = cWholeTag.c_str();
                delete pEffect;
                return FALSE;
            }
            InsertEffectIntoList(pEffect);
            ulLastTarget = pEffect->GetTargetImageHandle();
        }
        else if (cTagStr == "viewchange")
        {
            CIMFViewchangeEffect *pEffect = new CIMFViewchangeEffect(this);
            if (!pEffect)
            {
                return FALSE;
            }
            pEffect->SetAspectDefault(m_bAspectFlag);
            pEffect->SetDefaultMaxFps(m_ulMaxFps);
            if (!pEffect->InitFromText(cAttrStr))
            {
                rulErrorID = IDS_ERR_PIX_BADEFFECT;
                rErrText   = cWholeTag.c_str();
                delete pEffect;
                return FALSE;
            }
            // XXXMEH - design flaw in .rp language!!
            //
            // <viewchange> does not require the author to specify target - the target is
            // assumed from the last effect - therefore, we need to save the last
            // target and assign the viewchange's target from that.
            pEffect->SetTargetImageHandle(ulLastTarget);
            InsertEffectIntoList(pEffect);
        }
        else if (cTagStr == "effect")
        {
            CIMFExternalEffect *pEffect = new CIMFExternalEffect(this);
            if (!pEffect)
            {
                return FALSE;
            }
            pEffect->SetAspectDefault(m_bAspectFlag);
            pEffect->SetDefaultMaxFps(m_ulMaxFps);
            if (!pEffect->InitFromText(cAttrStr))
            {
                rulErrorID = IDS_ERR_PIX_BADEFFECT;
                rErrText   = cWholeTag.c_str();
                delete pEffect;
                return FALSE;
            }
            InsertEffectIntoList(pEffect);

            ulLastTarget = pEffect->GetTargetImageHandle();
        }
        else if (cTagStr == "animate")
        {
            // If we see an animate tag, we bump the content version
            // up to 0.1.0.0 instead of 0.0.0.0. This will force any
            // older renderers to auto-upgrade.
            if (m_ulContentVersion == BASE_VERSION)
            {
                m_ulContentVersion = U2_VERSION;
            }
            CIMFAnimateEffect *pEffect = new CIMFAnimateEffect(this);
            if (!pEffect)
            { 
                return FALSE;
            }
            pEffect->SetAspectDefault(m_bAspectFlag);
            pEffect->SetDefaultMaxFps(m_ulMaxFps);
            if (!pEffect->InitFromText(cAttrStr))
            {
                rulErrorID = IDS_ERR_PIX_BADEFFECT;
                rErrText   = cWholeTag.c_str();
                delete pEffect;
                return FALSE;
            }
            InsertEffectIntoList(pEffect);
            ulLastTarget = pEffect->GetTargetImageHandle();
        }
        else if (cTagStr == "!--")
        {
            // This is a comment tag - we ignore everything inside it
        }
        else
        {
            // This is an unknown tag - report an error
            rulErrorID = IDS_ERR_PIX_UNKNOWNTAG;
            rErrText   = cWholeTag.c_str();
            return FALSE;
        }
    }

    // Check for valid head tag
    if (bValidHead == FALSE)
    {
        rulErrorID = IDS_ERR_PIX_INVALIDHEAD;
        rErrText   = "";
        return FALSE;
    }

    // Make sure we have some effects
    if (m_cEffectList.Size() == 0)
    {
        rulErrorID = IDS_ERR_PIX_NOEFFECTS;
        rErrText   = "";
        return FALSE;
    }

    // Calculate the duration if none was specified
    if (bComputeDuration)
    {
        // Go through list of effects, computing max
        UINT32        ulMax = 0;
        GListIterator itr;
        for (itr = m_cEffectList.Begin(); itr != m_cEffectList.End(); itr++)
        {
            CIMFEffect *pEffect = (CIMFEffect *) *itr;
            if (pEffect)
            {
                UINT32 ulEffectMax = pEffect->GetStart();
                if (pEffect->HasDuration())
                {
                    ulEffectMax += pEffect->GetDuration();
                }
                if (ulEffectMax > ulMax)
                {
                    ulMax = ulEffectMax;
                }
            }
        }

        // Assign the duration
        m_ulDuration = ulMax;

        // Check for errors
        if (m_ulDuration == 0)
        {
            rulErrorID = IDS_ERR_PIX_NODURNOEFFECT;
            rErrText   = "";
            return FALSE;
        }
    }

    // Compute the first and last use flags
    retVal = InitImageUseFlags();
    if (retVal != HXR_OK)
    {
        rulErrorID = IDS_ERR_PIX_INVALIDEFFECTS;
        rErrText   = "";
        return FALSE;
    }

    // Now we do logical checking of parameters. We should check for:
    //
    // 1. Effect target handles which don't reference any image tag
    // 2. Effects with the same start time

    return TRUE;
}


BOOL CIMFFileObject::GetAttributeSubstring(GString &rStr, const char *pszAttr, GString &rSubstr)
{
    // Find attribute name in string
    LONG32 lPos = rStr.find(pszAttr);
    if (lPos < 0)
    {
        return FALSE;
    }

    // Go to next non-whitespace character after attribute name
    lPos = rStr.find_first_not_of(m_pszWhitespace, lPos + strlen(pszAttr));
    if (lPos < 0)
    {
        return FALSE;
    }
    if (rStr[lPos] != '=')
    {
        return FALSE;
    }

    // Go to beginning of whatever is after the "=" and following whitespace
    lPos = rStr.find_first_not_of(m_pszWhitespace, lPos + 1);
    if (lPos < 0)
    {
        return FALSE;
    }

    // If a quoted string, use string inside quotes
    if (rStr[lPos] == '"')
    {
        // Find matching quote
        LONG32 lNewPos = rStr.find('"', lPos + 1);
        if (lNewPos < 0)
        {
            return FALSE;
        }

        if (lPos + 1 > lNewPos - 1)
        {
            rSubstr = "";
            return TRUE;
        }

        // Build & return substring
        rSubstr = rStr.substr(lPos + 1, lNewPos - 1);

        return TRUE;
    }
    else
    {
        // Look for whitespace after the value string
        LONG32 lNewPos = rStr.find_first_of(m_pszWhitespace, lPos);
        if (lNewPos < 0)
        {
            // Look for tag end
            lNewPos = rStr.find(m_pszTagEnd, lPos);
            if (lNewPos < 0)
            {
                lNewPos = rStr.length();
            }

        }
        

        // Build & return substring
        rSubstr = rStr.substr(lPos, lNewPos - 1);

        return TRUE;
    }
}

BOOL CIMFFileObject::SetAttributeValue(GString &rStr, const char *pszAttr, GString &rStrTarget)
{
    return GetAttributeSubstring(rStr, pszAttr, rStrTarget);
}

BOOL CIMFFileObject::SetAttributeValue(GString &rStr, const char *pszAttr, INT32 &rlTarget)
{
    GString rStrTarget;

    if (GetAttributeSubstring(rStr, pszAttr, rStrTarget))
    {
        rlTarget = atol(rStrTarget.c_str());

        return TRUE;
    }

    return FALSE;
}

BOOL CIMFFileObject::SetAttributeValue(GString &rStr, const char *pszAttr, ULONG32 &rulTarget)
{
    GString rStrTarget;

    if (GetAttributeSubstring(rStr, pszAttr, rStrTarget))
    {
        rulTarget = strtoul(rStrTarget.c_str(), NULL, 10);

        return TRUE;
    }

    return FALSE;
}

BOOL CIMFFileObject::SetAttributeValueBOOL(GString &rStr, const char *pszAttr, BOOL &rbTarget)
{
    GString rStrTarget;

    if (GetAttributeSubstring(rStr, pszAttr, rStrTarget))
    {
        if (rStrTarget == "true")
        {
            rbTarget = TRUE;
            return TRUE;
        }

        if (rStrTarget == "false")
        {
            rbTarget = FALSE;
            return TRUE;
        }
    }

    return FALSE;
}

HX_RESULT CIMFFileObject::SetAttributeTimeValue(GString &rStr, const char *pszAttribute, ULONG32 ulFormat, ULONG32 &rulTimeTarget)
{
    GString cTimeStr;
    BOOL    bRet = GetAttributeSubstring(rStr, pszAttribute, cTimeStr);
    if (bRet == FALSE)
    {
        return HXR_PROP_NOT_FOUND;
    }

    if (ulFormat == kTimeFormatMilliseconds)
    {
        // The attribute is present and the head says we're using
        // the milliseconds time format
        rulTimeTarget = strtoul(cTimeStr.c_str(), NULL, 10);
    }
    else
    {
        // The attribute is present and the head says we're using
        // the dd:hh:mm:ss:xyz time format
        // HACK to workaround length bug in GString
        char *pszStr = (char *) cTimeStr.c_str();
        bRet         = ConvertTimeStringToULONG32(pszStr, strlen(pszStr), rulTimeTarget);
        if (bRet == FALSE)
        {
            // This indicates there was a formatting error in the time value
            rulTimeTarget = 0;
            return HXR_FAILED;
        }
    }

    return HXR_OK;
}

#define MIN_TIMEBUFFERLENGTH            1  //Min time-format string is "s" (0-9 secs)
#define NUM_DECIMAL_DIGITS_OF_SECONDS   3

BOOL CIMFFileObject::ConvertTimeStringToULONG32(char *pTimeBuf, ULONG32 timeBufLen, ULONG32 &timeValInMillisec)
{
    char   *pTimeBuffer            = pTimeBuf;
    ULONG32 timeBufferLen          = timeBufLen;
    char    savedEndChar           = '\0';   //for restoring '\"' char at end, if found.
    LONG32  bufIndx                = 0L;
    BOOL    bDotEncounteredAlready = FALSE;
    LONG32  indexOfDot             = -1;
    BOOL    endCharWasChanged      = FALSE;
    ULONG32 days_;
    ULONG32 hours_;
    ULONG32 minutes_;
    ULONG32 seconds_;
    ULONG32 milliseconds_;

    // Initialize
    days_ = hours_ = minutes_ = seconds_ = milliseconds_ = 0L;
    timeValInMillisec=0;

    // Check for input error
    if(!pTimeBuffer  ||  timeBufLen < MIN_TIMEBUFFERLENGTH)
    {
        return FALSE;
    }

    savedEndChar = pTimeBuffer[timeBufferLen-1];

    //Get rid of start & terminating quotation mark, if they exist:
    if(pTimeBuffer[0] == '\"')
    {
        pTimeBuffer++;
        timeBufferLen--;
        //Added this check to kill bug if (pTimeBuffer==")
        // and got shortened to an empty string:
        if(!timeBufferLen)
        {
            return FALSE;
        }
    }
    if(pTimeBuffer[timeBufferLen - 1] == '\"')
    {
        pTimeBuffer[timeBufferLen - 1] = '\0'; //get rid of end '\"'.
        timeBufferLen--;
        endCharWasChanged = TRUE;
    }
    
    // Work from right to left, searching first for milliseconds and then for
    // seconds (or seconds only if no '.' found):
    BOOL bColonWasFound = FALSE;
    for(bufIndx=timeBufferLen-1; 0L<=bufIndx; bufIndx--)
    {
        char ch = toupper(pTimeBuffer[bufIndx]);
        if('0' > ch  ||  '9' < ch)
        {
            if(' '==ch  ||  '\t'==ch  ||  '\n'==ch  ||  '\r'==ch)
            {
                //Added everything up to "break;" to
                // handle (valid) strings with leading space(s) like " 39":
                //previous found was seconds_, so translate into ULONG:
                seconds_ = atol(&pTimeBuffer[bufIndx+1L]);
                timeValInMillisec += seconds_*1000; //converts seconds to ms.
                break; //we're done; we found seconds only.
            }
            else if('.' == ch)
            {
                if(bDotEncounteredAlready)
                {
                    //Make sure pTimeBuffer is in original state:
                    if(endCharWasChanged)
                    {
                        timeBufferLen++;
                        pTimeBuffer[timeBufferLen-1] = savedEndChar;
                    }
                    if(indexOfDot >= 0)
                    {
                        pTimeBuffer[indexOfDot] = '.';
                    }
                    //this second '.' is unexpected, so return with
                    //  timeValInMillisec set to whatever was read so far:
                    return FALSE;
                }
                bDotEncounteredAlready = TRUE;
                indexOfDot = bufIndx;
                pTimeBuffer[bufIndx] = '\0'; //end the buffr at the '.' .

                //previously-read #'s are milliseconds, so count them:
                //added "-1" to fix bug if buf ends with ".":
                if(1L > timeBufferLen-bufIndx - 1)
                {
                    milliseconds_ = 0L;
                }
                else
                {
                    //Now, make sure that more than three digits (base 10)
                    //  are not present, e.g., reduce "46371" to "463" since
                    //  we only allow millisecond precision (3 digits past
                    //  the decimal point:
                    char    chTmp = '\0';
                    ULONG32 ulNumDecimalDigitsFound = timeBufferLen-1 - bufIndx;
                    if(NUM_DECIMAL_DIGITS_OF_SECONDS < ulNumDecimalDigitsFound)
                    {
                        chTmp                                                = pTimeBuffer[bufIndx + 1L];
                        pTimeBuffer[bufIndx+NUM_DECIMAL_DIGITS_OF_SECONDS+1] = '\0';
                    }
                    milliseconds_ = atol(&pTimeBuffer[bufIndx+1L]);

                    //Added this to fix "y.x" being converted
                    // to y00x instead of yx00 milliseconds:
                    if(ulNumDecimalDigitsFound < NUM_DECIMAL_DIGITS_OF_SECONDS)
                    {
                        for(ULONG32 ulDiff= NUM_DECIMAL_DIGITS_OF_SECONDS - ulNumDecimalDigitsFound; ulDiff > 0; ulDiff--)
                        {
                            milliseconds_ *= 10;
                        }
                    }

                    if(NUM_DECIMAL_DIGITS_OF_SECONDS < ulNumDecimalDigitsFound)
                    {
                        //restore the changed char in the pTimeBuffer:
                        pTimeBuffer[bufIndx+ NUM_DECIMAL_DIGITS_OF_SECONDS + 1] = chTmp;
                    }
                }
        
                timeValInMillisec = milliseconds_;
            }
            else if(':' == ch)
            {
                bColonWasFound = TRUE;
                //previous found was seconds_, so translate into ULONG:
                seconds_           = atol(&pTimeBuffer[bufIndx + 1L]);
                timeValInMillisec += seconds_ * 1000; //converts seconds to ms.
                break; //done with "seconds_[.milliseconds_]" part.
            }
            else  //unexpected char found, so quit parsing:
            {
                //Make sure pTimeBuffer is in original state:
                if(endCharWasChanged)
                {
                    timeBufferLen++;
                    pTimeBuffer[timeBufferLen - 1] = savedEndChar;
                }
                if(indexOfDot >= 0)
                {
                    pTimeBuffer[indexOfDot] = '.';
                }
                //this char is unexpected, so return FALSE with
                //  timeValInMillisec set to whatever was read so far:
                return FALSE;
            }
        }
        else if(0L == bufIndx) //we're done with the buffer:
        {
            //previous found was seconds_, so translate into ULONG:
            seconds_           = atol(pTimeBuffer);
            timeValInMillisec += seconds_*1000; //converts seconds to ms.
            break; //done with "seconds_[.milliseconds_]" part.
        }
    }

    if(bColonWasFound) //then get the "minutes" part:
    {
        bColonWasFound = FALSE;
        // We've got the ":seconds.msecs" part, so lets get the hours part:
        for(bufIndx--; 0L<=bufIndx; bufIndx--)
        {
            char ch = toupper(pTimeBuffer[bufIndx]);
            if('0' > ch  ||  '9' < ch)
            {
                if(' ' == ch  ||  '.' == ch)
                {
                    break;
                }
                else if(':' == ch)
                {
                    bColonWasFound = TRUE;
                    //previous found was seconds_, so translate into ULONG:
                    // (Note: this will do atol("min:sec") which ignores
                    // everything at & beyond the first non-num (":") char):
                    minutes_           = atol(&pTimeBuffer[bufIndx+1L]);
                    timeValInMillisec += minutes_*60000; //minutes to msec
                    break; //done w/ "minutes_:seconds_[milliseconds_]" part.
                }
                else  //unexpected char found, so quit parsing:
                {
                    //Make sure pTimeBuffer is in original state:
                    if(endCharWasChanged)
                    {
                        timeBufferLen++;
                        pTimeBuffer[timeBufferLen-1] = savedEndChar;
                    }
                    if(indexOfDot >= 0)
                    {
                        pTimeBuffer[indexOfDot] = '.';
                    }
                    //this char is unexpected, so return FALSE with
                    //  timeValInMillisec set to whatever was read so far:
                    return FALSE;
                }
            }
            else if(0L == bufIndx) //we're done with the buffer:
            {
                //previous found was seconds_, so translate into ULONG:
                minutes_           = atol(pTimeBuffer);
                timeValInMillisec += minutes_*60000; //minutes to msec
                break; //done w/ "minutes_:seconds_[milliseconds_]" part.
            }
        }
    }

    if(bColonWasFound) //then get the "hours" part:
    {
        bColonWasFound = FALSE;
        //We've got the ":minutes.seconds.msec" part, so lets get the hours:
        for(bufIndx--; 0L <= bufIndx; bufIndx--)
        {
            char ch = toupper(pTimeBuffer[bufIndx]);
            if('0' > ch  ||  '9' < ch)
            {
                if(' ' == ch  ||  '.' == ch)
                {
                    break;
                }
                else if(':' == ch)
                {
                    bColonWasFound = TRUE;
                    //previous found was minutes_, so translate into ULONG:
                    // (Note: this will do atol("hrs:min:sec") which ignores
                    // everything at & beyond the first non-num (":") char):
                    hours_             = atol(&pTimeBuffer[bufIndx + 1L]);
                    timeValInMillisec += hours_ * 3600000; //hours to msec
                    break;//done w/ "hours_:minutes_:seconds_[milliseconds_]"
                }
                else  //unexpected char found, so quit parsing:
                {
                    //Make sure pTimeBuffer is in original state:
                    if(endCharWasChanged)
                    {
                        timeBufferLen++;
                        pTimeBuffer[timeBufferLen-1] = savedEndChar;
                    }
                    if(indexOfDot >= 0)
                    {
                        pTimeBuffer[indexOfDot] = '.';
                    }
                    //this char is unexpected, so return FALSE with
                    //  timeValInMillisec set to whatever was read so far:
                    return FALSE;
                }
            }
            else if(0L == bufIndx) //we're done with the buffer:
            {
                //previous found was seconds_, so translate into ULONG:
                hours_             = atol(pTimeBuffer);
                timeValInMillisec += hours_ * 3600000; //hours to msec
                break; //done w/ "hours_:minutes_:seconds_[milliseconds_]".
            }
        }
    }

    if(bColonWasFound) //then get the "days" part:
    {
        bColonWasFound = FALSE;
        //We've got the "hours:minutes.seconds.msec" part, so lets get the days:
        for(bufIndx--; 0L <= bufIndx; bufIndx--)
        {
            char ch = toupper(pTimeBuffer[bufIndx]);
            if('0' > ch  ||  '9' < ch)
            {
                if(' ' == ch  ||  '.' == ch)
                {
                    break;
                }
                else if(':' == ch)
                {
                    bColonWasFound = TRUE;
                    //previous found was minutes_, so translate into ULONG:
                    // (Note: this will do atol("hrs:min:sec") which ignores
                    // everything at & beyond the first non-num (":") char):
                    days_              = atol(&pTimeBuffer[bufIndx+1L]);
                    timeValInMillisec += days_ * 86400000; //days to msec
                    break;//done w/ "days_:hours_:minutes_:seconds_[msecs_]"
                }
                else  //unexpected char found, so quit parsing:
                {
                    //Make sure pTimeBuffer is in original state:
                    if(endCharWasChanged)
                    {
                        timeBufferLen++;
                        pTimeBuffer[timeBufferLen - 1] = savedEndChar;
                    }
                    if(indexOfDot >= 0)
                    {
                        pTimeBuffer[indexOfDot] = '.';
                    }
                    //this char is unexpected, so return FALSE with
                    //  timeValInMillisec set to whatever was read so far:
                    return FALSE;
                }
            }
            else if(0L == bufIndx) //we're done with the buffer:
            {
                //previous found was seconds_, so translate into ULONG:
                hours_             = atol(pTimeBuffer);
                timeValInMillisec += hours_ * 86400000; //days to msec
                break; //done w/ "days_:hours_:minutes_:seconds_[msec_]".
            }
        }
    }

    if(endCharWasChanged)
    {
        timeBufferLen++;
        //Restore the orignial pTimeBuffer, in case end quote char was removed:
        pTimeBuffer[timeBufferLen - 1] = savedEndChar;
    }

    if(indexOfDot >= 0)
    {
        pTimeBuffer[indexOfDot] = '.';
    }

    return TRUE;

}

HX_RESULT CIMFFileObject::ConvertToNewFileObject(PXRealPixFile* pFile)
{
    HX_RESULT retVal = HXR_OK;

    if (pFile)
    {
        // Copy head attributes
        pFile->SetTitle(m_cTitle.c_str());
        pFile->SetAuthor(m_cAuthor.c_str());
        pFile->SetCopyright(m_cCopyright.c_str());
        pFile->SetStart(m_ulStartTime);
        pFile->SetDuration(m_ulDuration);
        pFile->SetPreroll(m_ulPreroll);
        pFile->SetBitrate(m_ulBitrate);
        pFile->SetDisplayWidth(m_ulDisplayWidth);
        pFile->SetDisplayHeight(m_ulDisplayHeight);
        pFile->SetTimeFormat(m_ulTimeFormat);
        pFile->SetDefaultAspectFlag(m_bAspectFlag);
        pFile->SetDefaultURL(m_cURL.c_str());
        pFile->SetDefaultMaxFps(m_ulMaxFps);
        pFile->SetBackgroundColor(m_ulBackgroundColor);
        pFile->SetBackgroundOpacity(m_ulOpacity);
        pFile->SetContentVersion(m_ulContentVersion);

        // Run through the image list adding images
        GListIterator itr;
        for (itr = m_cImageList.Begin(); itr != m_cImageList.End(); itr++)
        {
            CIMFImage* pImage = (CIMFImage*) *itr;
            if (pImage)
            {
                pFile->AddImage(pImage->GetHandle(), pImage->GetName().c_str());
                if (pImage->IsImageSizeSet())
                {
                    pFile->SetImageSize(pImage->GetHandle(), pImage->GetImageSize());
                }
            }
        }

        // Run through the list adding effects
        for (itr = m_cEffectList.Begin(); itr != m_cEffectList.End(); itr++)
        {
            CIMFEffect* pEffect = (CIMFEffect*) *itr;
            if (pEffect)
            {
                // Create a PXEffect object
                PXEffect* pNewEffect = NULL;
                retVal = PXEffect::CreateObject(&pNewEffect);
                if (SUCCEEDED(retVal))
                {
                    // AddRef the object
                    pNewEffect->AddRef();
                    // Fill in values which all CIMFEffect's have
                    pNewEffect->SetStart(pEffect->GetStart());
                    pNewEffect->SetMaxFps(pEffect->GetMaxFps());
                    pNewEffect->SetFirstUse(pEffect->GetFirstUse());
                    pNewEffect->SetLastUse(pEffect->GetLastUse());
                    pNewEffect->SetDstRect(pEffect->GetDstRect().GetX(),
                                           pEffect->GetDstRect().GetY(),
                                           pEffect->GetDstRect().GetWidth(),
                                           pEffect->GetDstRect().GetHeight());
                    pNewEffect->SetURL(pEffect->GetURL().c_str());

                    // Fill in values based on type
                    switch (pEffect->GetType())
                    {
                        case kTypeFill:
                            {
                                CIMFFillEffect* pFX = (CIMFFillEffect*) pEffect;
                                pNewEffect->SetEffectType(PXEffect::kEffectTypeFill);
                                pNewEffect->SetColor(pFX->GetColor().GetRed(),
                                                     pFX->GetColor().GetGreen(),
                                                     pFX->GetColor().GetBlue());
                            }
                            break;
                        case kTypeFadeIn:
                            {
                                CIMFFadeinEffect* pFX = (CIMFFadeinEffect*) pEffect;
                                pNewEffect->SetEffectType(PXEffect::kEffectTypeFadeIn);
                                pNewEffect->SetDuration(pFX->GetDuration());
                                pNewEffect->SetTarget(pFX->GetTargetImageHandle());
                                pNewEffect->SetSrcRect(pFX->GetSrcRect().GetX(),
                                                       pFX->GetSrcRect().GetY(),
                                                       pFX->GetSrcRect().GetWidth(),
                                                       pFX->GetSrcRect().GetHeight());
                                pNewEffect->SetAspectFlag(pFX->GetAspectFlag());
                            }
                            break;
                        case kTypeFadeOut:
                            {
                                CIMFFadeoutEffect* pFX = (CIMFFadeoutEffect*) pEffect;
                                pNewEffect->SetEffectType(PXEffect::kEffectTypeFadeOut);
                                pNewEffect->SetDuration(pFX->GetDuration());
                                pNewEffect->SetColor(pFX->GetColor().GetRed(),
                                                     pFX->GetColor().GetGreen(),
                                                     pFX->GetColor().GetBlue());
                            }
                            break;
                        case kTypeCrossFade:
                            {
                                CIMFCrossfadeEffect* pFX = (CIMFCrossfadeEffect*) pEffect;
                                pNewEffect->SetEffectType(PXEffect::kEffectTypeCrossFade);
                                pNewEffect->SetDuration(pFX->GetDuration());
                                pNewEffect->SetTarget(pFX->GetTargetImageHandle());
                                pNewEffect->SetSrcRect(pFX->GetSrcRect().GetX(),
                                                       pFX->GetSrcRect().GetY(),
                                                       pFX->GetSrcRect().GetWidth(),
                                                       pFX->GetSrcRect().GetHeight());
                                pNewEffect->SetAspectFlag(pFX->GetAspectFlag());
                            }
                            break;
                        case kTypeWipe:
                            {
                                CIMFWipeEffect* pFX = (CIMFWipeEffect*) pEffect;
                                pNewEffect->SetEffectType(PXEffect::kEffectTypeWipe);
                                pNewEffect->SetDuration(pFX->GetDuration());
                                pNewEffect->SetTarget(pFX->GetTargetImageHandle());
                                pNewEffect->SetSrcRect(pFX->GetSrcRect().GetX(),
                                                       pFX->GetSrcRect().GetY(),
                                                       pFX->GetSrcRect().GetWidth(),
                                                       pFX->GetSrcRect().GetHeight());
                                pNewEffect->SetAspectFlag(pFX->GetAspectFlag());
                                pNewEffect->SetWipeDirection(pFX->GetWipeDirection());
                                pNewEffect->SetWipeType(pFX->GetWipeType());
                            }
                            break;
                        case kTypeViewChange:
                            {
                                CIMFViewchangeEffect* pFX = (CIMFViewchangeEffect*) pEffect;
                                pNewEffect->SetEffectType(PXEffect::kEffectTypeViewChange);
                                pNewEffect->SetDuration(pFX->GetDuration());
                                pNewEffect->SetTarget(pFX->GetTargetImageHandle());
                                pNewEffect->SetSrcRect(pFX->GetSrcRect().GetX(),
                                                       pFX->GetSrcRect().GetY(),
                                                       pFX->GetSrcRect().GetWidth(),
                                                       pFX->GetSrcRect().GetHeight());
                                pNewEffect->SetAspectFlag(pFX->GetAspectFlag());
                            }
                            break;
                        case kTypeExternal:
                            {
                                CIMFExternalEffect* pFX = (CIMFExternalEffect*) pEffect;
                                pNewEffect->SetEffectType(PXEffect::kEffectTypeExternal);
                                pNewEffect->SetDuration(pFX->GetDuration());
                                pNewEffect->SetTarget(pFX->GetTargetImageHandle());
                                pNewEffect->SetSrcRect(pFX->GetSrcRect().GetX(),
                                                       pFX->GetSrcRect().GetY(),
                                                       pFX->GetSrcRect().GetWidth(),
                                                       pFX->GetSrcRect().GetHeight());
                                pNewEffect->SetAspectFlag(pFX->GetAspectFlag());
                                pNewEffect->SetExFxPackage(pFX->GetPackage().c_str());
                                pNewEffect->SetExFxName(pFX->GetName().c_str());
                                pNewEffect->SetExFxData(pFX->GetData().c_str());
                                pNewEffect->SetExFxFile(pFX->GetFile().c_str());
                            }
                            break;
                        case kTypeAnimate:
                            {
                                CIMFAnimateEffect* pFX = (CIMFAnimateEffect*) pEffect;
                                pNewEffect->SetEffectType(PXEffect::kEffectTypeAnimate);
                                pNewEffect->SetDuration(pFX->GetDuration());
                                pNewEffect->SetTarget(pFX->GetTargetImageHandle());
                                pNewEffect->SetSrcRect(pFX->GetSrcRect().GetX(),
                                                       pFX->GetSrcRect().GetY(),
                                                       pFX->GetSrcRect().GetWidth(),
                                                       pFX->GetSrcRect().GetHeight());
                                pNewEffect->SetAspectFlag(pFX->GetAspectFlag());
                            }
                            break;
                    }

                    // Add the effect to the PXRealPixFile object
                    pFile->AddEffect(pNewEffect);
                }
                HX_RELEASE(pNewEffect);
            }
        }
    }
    else
    {
        retVal = HXR_INVALID_PARAMETER;
    }

    return retVal;
}

