[Helix-client-dev] CR/CN: Implemented redirection of output into log file [GMPMetaEditor branch]

Petar Basic <[email protected]> Sat, 16 Jan 2010 23:36:20 +0100
Newsgroups gmane.comp.multimedia.helix.devel
Message-ID <[email protected]>
Modified by: pbasic at real.com
Date: 2010/01/16
Project: GMPMetaEditor (meta3gp.exe)

Synopsis:
Implemented redirection of output into log file [GMPMetaEditor branch]

Details:
Implemented -log option which redirects all output (stdout, stderr)
into specified log file.

Files Modified:
datatype/tools/dtdriver/apps/meta3gp/main.cpp

Platforms and Profiles Affected:
All

Image Size and Heap Use impact:
None

Platforms and Profiles Build Verified:
system id: win32-i386-vc7, sunos-5.10-sparc-studio11
profile: helix-client-all-defines

Platforms and Profiles Functionality Verified:
x86 Windows XP SP2
Sparc SunOS 5.10

Branch:
GMPMetaEditor

Copyright assignment:
I am a RealNetworks employee or contractor.

_______________________________________________
Helix-client-dev mailing list
[email protected]
http://lists.helixcommunity.org/mailman/listinfo/helix-client-dev
datatype_tools_dtdriver_apps_meta3gp.diff (application/octet-stream, 52.2 KB)
Index: main.cpp
===================================================================
RCS file: /cvsroot/datatype/tools/dtdriver/apps/meta3gp/main.cpp,v
retrieving revision 1.7.2.2
diff -d -H -w -U30 -r1.7.2.2 main.cpp
--- main.cpp	14 Jan 2010 22:59:00 -0000	1.7.2.2
+++ main.cpp	16 Jan 2010 22:32:09 -0000
@@ -149,60 +149,61 @@
  */
 ENABLE_DLLACCESS_PATHS(g_Meta3GPAccessPath);
 
 
 /****************************************************************************
  *  Defines
  */
 #define PROGRAM_NAME_STRING         "RealNetworks meta-data editor"
 #define PROGRAM_COPYRIGHT_STRING    "Copyright (c) RealNetworks 2010"
 
 // input/output control
 #ifdef _WINDOWS
 #define OPTION_STRING_ASYNCMODE      "dev-asyncmode"
 #endif //_WINDOWS
 
 #define OPTION_STRING_DEFMETAPROC    "dev-defmetaproc"
 #define OPTION_STRING_INPUTFILE      "if"
 #define OPTION_STRING_OUTPUTFILE     "of"
 #define OPTION_STRING_XMLINPUTFILE   "xmlin"
 #define OPTION_STRING_XMLOUTPUTFILE  "xmlout"
 #define OPTION_STRING_CLEARMETADATA  "metaclear"
 #define OPTION_STRING_METASTYLE      "metastyle"
 #define OPTION_STRING_UTF16OUTPUT    "utf16"
 #define OPTION_STRING_ID3V240OUTPUT  "id3v240"
 #define OPTION_STRING_ID3V230OUTPUT  "id3v230"
 #define OPTION_STRING_ID3V2NOOUTPUT  "id3v2no"
 #define OPTION_STRING_OVERWRITEFILE  "overwrite"
 #define OPTION_STRING_PRINTMETADATA  "print"
 #define OPTION_STRING_HELP           "help"
 #define OPTION_STRING_GENERATEHASH   "audiohash"
+#define OPTION_STRING_LOGFILE        "log"
 
 // common metadata
 #define OPTION_STRING_TITLE            "title"
 #define OPTION_STRING_ARTIST           "artist"
 #define OPTION_STRING_ALBUM            "album"
 #define OPTION_STRING_GENRE            "genre"
 #define OPTION_STRING_COMPOSER         "composer"
 #define OPTION_STRING_COMMENT          "comment"
 #define OPTION_STRING_TRACKNUMBER      "track"
 #define OPTION_STRING_YEAR             "year"
 
 #define OPTION_STRING_PICTURE_ADD      "addpic"
 #define OPTION_STRING_PICTURE_REMOVE   "rempic"
 #define OPTION_STRING_PICTURE_CLEAR    "clrpic"
 #define OPTION_STRING_PICTURE_EXTRACT  "savepic"
 
 #define OPTION_STRING_UITS_INSERT      "insertsignature"
 #define OPTION_STRING_UITS_EXTRACT     "extractsignature"
 
 // iTunes specific metadata
 #define OPTION_STRING_SOFTWARE     "software"
 #define OPTION_STRING_ENCODEDBY    "encodedby"
 #define OPTION_STRING_GROUPING     "grouping"
 #define OPTION_STRING_ALBUMARTIST  "albumartist"
 #define OPTION_STRING_LYRICS       "lyrics"
 #define OPTION_STRING_SUBTITLE     "subtitle"
 #define OPTION_STRING_BPM          "bpm"
 #define OPTION_STRING_PARTOFCOMP   "partofcompilation"
 #define OPTION_STRING_EXPLICIT     "explicitcontent"
 #define OPTION_STRING_TRACKSTOTAL  "trackstotal"
@@ -214,128 +215,128 @@
 #define OPTION_STRING_COPYRIGHT      "copyright"
 
 #define OPTION_STRING_RATING_ENTITY     "rtngent"
 #define OPTION_STRING_RATING_CRITERIA   "rtngcrit"
 #define OPTION_STRING_RATING_INFO       "rtnginfo"
 
 #define OPTION_STRING_CLASSIFICATION_ENTITY  "clsent"
 #define OPTION_STRING_CLASSIFICATION_TABLE   "clstbl"
 #define OPTION_STRING_CLASSIFICATION_INFO    "clsinfo"
 
 #define OPTION_STRING_KEYWORD_ADD      "addkwd"
 #define OPTION_STRING_KEYWORD_REMOVE   "remkwd"
 #define OPTION_STRING_KEYWORD_CLEAR    "clrkwd"
 
 #define OPTION_STRING_LOCATION_NAME               "locname"
 #define OPTION_STRING_LOCATION_ASTRONOMICAL_BODY  "locastro"
 #define OPTION_STRING_LOCATION_ADDITIONAL_NOTES   "locnotes"
 #define OPTION_STRING_LOCATION_ROLE               "locrole"
 #define OPTION_STRING_LOCATION_LONGITUDE          "loclong"
 #define OPTION_STRING_LOCATION_LATITUDE           "loclat"
 #define OPTION_STRING_LOCATION_ALTITUDE           "localt"
 
 
 /****************************************************************************
 *  Help display utilities
 */
 class OptionDesc
 {
 public:
     OptionDesc(const char* option_ = 0, const char* option_args_ = 0, const char* desc_ = 0, 
-        int option_num_ = 0, const char* xpath_ = 0)
+        int arg_count_ = 0, const char* xpath_ = 0)
     {
         option = option_;
         option_args = option_args_;
         desc = desc_;
-        option_num = option_num_;
+        arg_count = arg_count_;
         xpath = xpath_;
     }
 
     UINT32 GetLeftColumnWidth()
     {
         UINT32 len = 0;
         if(option)
         {
             len += 2 + strlen(option); //leading whitespace and option prefix
             if(option_args)
             {
                 len += 1 + strlen(option_args); //whitespace separator and option args
             }
         }
         return len;
     }
 
     CHXString BuildLine(UINT32 leftColumnWidth)
     {
         CHXString out = "";
         if(option)
         {
             out += " -"; //leading whitespace and option prefix
             out += option;
             if(option_args)
             {
                 out += " "; //whitespace separator
                 out += option_args;
             }
             if(desc)
             {
                 INT32 diff = leftColumnWidth - strlen(out);
                 for(int d = 0; d < diff; d++)
                 {
                     out += " ";
                 }
                 out += desc;
             }
         }
         return out;
     }
 
     const char* GetOptionString() const
     {
         return option ? option : "";
     }
 
     const char* GetXPath() const
     {
         return xpath ? xpath : "";
     }
 
-    int GetNumberOfOptions()
+    int GetNumberOfArguments()
     {
-        return option_num;
+        return arg_count;
     }
 
 private:
     const char* option;
     const char* option_args;
     const char* desc;
-    int option_num;
+    int arg_count;
     const char* xpath;
 };
 
 class OptionRegistry
 {
 public:
     ~OptionRegistry()
     {
         for(int i = 0; i < descs.GetSize(); i++)
         {
             delete (OptionDesc*)descs.GetAt(i);
         }
     }
 
     void Add(OptionDesc* od)
     {
         descs.Add(od);
     }
 
     int CalcLeftColumnWidth()
     {
         int leftColumnWidth = 0;
 
         for(int i = 0; i < descs.GetSize(); i++)
         {
             OptionDesc* od = (OptionDesc*)descs.GetAt(i);
             int width = od->GetLeftColumnWidth();
             if(width > leftColumnWidth)
             {
                 leftColumnWidth = width;
@@ -400,77 +401,93 @@
     OptionDesc* GetOptionDescWithPath(CHXString& strArg) const
     {
         for(int i = 0; i < descs.GetSize(); i++)
         {
             OptionDesc* od = (OptionDesc*)descs.GetAt(i);
             if(strArg == od->GetXPath())
             {
                 return od;
             }
         }
         return NULL;
     }
 
     void InitializeXmlPairs(CParsedXmlPairs *xmlPairs)
     {
         for(int i = 0; i < descs.GetSize(); i++)
         {
             OptionDesc* desc = (OptionDesc*)descs.GetAt(i);
             CHXString xpath(desc->GetXPath());
             if(!xpath.IsEmpty())
             {
                 xmlPairs->Add(new XmlPair(xpath, EncodedString()));
             }
         }
     }
 
 private:
     CHXPtrArray descs;
 };
 
-void DisplayUsage(const TCHAR* argv0, OptionRegistry& optionReg)
+void DisplayProgramInfo()
 {
     const char* pszDbg = "";
 #if defined(DEBUG) || defined(_DEBUG)
     pszDbg = ".debug";
 #endif
 
-    CHXString exeName = CHXFileSpecUtils::GetCurrentApplication().GetName();
-
     printf("\n--- %s ---\n    %s\n    Version %s%s\n\n",
             PROGRAM_NAME_STRING, PROGRAM_COPYRIGHT_STRING, TARVER_STRING_VERSION, pszDbg);
+}
+
+void DisplayUsage(const TCHAR* argv0, OptionRegistry& optionReg)
+{
+    CHXString exeName = CHXFileSpecUtils::GetCurrentApplication().GetName();
+
     printf("usage: %s [ -option [value]... ]...\n", (const char*)exeName);
     printf("with options:\n");
 
     optionReg.PrintColumns(2);
 }
 
+CHXString GetUTCTimeString()
+{
+    time_t currTime = time(0);
+    tm* pUTCTime = gmtime(&currTime);
+
+    const UINT32 bufSize = 64;
+    char buffer[bufSize];
+    strftime(buffer, bufSize, "%Y-%m-%dT%H:%M:%SZ", pUTCTime);
+
+    return CHXString(buffer);
+}
+
 
 /****************************************************************************
 *  Asynchronous mode helpers
 */
 void SignalAsyncDriveEnd()
 {
 #ifdef _WINDOWS
     ::PostMessage(0, (WM_USER + 202), 0, 0);
 #else
     printf("ERROR: SignalAsyncDriveEnd not implemented.\n");
 #endif
 }
 
 void WaitAsyncDriveEnd()
 {
 #ifdef _WINDOWS
     MSG msg;
     while(GetMessage(&msg, 0, 0, 0))
     {
         if(msg.message == (WM_USER + 202))
         {
             break;
         }
         DispatchMessage(&msg);
     }
 #else
     printf("ERROR: WaitAsyncDriveEnd not implemented.\n");
 #endif
 }
 
@@ -717,80 +734,93 @@
         argPosition += step;
         if(argPosition < 0)
         {
             argPosition = 0;
         }
     }
 
     const TCHAR* CurrentRawArg()
     {
         if(argPosition < argc)
         {
             return argv[argPosition];
         }
         return 0;
     }
 
     // Returns pointer to option name
     const TCHAR* CurrentOption(HXBOOL bWithPrefix)
     {
         if(argPosition < argc)
         {
             const TCHAR* pVal = argv[argPosition];
             if(pVal && (pVal[0] == _T('-')))
             {
                 return bWithPrefix ? pVal : (pVal + 1);
             }
         }
         return 0;
     }
 
-    const TCHAR* CurrentArg(UINT32 offset, HXBOOL bOptional = FALSE)
+    const TCHAR* CurrentArg(UINT32 offset, HXBOOL bOptional, HXBOOL bPrintError)
     {
         int argIndex = argPosition + (int)offset;
         if(argIndex < argc)
         {
             const TCHAR* pVal = argv[argIndex];
             if(pVal && !optionReg.IsOption(pVal))
             {
                 return pVal;
             }
         }
 
-        if(!bOptional)
+        if(!bOptional && bPrintError)
         {
             CHXString strOption = TCHXStringCtor(CurrentOption(TRUE));
             printf("Missing argument for option: %s\n", (const char*)strOption);
         }
         return 0;
     }
 
+    void DumpCommandLine()
+    {
+        for(int i = 0; i < argc; i++)
+        {
+#ifdef USE_WIDE_CHAR_COMMAND_LINE
+            wprintf(L"%ls ", argv[i]);
+#else
+            printf("%s ", argv[i]);
+#endif
+        }
+        printf("\n");
+    }
+
 private:
     int argc;
     TCHAR** argv;
     int argPosition;
     OptionRegistry& optionReg;
 };
 
 void ReportUnsupportedMetaStyleOption(const char* optionName)
 {
     printf("Option -%s not supported in combination with specified metastyle(s)\n", optionName);
 }
 
 void ReportInvalidValueForOption(const char* optionName, const EncodedString& optionValueUTF8)
 {
     printf("Invalid value for option -%s: ", optionName);
     EncStrUtils::PrintTextUTF8(optionValueUTF8.GetData());
     printf("\n");
 }
 
 HX_RESULT ProcessStringOption(bool bSkipArgOnly, InputOption* option, MetaInfo& metainfo,
                              MetaInfo::eStringMetaItem itemName, UINT32 metaFlavorFlags)
 {
     if(option->OptionValueUTF8.IsEmpty() && option->XmlOptionValueUTF8.IsEmpty())
     {
         return HXR_PARSE_ERROR;
     }
 
     if(bSkipArgOnly)
     {
         return HXR_OK;
@@ -1078,76 +1108,310 @@
         }
 
         if(FAILED(retVal))
         {
             printf("Invalid image file for option %s: %s\n",
                     (const char*)option->OptionName, (const char*)sFileName);
         }
 
         // export frame
         if(SUCCEEDED(retVal) && pFrame)
         {
             if(!aAddedAPICFrames.AddIfUnique(pFrame))
             {
                 retVal = HXR_FAILED;
             }
         }
 
         if(FAILED(retVal))
         {
             // cleanup
             HX_DELETE(pFrame);
         }
 
         return retVal;
     }
 
     ReportUnsupportedMetaStyleOption(option->OptionName);
     return HXR_PARSE_ERROR;
 }
 
+// Only moves argVars position if successful
 HX_RESULT CopyOptionArgumentString(ArgVars& argVars, EncodedString& sOut, HXBOOL bOptional = FALSE)
 {
     sOut.Clear();
 
-    const TCHAR* pVal = argVars.CurrentArg(1, bOptional);
+    const TCHAR* pVal = argVars.CurrentArg(1, bOptional, TRUE);
     if(!pVal && !bOptional)
     {
         return HXR_PARSE_ERROR;
     }
 
     sOut = TUTF8EncodedStringCtor(pVal);
     argVars.MovePosition(1);
 
     return HXR_OK;
 }
 
+class InputOptions
+{
+public:
+    InputOptions() 
+        : position(0)
+    {
+    }
+
+    ~InputOptions()
+    {
+        Clear();
+    }
+
+    void Clear()
+    {
+        for(int i = 0; i < options.GetSize(); i++)
+        {
+            delete (InputOption*)options.GetAt(i);
+        }
+
+        options.RemoveAll();
+        position = 0;
+    }
+
+    void Add(InputOption* inputOpt)
+    {
+        options.Add(inputOpt);
+    }
+
+    void ResetPosition(int pos = 0)
+    {
+        position = pos;
+    }
+
+    HXBOOL ReachedEndPosition()
+    {
+        return (position >= options.GetSize());
+    }
+
+    void MovePosition(int step = 1)
+    {
+        position += step;
+        if(position < 0)
+        {
+            position = 0;
+        }
+    }
+
+    InputOption* GetOption(const CHXString& optionName) const
+    {
+        for(int i = 0; i < options.GetSize(); i++)
+        {
+            InputOption* option = (InputOption*)options.GetAt(i);
+
+            if(option->OptionName == optionName)
+            {
+                return option;
+            }
+        }
+
+        return 0;
+    }
+
+    // Returns pointer to option
+    InputOption* CurrentOption() const
+    {
+        if(position < options.GetSize())
+        {
+            return (InputOption*)options.GetAt(position);
+        }
+
+        return 0;
+    }
+
+private:
+    CHXPtrArray options;
+    int position;
+};
+
+HX_RESULT ParseCommandLineInputOptions(ArgVars& argVars, OptionRegistry& optionReg, InputOptions& inputOptions,
+                                       HXBOOL bStrictMode, HXBOOL bPrintErrors)
+{
+    HX_RESULT retVal = HXR_OK;
+
+    for(argVars.ResetPosition(1); SUCCEEDED(retVal) && !argVars.ReachedEndPosition(); argVars.MovePosition(1))
+    {
+        // accept option format only
+        CHXString strOption = TCHXStringCtor(argVars.CurrentOption(FALSE));
+
+        if(strOption.IsEmpty())
+        {
+            if(bPrintErrors)
+            {
+                printf("Found non-option command-line argument: %s\n",
+                                (const char*)TCHXStringCtor(argVars.CurrentRawArg()));
+            }
+            if(bStrictMode)
+            {
+                retVal = HXR_PARSE_ERROR;
+            }
+            continue;
+        }
+
+        // check if we're looking at known option
+        OptionDesc* od = optionReg.GetOptionDesc(strOption);
+        if(od == NULL)
+        {
+            if(bPrintErrors)
+            {
+                printf("Unrecognized option: -%s\n", (const char*)strOption);
+            }
+            if(bStrictMode)
+            {
+                retVal = HXR_PARSE_ERROR;
+            }
+            continue;
+        }
+
+        EncodedString optionArgument;
+
+        // retrieve argument values and store them into inputOptions
+        UINT32 numArgs = od->GetNumberOfArguments();
+        switch(numArgs)
+        {
+            case -1:
+            {
+                // zero or one argument
+                numArgs = 1;
+                retVal = CopyOptionArgumentString(argVars, optionArgument, TRUE);
+                break;
+            }
+
+            case 0:
+                // no arguments
+                break;
+
+            case 1:
+            {
+                // one argument
+                retVal = CopyOptionArgumentString(argVars, optionArgument, FALSE);
+                break;
+            }
+
+            default:
+                // unsupported number of option arguments
+                retVal = HXR_PARSE_ERROR;
+                continue;
+        }
+
+        if(FAILED(retVal) && !bStrictMode)
+        {
+            retVal = HXR_OK;
+            argVars.MovePosition(numArgs);
+            continue;
+        }
+
+        if(SUCCEEDED(retVal))
+        {
+            inputOptions.Add(new InputOption(strOption, optionArgument, EncodedString()));
+        }
+    }
+
+    return retVal;
+}
+
+HX_RESULT ParseXMLInputOptions(IHXCommonClassFactory* pCommonClassFactory, CHXXmlInputParser* pInputParser, 
+                               InputOption* option, CParsedXmlPairs* parsedXmlResults)
+{
+    if(option->OptionValueUTF8.IsEmpty())
+    {
+        return HXR_FAIL;
+    }
+
+    FILE* fp = fopen((const char*)option->GetEffectiveOptionValueCCP(), "r");
+    if(fp == NULL)
+    {
+        printf("Unable to open file: %s\n", (const char*)option->GetEffectiveOptionValueCCP());
+        return HXR_FAIL;
+    }
+
+    HX_RESULT retVal = HXR_OK;
+    retVal = pInputParser->Start(parsedXmlResults);
+
+    ULONG32 uParserBufferSize = 64 * 1024;
+
+    if(SUCCEEDED(retVal))
+    {
+        // create temporary buffer for manifest chunks
+        IHXBuffer* pBuffer = NULL;
+        retVal = CreateBufferCCF(pBuffer, pCommonClassFactory);
+        if(SUCCEEDED(retVal))
+        {
+            pBuffer->SetSize(uParserBufferSize);
+
+            // read manifest, chunk by chunk
+            while(true)
+            {
+                size_t nItemsRead = fread(pBuffer->GetBuffer(), 1, uParserBufferSize, fp);
+                if(nItemsRead != 0)
+                {
+                    if(uParserBufferSize != nItemsRead)
+                    {
+                        pBuffer->SetSize(nItemsRead);
+                    }
+
+                    // parse chunk
+                    HXBOOL bIsFinal = uParserBufferSize != nItemsRead;
+                    retVal = pInputParser->Parse(pBuffer, bIsFinal);
+                    if(!SUCCEEDED(retVal))
+                    {
+                        break;
+                    }
+                }
+                if(nItemsRead < uParserBufferSize)
+                {
+                    break;
+                }
+            }
+        }
+    }
+
+
+    if(SUCCEEDED(retVal))
+    {
+        // retrieve manifest
+        retVal = pInputParser->EndParse();
+    }
+
+    fclose(fp);
+
+    return retVal;
+}
+
 HXBOOL NeedOverwriteConfirmation(const char* pFileName, HXBOOL bOverwriteFile)
 {
     HXBOOL bNeedConfirmation = FALSE;
     if(pFileName)
     {
         if(CHXFileSpecUtils::FileExists(CHXFileSpecifier(pFileName)))
         {
             if(!bOverwriteFile)
             {
                 bNeedConfirmation = TRUE;
                 printf("Output file [%s] already exists. Use -%s option to force overwrite.\n",
                         pFileName, OPTION_STRING_OVERWRITEFILE);
             }
         }
     }
     return bNeedConfirmation;
 }
 
 
 /****************************************************************************
 *  Meta-data processor
 */
 class CProcessor : public IHXMetaDataProcessor
 {
 public:
     CProcessor(MetaProcessorVars& vars)
         : m_lRefCount(0)
         , m_vars(vars)
         , m_pContext(0)
     {
@@ -1297,318 +1561,243 @@
 #ifdef HELIX_FEATURE_METAEDIT_AUDIO_HASH
         if(m_vars.GenerateHash)
         {
              sha256_init(&m_vars.HashState);
              m_bHashGenerated = FALSE;
         }
 #endif
         return HXR_OK;
     }
 
     STDMETHOD(OnStreamDone)(UINT16 unStreamNumber)
     {
 #ifdef HELIX_FEATURE_METAEDIT_AUDIO_HASH
         if(m_vars.GenerateHash && !m_bHashGenerated)
         {
              sha256_done(&m_vars.HashState, m_vars.SHAHash);
              // make sure, that even if there is more than one call to OnStreamDone, we execute sha finalizer just one time 
              m_bHashGenerated = TRUE;
         }
 #endif
         return HXR_OK;
     }
 
 private:
     LONG32 m_lRefCount;
     MetaProcessorVars& m_vars;
     IUnknown* m_pContext;
     HXBOOL m_bHashGenerated;
 };
 
-class InputOptions
-{
-public:
-    InputOptions() 
-        :  position(0)
-    {
-    }
-
-    ~InputOptions()
-    {
-        for(int i = 0; i < options.GetSize(); i++)
-        {
-            delete (InputOption*)options.GetAt(i);
-        }
-    }
-
-    void Add(InputOption* inputOpt)
-    {
-        options.Add(inputOpt);
-    }
-
-    void ResetPosition(int pos = 0)
-    {
-        position = pos;
-    }
-
-    HXBOOL ReachedEndPosition()
-    {
-        return (position >= options.GetSize());
-    }
-
-    void MovePosition(int step = 1)
-    {
-        position += step;
-        if(position < 0)
-        {
-            position = 0;
-        }
-    }
-
-    InputOption* GetOption(const CHXString& optionName) const
-    {
-        for(int i = 0; i < options.GetSize(); i++)
-        {
-            InputOption* option = (InputOption*)options.GetAt(i);
-
-            if(option->OptionName == optionName)
-            {
-                return option;
-            }
-        }
-
-        return 0;
-    }
-
-    // Returns pointer to option
-    InputOption* CurrentOption() const
-    {
-        if(position < options.GetSize())
-        {
-            return (InputOption*)options.GetAt(position);
-        }
-
-        return 0;
-    }
-
-private:
-    CHXPtrArray options;
-    int position;
-};
-
-HX_RESULT ParseCommandLineInputOptions(ArgVars& argVars, OptionRegistry& optionReg, InputOptions& inputOptions)
-{
-    HX_RESULT retVal = HXR_OK;
-
-    for(argVars.ResetPosition(1); SUCCEEDED(retVal) && !argVars.ReachedEndPosition(); argVars.MovePosition(1))
-    {
-        // accept option format only
-        CHXString strOption = TCHXStringCtor(argVars.CurrentOption(FALSE));
-
-        if(strOption.IsEmpty())
-        {
-            printf("Found non-option command-line argument: %s\n",
-                            (const char*)TCHXStringCtor(argVars.CurrentRawArg()));
-            retVal = HXR_PARSE_ERROR;
-            continue;
-        }
-
-        // check if we're looking at known option
-        OptionDesc* od = optionReg.GetOptionDesc(strOption);
-        if(od == NULL)
-        {
-            printf("Unrecognized option: -%s\n", (const char*)strOption);
-            retVal = HXR_PARSE_ERROR;
-            continue;
-        }
-
-        EncodedString optionArgument;
-
-        // get the option value and store it into inputOptions
-        switch(od->GetNumberOfOptions())
-        {
-            case -1:
-            {
-                // zero or one parameter
-                retVal = CopyOptionArgumentString(argVars, optionArgument, TRUE);
-                break;
-            }
-
-            case 0:
-                // no parameters
-                break;
-
-            case 1:
-            {
-                // one parameter
-                retVal = CopyOptionArgumentString(argVars, optionArgument, FALSE);
-                break;
-            }
-
-            default:
-                // unsupported number of option arguments
-                retVal = HXR_PARSE_ERROR;
-                break;
-        }
-
-        if(SUCCEEDED(retVal))
-        {
-            inputOptions.Add(new InputOption(strOption, optionArgument, EncodedString()));
-        }
-    }
-
-    return retVal;
-}
-
-HX_RESULT ParseXMLInputOptions(IHXCommonClassFactory* pCommonClassFactory, CHXXmlInputParser* pInputParser, 
-                               InputOption* option, CParsedXmlPairs* parsedXmlResults)
-{
-    if(option->OptionValueUTF8.IsEmpty())
-    {
-        return HXR_FAIL;
-    }
-
-    FILE* fp = fopen((const char*)option->GetEffectiveOptionValueCCP(), "r");
-    if(fp == NULL)
-    {
-        printf("Unable to open file: %s\n", (const char*)option->GetEffectiveOptionValueCCP());
-        return HXR_FAIL;
-    }
-
-    HX_RESULT retVal = HXR_OK;
-    retVal = pInputParser->Start(parsedXmlResults);
-
-    ULONG32 uParserBufferSize = 64 * 1024;
-
-    if(SUCCEEDED(retVal))
-    {
-        // create temporary buffer for manifest chunks
-        IHXBuffer* pBuffer = NULL;
-        retVal = CreateBufferCCF(pBuffer, pCommonClassFactory);
-        if(SUCCEEDED(retVal))
-        {
-            pBuffer->SetSize(uParserBufferSize);
-
-            // read manifest, chunk by chunk
-            while(true)
-            {
-                size_t nItemsRead = fread(pBuffer->GetBuffer(), 1, uParserBufferSize, fp);
-                if(nItemsRead != 0)
-                {
-                    if(uParserBufferSize != nItemsRead)
-                    {
-                        pBuffer->SetSize(nItemsRead);
-                    }
-
-                    // parse chunk
-                    HXBOOL bIsFinal = uParserBufferSize != nItemsRead;
-                    retVal = pInputParser->Parse(pBuffer, bIsFinal);
-                    if(!SUCCEEDED(retVal))
-                    {
-                        break;
-                    }
-                }
-                if(nItemsRead < uParserBufferSize)
-                {
-                    break;
-                }
-            }
-        }
-    }
-
-
-    if(SUCCEEDED(retVal))
-    {
-        // retrieve manifest
-        retVal = pInputParser->EndParse();
-    }
-
-    fclose(fp);
-
-    return retVal;
-}
 
 /****************************************************************************
 *  Main entry point
 */
 int TMAIN(int argc, TCHAR* argv[])
 {
-    // separate shell command from output
-    printf("\n");
-
     // initialize variables
-    CHXFileSpecifier exeFileSpec;
+    HX_RESULT retVal = HXR_OK;
+
     UINT32 startTime = 0;
+    CHXFileSpecifier exeFileSpec;
+    HXBOOL bUsingLog = FALSE;
+    HXBOOL bFailedToOpenLog = FALSE;
     
     DLLAccess* pDLLAccess = 0;
     FPHXMEDIAPLATFORMOPEN fpHXMediaPlatformOpen = 0;
     FPHXCREATEMEDIAPLATFORM fpHXCreateMediaPlatform = 0;
     FPHXMEDIAPLATFORMCLOSE fpHXMediaPlatformClose = 0;
 
     IHXMediaPlatform* pMediaPlatform = 0;
     IHXCommonClassFactory* pCommonClassFactory = 0;
+    CHXXmlInputParser* pInputParser = 0;
     IHXMetaDataEditor* pEditor = 0;
     CResponse* pResponse = 0;
     CProcessor* pProcessor = 0;
     IHXValues* pOptions = 0;
 
     char pDllPath[_MAX_PATH] = {0};
     char pDllFile[_MAX_PATH] = {0};
     const char* pszHelixPluginPath = 0;
 
     CHXString strInputFileName;
     CHXString strOutputFileName;
     CHXString strPictureFileNamePrefix;
     CHXString strUITSDataOutputFileName;
     CHXString strMetaStyleName;
 
     HXBOOL bDisplayHelp = FALSE;
     HXBOOL bOverwriteFile = FALSE;
     HXBOOL bPrintMetaData = FALSE;
     HXBOOL bExtractPicturesToFiles = FALSE;
     HXBOOL bUTF16Output = FALSE;
     UINT32 ulMetaDataID3VersionOutput = METADATAID3VERSION_240;
 
     MetaProcessorVars metaProcessorVars;
+
+    // construct option registry
     OptionRegistry optionReg;
-    eCommandLineProcessorPhase clpp = CLPP_IndependentOptions;
 
-    // input options parsed from the command line and xml file
+    optionReg.Add(new OptionDesc(OPTION_STRING_LOGFILE, "<filename>", "redirect console output to log file", 1));
+    optionReg.Add(new OptionDesc(OPTION_STRING_INPUTFILE, "<filename>", "input file", 1, "/albums/album/track/mediaFile/fileName"));
+    optionReg.Add(new OptionDesc(OPTION_STRING_OUTPUTFILE, "<filename>", "output file", 1, "/albums/album/track/outputFileName"));
+    optionReg.Add(new OptionDesc(OPTION_STRING_XMLINPUTFILE, "<filename>", "XML file with input parameters", 1));
+    optionReg.Add(new OptionDesc(OPTION_STRING_OVERWRITEFILE, 0, "overwrite output file if it exists", 0));
+    optionReg.Add(new OptionDesc(OPTION_STRING_CLEARMETADATA, 0, "clear all extracted metadata before injection", 0));
+    optionReg.Add(new OptionDesc(OPTION_STRING_METASTYLE, "<3GPP | iTunes>", "flavor of injected metadata", 1));
+    optionReg.Add(new OptionDesc(OPTION_STRING_UTF16OUTPUT, 0, "inject metadata strings in UTF16 format", 0));
+    optionReg.Add(new OptionDesc(OPTION_STRING_ID3V240OUTPUT, 0, "inject ID3v2 version 2.4.0, default", 0));
+    optionReg.Add(new OptionDesc(OPTION_STRING_ID3V230OUTPUT, 0, "inject ID3v2 version 2.3.0", 0));
+    optionReg.Add(new OptionDesc(OPTION_STRING_ID3V2NOOUTPUT, 0, "skip injecting ID3v2 tag", 0));
+    optionReg.Add(new OptionDesc(OPTION_STRING_PRINTMETADATA, 0, "print updated metadata", 0));
+    optionReg.Add(new OptionDesc(OPTION_STRING_PICTURE_EXTRACT, "[filename-prefix]", "save extracted pictures", -1));
+    optionReg.Add(new OptionDesc(OPTION_STRING_GENERATEHASH, 0, "generate hash", 0));
+    optionReg.Add(new OptionDesc());
+    optionReg.Add(new OptionDesc(OPTION_STRING_TITLE, "<string>", "update title", 1, "/albums/album/track/name"));
+    optionReg.Add(new OptionDesc(OPTION_STRING_ARTIST, "<string>", "update 3GPP performer, iTunes artist", 1, "/albums/album/track/artist/name"));
+    optionReg.Add(new OptionDesc(OPTION_STRING_ALBUM, "<string>", "update album", 1, "/albums/album/name"));
+    optionReg.Add(new OptionDesc(OPTION_STRING_GENRE, "<string>", "update genre", 1, "/albums/album/track/style/name"));
+    optionReg.Add(new OptionDesc(OPTION_STRING_COMPOSER, "<string>", "update 3GPP author, iTunes composer", 1, "/albums/album/track/artist/composer"));
+    optionReg.Add(new OptionDesc(OPTION_STRING_COMMENT, "<string>", "update 3GPP description, iTunes comment", 1, "/albums/album/pline"));
+    optionReg.Add(new OptionDesc(OPTION_STRING_COPYRIGHT, "<string>", "update copyright", 1, "/albums/album/cline"));
+    optionReg.Add(new OptionDesc(OPTION_STRING_TRACKNUMBER, "<integer 0..255>", "update track number", 1, "/albums/album/track/sequence"));
+    optionReg.Add(new OptionDesc(OPTION_STRING_YEAR, "<integer 0..65536>", "update 3GPP recording year, iTunes release date", 1, "/albums/album/OrigReleaseYear"));
+    optionReg.Add(new OptionDesc(OPTION_STRING_PICTURE_ADD, "<filename>", "add picture", 1));
+    optionReg.Add(new OptionDesc(OPTION_STRING_PICTURE_REMOVE, "<APIC picture type>", "remove picture", 1));
+    optionReg.Add(new OptionDesc(OPTION_STRING_PICTURE_CLEAR, 0, "clear all extracted pictures", 0));
+    optionReg.Add(new OptionDesc(OPTION_STRING_UITS_INSERT, "<filename>", "insert UITS signature from file", 1));
+    optionReg.Add(new OptionDesc(OPTION_STRING_UITS_EXTRACT, "<filename>", "extract UITS signature into file", 1));
+    optionReg.Add(new OptionDesc());
+    optionReg.Add(new OptionDesc(OPTION_STRING_SOFTWARE, "<string>", "update iTunes generator tool", 1));
+    optionReg.Add(new OptionDesc(OPTION_STRING_ENCODEDBY, "<string>", "update iTunes encoded by", 1));
+    optionReg.Add(new OptionDesc(OPTION_STRING_GROUPING, "<string>", "update iTunes grouping", 1));
+    optionReg.Add(new OptionDesc(OPTION_STRING_ALBUMARTIST, "<string>", "update iTunes album artist", 1, "/albums/album/artist/name"));
+    optionReg.Add(new OptionDesc(OPTION_STRING_LYRICS, "<string>", "update iTunes lyrics", 1));
+    optionReg.Add(new OptionDesc(OPTION_STRING_SUBTITLE, "<string>", "update iTunes track subtitle", 1));
+    optionReg.Add(new OptionDesc(OPTION_STRING_BPM, "<integer>", "update iTunes beats per minute", 1));
+    optionReg.Add(new OptionDesc(OPTION_STRING_PARTOFCOMP, "<0 | 1>", "update iTunes part of compilation", 1, "/albums/album/compilation"));
+    optionReg.Add(new OptionDesc(OPTION_STRING_EXPLICIT, "<0 | 1>", "update iTunes content rating", 1, "/albums/album/explicit"));
+    optionReg.Add(new OptionDesc(OPTION_STRING_TRACKSTOTAL, "<integer>", "update iTunes track count", 1, "/albums/album/trackCount"));
+    optionReg.Add(new OptionDesc(OPTION_STRING_DISKNUMBER, "<integer>", "update iTunes disc number", 1, "/albums/album/track/discNumber"));
+    optionReg.Add(new OptionDesc(OPTION_STRING_DISKSTOTAL, "<integer>", "update iTunes disc count", 1, "/albums/album/discCount"));
+    optionReg.Add(new OptionDesc());
+    optionReg.Add(new OptionDesc(OPTION_STRING_RATING_ENTITY, "<4 character code>", "update 3GPP rating entity", 1));
+    optionReg.Add(new OptionDesc(OPTION_STRING_RATING_CRITERIA, "<4 character code>", "update 3GPP rating criteria", 1));
+    optionReg.Add(new OptionDesc(OPTION_STRING_RATING_INFO, "<string>", "update 3GPP rating info", 1));
+    optionReg.Add(new OptionDesc(OPTION_STRING_CLASSIFICATION_ENTITY, "<4 character code>", "update 3GPP classification entity", 1));
+    optionReg.Add(new OptionDesc(OPTION_STRING_CLASSIFICATION_TABLE, "<integer 0..65535>", "update 3GPP classification table", 1));
+    optionReg.Add(new OptionDesc(OPTION_STRING_CLASSIFICATION_INFO, "<string>", "update 3GPP classification info", 1));
+    optionReg.Add(new OptionDesc(OPTION_STRING_KEYWORD_ADD, "<string>", "add 3GPP keyword", 1));
+    optionReg.Add(new OptionDesc(OPTION_STRING_KEYWORD_REMOVE, "<string>", "remove 3GPP keyword", 1));
+    optionReg.Add(new OptionDesc(OPTION_STRING_KEYWORD_CLEAR, 0, "clear all extracted 3GPP keywords", 0));
+    optionReg.Add(new OptionDesc(OPTION_STRING_LOCATION_NAME, "<string>", "update 3GPP location name", 1));
+    optionReg.Add(new OptionDesc(OPTION_STRING_LOCATION_ASTRONOMICAL_BODY, "<string>", "update 3GPP location astronomical body", 1));
+    optionReg.Add(new OptionDesc(OPTION_STRING_LOCATION_ADDITIONAL_NOTES, "<string>", "update 3GPP location notes", 1));
+    optionReg.Add(new OptionDesc(OPTION_STRING_LOCATION_ROLE, "<integer 0..255>", "update 3GPP location role", 1));
+    optionReg.Add(new OptionDesc(OPTION_STRING_LOCATION_LONGITUDE, "<decimal number>", "update 3GPP location longitude", 1));
+    optionReg.Add(new OptionDesc(OPTION_STRING_LOCATION_LATITUDE, "<decimal number>", "update 3GPP location latitude", 1));
+    optionReg.Add(new OptionDesc(OPTION_STRING_LOCATION_ALTITUDE, "<decimal number>", "update 3GPP location altitude", 1));
+    optionReg.Add(new OptionDesc(OPTION_STRING_LANGUAGE_CODE, "<3 character code>", "ISO639.2/T language code of 3GPP metadata text", 1));
+    optionReg.Add(new OptionDesc());
+    
+#ifdef _WINDOWS
+    optionReg.Add(new OptionDesc(OPTION_STRING_ASYNCMODE, 0, "dtdriver asynchronous mode (developer option)", 0));
+#endif //_WINDOWS
+
+    optionReg.Add(new OptionDesc(OPTION_STRING_DEFMETAPROC, 0, "default meta processor (developer option)", 0));
+    optionReg.Add(new OptionDesc(OPTION_STRING_HELP, 0, "display help text", 0));
+
+    // parse the command line
+    ArgVars argVars(argc, argv, optionReg);
     InputOptions inputOptions;
 
-    // xml parser to parse xml input file
-    CHXXmlInputParser* pInputParser = 0;
+    // scan for log option, but do not print syntax errors yet
+    retVal = ParseCommandLineInputOptions(argVars, optionReg, inputOptions, FALSE, FALSE);
 
-    // initialize Helix DNA client media-platform
-    HX_RESULT retVal = HXR_OK;
+    // see if we should redirect output to a log file
+    CHXString sLogFilename;
+    InputOption* pOptionLogfile = inputOptions.GetOption(OPTION_STRING_LOGFILE);
+    if(pOptionLogfile)
+    {
+        sLogFilename = pOptionLogfile->GetEffectiveOptionValueCCP();
+        if(!sLogFilename.IsEmpty())
+        {
+            // see if we can actually open the log file before disconnecting from console
+            FILE* fpLog = fopen((const char*)sLogFilename, "a");
+            if(fpLog)
+            {
+                bUsingLog = TRUE;
 
-    // load Helix media-platform library
+                // reopen standard output and standard error in append mode
+                freopen((const char*)sLogFilename, "a", stdout);
+                freopen((const char*)sLogFilename, "a", stderr);
+
+                // we can close our local pointer now
+                fclose(fpLog);
+                fpLog = 0;
+            }
+            else
+            {
+                bFailedToOpenLog = TRUE;
+            }
+        }
+    }
+
+    if(bUsingLog)
+    {
+        // echo current time to log
+        printf("\n========== %s ==========\n\n", (const char*)GetUTCTimeString());
+
+        // echo command line to log
+        argVars.DumpCommandLine();
+    }
+
+    // echo program startup header
+    DisplayProgramInfo();
+
+    // clear results of previous parsing
+    inputOptions.Clear();
+
+    // parse the command line again, this time print errors
+    retVal = ParseCommandLineInputOptions(argVars, optionReg, inputOptions, TRUE, TRUE);
+
+    if(bFailedToOpenLog)
+    {
+        printf("Failed to open log file: [%s]\n\n", (const char*)sLogFilename);
+    }
+
+    if(FAILED(retVal))
+    {
+        goto cleanup;
+    }
+
+    // initialize Helix DNA client media-platform
     pDLLAccess = new DLLAccess();
     if(!pDLLAccess)
     {
         retVal = HXR_OUTOFMEMORY;
         goto cleanup;
     }
 
     // extract plugin directory
 #ifdef _WINDOWS
     exeFileSpec = CHXFileSpecUtils::GetCurrentApplication();
     strncpy(pDllPath, (const char*)exeFileSpec.GetParentDirectory().GetPathName(), _MAX_PATH);
     snprintf(pDllFile, _MAX_PATH, "%s\\%s", pDllPath, "hxmedpltfm.dll");
 
 #else
     pszHelixPluginPath = getenv(PLUGIN_PATH_ENV_VAR_NAME);
     if(pszHelixPluginPath && (strlen(pszHelixPluginPath) > 0))
     {
         strncpy(pDllPath, pszHelixPluginPath, _MAX_PATH);
         snprintf(pDllFile, _MAX_PATH, "%s/%s", pDllPath, "hxmedpltfm.so");
     }
     else
     {
         printf("Missing environment variable %s.\n", PLUGIN_PATH_ENV_VAR_NAME);
         goto cleanup;
     }
 #endif
 
     pDllPath[_MAX_PATH - 1] = '\0';
     pDllFile[_MAX_PATH - 1] = '\0';
     
@@ -1681,255 +1870,191 @@
     }
 
     // initialize meta-data processor object
     pProcessor = new CProcessor(metaProcessorVars);
     HX_ADDREF(pProcessor);
     if(!pProcessor)
     {
         retVal = HXR_OUTOFMEMORY;
     }
 
     if(FAILED(retVal))
     {
         printf("Failed to initialize Helix meta-data processor object.\n");
         goto cleanup;
     }
 
     // initialize meta-data editor plugin
     retVal = CreateInstanceCCF_QI(CLSID_IHXMetaDataEditor, IID_IHXMetaDataEditor,
                                         (void**)&pEditor, pCommonClassFactory);
     if(SUCCEEDED(retVal))
     {
         retVal = pEditor->InitMetaDataEditor(pCommonClassFactory);
     }
 
     if(FAILED(retVal))
     {
         printf("Failed to initialize Helix meta-data editor plugin.\n");
         goto cleanup;
     }
 
-    // create xml parser
+    // initialize xml parser
+    if(SUCCEEDED(retVal))
+    {
+        retVal = HXR_OUTOFMEMORY;
     pInputParser = new CHXXmlInputParser();
+
+        if(pInputParser)
+        {
     pInputParser->AddRef();
+            retVal = HXR_OK;
+        }
+    }
 
     if(SUCCEEDED(retVal))
     {
         retVal = pInputParser->Init(pCommonClassFactory);
     }
 
     if(FAILED(retVal))
     {
-        printf("Failed to initialize xml parser.\n");
+        printf("Failed to initialize XML parser.\n");
         goto cleanup;
     }
 
-    // construct option registry
-    optionReg.Add(new OptionDesc(OPTION_STRING_INPUTFILE, "<filename>", "input file", 1, "/albums/album/track/mediaFile/fileName"));
-    optionReg.Add(new OptionDesc(OPTION_STRING_OUTPUTFILE, "<filename>", "output file", 1, "/albums/album/track/outputFileName"));
-    optionReg.Add(new OptionDesc(OPTION_STRING_XMLINPUTFILE, "<filename>", "xml file with input parameters", 1));
-    optionReg.Add(new OptionDesc(OPTION_STRING_OVERWRITEFILE, 0, "overwrite output file if it exists", 0));
-    optionReg.Add(new OptionDesc(OPTION_STRING_CLEARMETADATA, 0, "clear all extracted metadata before injection", 0));
-    optionReg.Add(new OptionDesc(OPTION_STRING_METASTYLE, "<3GPP | iTunes>", "flavor of injected metadata, default=all", 1));
-    optionReg.Add(new OptionDesc(OPTION_STRING_UTF16OUTPUT, 0, "inject metadata strings in UTF16 format", 0));
-    optionReg.Add(new OptionDesc(OPTION_STRING_ID3V240OUTPUT, 0, "inject ID3v2 version 2.4.0, default", 0));
-    optionReg.Add(new OptionDesc(OPTION_STRING_ID3V230OUTPUT, 0, "inject ID3v2 version 2.3.0", 0));
-    optionReg.Add(new OptionDesc(OPTION_STRING_ID3V2NOOUTPUT, 0, "skip injecting ID3v2 tag", 0));
-    optionReg.Add(new OptionDesc(OPTION_STRING_PRINTMETADATA, 0, "print updated metadata", 0));
-    optionReg.Add(new OptionDesc(OPTION_STRING_PICTURE_EXTRACT, "[filename-prefix]", "save extracted pictures", -1));
-    optionReg.Add(new OptionDesc(OPTION_STRING_GENERATEHASH, 0, "generate hash", 0));
-    optionReg.Add(new OptionDesc());
-    optionReg.Add(new OptionDesc(OPTION_STRING_TITLE, "<string>", "update title", 1, "/albums/album/track/name"));
-    optionReg.Add(new OptionDesc(OPTION_STRING_ARTIST, "<string>", "update 3GPP performer, iTunes artist", 1, "/albums/album/track/artist/name"));
-    optionReg.Add(new OptionDesc(OPTION_STRING_ALBUM, "<string>", "update album", 1, "/albums/album/name"));
-    optionReg.Add(new OptionDesc(OPTION_STRING_GENRE, "<string>", "update genre", 1, "/albums/album/track/style/name"));
-    optionReg.Add(new OptionDesc(OPTION_STRING_COMPOSER, "<string>", "update 3GPP author, iTunes composer", 1, "/albums/album/track/artist/composer"));
-    optionReg.Add(new OptionDesc(OPTION_STRING_COMMENT, "<string>", "update 3GPP description, iTunes comment", 1, "/albums/album/pline"));
-    optionReg.Add(new OptionDesc(OPTION_STRING_COPYRIGHT, "<string>", "update copyright", 1, "/albums/album/cline"));
-    optionReg.Add(new OptionDesc(OPTION_STRING_TRACKNUMBER, "<integer 0..255>", "update track number", 1, "/albums/album/track/sequence"));
-    optionReg.Add(new OptionDesc(OPTION_STRING_YEAR, "<integer 0..65536>", "update 3GPP recording year, iTunes release date", 1, "/albums/album/OrigReleaseYear"));
-    optionReg.Add(new OptionDesc(OPTION_STRING_PICTURE_ADD, "<filename>", "add picture", 1));
-    optionReg.Add(new OptionDesc(OPTION_STRING_PICTURE_REMOVE, "<APIC picture type>", "remove picture", 1));
-    optionReg.Add(new OptionDesc(OPTION_STRING_PICTURE_CLEAR, 0, "clear all extracted pictures", 0));
-    optionReg.Add(new OptionDesc(OPTION_STRING_UITS_INSERT, "<filename>", "insert UITS signature from file", 1));
-    optionReg.Add(new OptionDesc(OPTION_STRING_UITS_EXTRACT, "<filename>", "extract UITS signature into file", 1));
-    optionReg.Add(new OptionDesc());
-    optionReg.Add(new OptionDesc(OPTION_STRING_SOFTWARE, "<string>", "update iTunes generator tool", 1));
-    optionReg.Add(new OptionDesc(OPTION_STRING_ENCODEDBY, "<string>", "update iTunes encoded by", 1));
-    optionReg.Add(new OptionDesc(OPTION_STRING_GROUPING, "<string>", "update iTunes grouping", 1));
-    optionReg.Add(new OptionDesc(OPTION_STRING_ALBUMARTIST, "<string>", "update iTunes album artist", 1, "/albums/album/artist/name"));
-    optionReg.Add(new OptionDesc(OPTION_STRING_LYRICS, "<string>", "update iTunes lyrics", 1));
-    optionReg.Add(new OptionDesc(OPTION_STRING_SUBTITLE, "<string>", "update iTunes track subtitle", 1));
-    optionReg.Add(new OptionDesc(OPTION_STRING_BPM, "<integer>", "update iTunes beats per minute", 1));
-    optionReg.Add(new OptionDesc(OPTION_STRING_PARTOFCOMP, "<0 | 1>", "update iTunes part of compilation", 1, "/albums/album/compilation"));
-    optionReg.Add(new OptionDesc(OPTION_STRING_EXPLICIT, "<0 | 1>", "update iTunes content rating", 1, "/albums/album/explicit"));
-    optionReg.Add(new OptionDesc(OPTION_STRING_TRACKSTOTAL, "<integer>", "update iTunes track count", 1, "/albums/album/trackCount"));
-    optionReg.Add(new OptionDesc(OPTION_STRING_DISKNUMBER, "<integer>", "update iTunes disc number", 1, "/albums/album/track/discNumber"));
-    optionReg.Add(new OptionDesc(OPTION_STRING_DISKSTOTAL, "<integer>", "update iTunes disc count", 1, "/albums/album/discCount"));
-    optionReg.Add(new OptionDesc());
-    optionReg.Add(new OptionDesc(OPTION_STRING_RATING_ENTITY, "<4 character code>", "update 3GPP rating entity", 1));
-    optionReg.Add(new OptionDesc(OPTION_STRING_RATING_CRITERIA, "<4 character code>", "update 3GPP rating criteria", 1));
-    optionReg.Add(new OptionDesc(OPTION_STRING_RATING_INFO, "<string>", "update 3GPP rating info", 1));
-    optionReg.Add(new OptionDesc(OPTION_STRING_CLASSIFICATION_ENTITY, "<4 character code>", "update 3GPP classification entity", 1));
-    optionReg.Add(new OptionDesc(OPTION_STRING_CLASSIFICATION_TABLE, "<integer 0..65535>", "update 3GPP classification table", 1));
-    optionReg.Add(new OptionDesc(OPTION_STRING_CLASSIFICATION_INFO, "<string>", "update 3GPP classification info", 1));
-    optionReg.Add(new OptionDesc(OPTION_STRING_KEYWORD_ADD, "<string>", "add 3GPP keyword", 1));
-    optionReg.Add(new OptionDesc(OPTION_STRING_KEYWORD_REMOVE, "<string>", "remove 3GPP keyword", 1));
-    optionReg.Add(new OptionDesc(OPTION_STRING_KEYWORD_CLEAR, 0, "clear all extracted 3GPP keywords", 0));
-    optionReg.Add(new OptionDesc(OPTION_STRING_LOCATION_NAME, "<string>", "update 3GPP location name", 1));
-    optionReg.Add(new OptionDesc(OPTION_STRING_LOCATION_ASTRONOMICAL_BODY, "<string>", "update 3GPP location astronomical body", 1));
-    optionReg.Add(new OptionDesc(OPTION_STRING_LOCATION_ADDITIONAL_NOTES, "<string>", "update 3GPP location notes", 1));
-    optionReg.Add(new OptionDesc(OPTION_STRING_LOCATION_ROLE, "<integer 0..255>", "update 3GPP location role", 1));
-    optionReg.Add(new OptionDesc(OPTION_STRING_LOCATION_LONGITUDE, "<decimal number>", "update 3GPP location longitude", 1));
-    optionReg.Add(new OptionDesc(OPTION_STRING_LOCATION_LATITUDE, "<decimal number>", "update 3GPP location latitude", 1));
-    optionReg.Add(new OptionDesc(OPTION_STRING_LOCATION_ALTITUDE, "<decimal number>", "update 3GPP location altitude", 1));
-    optionReg.Add(new OptionDesc(OPTION_STRING_LANGUAGE_CODE, "<3 character code>", "ISO639.2/T language code of 3GPP metadata text", 1));
-    optionReg.Add(new OptionDesc());
-    
-#ifdef _WINDOWS
-    optionReg.Add(new OptionDesc(OPTION_STRING_ASYNCMODE, 0, "dtdriver async mode (developer option)", 0));
-#endif //_WINDOWS
-
-    optionReg.Add(new OptionDesc(OPTION_STRING_DEFMETAPROC, 0, "default meta processor (developer option)", 0));
-    optionReg.Add(new OptionDesc(OPTION_STRING_HELP, 0, "display help text", 0));
-
-    {
-        // parse command line
-        ArgVars argVars(argc, argv, optionReg);
-        retVal = ParseCommandLineInputOptions(argVars, optionReg, inputOptions);
-
+    // parse XML input file
         if(SUCCEEDED(retVal))
         {
             InputOption* optionXmlIn = inputOptions.GetOption(CHXString(OPTION_STRING_XMLINPUTFILE));
             if(optionXmlIn && !optionXmlIn->OptionValueUTF8.IsEmpty())
             {
-                // parse xml input file
-                if(SUCCEEDED(retVal))
-                {
                     printf("Processing XML input file:\n");
-                }
 
                 CParsedXmlPairs parsedXmlResults;
                 optionReg.InitializeXmlPairs(&parsedXmlResults);
 
                 retVal = ParseXMLInputOptions(pCommonClassFactory, pInputParser, optionXmlIn, &parsedXmlResults);
 
                 if(SUCCEEDED(retVal))
                 {
                     // merge parsed results into inputOptions
                     CHXPtrArray *xmlPairs = parsedXmlResults.GetAllPairs();
                     for(int i = 0; i < xmlPairs->GetSize(); i++)
                     {
                         XmlPair *pair = (XmlPair*)xmlPairs->GetAt(i);
                         OptionDesc* optDesc = optionReg.GetOptionDescWithPath(pair->xmlPath);
 
                         // Add only results of XML file parsing which have non-empty value.
                         if((optDesc != NULL) && !pair->xmlValueUTF8.IsEmpty())
                         {
                             CHXString strName = optDesc->GetOptionString();
                             InputOption* inOpt = inputOptions.GetOption(strName);
 
                             if(inOpt == NULL)
                             {
                                 inputOptions.Add(new InputOption(strName, EncodedString(), pair->xmlValueUTF8));
                             }
                             else
                             {
                                 inOpt->XmlOptionValueUTF8 = pair->xmlValueUTF8;
                             }
 
                             printf("-%s taken from location [%s], value=[", (const char*)strName, (const char*)pair->xmlPath);
                             EncStrUtils::PrintTextUTF8(pair->xmlValueUTF8.GetData());
                             printf("]\n");
                         }
                     }
 
                     printf("\n");
                 }
             }
         }
-    }
 
+    // Process final combination of command-line and XML file input.
     // The first phase seeks for independent arguments, the second phase seeks for dependent arguments.
-    clpp = CLPP_IndependentOptions;
+    eCommandLineProcessorPhase clpp = CLPP_IndependentOptions;
 
     while(SUCCEEDED(retVal) && (clpp != CLPP_Done))
     {
         for(inputOptions.ResetPosition(0); SUCCEEDED(retVal) && !inputOptions.ReachedEndPosition(); 
                 inputOptions.MovePosition(1))
         {
             InputOption* option = inputOptions.CurrentOption();
             CHXString strOption = option->OptionName;
 
             if(strOption == OPTION_STRING_HELP)
             {
                 bDisplayHelp = TRUE;
             }
 
 #ifdef _WINDOWS
             else if(strOption == OPTION_STRING_ASYNCMODE)
             {
                 g_bUseDTDriverSynchronousMode = FALSE;
             }
 #endif //_WINDOWS
 
             else if(strOption == OPTION_STRING_DEFMETAPROC)
             {
                 g_bUseDefaultMetaEditorProcessor = TRUE;
             }
+            else if(strOption == OPTION_STRING_LOGFILE)
+            {
+                // do nothing, already processed
+            }
             else if(strOption == OPTION_STRING_INPUTFILE)
             {
                 strInputFileName = option->GetEffectiveOptionValueCCP();
             }
             else if(strOption == OPTION_STRING_OUTPUTFILE)
             {
                 strOutputFileName = option->GetEffectiveOptionValueCCP();
             }
             else if(strOption == OPTION_STRING_OVERWRITEFILE)
             {
                 bOverwriteFile = TRUE;
             }
             else if(strOption == OPTION_STRING_CLEARMETADATA)
             {
                 metaProcessorVars.ClearMetaData = TRUE;
             }
             else if(strOption == OPTION_STRING_PRINTMETADATA)
             {
                 bPrintMetaData = TRUE;
             }
             else if(strOption == OPTION_STRING_GENERATEHASH)
             {
                 metaProcessorVars.GenerateHash = TRUE;
             }
             else if(strOption == OPTION_STRING_XMLINPUTFILE)
             {
-                // do nothing, since it was already done what needs to be done
+                // do nothing, already processed
             }
             else if(strOption == OPTION_STRING_METASTYLE)
             {
                 strMetaStyleName = option->GetEffectiveOptionValueCCP();
 
                 if(clpp == CLPP_IndependentOptions)
                 {
                     if(SUCCEEDED(retVal) && !strMetaStyleName.IsEmpty())
                     {
                         if(!strMetaStyleName.CompareNoCase("3gpp"))
                         {
                             metaProcessorVars.InjectedMetaFlavors |= METADATA_FLAVOR_3GPP;
                         }
                         else if(!strMetaStyleName.CompareNoCase("itunes"))
                         {
                             metaProcessorVars.InjectedMetaFlavors |= METADATA_FLAVOR_ITUNES;
                         }
                         else
                         {
                             retVal = HXR_PARSE_ERROR;
                             ReportInvalidValueForOption(OPTION_STRING_METASTYLE, option->GetEffectiveOptionValueUTF8());
                         }
                     }
                 }
             }
             else if(strOption == OPTION_STRING_UTF16OUTPUT)
             {
                 bUTF16Output = TRUE;
             }
             else if(strOption == OPTION_STRING_ID3V240OUTPUT)