diff options
author | sr55 <[email protected]> | 2010-06-06 18:22:39 +0000 |
---|---|---|
committer | sr55 <[email protected]> | 2010-06-06 18:22:39 +0000 |
commit | 0c9a71f626e0e552cf670103b8dad8e61de1fb69 (patch) | |
tree | 8bda1188ea4fd4f15700b5c997c491bbe37f9f4e /win/C#/HandBrake.ApplicationServices | |
parent | 21edb5248c8d25d334e3225e2f52ff9e8d9782dd (diff) |
WinGui:
- Moved all the services that handle parsing, scanning, encodes and the queue out into a separate library.
git-svn-id: svn://svn.handbrake.fr/HandBrake/trunk@3362 b64f7644-9d1e-0410-96f1-a4d463321fa5
Diffstat (limited to 'win/C#/HandBrake.ApplicationServices')
29 files changed, 4161 insertions, 0 deletions
diff --git a/win/C#/HandBrake.ApplicationServices/Functions/GrowlCommunicator.cs b/win/C#/HandBrake.ApplicationServices/Functions/GrowlCommunicator.cs new file mode 100644 index 000000000..579b92a46 --- /dev/null +++ b/win/C#/HandBrake.ApplicationServices/Functions/GrowlCommunicator.cs @@ -0,0 +1,119 @@ +/* GrowlCommunicator.cs $
+ This file is part of the HandBrake source code.
+ Homepage: <http://handbrake.fr>.
+ It may be used under the terms of the GNU General Public License. */
+
+namespace HandBrake.ApplicationServices.Functions
+{
+ using System;
+
+ using Growl.Connector;
+
+ /// <summary>
+ /// Provides all functionality for communicating with Growl for Windows.
+ /// </summary>
+ /// <remarks>
+ /// This class is implemented as a static class because:
+ /// 1. It allows nearly all of the Growl-related code to be in one place
+ /// 2. It prevents the main form, queue handler, and any other part of Handbrake from having to declare
+ /// or track any new instance variables
+ /// </remarks>
+ public static class GrowlCommunicator
+ {
+ /// <summary>
+ /// The <see cref="GrowlConnector"/> that actually talks to Growl
+ /// </summary>
+ private static GrowlConnector growl;
+
+ /// <summary>
+ /// The Handbrake application instance that is registered with Growl
+ /// </summary>
+ private static Application application;
+
+ /// <summary>
+ /// Notification shown upon completion of encoding
+ /// </summary>
+ private static NotificationType encodeOrQueueCompleted = new NotificationType("EncodeOrQueue", "HandBrake Status");
+
+ /// <summary>
+ /// Checks to see if Growl is currently running on the local machine.
+ /// </summary>
+ /// <returns>
+ /// <c>true</c> if Growl is running;
+ /// <c>false</c> otherwise
+ /// </returns>
+ public static bool IsRunning()
+ {
+ Initialize();
+
+ return growl.IsGrowlRunning();
+ }
+
+ /// <summary>
+ /// Registers Handbrake with the local Growl instance
+ /// </summary>
+ /// <remarks>
+ /// This should usually be called at application start-up
+ /// </remarks>
+ public static void Register()
+ {
+ Initialize();
+ growl.Register(application, new[] {encodeOrQueueCompleted});
+ }
+
+ /// <summary>
+ /// Sends a notification to Growl. (Since Handbrake currently only supports one type of notification with
+ /// static text, this is a shortcut method).
+ /// </summary>
+ /// <param name="title">
+ /// The title.
+ /// </param>
+ /// <param name="text">
+ /// The text to display.
+ /// </param>
+ public static void Notify(string title, string text)
+ {
+ Notification notification = new Notification(application.Name, encodeOrQueueCompleted.Name, String.Empty,
+ title, text);
+ growl.Notify(notification);
+ }
+
+ /// <summary>
+ /// Sends a notification to Growl. (This is the more generic version that could be used in the future if
+ /// more notification types are implemented)
+ /// </summary>
+ /// <param name="notificationType">The <see cref="NotificationType">type</see> of notification to send</param>
+ /// <param name="title">The notification title</param>
+ /// <param name="text">The notification text</param>
+ /// <param name="imageUrl">The notification image as a url</param>
+ public static void Notify(NotificationType notificationType, string title, string text, string imageUrl)
+ {
+ Notification notification = new Notification(application.Name, notificationType.Name, String.Empty, title,
+ text)
+ {
+ Icon = imageUrl
+ };
+
+ growl.Notify(notification);
+ }
+
+ /// <summary>
+ /// Initializes the GrowlCommunicator
+ /// </summary>
+ private static void Initialize()
+ {
+ if (growl == null)
+ {
+ growl = new GrowlConnector
+ {
+ EncryptionAlgorithm = Cryptography.SymmetricAlgorithmType.PlainText
+ };
+
+ application = new Application("Handbrake")
+ {
+ Icon = Properties.Resources.logo64
+ };
+ }
+ }
+ }
+}
\ No newline at end of file diff --git a/win/C#/HandBrake.ApplicationServices/Functions/Main.cs b/win/C#/HandBrake.ApplicationServices/Functions/Main.cs new file mode 100644 index 000000000..0696101c1 --- /dev/null +++ b/win/C#/HandBrake.ApplicationServices/Functions/Main.cs @@ -0,0 +1,83 @@ +/* Main.cs $
+ This file is part of the HandBrake source code.
+ Homepage: <http://handbrake.fr>.
+ It may be used under the terms of the GNU General Public License. */
+
+namespace HandBrake.ApplicationServices.Functions
+{
+ using System;
+ using System.Collections.Generic;
+ using System.Diagnostics;
+
+ /// <summary>
+ /// Useful functions which various screens can use.
+ /// </summary>
+ public static class Main
+ {
+ /// <summary>
+ /// Get the Process ID of HandBrakeCLI for the current instance.
+ /// </summary>
+ /// <param name="before">List of processes before the new process was started</param>
+ /// <returns>Int - Process ID</returns>
+ public static int GetCliProcess(Process[] before)
+ {
+ // This is a bit of a cludge. Maybe someone has a better idea on how to impliment this.
+ // Since we used CMD to start HandBrakeCLI, we don't get the process ID from hbProc.
+ // Instead we take the processes before and after, and get the ID of HandBrakeCLI.exe
+ // avoiding any previous instances of HandBrakeCLI.exe in before.
+ // Kill the current process.
+
+ DateTime startTime = DateTime.Now;
+ TimeSpan duration;
+
+ Process[] hbProcesses = Process.GetProcessesByName("HandBrakeCLI");
+ while (hbProcesses.Length == 0)
+ {
+ hbProcesses = Process.GetProcessesByName("HandBrakeCLI");
+ duration = DateTime.Now - startTime;
+ if (duration.Seconds > 5 && hbProcesses.Length == 0)
+ // Make sure we don't wait forever if the process doesn't start
+ return -1;
+ }
+
+ Process hbProcess = null;
+ foreach (Process process in hbProcesses)
+ {
+ bool found = false;
+ // Check if the current CLI instance was running before we started the current one
+ foreach (Process bprocess in before)
+ {
+ if (process.Id == bprocess.Id)
+ found = true;
+ }
+
+ // If it wasn't running before, we found the process we want.
+ if (!found)
+ {
+ hbProcess = process;
+ break;
+ }
+ }
+ if (hbProcess != null)
+ return hbProcess.Id;
+
+ return -1;
+ }
+
+ /// <summary>
+ /// Show the Exception Window
+ /// </summary>
+ /// <param name="shortError">
+ /// The short error.
+ /// </param>
+ /// <param name="longError">
+ /// The long error.
+ /// </param>
+ public static void ShowExceptiowWindow(string shortError, string longError)
+ {
+ frmExceptionWindow exceptionWindow = new frmExceptionWindow();
+ exceptionWindow.Setup(shortError, longError);
+ exceptionWindow.Show();
+ }
+ }
+}
\ No newline at end of file diff --git a/win/C#/HandBrake.ApplicationServices/Functions/Win32.cs b/win/C#/HandBrake.ApplicationServices/Functions/Win32.cs new file mode 100644 index 000000000..a3caf38ab --- /dev/null +++ b/win/C#/HandBrake.ApplicationServices/Functions/Win32.cs @@ -0,0 +1,143 @@ +/* win32.cs $
+ This file is part of the HandBrake source code.
+ Homepage: <http://handbrake.fr>.
+ It may be used under the terms of the GNU General Public License. */
+
+namespace HandBrake.ApplicationServices.Functions
+{
+ using System;
+ using System.Runtime.InteropServices;
+
+ /// <summary>
+ /// Win32 API calls
+ /// </summary>
+ public class Win32
+ {
+ /// <summary>
+ /// Set the Forground Window
+ /// </summary>
+ /// <param name="hWnd">
+ /// The h wnd.
+ /// </param>
+ /// <returns>
+ /// A Boolean true when complete.
+ /// </returns>
+ [DllImport("user32.dll")]
+ public static extern bool SetForegroundWindow(int hWnd);
+
+ /// <summary>
+ /// Lock the workstation
+ /// </summary>
+ [DllImport("user32.dll")]
+ public static extern void LockWorkStation();
+
+ /// <summary>
+ /// Exit Windows
+ /// </summary>
+ /// <param name="uFlags">
+ /// The u flags.
+ /// </param>
+ /// <param name="dwReason">
+ /// The dw reason.
+ /// </param>
+ /// <returns>
+ /// an integer
+ /// </returns>
+ [DllImport("user32.dll")]
+ public static extern int ExitWindowsEx(int uFlags, int dwReason);
+
+ /// <summary>
+ /// System Memory Status
+ /// </summary>
+ public struct MEMORYSTATUS // Unused var's are required here.
+ {
+ /// <summary>
+ /// Unknown
+ /// </summary>
+ public UInt32 dwLength;
+
+ /// <summary>
+ /// Memory Load
+ /// </summary>
+ public UInt32 dwMemoryLoad;
+
+ /// <summary>
+ /// Total Physical Memory
+ /// </summary>
+ public UInt32 dwTotalPhys; // Used
+
+ /// <summary>
+ /// Available Physical Memory
+ /// </summary>
+ public UInt32 dwAvailPhys;
+
+ /// <summary>
+ /// Total Page File
+ /// </summary>
+ public UInt32 dwTotalPageFile;
+
+ /// <summary>
+ /// Available Page File
+ /// </summary>
+ public UInt32 dwAvailPageFile;
+
+ /// <summary>
+ /// Total Virtual Memory
+ /// </summary>
+ public UInt32 dwTotalVirtual;
+
+ /// <summary>
+ /// Available Virtual Memory
+ /// </summary>
+ public UInt32 dwAvailVirtual;
+ }
+
+ /// <summary>
+ /// Global Memory Status
+ /// </summary>
+ /// <param name="lpBuffer">
+ /// The lp buffer.
+ /// </param>
+ [DllImport("kernel32.dll")]
+ public static extern void GlobalMemoryStatus
+ (
+ ref MEMORYSTATUS lpBuffer
+ );
+
+ /// <summary>
+ /// Generate a Console Ctrl Event
+ /// </summary>
+ /// <param name="sigevent">
+ /// The sigevent.
+ /// </param>
+ /// <param name="dwProcessGroupId">
+ /// The dw process group id.
+ /// </param>
+ /// <returns>
+ /// Bool true is sucess
+ /// </returns>
+ [DllImport("kernel32.dll", SetLastError = true)]
+ public static extern bool GenerateConsoleCtrlEvent(ConsoleCtrlEvent sigevent, int dwProcessGroupId);
+
+ /// <summary>
+ /// Console Ctrl Event
+ /// </summary>
+ public enum ConsoleCtrlEvent
+ {
+ /// <summary>
+ /// Ctrl - C
+ /// </summary>
+ CTRL_C = 0,
+
+ /// <summary>
+ /// Ctrl - Break
+ /// </summary>
+ CTRL_BREAK = 1,
+
+ /// <summary>
+ /// Ctrl - Close
+ /// </summary>
+ CTRL_CLOSE = 2,
+ }
+ }
+}
diff --git a/win/C#/HandBrake.ApplicationServices/HandBrake.ApplicationServices.csproj b/win/C#/HandBrake.ApplicationServices/HandBrake.ApplicationServices.csproj new file mode 100644 index 000000000..ebeb307dc --- /dev/null +++ b/win/C#/HandBrake.ApplicationServices/HandBrake.ApplicationServices.csproj @@ -0,0 +1,127 @@ +<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="3.5" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <PropertyGroup>
+ <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+ <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+ <ProductVersion>9.0.21022</ProductVersion>
+ <SchemaVersion>2.0</SchemaVersion>
+ <ProjectGuid>{087A2BA8-BAC2-4577-A46F-07FF9D420016}</ProjectGuid>
+ <OutputType>Library</OutputType>
+ <AppDesignerFolder>Properties</AppDesignerFolder>
+ <RootNamespace>HandBrake.ApplicationServices</RootNamespace>
+ <AssemblyName>HandBrake.ApplicationServices</AssemblyName>
+ <TargetFrameworkVersion>v3.5</TargetFrameworkVersion>
+ <FileAlignment>512</FileAlignment>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+ <DebugSymbols>true</DebugSymbols>
+ <DebugType>full</DebugType>
+ <Optimize>false</Optimize>
+ <OutputPath>bin\Debug\</OutputPath>
+ <DefineConstants>DEBUG;TRACE</DefineConstants>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+ <DebugType>pdbonly</DebugType>
+ <Optimize>true</Optimize>
+ <OutputPath>bin\Release\</OutputPath>
+ <DefineConstants>TRACE</DefineConstants>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ </PropertyGroup>
+ <ItemGroup>
+ <Reference Include="Growl.Connector, Version=2.0.0.0, Culture=neutral, PublicKeyToken=980c2339411be384, processorArchitecture=MSIL">
+ <SpecificVersion>False</SpecificVersion>
+ <HintPath>..\libraries\Growl.Connector.dll</HintPath>
+ </Reference>
+ <Reference Include="Growl.CoreLibrary, Version=2.0.0.0, Culture=neutral, PublicKeyToken=13e59d82e007b064, processorArchitecture=MSIL">
+ <SpecificVersion>False</SpecificVersion>
+ <HintPath>..\libraries\Growl.CoreLibrary.dll</HintPath>
+ </Reference>
+ <Reference Include="System" />
+ <Reference Include="System.Core">
+ <RequiredTargetFramework>3.5</RequiredTargetFramework>
+ </Reference>
+ <Reference Include="System.Drawing" />
+ <Reference Include="System.Windows.Forms" />
+ <Reference Include="System.Xml.Linq">
+ <RequiredTargetFramework>3.5</RequiredTargetFramework>
+ </Reference>
+ <Reference Include="System.Data.DataSetExtensions">
+ <RequiredTargetFramework>3.5</RequiredTargetFramework>
+ </Reference>
+ <Reference Include="System.Data" />
+ <Reference Include="System.Xml" />
+ </ItemGroup>
+ <ItemGroup>
+ <Compile Include="frmExceptionWindow.cs">
+ <SubType>Form</SubType>
+ </Compile>
+ <Compile Include="frmExceptionWindow.Designer.cs">
+ <DependentUpon>frmExceptionWindow.cs</DependentUpon>
+ </Compile>
+ <Compile Include="Functions\GrowlCommunicator.cs" />
+ <Compile Include="Functions\Main.cs" />
+ <Compile Include="Functions\Win32.cs" />
+ <Compile Include="Init.cs" />
+ <Compile Include="Model\Cropping.cs" />
+ <Compile Include="Model\Job.cs" />
+ <Compile Include="Model\SubtitleType.cs" />
+ <Compile Include="Parsing\AudioTrack.cs" />
+ <Compile Include="Parsing\Chapter.cs" />
+ <Compile Include="Parsing\DVD.cs" />
+ <Compile Include="Parsing\Parser.cs" />
+ <Compile Include="Parsing\Subtitle.cs" />
+ <Compile Include="Parsing\Title.cs" />
+ <Compile Include="Properties\AssemblyInfo.cs" />
+ <Compile Include="Properties\Resources.Designer.cs">
+ <AutoGen>True</AutoGen>
+ <DesignTime>True</DesignTime>
+ <DependentUpon>Resources.resx</DependentUpon>
+ </Compile>
+ <Compile Include="Properties\Settings.Designer.cs">
+ <AutoGen>True</AutoGen>
+ <DesignTimeSharedInput>True</DesignTimeSharedInput>
+ <DependentUpon>Settings.settings</DependentUpon>
+ </Compile>
+ <Compile Include="Services\Encode.cs" />
+ <Compile Include="Services\Queue.cs" />
+ <Compile Include="Services\Scan.cs" />
+ </ItemGroup>
+ <ItemGroup>
+ <None Include="app.config" />
+ <None Include="Properties\Settings.settings">
+ <Generator>SettingsSingleFileGenerator</Generator>
+ <LastGenOutput>Settings.Designer.cs</LastGenOutput>
+ </None>
+ </ItemGroup>
+ <ItemGroup>
+ <EmbeddedResource Include="frmExceptionWindow.resx">
+ <DependentUpon>frmExceptionWindow.cs</DependentUpon>
+ <SubType>Designer</SubType>
+ </EmbeddedResource>
+ <EmbeddedResource Include="Properties\Resources.resx">
+ <Generator>ResXFileCodeGenerator</Generator>
+ <LastGenOutput>Resources.Designer.cs</LastGenOutput>
+ <SubType>Designer</SubType>
+ </EmbeddedResource>
+ </ItemGroup>
+ <ItemGroup>
+ <None Include="Resources\logo64.png" />
+ </ItemGroup>
+ <ItemGroup>
+ <None Include="Resources\ErrorX.png" />
+ </ItemGroup>
+ <ItemGroup>
+ <None Include="Resources\copy.png" />
+ </ItemGroup>
+ <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+ <!-- To modify your build process, add your task inside one of the targets below and uncomment it.
+ Other similar extension points exist, see Microsoft.Common.targets.
+ <Target Name="BeforeBuild">
+ </Target>
+ <Target Name="AfterBuild">
+ </Target>
+ -->
+</Project>
\ No newline at end of file diff --git a/win/C#/HandBrake.ApplicationServices/Init.cs b/win/C#/HandBrake.ApplicationServices/Init.cs new file mode 100644 index 000000000..a0a85d40e --- /dev/null +++ b/win/C#/HandBrake.ApplicationServices/Init.cs @@ -0,0 +1,69 @@ +/* Init.cs $
+ This file is part of the HandBrake source code.
+ Homepage: <http://handbrake.fr>.
+ It may be used under the terms of the GNU General Public License. */
+
+namespace HandBrake.ApplicationServices
+{
+ /// <summary>
+ /// Initialize ApplicationServices
+ /// </summary>
+ public class Init
+ {
+ /// <summary>
+ /// Setup the Settings used by the applicaiton with this library
+ /// </summary>
+ /// <param name="cli_minimized">
+ /// The cli_minimized.
+ /// </param>
+ /// <param name="completionOption">
+ /// The completion option.
+ /// </param>
+ /// <param name="disableDvdNav">
+ /// The disable dvd nav.
+ /// </param>
+ /// <param name="enocdeStatusInGui">
+ /// The enocde status in gui.
+ /// </param>
+ /// <param name="growlEncode">
+ /// The growl encode.
+ /// </param>
+ /// <param name="growlQueue">
+ /// The growl queue.
+ /// </param>
+ /// <param name="processPriority">
+ /// The process priority.
+ /// </param>
+ /// <param name="saveLogPath">
+ /// The save log path.
+ /// </param>
+ /// <param name="saveLogToSpecifiedPath">
+ /// The save log to specified path.
+ /// </param>
+ /// <param name="saveLogWithVideo">
+ /// The save log with video.
+ /// </param>
+ /// <param name="showCliForInGuiEncodeStatus">
+ /// The show cli for in gui encode status.
+ /// </param>
+ public static void SetupSettings(bool cli_minimized, string completionOption, bool disableDvdNav, bool enocdeStatusInGui,
+ bool growlEncode, bool growlQueue, string processPriority, string saveLogPath, bool saveLogToSpecifiedPath,
+ bool saveLogWithVideo, bool showCliForInGuiEncodeStatus)
+ {
+ Properties.Settings.Default.cli_minimized = cli_minimized;
+ Properties.Settings.Default.CompletionOption = completionOption;
+ Properties.Settings.Default.disableDvdNav = disableDvdNav;
+ Properties.Settings.Default.enocdeStatusInGui = enocdeStatusInGui;
+ Properties.Settings.Default.growlEncode = growlEncode;
+ Properties.Settings.Default.growlQueue = growlQueue;
+ Properties.Settings.Default.processPriority = processPriority;
+ Properties.Settings.Default.saveLogPath = saveLogPath;
+ Properties.Settings.Default.saveLogToSpecifiedPath = saveLogToSpecifiedPath;
+ Properties.Settings.Default.saveLogWithVideo = saveLogWithVideo;
+ Properties.Settings.Default.showCliForInGuiEncodeStatus = showCliForInGuiEncodeStatus;
+
+ Properties.Settings.Default.Save();
+ }
+
+ }
+}
diff --git a/win/C#/HandBrake.ApplicationServices/Model/Cropping.cs b/win/C#/HandBrake.ApplicationServices/Model/Cropping.cs new file mode 100644 index 000000000..ae39636f9 --- /dev/null +++ b/win/C#/HandBrake.ApplicationServices/Model/Cropping.cs @@ -0,0 +1,56 @@ +/* Cropping.cs $
+ This file is part of the HandBrake source code.
+ Homepage: <http://handbrake.fr>.
+ It may be used under the terms of the GNU General Public License. */
+
+namespace HandBrake.ApplicationServices.Model
+{
+ /// <summary>
+ /// Cropping T B L R
+ /// </summary>
+ public class Cropping
+ {
+ /// <summary>
+ /// Gets or sets Top.
+ /// </summary>
+ public int Top { get; set; }
+
+ /// <summary>
+ /// Gets or sets Bottom.
+ /// </summary>
+ public int Bottom { get; set; }
+
+ /// <summary>
+ /// Gets or sets Left.
+ /// </summary>
+ public int Left { get; set; }
+
+ /// <summary>
+ /// Gets or sets Right.
+ /// </summary>
+ public int Right { get; set; }
+
+ /// <summary>
+ /// Create a cropping object
+ /// </summary>
+ /// <param name="top">
+ /// The top.
+ /// </param>
+ /// <param name="bottom">
+ /// The bottom.
+ /// </param>
+ /// <param name="left">
+ /// The left.
+ /// </param>
+ /// <param name="right">
+ /// The right.
+ /// </param>
+ /// <returns>
+ /// A Cropping object
+ /// </returns>
+ public static Cropping CreateCroppingObject(int top, int bottom, int left, int right)
+ {
+ return new Cropping { Top = top, Bottom = bottom, Left = left, Right = right };
+ }
+ }
+}
diff --git a/win/C#/HandBrake.ApplicationServices/Model/Job.cs b/win/C#/HandBrake.ApplicationServices/Model/Job.cs new file mode 100644 index 000000000..9d4b37510 --- /dev/null +++ b/win/C#/HandBrake.ApplicationServices/Model/Job.cs @@ -0,0 +1,55 @@ +/* QueueItem.cs $
+ This file is part of the HandBrake source code.
+ Homepage: <http://handbrake.fr>.
+ It may be used under the terms of the GNU General Public License. */
+
+namespace HandBrake.ApplicationServices.Model
+{
+ /// <summary>
+ /// The job.
+ /// </summary>
+ public struct Job
+ {
+ /// <summary>
+ /// Gets or sets the job ID.
+ /// </summary>
+ public int Id { get; set; }
+
+ /// <summary>
+ /// Gets or sets the selected Title.
+ /// </summary>
+ public int Title { get; set; }
+
+ /// <summary>
+ /// Gets or sets the query string.
+ /// </summary>
+ public string Query { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether if this is a user or GUI generated query
+ /// </summary>
+ public bool CustomQuery { get; set; }
+
+ /// <summary>
+ /// Gets or sets the source file of encoding.
+ /// </summary>
+ public string Source { get; set; }
+
+ /// <summary>
+ /// Gets or sets the destination for the file to be encoded.
+ /// </summary>
+ public string Destination { get; set; }
+
+ /// <summary>
+ /// Gets a value indicating whether or not this instance is empty.
+ /// </summary>
+ public bool IsEmpty
+ {
+ get
+ {
+ return this.Id == 0 && string.IsNullOrEmpty(this.Query) && string.IsNullOrEmpty(this.Source) &&
+ string.IsNullOrEmpty(this.Destination);
+ }
+ }
+ }
+}
\ No newline at end of file diff --git a/win/C#/HandBrake.ApplicationServices/Model/SubtitleType.cs b/win/C#/HandBrake.ApplicationServices/Model/SubtitleType.cs new file mode 100644 index 000000000..b7ceabdbf --- /dev/null +++ b/win/C#/HandBrake.ApplicationServices/Model/SubtitleType.cs @@ -0,0 +1,18 @@ +/* SubtitleType.cs $
+ This file is part of the HandBrake source code.
+ Homepage: <http://handbrake.fr>.
+ It may be used under the terms of the GNU General Public License. */
+
+namespace HandBrake.ApplicationServices.Model
+{
+ /// <summary>
+ /// Subtitle Type.
+ /// 0: Picture
+ /// 1: Text
+ /// </summary>
+ public enum SubtitleType
+ {
+ Picture,
+ Text
+ }
+}
diff --git a/win/C#/HandBrake.ApplicationServices/Parsing/AudioTrack.cs b/win/C#/HandBrake.ApplicationServices/Parsing/AudioTrack.cs new file mode 100644 index 000000000..f83660bb8 --- /dev/null +++ b/win/C#/HandBrake.ApplicationServices/Parsing/AudioTrack.cs @@ -0,0 +1,171 @@ +/* AudioTrack.cs $
+ This file is part of the HandBrake source code.
+ Homepage: <http://handbrake.fr>.
+ It may be used under the terms of the GNU General Public License. */
+
+namespace HandBrake.ApplicationServices.Parsing
+{
+ using System.Collections.Generic;
+ using System.IO;
+ using System.Text.RegularExpressions;
+
+ /// <summary>
+ /// An object represending an AudioTrack associated with a Title, in a DVD
+ /// </summary>
+ public class AudioTrack
+ {
+ /// <summary>
+ /// Gets The track number of this Audio Track
+ /// </summary>
+ public int TrackNumber { get; private set; }
+
+ /// <summary>
+ /// Gets The language (if detected) of this Audio Track
+ /// </summary>
+ public string Language { get; private set; }
+
+ /// <summary>
+ /// Gets or sets LanguageCode.
+ /// </summary>
+ public string LanguageCode { get; set; }
+
+ /// <summary>
+ /// Gets or sets Description.
+ /// </summary>
+ public string Description { get; set; }
+
+ /// <summary>
+ /// Gets The primary format of this Audio Track
+ /// </summary>
+ public string Format { get; private set; }
+
+ /// <summary>
+ /// Gets The frequency (in MHz) of this Audio Track
+ /// </summary>
+ public int SampleRate { get; private set; }
+
+ /// <summary>
+ /// Gets The bitrate (in kbps) of this Audio Track
+ /// </summary>
+ public int Bitrate { get; private set; }
+
+ /// <summary>
+ /// Create a new Audio Track object
+ /// </summary>
+ /// <param name="track">
+ /// The track.
+ /// </param>
+ /// <param name="lang">
+ /// The lang.
+ /// </param>
+ /// <param name="langCode">
+ /// The lang code.
+ /// </param>
+ /// <param name="desc">
+ /// The desc.
+ /// </param>
+ /// <param name="format">
+ /// The format.
+ /// </param>
+ /// <param name="samplerate">
+ /// The samplerate.
+ /// </param>
+ /// <param name="bitrate">
+ /// The bitrate.
+ /// </param>
+ /// <returns>
+ /// A new Audio Track
+ /// </returns>
+ public static AudioTrack CreateAudioTrack(int track, string lang, string langCode, string desc, string format, int samplerate, int bitrate)
+ {
+ AudioTrack newTrack = new AudioTrack
+ {
+ TrackNumber = track,
+ Language = lang,
+ LanguageCode = langCode,
+ Description = desc,
+ Format = format,
+ SampleRate = samplerate,
+ Bitrate = bitrate
+ };
+
+ return newTrack;
+
+ }
+
+ /// <summary>
+ /// Parse the CLI input to an Audio Track object
+ /// </summary>
+ /// <param name="output">
+ /// The output.
+ /// </param>
+ /// <returns>
+ /// An Audio Track obkect
+ /// </returns>
+ public static AudioTrack Parse(StringReader output)
+ {
+ string audioTrack = output.ReadLine();
+ Match m = Regex.Match(audioTrack, @"^ \+ ([0-9]*), ([A-Za-z0-9,\s]*) \((.*)\) \((.*)\)");
+ Match track = Regex.Match(audioTrack, @"^ \+ ([0-9]*), ([A-Za-z0-9,\s]*) \((.*)\)"); // ID and Language
+ Match iso639_2 = Regex.Match(audioTrack, @"iso639-2: ([a-zA-Z]*)\)");
+ Match samplerate = Regex.Match(audioTrack, @"([0-9]*)Hz");
+ Match bitrate = Regex.Match(audioTrack, @"([0-9]*)bps");
+
+ string subformat = m.Groups[4].Value.Trim().Contains("iso639") ? null : m.Groups[4].Value;
+ string samplerateVal = samplerate.Success ? samplerate.Groups[0].Value.Replace("Hz", string.Empty).Trim() : "0";
+ string bitrateVal = bitrate.Success ? bitrate.Groups[0].Value.Replace("bps", string.Empty).Trim() : "0";
+
+ if (track.Success)
+ {
+ var thisTrack = new AudioTrack
+ {
+ TrackNumber = int.Parse(track.Groups[1].Value.Trim()),
+ Language = track.Groups[2].Value,
+ Format = m.Groups[3].Value,
+ Description = subformat,
+ SampleRate = int.Parse(samplerateVal),
+ Bitrate = int.Parse(bitrateVal),
+ LanguageCode = iso639_2.Value.Replace("iso639-2: ", string.Empty).Replace(")", string.Empty)
+ };
+ return thisTrack;
+ }
+
+ return null;
+ }
+
+ /// <summary>
+ /// Pase a list of audio tracks
+ /// </summary>
+ /// <param name="output">
+ /// The output.
+ /// </param>
+ /// <returns>
+ /// An array of audio tracks
+ /// </returns>
+ public static AudioTrack[] ParseList(StringReader output)
+ {
+ var tracks = new List<AudioTrack>();
+ while (true)
+ {
+ AudioTrack thisTrack = Parse(output);
+ if (thisTrack != null)
+ tracks.Add(thisTrack);
+ else
+ break;
+ }
+ return tracks.ToArray();
+ }
+
+ /// <summary>
+ /// Override of the ToString method to make this object easier to use in the UI
+ /// </summary>
+ /// <returns>A string formatted as: {track #} {language} ({format}) ({sub-format})</returns>
+ public override string ToString()
+ {
+ if (Description == null)
+ return string.Format("{0} {1} ({2})", TrackNumber, Language, Format);
+
+ return string.Format("{0} {1} ({2}) ({3})", TrackNumber, Language, Format, Description);
+ }
+ }
+}
\ No newline at end of file diff --git a/win/C#/HandBrake.ApplicationServices/Parsing/Chapter.cs b/win/C#/HandBrake.ApplicationServices/Parsing/Chapter.cs new file mode 100644 index 000000000..b39f2baa4 --- /dev/null +++ b/win/C#/HandBrake.ApplicationServices/Parsing/Chapter.cs @@ -0,0 +1,110 @@ +/* Chapter.cs $
+ This file is part of the HandBrake source code.
+ Homepage: <http://handbrake.fr>.
+ It may be used under the terms of the GNU General Public License. */
+
+namespace HandBrake.ApplicationServices.Parsing
+{
+ using System;
+ using System.Collections.Generic;
+ using System.IO;
+ using System.Text.RegularExpressions;
+
+ /// <summary>
+ /// An object representing a Chapter aosciated with a Title, in a DVD
+ /// </summary>
+ public class Chapter
+ {
+ /// <summary>
+ /// Gets The number of this Chapter, in regards to it's parent Title
+ /// </summary>
+ public int ChapterNumber { get; private set; }
+
+ /// <summary>
+ /// Gets The length in time this Chapter spans
+ /// </summary>
+ public TimeSpan Duration { get; private set; }
+
+ /// <summary>
+ /// Create a chapter Object
+ /// </summary>
+ /// <param name="number">
+ /// The number.
+ /// </param>
+ /// <param name="duration">
+ /// The duration.
+ /// </param>
+ /// <returns>
+ /// A new Chapter Object
+ /// </returns>
+ public static Chapter CreateChapterOjbect(int number, TimeSpan duration)
+ {
+ return new Chapter { ChapterNumber = number, Duration = duration };
+ }
+
+ /// <summary>
+ /// Parse a CLI string to a Chapter object
+ /// </summary>
+ /// <param name="output">
+ /// The output.
+ /// </param>
+ /// <returns>
+ /// A chapter Object
+ /// </returns>
+ public static Chapter Parse(StringReader output)
+ {
+ Match m = Regex.Match(
+ output.ReadLine(),
+ @"^ \+ ([0-9]*): cells ([0-9]*)->([0-9]*), ([0-9]*) blocks, duration ([0-9]{2}:[0-9]{2}:[0-9]{2})");
+ if (m.Success)
+ {
+ var thisChapter = new Chapter
+ {
+ ChapterNumber = int.Parse(m.Groups[1].Value.Trim()),
+ Duration = TimeSpan.Parse(m.Groups[5].Value)
+ };
+ return thisChapter;
+ }
+ return null;
+ }
+
+ /// <summary>
+ /// Prase a list of strings / chatpers
+ /// </summary>
+ /// <param name="output">
+ /// The output.
+ /// </param>
+ /// <returns>
+ /// An array of chapter objects
+ /// </returns>
+ public static Chapter[] ParseList(StringReader output)
+ {
+ var chapters = new List<Chapter>();
+
+ // this is to read the " + chapters:" line from the buffer
+ // so we can start reading the chapters themselvs
+ output.ReadLine();
+
+ while (true)
+ {
+ // Start of the chapter list for this Title
+ Chapter thisChapter = Parse(output);
+
+ if (thisChapter != null)
+ chapters.Add(thisChapter);
+ else
+ break;
+ }
+ return chapters.ToArray();
+ }
+
+ /// <summary>
+ /// Override of the ToString method to make this object easier to use in the UI
+ /// </summary>
+ /// <returns>A string formatted as: {chapter #}</returns>
+ public override string ToString()
+ {
+ return ChapterNumber.ToString();
+ }
+ }
+}
\ No newline at end of file diff --git a/win/C#/HandBrake.ApplicationServices/Parsing/DVD.cs b/win/C#/HandBrake.ApplicationServices/Parsing/DVD.cs new file mode 100644 index 000000000..c7e5a27bb --- /dev/null +++ b/win/C#/HandBrake.ApplicationServices/Parsing/DVD.cs @@ -0,0 +1,53 @@ +/* DVD.cs $ This file is part of the HandBrake source code.
+ Homepage: <http://handbrake.fr>.
+ It may be used under the terms of the GNU General Public License. */
+
+namespace HandBrake.ApplicationServices.Parsing
+{
+ using System.Collections.Generic;
+ using System.IO;
+
+ /// <summary>
+ /// An object representing a scanned DVD
+ /// </summary>
+ public class DVD
+ {
+ /// <summary>
+ /// Initializes a new instance of the <see cref="DVD"/> class.
+ /// Default constructor for this object
+ /// </summary>
+ public DVD()
+ {
+ Titles = new List<Title>();
+ }
+
+ /// <summary>
+ /// Gets Titles. A list of titles from the source
+ /// </summary>
+ public List<Title> Titles { get; private set; }
+
+ /// <summary>
+ /// Parse the StreamReader output into a List of Titles
+ /// </summary>
+ /// <param name="output">
+ /// The output.
+ /// </param>
+ /// <returns>
+ /// A DVD object which contains a list of title inforamtion
+ /// </returns>
+ public static DVD Parse(StreamReader output)
+ {
+ var thisDVD = new DVD();
+
+ while (!output.EndOfStream)
+ {
+ if ((char) output.Peek() == '+')
+ thisDVD.Titles.AddRange(Title.ParseList(output.ReadToEnd()));
+ else
+ output.ReadLine();
+ }
+
+ return thisDVD;
+ }
+ }
+}
\ No newline at end of file diff --git a/win/C#/HandBrake.ApplicationServices/Parsing/Parser.cs b/win/C#/HandBrake.ApplicationServices/Parsing/Parser.cs new file mode 100644 index 000000000..1e6329db1 --- /dev/null +++ b/win/C#/HandBrake.ApplicationServices/Parsing/Parser.cs @@ -0,0 +1,159 @@ +/* Parser.cs $
+ This file is part of the HandBrake source code.
+ Homepage: <http://handbrake.fr>.
+ It may be used under the terms of the GNU General Public License. */
+
+namespace HandBrake.ApplicationServices.Parsing
+{
+ using System;
+ using System.Globalization;
+ using System.IO;
+ using System.Text;
+ using System.Text.RegularExpressions;
+
+ /// <summary>
+ /// A delegate to handle custom events regarding data being parsed from the buffer
+ /// </summary>
+ /// <param name="sender">The object which raised this delegate</param>
+ /// <param name="data">The data parsed from the stream</param>
+ public delegate void DataReadEventHandler(object sender, string data);
+
+ /// <summary>
+ /// A delegate to handle events regarding progress during DVD scanning
+ /// </summary>
+ /// <param name="sender">The object who's raising the event</param>
+ /// <param name="currentTitle">The title number currently being processed</param>
+ /// <param name="titleCount">The total number of titiles to be processed</param>
+ public delegate void ScanProgressEventHandler(object sender, int currentTitle, int titleCount);
+
+ /// <summary>
+ /// A delegate to handle encode progress updates // EXPERIMENTAL
+ /// </summary>
+ /// <param name="sender">The object which raised the event</param>
+ /// <param name="currentTask">The current task being processed from the queue</param>
+ /// <param name="taskCount">The total number of tasks in queue</param>
+ /// <param name="percentComplete">The percentage this task is complete</param>
+ /// <param name="currentFps">The current encoding fps</param>
+ /// <param name="averageFps">The average encoding fps for this task</param>
+ /// <param name="timeRemaining">The estimated time remaining for this task to complete</param>
+ public delegate void EncodeProgressEventHandler(object sender, int currentTask, int taskCount, float percentComplete, float currentFps, float averageFps, TimeSpan timeRemaining);
+
+ /// <summary>
+ /// A simple wrapper around a StreamReader to keep track of the entire output from a cli process
+ /// </summary>
+ public class Parser : StreamReader
+ {
+ /// <summary>
+ /// The Buffer StringBuilder
+ /// </summary>
+ private readonly StringBuilder buffer = new StringBuilder(string.Empty);
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="Parser"/> class.
+ /// Default constructor for this object
+ /// </summary>
+ /// <param name="baseStream">
+ /// The stream to parse from
+ /// </param>
+ public Parser(Stream baseStream) : base(baseStream)
+ {
+ }
+
+ /// <summary>
+ /// Raised upon a new line being read from stdout/stderr
+ /// </summary>
+ public event DataReadEventHandler OnReadLine;
+
+ /// <summary>
+ /// Raised upon the entire stdout/stderr stream being read in a single call
+ /// </summary>
+ public event DataReadEventHandler OnReadToEnd;
+
+ /// <summary>
+ /// Raised upon the catching of a "Scanning title # of #..." in the stream
+ /// </summary>
+ public event ScanProgressEventHandler OnScanProgress;
+
+ /// <summary>
+ /// Raised upon the catching of a "Scanning title # of #..." in the stream
+ /// </summary>
+ public event EncodeProgressEventHandler OnEncodeProgress;
+
+ /// <summary>
+ /// Gets the buffer of data that came from the CLI standard input/error
+ /// </summary>
+ public StringBuilder Buffer
+ {
+ get { return buffer; }
+ }
+
+ /// <summary>
+ /// Read a line from standard in/err
+ /// </summary>
+ /// <returns>
+ /// The read line
+ /// </returns>
+ public override string ReadLine()
+ {
+ string tmp = base.ReadLine();
+
+ buffer.Append(tmp + Environment.NewLine);
+
+ Match m = null;
+ if (tmp.Contains("Scanning title"))
+ m = Regex.Match(tmp, "^Scanning title ([0-9]*) of ([0-9]*)");
+
+ if (OnReadLine != null)
+ OnReadLine(this, tmp);
+
+ if (m != null)
+ if (m.Success && OnScanProgress != null)
+ OnScanProgress(this, int.Parse(m.Groups[1].Value), int.Parse(m.Groups[2].Value));
+
+ return tmp;
+ }
+
+ /// <summary>
+ /// Read to the end of the input stream
+ /// </summary>
+ /// <returns>
+ /// A string of the input data
+ /// </returns>
+ public override string ReadToEnd()
+ {
+ string tmp = base.ReadToEnd();
+
+ buffer.Append(tmp + Environment.NewLine);
+
+ if (OnReadToEnd != null)
+ OnReadToEnd(this, tmp);
+
+ return tmp;
+ }
+
+ /// <summary>
+ /// Pase the CLI status output (from standard output)
+ /// </summary>
+ public void ReadEncodeStatus()
+ {
+ CultureInfo culture = CultureInfo.CreateSpecificCulture("en-US");
+ string tmp = base.ReadLine();
+
+ Match m = Regex.Match(tmp, @"^Encoding: task ([0-9]*) of ([0-9]*), ([0-9]*\.[0-9]*) %( \(([0-9]*\.[0-9]*) fps, avg ([0-9]*\.[0-9]*) fps, ETA ([0-9]{2})h([0-9]{2})m([0-9]{2})s\))?");
+ if (m.Success && OnEncodeProgress != null)
+ {
+ int currentTask = int.Parse(m.Groups[1].Value);
+ int totalTasks = int.Parse(m.Groups[2].Value);
+ float percent = float.Parse(m.Groups[3].Value, culture);
+ float currentFps = m.Groups[5].Value == string.Empty ? 0.0F : float.Parse(m.Groups[5].Value, culture);
+ float avgFps = m.Groups[6].Value == string.Empty ? 0.0F : float.Parse(m.Groups[6].Value, culture);
+ TimeSpan remaining = TimeSpan.Zero;
+ if (m.Groups[7].Value != string.Empty)
+ {
+ remaining = TimeSpan.Parse(m.Groups[7].Value + ":" + m.Groups[8].Value + ":" + m.Groups[9].Value);
+ }
+ OnEncodeProgress(this, currentTask, totalTasks, percent, currentFps, avgFps, remaining);
+ }
+ }
+ }
+}
\ No newline at end of file diff --git a/win/C#/HandBrake.ApplicationServices/Parsing/Subtitle.cs b/win/C#/HandBrake.ApplicationServices/Parsing/Subtitle.cs new file mode 100644 index 000000000..31ec350fa --- /dev/null +++ b/win/C#/HandBrake.ApplicationServices/Parsing/Subtitle.cs @@ -0,0 +1,134 @@ +/* Subtitle.cs $
+ This file is part of the HandBrake source code.
+ Homepage: <http://handbrake.fr>.
+ It may be used under the terms of the GNU General Public License. */
+
+namespace HandBrake.ApplicationServices.Parsing
+{
+ using System.Collections.Generic;
+ using System.IO;
+ using System.Text.RegularExpressions;
+
+ using HandBrake.ApplicationServices.Model;
+
+ /// <summary>
+ /// An object that represents a subtitle associated with a Title, in a DVD
+ /// </summary>
+ public class Subtitle
+ {
+ /// <summary>
+ /// Gets the track number of this Subtitle
+ /// </summary>
+ public int TrackNumber { get; private set; }
+
+ /// <summary>
+ /// Gets the The language (if detected) of this Subtitle
+ /// </summary>
+ public string Language { get; private set; }
+
+ /// <summary>
+ /// Gets the Langauage Code
+ /// </summary>
+ public string LanguageCode { get; private set; }
+
+ /// <summary>
+ /// Gets the Subtitle Type
+ /// </summary>
+ public SubtitleType SubtitleType { get; private set; }
+
+ /// <summary>
+ /// Gets Subtitle Type
+ /// </summary>
+ public string TypeString
+ {
+ get
+ {
+ return this.SubtitleType == SubtitleType.Picture ? "Bitmap" : "Text";
+ }
+ }
+
+ /// <summary>
+ /// Create a new Subtitle Object
+ /// </summary>
+ /// <param name="track">
+ /// The track.
+ /// </param>
+ /// <param name="lang">
+ /// The lang.
+ /// </param>
+ /// <param name="langCode">
+ /// The lang code.
+ /// </param>
+ /// <param name="type">
+ /// The type.
+ /// </param>
+ /// <returns>
+ /// A Subtitle Object
+ /// </returns>
+ public static Subtitle CreateSubtitleObject(int track, string lang, string langCode, SubtitleType type)
+ {
+ return new Subtitle { TrackNumber = track, Language = lang, LanguageCode = langCode, SubtitleType = type };
+ }
+
+ /// <summary>
+ /// Parse the input strings related to subtitles
+ /// </summary>
+ /// <param name="output">
+ /// The output.
+ /// </param>
+ /// <returns>
+ /// A Subitle object
+ /// </returns>
+ public static Subtitle Parse(StringReader output)
+ {
+ string curLine = output.ReadLine();
+
+ Match m = Regex.Match(curLine, @"^ \+ ([0-9]*), ([A-Za-z, ]*) \((.*)\) \(([a-zA-Z]*)\)");
+ if (m.Success && !curLine.Contains("HandBrake has exited."))
+ {
+ var thisSubtitle = new Subtitle
+ {
+ TrackNumber = int.Parse(m.Groups[1].Value.Trim()),
+ Language = m.Groups[2].Value,
+ LanguageCode = m.Groups[3].Value,
+ SubtitleType = m.Groups[4].Value.Contains("Text") ? SubtitleType.Text : SubtitleType.Picture
+ };
+ return thisSubtitle;
+ }
+ return null;
+ }
+
+ /// <summary>
+ /// Parse a list of Subtitle tracks from an input string.
+ /// </summary>
+ /// <param name="output">
+ /// The output.
+ /// </param>
+ /// <returns>
+ /// An array of Subtitle objects
+ /// </returns>
+ public static IEnumerable<Subtitle> ParseList(StringReader output)
+ {
+ var subtitles = new List<Subtitle>();
+ while ((char) output.Peek() != '+')
+ {
+ Subtitle thisSubtitle = Parse(output);
+
+ if (thisSubtitle != null)
+ subtitles.Add(thisSubtitle);
+ else
+ break;
+ }
+ return subtitles.ToArray();
+ }
+
+ /// <summary>
+ /// Override of the ToString method to make this object easier to use in the UI
+ /// </summary>
+ /// <returns>A string formatted as: {track #} {language}</returns>
+ public override string ToString()
+ {
+ return string.Format("{0} {1} ({2})", TrackNumber, Language, TypeString);
+ }
+ }
+}
\ No newline at end of file diff --git a/win/C#/HandBrake.ApplicationServices/Parsing/Title.cs b/win/C#/HandBrake.ApplicationServices/Parsing/Title.cs new file mode 100644 index 000000000..0aad4d5f3 --- /dev/null +++ b/win/C#/HandBrake.ApplicationServices/Parsing/Title.cs @@ -0,0 +1,291 @@ +/* Title.cs $
+ This file is part of the HandBrake source code.
+ Homepage: <http://handbrake.fr>.
+ It may be used under the terms of the GNU General Public License. */
+
+namespace HandBrake.ApplicationServices.Parsing
+{
+ using System;
+ using System.Collections.Generic;
+ using System.Drawing;
+ using System.Globalization;
+ using System.IO;
+ using System.Text.RegularExpressions;
+
+ using HandBrake.ApplicationServices.Model;
+
+ /// <summary>
+ /// An object that represents a single Title of a DVD
+ /// </summary>
+ public class Title
+ {
+ /// <summary>
+ /// The Culture Info
+ /// </summary>
+ private static readonly CultureInfo Culture = new CultureInfo("en-US", false);
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="Title"/> class.
+ /// </summary>
+ public Title()
+ {
+ AudioTracks = new List<AudioTrack>();
+ Chapters = new List<Chapter>();
+ Subtitles = new List<Subtitle>();
+ }
+
+ #region Properties
+
+ /// <summary>
+ /// Gets a Collection of chapters in this Title
+ /// </summary>
+ public List<Chapter> Chapters { get; private set; }
+
+ /// <summary>
+ /// Gets a Collection of audio tracks associated with this Title
+ /// </summary>
+ public List<AudioTrack> AudioTracks { get; private set; }
+
+ /// <summary>
+ /// Gets aCollection of subtitles associated with this Title
+ /// </summary>
+ public List<Subtitle> Subtitles { get; private set; }
+
+ /// <summary>
+ /// Gets The track number of this Title
+ /// </summary>
+ public int TitleNumber { get; private set; }
+
+ /// <summary>
+ /// Gets the length in time of this Title
+ /// </summary>
+ public TimeSpan Duration { get; private set; }
+
+ /// <summary>
+ /// Gets the resolution (width/height) of this Title
+ /// </summary>
+ public Size Resolution { get; private set; }
+
+ /// <summary>
+ /// Gets the aspect ratio of this Title
+ /// </summary>
+ public double AspectRatio { get; private set; }
+
+ /// <summary>
+ /// Gets AngleCount.
+ /// </summary>
+ public int AngleCount { get; private set; }
+
+ /// <summary>
+ /// Gets Par Value
+ /// </summary>
+ public Size ParVal { get; private set; }
+
+ /// <summary>
+ /// Gets the automatically detected crop region for this Title.
+ /// This is an int array with 4 items in it as so:
+ /// 0: T
+ /// 1: B
+ /// 2: L
+ /// 3: R
+ /// </summary>
+ public Cropping AutoCropDimensions { get; private set; }
+
+ /// <summary>
+ /// Gets the FPS of the source.
+ /// </summary>
+ public double Fps { get; private set; }
+
+ /// <summary>
+ /// Gets a value indicating whether this is a MainTitle.
+ /// </summary>
+ public bool MainTitle { get; private set; }
+
+ /// <summary>
+ /// Gets the Source Name
+ /// </summary>
+ public string SourceName { get; private set; }
+
+ #endregion
+
+ /// <summary>
+ /// Creates a Title
+ /// </summary>
+ /// <param name="angles">
+ /// The angles.
+ /// </param>
+ /// <param name="aspectRatio">
+ /// The aspect Ratio.
+ /// </param>
+ /// <param name="audioTracks">
+ /// The audio Tracks.
+ /// </param>
+ /// <param name="crop">
+ /// The crop.
+ /// </param>
+ /// <param name="chapters">
+ /// The chapters.
+ /// </param>
+ /// <param name="duration">
+ /// The duration.
+ /// </param>
+ /// <param name="fps">
+ /// The fps.
+ /// </param>
+ /// <param name="mainTitle">
+ /// The main Title.
+ /// </param>
+ /// <param name="parVal">
+ /// The par Val.
+ /// </param>
+ /// <param name="resolution">
+ /// The resolution.
+ /// </param>
+ /// <param name="sourceName">
+ /// The source Name.
+ /// </param>
+ /// <param name="subtitles">
+ /// The subtitles.
+ /// </param>
+ /// <param name="titleNumber">
+ /// The title Number.
+ /// </param>
+ /// <returns>
+ /// A Title Object
+ /// </returns>
+ public static Title CreateTitle(int angles, double aspectRatio, List<AudioTrack> audioTracks, Cropping crop, List<Chapter> chapters,
+ TimeSpan duration, float fps, bool mainTitle, Size parVal, Size resolution, string sourceName, List<Subtitle> subtitles,
+ int titleNumber)
+ {
+ Title title = new Title
+ {
+ AngleCount = angles,
+ AspectRatio = aspectRatio,
+ AudioTracks = audioTracks,
+ AutoCropDimensions = crop,
+ Chapters = chapters,
+ Duration = duration,
+ Fps = fps,
+ MainTitle = mainTitle,
+ ParVal = parVal,
+ Resolution = resolution,
+ SourceName = sourceName,
+ Subtitles = subtitles,
+ TitleNumber = titleNumber
+ };
+
+ return title;
+ }
+
+ /// <summary>
+ /// Parse the Title Information
+ /// </summary>
+ /// <param name="output">A stingreader of output data</param>
+ /// <returns>A Title</returns>
+ public static Title Parse(StringReader output)
+ {
+ var thisTitle = new Title();
+
+ Match m = Regex.Match(output.ReadLine(), @"^\+ title ([0-9]*):");
+ // Match track number for this title
+ if (m.Success)
+ thisTitle.TitleNumber = int.Parse(m.Groups[1].Value.Trim());
+
+ // If we are scanning a groupd of files, we'll want to get the source name.
+ string path = output.ReadLine();
+
+ m = Regex.Match(path, @" \+ Main Feature");
+ if (m.Success)
+ {
+ thisTitle.MainTitle = true;
+ path = output.ReadLine();
+ }
+
+ m = Regex.Match(path, @"^ \+ stream:");
+ if (m.Success)
+ thisTitle.SourceName = path.Replace("+ stream:", string.Empty).Trim();
+
+ if (!Properties.Settings.Default.disableDvdNav)
+ {
+ // Get the Angles for the title.
+ m = Regex.Match(output.ReadLine(), @" \+ angle\(s\) ([0-9])");
+ if (m.Success)
+ {
+ string angleList = m.Value.Replace("+ angle(s) ", string.Empty).Trim();
+ int angleCount;
+ int.TryParse(angleList, out angleCount);
+
+ thisTitle.AngleCount = angleCount;
+ }
+ }
+
+ // Get duration for this title
+ m = Regex.Match(output.ReadLine(), @"^ \+ duration: ([0-9]{2}:[0-9]{2}:[0-9]{2})");
+ if (m.Success)
+ thisTitle.Duration = TimeSpan.Parse(m.Groups[1].Value);
+
+ // Get resolution, aspect ratio and FPS for this title
+ m = Regex.Match(output.ReadLine(), @"^ \+ size: ([0-9]*)x([0-9]*), pixel aspect: ([0-9]*)/([0-9]*), display aspect: ([0-9]*\.[0-9]*), ([0-9]*\.[0-9]*) fps");
+ if (m.Success)
+ {
+ thisTitle.Resolution = new Size(int.Parse(m.Groups[1].Value), int.Parse(m.Groups[2].Value));
+ thisTitle.ParVal = new Size(int.Parse(m.Groups[3].Value), int.Parse(m.Groups[4].Value));
+ thisTitle.AspectRatio = float.Parse(m.Groups[5].Value, Culture);
+ thisTitle.Fps = float.Parse(m.Groups[6].Value, Culture);
+ }
+
+ // Get autocrop region for this title
+ m = Regex.Match(output.ReadLine(), @"^ \+ autocrop: ([0-9]*)/([0-9]*)/([0-9]*)/([0-9]*)");
+ if (m.Success)
+ {
+ thisTitle.AutoCropDimensions = new Cropping
+ {
+ Top = int.Parse(m.Groups[1].Value),
+ Bottom = int.Parse(m.Groups[2].Value),
+ Left = int.Parse(m.Groups[3].Value),
+ Right = int.Parse(m.Groups[4].Value)
+ };
+ }
+
+ thisTitle.Chapters.AddRange(Chapter.ParseList(output));
+
+ thisTitle.AudioTracks.AddRange(AudioTrack.ParseList(output));
+
+ thisTitle.Subtitles.AddRange(Subtitle.ParseList(output));
+
+ return thisTitle;
+ }
+
+ /// <summary>
+ /// Return a list of parsed titles
+ /// </summary>
+ /// <param name="output">The Output</param>
+ /// <returns>A List of titles</returns>
+ public static Title[] ParseList(string output)
+ {
+ var titles = new List<Title>();
+ var sr = new StringReader(output);
+
+ while (sr.Peek() == '+' || sr.Peek() == ' ')
+ {
+ // If the the character is a space, then chances are the line
+ if (sr.Peek() == ' ') // If the character is a space, then chances are it's the combing detected line.
+ sr.ReadLine(); // Skip over it
+ else
+ titles.Add(Parse(sr));
+ }
+
+ return titles.ToArray();
+ }
+
+ /// <summary>
+ /// Override of the ToString method to provide an easy way to use this object in the UI
+ /// </summary>
+ /// <returns>A string representing this track in the format: {title #} (00:00:00)</returns>
+ public override string ToString()
+ {
+ return string.Format("{0} ({1:00}:{2:00}:{3:00})", TitleNumber, Duration.Hours, Duration.Minutes, Duration.Seconds);
+ }
+
+ }
+}
\ No newline at end of file diff --git a/win/C#/HandBrake.ApplicationServices/Properties/AssemblyInfo.cs b/win/C#/HandBrake.ApplicationServices/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..98c308af0 --- /dev/null +++ b/win/C#/HandBrake.ApplicationServices/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("HandBrake.ApplicationServices")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("Microsoft")]
+[assembly: AssemblyProduct("HandBrake.ApplicationServices")]
+[assembly: AssemblyCopyright("Copyright © Microsoft 2010")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("1fe2c0f6-53e0-4633-88ef-c3cbd8be02a7")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/win/C#/HandBrake.ApplicationServices/Properties/Resources.Designer.cs b/win/C#/HandBrake.ApplicationServices/Properties/Resources.Designer.cs new file mode 100644 index 000000000..9b12f7278 --- /dev/null +++ b/win/C#/HandBrake.ApplicationServices/Properties/Resources.Designer.cs @@ -0,0 +1,84 @@ +//------------------------------------------------------------------------------
+// <auto-generated>
+// This code was generated by a tool.
+// Runtime Version:2.0.50727.4927
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+namespace HandBrake.ApplicationServices.Properties {
+ using System;
+
+
+ /// <summary>
+ /// A strongly-typed resource class, for looking up localized strings, etc.
+ /// </summary>
+ // This class was auto-generated by the StronglyTypedResourceBuilder
+ // class via a tool like ResGen or Visual Studio.
+ // To add or remove a member, edit your .ResX file then rerun ResGen
+ // with the /str option, or rebuild your VS project.
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "2.0.0.0")]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+ internal class Resources {
+
+ private static global::System.Resources.ResourceManager resourceMan;
+
+ private static global::System.Globalization.CultureInfo resourceCulture;
+
+ [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+ internal Resources() {
+ }
+
+ /// <summary>
+ /// Returns the cached ResourceManager instance used by this class.
+ /// </summary>
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Resources.ResourceManager ResourceManager {
+ get {
+ if (object.ReferenceEquals(resourceMan, null)) {
+ global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("HandBrake.ApplicationServices.Properties.Resources", typeof(Resources).Assembly);
+ resourceMan = temp;
+ }
+ return resourceMan;
+ }
+ }
+
+ /// <summary>
+ /// Overrides the current thread's CurrentUICulture property for all
+ /// resource lookups using this strongly typed resource class.
+ /// </summary>
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Globalization.CultureInfo Culture {
+ get {
+ return resourceCulture;
+ }
+ set {
+ resourceCulture = value;
+ }
+ }
+
+ internal static System.Drawing.Bitmap copy {
+ get {
+ object obj = ResourceManager.GetObject("copy", resourceCulture);
+ return ((System.Drawing.Bitmap)(obj));
+ }
+ }
+
+ internal static System.Drawing.Bitmap ErrorX {
+ get {
+ object obj = ResourceManager.GetObject("ErrorX", resourceCulture);
+ return ((System.Drawing.Bitmap)(obj));
+ }
+ }
+
+ internal static System.Drawing.Bitmap logo64 {
+ get {
+ object obj = ResourceManager.GetObject("logo64", resourceCulture);
+ return ((System.Drawing.Bitmap)(obj));
+ }
+ }
+ }
+}
diff --git a/win/C#/HandBrake.ApplicationServices/Properties/Resources.resx b/win/C#/HandBrake.ApplicationServices/Properties/Resources.resx new file mode 100644 index 000000000..9971f5d8c --- /dev/null +++ b/win/C#/HandBrake.ApplicationServices/Properties/Resources.resx @@ -0,0 +1,130 @@ +<?xml version="1.0" encoding="utf-8"?>
+<root>
+ <!--
+ Microsoft ResX Schema
+
+ Version 2.0
+
+ The primary goals of this format is to allow a simple XML format
+ that is mostly human readable. The generation and parsing of the
+ various data types are done through the TypeConverter classes
+ associated with the data types.
+
+ Example:
+
+ ... ado.net/XML headers & schema ...
+ <resheader name="resmimetype">text/microsoft-resx</resheader>
+ <resheader name="version">2.0</resheader>
+ <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+ <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+ <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+ <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+ <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+ <value>[base64 mime encoded serialized .NET Framework object]</value>
+ </data>
+ <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+ <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+ <comment>This is a comment</comment>
+ </data>
+
+ There are any number of "resheader" rows that contain simple
+ name/value pairs.
+
+ Each data row contains a name, and value. The row also contains a
+ type or mimetype. Type corresponds to a .NET class that support
+ text/value conversion through the TypeConverter architecture.
+ Classes that don't support this are serialized and stored with the
+ mimetype set.
+
+ The mimetype is used for serialized objects, and tells the
+ ResXResourceReader how to depersist the object. This is currently not
+ extensible. For a given mimetype the value must be set accordingly:
+
+ Note - application/x-microsoft.net.object.binary.base64 is the format
+ that the ResXResourceWriter will generate, however the reader can
+ read any of the formats listed below.
+
+ mimetype: application/x-microsoft.net.object.binary.base64
+ value : The object must be serialized with
+ : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
+ : and then encoded with base64 encoding.
+
+ mimetype: application/x-microsoft.net.object.soap.base64
+ value : The object must be serialized with
+ : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+ : and then encoded with base64 encoding.
+
+ mimetype: application/x-microsoft.net.object.bytearray.base64
+ value : The object must be serialized into a byte array
+ : using a System.ComponentModel.TypeConverter
+ : and then encoded with base64 encoding.
+ -->
+ <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+ <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
+ <xsd:element name="root" msdata:IsDataSet="true">
+ <xsd:complexType>
+ <xsd:choice maxOccurs="unbounded">
+ <xsd:element name="metadata">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" />
+ </xsd:sequence>
+ <xsd:attribute name="name" use="required" type="xsd:string" />
+ <xsd:attribute name="type" type="xsd:string" />
+ <xsd:attribute name="mimetype" type="xsd:string" />
+ <xsd:attribute ref="xml:space" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="assembly">
+ <xsd:complexType>
+ <xsd:attribute name="alias" type="xsd:string" />
+ <xsd:attribute name="name" type="xsd:string" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="data">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+ <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
+ <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+ <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+ <xsd:attribute ref="xml:space" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="resheader">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required" />
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:choice>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:schema>
+ <resheader name="resmimetype">
+ <value>text/microsoft-resx</value>
+ </resheader>
+ <resheader name="version">
+ <value>2.0</value>
+ </resheader>
+ <resheader name="reader">
+ <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+ </resheader>
+ <resheader name="writer">
+ <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+ </resheader>
+ <assembly alias="System.Windows.Forms" name="System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
+ <data name="copy" type="System.Resources.ResXFileRef, System.Windows.Forms">
+ <value>..\Resources\copy.png;System.Drawing.Bitmap, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
+ </data>
+ <data name="ErrorX" type="System.Resources.ResXFileRef, System.Windows.Forms">
+ <value>..\Resources\ErrorX.png;System.Drawing.Bitmap, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
+ </data>
+ <data name="logo64" type="System.Resources.ResXFileRef, System.Windows.Forms">
+ <value>..\Resources\logo64.png;System.Drawing.Bitmap, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
+ </data>
+</root>
\ No newline at end of file diff --git a/win/C#/HandBrake.ApplicationServices/Properties/Settings.Designer.cs b/win/C#/HandBrake.ApplicationServices/Properties/Settings.Designer.cs new file mode 100644 index 000000000..bf2781961 --- /dev/null +++ b/win/C#/HandBrake.ApplicationServices/Properties/Settings.Designer.cs @@ -0,0 +1,158 @@ +//------------------------------------------------------------------------------
+// <auto-generated>
+// This code was generated by a tool.
+// Runtime Version:2.0.50727.4927
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+namespace HandBrake.ApplicationServices.Properties {
+
+
+ [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "9.0.0.0")]
+ internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
+
+ private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
+
+ public static Settings Default {
+ get {
+ return defaultInstance;
+ }
+ }
+
+ [global::System.Configuration.UserScopedSettingAttribute()]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Configuration.DefaultSettingValueAttribute("False")]
+ public bool disableDvdNav {
+ get {
+ return ((bool)(this["disableDvdNav"]));
+ }
+ set {
+ this["disableDvdNav"] = value;
+ }
+ }
+
+ [global::System.Configuration.UserScopedSettingAttribute()]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Configuration.DefaultSettingValueAttribute("False")]
+ public bool saveLogWithVideo {
+ get {
+ return ((bool)(this["saveLogWithVideo"]));
+ }
+ set {
+ this["saveLogWithVideo"] = value;
+ }
+ }
+
+ [global::System.Configuration.UserScopedSettingAttribute()]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Configuration.DefaultSettingValueAttribute("")]
+ public string saveLogPath {
+ get {
+ return ((string)(this["saveLogPath"]));
+ }
+ set {
+ this["saveLogPath"] = value;
+ }
+ }
+
+ [global::System.Configuration.UserScopedSettingAttribute()]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Configuration.DefaultSettingValueAttribute("False")]
+ public bool saveLogToSpecifiedPath {
+ get {
+ return ((bool)(this["saveLogToSpecifiedPath"]));
+ }
+ set {
+ this["saveLogToSpecifiedPath"] = value;
+ }
+ }
+
+ [global::System.Configuration.UserScopedSettingAttribute()]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Configuration.DefaultSettingValueAttribute("False")]
+ public bool growlQueue {
+ get {
+ return ((bool)(this["growlQueue"]));
+ }
+ set {
+ this["growlQueue"] = value;
+ }
+ }
+
+ [global::System.Configuration.UserScopedSettingAttribute()]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Configuration.DefaultSettingValueAttribute("")]
+ public string CompletionOption {
+ get {
+ return ((string)(this["CompletionOption"]));
+ }
+ set {
+ this["CompletionOption"] = value;
+ }
+ }
+
+ [global::System.Configuration.UserScopedSettingAttribute()]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Configuration.DefaultSettingValueAttribute("")]
+ public string processPriority {
+ get {
+ return ((string)(this["processPriority"]));
+ }
+ set {
+ this["processPriority"] = value;
+ }
+ }
+
+ [global::System.Configuration.UserScopedSettingAttribute()]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Configuration.DefaultSettingValueAttribute("False")]
+ public bool cli_minimized {
+ get {
+ return ((bool)(this["cli_minimized"]));
+ }
+ set {
+ this["cli_minimized"] = value;
+ }
+ }
+
+ [global::System.Configuration.UserScopedSettingAttribute()]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Configuration.DefaultSettingValueAttribute("False")]
+ public bool showCliForInGuiEncodeStatus {
+ get {
+ return ((bool)(this["showCliForInGuiEncodeStatus"]));
+ }
+ set {
+ this["showCliForInGuiEncodeStatus"] = value;
+ }
+ }
+
+ [global::System.Configuration.UserScopedSettingAttribute()]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Configuration.DefaultSettingValueAttribute("False")]
+ public bool enocdeStatusInGui {
+ get {
+ return ((bool)(this["enocdeStatusInGui"]));
+ }
+ set {
+ this["enocdeStatusInGui"] = value;
+ }
+ }
+
+ [global::System.Configuration.UserScopedSettingAttribute()]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Configuration.DefaultSettingValueAttribute("False")]
+ public bool growlEncode {
+ get {
+ return ((bool)(this["growlEncode"]));
+ }
+ set {
+ this["growlEncode"] = value;
+ }
+ }
+ }
+}
diff --git a/win/C#/HandBrake.ApplicationServices/Properties/Settings.settings b/win/C#/HandBrake.ApplicationServices/Properties/Settings.settings new file mode 100644 index 000000000..20239afcc --- /dev/null +++ b/win/C#/HandBrake.ApplicationServices/Properties/Settings.settings @@ -0,0 +1,39 @@ +<?xml version='1.0' encoding='utf-8'?>
+<SettingsFile xmlns="http://schemas.microsoft.com/VisualStudio/2004/01/settings" CurrentProfile="(Default)" GeneratedClassNamespace="HandBrake.ApplicationServices.Properties" GeneratedClassName="Settings">
+ <Profiles />
+ <Settings>
+ <Setting Name="disableDvdNav" Type="System.Boolean" Scope="User">
+ <Value Profile="(Default)">False</Value>
+ </Setting>
+ <Setting Name="saveLogWithVideo" Type="System.Boolean" Scope="User">
+ <Value Profile="(Default)">False</Value>
+ </Setting>
+ <Setting Name="saveLogPath" Type="System.String" Scope="User">
+ <Value Profile="(Default)" />
+ </Setting>
+ <Setting Name="saveLogToSpecifiedPath" Type="System.Boolean" Scope="User">
+ <Value Profile="(Default)">False</Value>
+ </Setting>
+ <Setting Name="growlQueue" Type="System.Boolean" Scope="User">
+ <Value Profile="(Default)">False</Value>
+ </Setting>
+ <Setting Name="CompletionOption" Type="System.String" Scope="User">
+ <Value Profile="(Default)" />
+ </Setting>
+ <Setting Name="processPriority" Type="System.String" Scope="User">
+ <Value Profile="(Default)" />
+ </Setting>
+ <Setting Name="cli_minimized" Type="System.Boolean" Scope="User">
+ <Value Profile="(Default)">False</Value>
+ </Setting>
+ <Setting Name="showCliForInGuiEncodeStatus" Type="System.Boolean" Scope="User">
+ <Value Profile="(Default)">False</Value>
+ </Setting>
+ <Setting Name="enocdeStatusInGui" Type="System.Boolean" Scope="User">
+ <Value Profile="(Default)">False</Value>
+ </Setting>
+ <Setting Name="growlEncode" Type="System.Boolean" Scope="User">
+ <Value Profile="(Default)">False</Value>
+ </Setting>
+ </Settings>
+</SettingsFile>
\ No newline at end of file diff --git a/win/C#/HandBrake.ApplicationServices/Resources/ErrorX.png b/win/C#/HandBrake.ApplicationServices/Resources/ErrorX.png Binary files differnew file mode 100644 index 000000000..75c63c08a --- /dev/null +++ b/win/C#/HandBrake.ApplicationServices/Resources/ErrorX.png diff --git a/win/C#/HandBrake.ApplicationServices/Resources/copy.png b/win/C#/HandBrake.ApplicationServices/Resources/copy.png Binary files differnew file mode 100644 index 000000000..c11c6a753 --- /dev/null +++ b/win/C#/HandBrake.ApplicationServices/Resources/copy.png diff --git a/win/C#/HandBrake.ApplicationServices/Resources/logo64.png b/win/C#/HandBrake.ApplicationServices/Resources/logo64.png Binary files differnew file mode 100644 index 000000000..12808f636 --- /dev/null +++ b/win/C#/HandBrake.ApplicationServices/Resources/logo64.png diff --git a/win/C#/HandBrake.ApplicationServices/Services/Encode.cs b/win/C#/HandBrake.ApplicationServices/Services/Encode.cs new file mode 100644 index 000000000..491668d22 --- /dev/null +++ b/win/C#/HandBrake.ApplicationServices/Services/Encode.cs @@ -0,0 +1,553 @@ +/* Encode.cs $
+ This file is part of the HandBrake source code.
+ Homepage: <http://handbrake.fr/>.
+ It may be used under the terms of the GNU General Public License. */
+
+namespace HandBrake.ApplicationServices.Services
+{
+ using System;
+ using System.Diagnostics;
+ using System.IO;
+ using System.Text;
+ using System.Threading;
+ using System.Windows.Forms;
+
+ using HandBrake.ApplicationServices.Functions;
+ using HandBrake.ApplicationServices.Model;
+ using HandBrake.ApplicationServices.Properties;
+
+ using Timer = System.Threading.Timer;
+
+ /// <summary>
+ /// Class which handles the CLI
+ /// </summary>
+ public class Encode
+ {
+ /* Private Variables */
+
+ /// <summary>
+ /// An Encode Job
+ /// </summary>
+ private Job job;
+
+ /// <summary>
+ /// The Log Buffer
+ /// </summary>
+ private StringBuilder logBuffer;
+
+ /// <summary>
+ /// The line number thats been read to in the log file
+ /// </summary>
+ private int logFilePosition;
+
+ /// <summary>
+ /// A Timer for this window
+ /// </summary>
+ private Timer windowTimer;
+
+ /// <summary>
+ /// Gets The Process Handle
+ /// </summary>
+ private IntPtr processHandle;
+
+ /// <summary>
+ /// Gets the Process ID
+ /// </summary>
+ private int processID;
+
+ /* Event Handlers */
+
+ /// <summary>
+ /// Fires when a new CLI Job starts
+ /// </summary>
+ public event EventHandler EncodeStarted;
+
+ /// <summary>
+ /// Fires when a CLI job finishes.
+ /// </summary>
+ public event EventHandler EncodeEnded;
+
+ /* Properties */
+
+ /// <summary>
+ /// Gets or sets The HB Process
+ /// </summary>
+ public Process HbProcess { get; set; }
+
+ /// <summary>
+ /// Gets a value indicating whether IsEncoding.
+ /// </summary>
+ public bool IsEncoding { get; private set; }
+
+ /* Public Methods */
+
+ /// <summary>
+ /// Gets ActivityLog.
+ /// </summary>
+ public string ActivityLog
+ {
+ get
+ {
+ if (logBuffer == null)
+ {
+ ResetLogReader();
+ ReadFile(null);
+ }
+
+ return logBuffer != null ? logBuffer.ToString() : string.Empty;
+ }
+ }
+
+ /// <summary>
+ /// Create a preview sample video
+ /// </summary>
+ /// <param name="query">
+ /// The CLI Query
+ /// </param>
+ public void CreatePreviewSample(string query)
+ {
+ this.Run(new Job { Query = query }, true);
+ }
+
+ /// <summary>
+ /// Kill the CLI process
+ /// </summary>
+ public void Stop()
+ {
+ if (this.HbProcess != null)
+ this.HbProcess.Kill();
+
+ Process[] list = Process.GetProcessesByName("HandBrakeCLI");
+ foreach (Process process in list)
+ process.Kill();
+
+ if (this.EncodeEnded != null)
+ this.EncodeEnded(this, new EventArgs());
+ }
+
+ /// <summary>
+ /// Attempt to Safely kill a DirectRun() CLI
+ /// NOTE: This will not work with a MinGW CLI
+ /// Note: http://www.cygwin.com/ml/cygwin/2006-03/msg00330.html
+ /// </summary>
+ public void SafelyClose()
+ {
+ if ((int)this.processHandle == 0)
+ return;
+
+ // Allow the CLI to exit cleanly
+ Win32.SetForegroundWindow((int)this.processHandle);
+ SendKeys.Send("^C");
+ SendKeys.Flush();
+
+ // HbProcess.StandardInput.AutoFlush = true;
+ // HbProcess.StandardInput.WriteLine("^C");
+ }
+
+ /// <summary>
+ /// Execute a HandBrakeCLI process.
+ /// </summary>
+ /// <param name="encJob">
+ /// The enc Job.
+ /// </param>
+ /// <param name="RequireStandardOuput">
+ /// Set to True to show no window and force standard output redirect
+ /// </param>
+ protected void Run(Job encJob, bool RequireStandardOuput)
+ {
+ this.job = encJob;
+ try
+ {
+ ResetLogReader();
+ IsEncoding = true;
+
+ string handbrakeCLIPath = Path.Combine(Application.StartupPath, "HandBrakeCLI.exe");
+ string logPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + "\\HandBrake\\logs", "last_encode_log.txt");
+ string strCmdLine = String.Format(@" /C """"{0}"" {1} 2>""{2}"" """, handbrakeCLIPath, encJob.Query, logPath);
+ var cliStart = new ProcessStartInfo("CMD.exe", strCmdLine);
+
+ if (Settings.Default.enocdeStatusInGui || RequireStandardOuput)
+ {
+ cliStart.RedirectStandardOutput = true;
+ cliStart.UseShellExecute = false;
+ if (!Settings.Default.showCliForInGuiEncodeStatus || RequireStandardOuput)
+ cliStart.CreateNoWindow = true;
+ }
+ if (Settings.Default.cli_minimized)
+ cliStart.WindowStyle = ProcessWindowStyle.Minimized;
+
+ Process[] before = Process.GetProcesses(); // Get a list of running processes before starting.
+ HbProcess = Process.Start(cliStart);
+ this.processID = Main.GetCliProcess(before);
+
+ if (HbProcess != null)
+ this.processHandle = HbProcess.MainWindowHandle; // Set the process Handle
+
+ // Start the Log Monitor
+ windowTimer = new Timer(new TimerCallback(ReadFile), null, 1000, 1000);
+
+ // Set the process Priority
+ Process hbCliProcess = null;
+ if (this.processID != -1)
+ {
+ hbCliProcess = Process.GetProcessById(this.processID);
+ hbCliProcess.EnableRaisingEvents = true;
+ hbCliProcess.Exited += new EventHandler(HbProcess_Exited);
+ }
+
+ if (hbCliProcess != null)
+ switch (Settings.Default.processPriority)
+ {
+ case "Realtime":
+ hbCliProcess.PriorityClass = ProcessPriorityClass.RealTime;
+ break;
+ case "High":
+ hbCliProcess.PriorityClass = ProcessPriorityClass.High;
+ break;
+ case "Above Normal":
+ hbCliProcess.PriorityClass = ProcessPriorityClass.AboveNormal;
+ break;
+ case "Normal":
+ hbCliProcess.PriorityClass = ProcessPriorityClass.Normal;
+ break;
+ case "Low":
+ hbCliProcess.PriorityClass = ProcessPriorityClass.Idle;
+ break;
+ default:
+ hbCliProcess.PriorityClass = ProcessPriorityClass.BelowNormal;
+ break;
+ }
+
+ // Fire the Encode Started Event
+ if (this.EncodeStarted != null)
+ this.EncodeStarted(this, new EventArgs());
+ }
+ catch (Exception exc)
+ {
+ Main.ShowExceptiowWindow("It would appear that HandBrakeCLI has not started correctly. You should take a look at the Activity log as it may indicate the reason why.\n\nDetailed Error Information: error occured in runCli()", exc.ToString());
+ }
+ }
+
+ /// <summary>
+ /// The HandBrakeCLI process has exited.
+ /// </summary>
+ /// <param name="sender">
+ /// The sender.
+ /// </param>
+ /// <param name="e">
+ /// The EventArgs.
+ /// </param>
+ private void HbProcess_Exited(object sender, EventArgs e)
+ {
+ IsEncoding = false;
+ }
+
+ /// <summary>
+ /// Function to run the CLI directly rather than via CMD
+ /// TODO: Code to handle the Log data has yet to be written.
+ /// TODO: Code to handle the % / ETA info has to be written.
+ /// </summary>
+ /// <param name="query">
+ /// The query.
+ /// </param>
+ protected void DirectRun(string query)
+ {
+ try
+ {
+ if (this.EncodeStarted != null)
+ this.EncodeStarted(this, new EventArgs());
+
+ IsEncoding = true;
+
+ ResetLogReader();
+
+ // Setup the job
+ string handbrakeCLIPath = Path.Combine(Environment.CurrentDirectory, "HandBrakeCLI.exe");
+ HbProcess = new Process
+ {
+ StartInfo =
+ {
+ FileName = handbrakeCLIPath,
+ Arguments = query,
+ UseShellExecute = false,
+ RedirectStandardOutput = true,
+ RedirectStandardError = true,
+ RedirectStandardInput = true,
+ CreateNoWindow = false,
+ WindowStyle = ProcessWindowStyle.Minimized
+ }
+ };
+
+ // Setup event handlers for rediected data
+ HbProcess.ErrorDataReceived += new DataReceivedEventHandler(HbProcErrorDataReceived);
+ HbProcess.OutputDataReceived += new DataReceivedEventHandler(HbProcOutputDataReceived);
+
+ // Start the process
+ HbProcess.Start();
+
+ // Setup the asynchronous reading of stdin and stderr
+ HbProcess.BeginErrorReadLine();
+ HbProcess.BeginOutputReadLine();
+
+ // Set the Process Priority);
+ switch (Settings.Default.processPriority)
+ {
+ case "Realtime":
+ HbProcess.PriorityClass = ProcessPriorityClass.RealTime;
+ break;
+ case "High":
+ HbProcess.PriorityClass = ProcessPriorityClass.High;
+ break;
+ case "Above Normal":
+ HbProcess.PriorityClass = ProcessPriorityClass.AboveNormal;
+ break;
+ case "Normal":
+ HbProcess.PriorityClass = ProcessPriorityClass.Normal;
+ break;
+ case "Low":
+ HbProcess.PriorityClass = ProcessPriorityClass.Idle;
+ break;
+ default:
+ HbProcess.PriorityClass = ProcessPriorityClass.BelowNormal;
+ break;
+ }
+
+ // Set the class items
+ this.processID = HbProcess.Id;
+ this.processHandle = HbProcess.Handle;
+ }
+ catch (Exception exc)
+ {
+ Console.WriteLine(exc);
+ }
+ }
+
+ /// <summary>
+ /// Perform an action after an encode. e.g a shutdown, standby, restart etc.
+ /// </summary>
+ protected void Finish()
+ {
+ if (!IsEncoding)
+ {
+ windowTimer.Dispose();
+ ReadFile(null);
+ }
+
+ if (this.EncodeEnded != null)
+ this.EncodeEnded(this, new EventArgs());
+
+ // Growl
+ if (Settings.Default.growlQueue)
+ GrowlCommunicator.Notify("Queue Completed", "Put down that cocktail...\nyour Handbrake queue is done.");
+
+ // Do something whent he encode ends.
+ switch (Settings.Default.CompletionOption)
+ {
+ case "Shutdown":
+ Process.Start("Shutdown", "-s -t 60");
+ break;
+ case "Log Off":
+ Win32.ExitWindowsEx(0, 0);
+ break;
+ case "Suspend":
+ Application.SetSuspendState(PowerState.Suspend, true, true);
+ break;
+ case "Hibernate":
+ Application.SetSuspendState(PowerState.Hibernate, true, true);
+ break;
+ case "Lock System":
+ Win32.LockWorkStation();
+ break;
+ case "Quit HandBrake":
+ Application.Exit();
+ break;
+ default:
+ break;
+ }
+ }
+
+ /// <summary>
+ /// Add the CLI Query to the Log File.
+ /// </summary>
+ /// <param name="encJob">
+ /// The Encode Job Object
+ /// </param>
+ protected void AddCLIQueryToLog(Job encJob)
+ {
+ try
+ {
+ string logDir = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) +
+ "\\HandBrake\\logs";
+ string logPath = Path.Combine(logDir, "last_encode_log.txt");
+
+ var reader = new StreamReader(File.Open(logPath, FileMode.Open, FileAccess.Read, FileShare.Read));
+ string log = reader.ReadToEnd();
+ reader.Close();
+
+ var writer = new StreamWriter(File.Create(logPath));
+
+ writer.WriteLine("### CLI Query: " + encJob.Query);
+ writer.WriteLine("### User Query: " + encJob.CustomQuery);
+ writer.WriteLine("#########################################");
+ writer.WriteLine(log);
+ writer.Flush();
+ writer.Close();
+ }
+ catch (Exception)
+ {
+ return;
+ }
+ }
+
+ /// <summary>
+ /// Save a copy of the log to the users desired location or a default location
+ /// if this feature is enabled in options.
+ /// </summary>
+ /// <param name="destination">
+ /// The Destination File Path
+ /// </param>
+ protected void CopyLog(string destination)
+ {
+ try
+ {
+ string logDir = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) +
+ "\\HandBrake\\logs";
+ string tempLogFile = Path.Combine(logDir, "last_encode_log.txt");
+
+ string encodeDestinationPath = Path.GetDirectoryName(destination);
+ string destinationFile = Path.GetFileName(destination);
+ string encodeLogFile = destinationFile + " " +
+ DateTime.Now.ToString().Replace("/", "-").Replace(":", "-") + ".txt";
+
+ // Make sure the log directory exists.
+ if (!Directory.Exists(logDir))
+ Directory.CreateDirectory(logDir);
+
+ // Copy the Log to HandBrakes log folder in the users applciation data folder.
+ File.Copy(tempLogFile, Path.Combine(logDir, encodeLogFile));
+
+ // Save a copy of the log file in the same location as the enocde.
+ if (Settings.Default.saveLogWithVideo)
+ File.Copy(tempLogFile, Path.Combine(encodeDestinationPath, encodeLogFile));
+
+ // Save a copy of the log file to a user specified location
+ if (Directory.Exists(Settings.Default.saveLogPath))
+ if (Settings.Default.saveLogPath != String.Empty && Settings.Default.saveLogToSpecifiedPath)
+ File.Copy(tempLogFile, Path.Combine(Settings.Default.saveLogPath, encodeLogFile));
+ }
+ catch (Exception exc)
+ {
+ Main.ShowExceptiowWindow("Unable to make a copy of the log file", exc.ToString());
+ }
+ }
+
+ /// <summary>
+ /// Read the log file
+ /// </summary>
+ /// <param name="n">
+ /// The object.
+ /// </param>
+ private void ReadFile(object n)
+ {
+ lock (logBuffer)
+ {
+ // last_encode_log.txt is the primary log file. Since .NET can't read this file whilst the CLI is outputing to it (Not even in read only mode),
+ // we'll need to make a copy of it.
+ string logDir = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) +
+ "\\HandBrake\\logs";
+ string logFile = Path.Combine(logDir, "last_encode_log.txt");
+ string logFile2 = Path.Combine(logDir, "tmp_appReadable_log.txt");
+
+ try
+ {
+ // Make sure the application readable log file does not already exist. FileCopy fill fail if it does.
+ if (File.Exists(logFile2))
+ File.Delete(logFile2);
+
+ // Copy the log file.
+ if (File.Exists(logFile))
+ File.Copy(logFile, logFile2, true);
+ else
+ {
+ ResetLogReader();
+ return;
+ }
+
+ // Put the Query and User Generated Query Flag on the log.
+ if (logFilePosition == 0 && job.Query != null)
+ {
+ logBuffer.AppendLine("### CLI Query: " + job.Query);
+ logBuffer.AppendLine("### User Query: " + job.CustomQuery);
+ logBuffer.AppendLine("#########################################");
+ }
+
+ // Start the Reader
+ // Only use text which continues on from the last read line
+ StreamReader sr = new StreamReader(logFile2);
+ string line;
+ int i = 1;
+ while ((line = sr.ReadLine()) != null)
+ {
+ if (i > logFilePosition)
+ {
+ logBuffer.AppendLine(line);
+ logFilePosition++;
+ }
+ i++;
+ }
+ sr.Close();
+ sr.Dispose();
+ }
+ catch (Exception)
+ {
+ ResetLogReader();
+ }
+ }
+ }
+
+ /// <summary>
+ /// Reset the Log Reader
+ /// </summary>
+ private void ResetLogReader()
+ {
+ logFilePosition = 0;
+ logBuffer = new StringBuilder();
+ }
+
+ /// <summary>
+ /// Recieve the Standard Error information and process it
+ /// </summary>
+ /// <param name="sender">
+ /// The Sender Object
+ /// </param>
+ /// <param name="e">
+ /// DataReceived EventArgs
+ /// </param>
+ private void HbProcErrorDataReceived(object sender, DataReceivedEventArgs e)
+ {
+ if (!String.IsNullOrEmpty(e.Data))
+ {
+ lock (logBuffer)
+ logBuffer.AppendLine(e.Data);
+ }
+ }
+
+ /// <summary>
+ /// Standard Input Data Recieved from the CLI
+ /// </summary>
+ /// <param name="sender">
+ /// The Sender Object
+ /// </param>
+ /// <param name="e">
+ /// DataReceived EventArgs
+ /// </param>
+ private void HbProcOutputDataReceived(object sender, DataReceivedEventArgs e)
+ {
+ if (!String.IsNullOrEmpty(e.Data))
+ {
+ lock (logBuffer)
+ logBuffer.AppendLine(e.Data);
+ }
+ }
+ }
+}
\ No newline at end of file diff --git a/win/C#/HandBrake.ApplicationServices/Services/Queue.cs b/win/C#/HandBrake.ApplicationServices/Services/Queue.cs new file mode 100644 index 000000000..f61bb6e29 --- /dev/null +++ b/win/C#/HandBrake.ApplicationServices/Services/Queue.cs @@ -0,0 +1,410 @@ +/* Queue.cs $
+ This file is part of the HandBrake source code.
+ Homepage: <http://handbrake.fr/>.
+ It may be used under the terms of the GNU General Public License. */
+
+namespace HandBrake.ApplicationServices.Services
+{
+ using System;
+ using System.Collections.Generic;
+ using System.Collections.ObjectModel;
+ using System.IO;
+ using System.Linq;
+ using System.Threading;
+ using System.Windows.Forms;
+ using System.Xml.Serialization;
+
+ using HandBrake.ApplicationServices.Functions;
+ using HandBrake.ApplicationServices.Model;
+
+ /// <summary>
+ /// The HandBrake Queue
+ /// </summary>
+ public class Queue : Encode
+ {
+ /// <summary>
+ /// The Queue Job List
+ /// </summary>
+ private readonly List<Job> queue = new List<Job>();
+
+ /// <summary>
+ /// An XML Serializer
+ /// </summary>
+ private static XmlSerializer serializer;
+
+ /// <summary>
+ /// The Next Job ID
+ /// </summary>
+ private int nextJobId;
+
+ /// <summary>
+ /// Fires when the Queue has started
+ /// </summary>
+ public event EventHandler QueueStarted;
+
+ /// <summary>
+ /// Fires when a job is Added, Removed or Re-Ordered.
+ /// Should be used for triggering an update of the Queue Window.
+ /// </summary>
+ public event EventHandler QueueListChanged;
+
+ /// <summary>
+ /// Fires when a pause to the encode queue has been requested.
+ /// </summary>
+ public event EventHandler QueuePauseRequested;
+
+ /// <summary>
+ /// Fires when the entire encode queue has completed.
+ /// </summary>
+ public event EventHandler QueueCompleted;
+
+ #region Properties
+ /// <summary>
+ /// Gets or sets the last encode that was processed.
+ /// </summary>
+ /// <returns></returns>
+ public Job LastEncode { get; set; }
+
+ /// <summary>
+ /// Gets a value indicating whether Request Pause
+ /// </summary>
+ public bool Paused { get; private set; }
+
+ /// <summary>
+ /// Gets the current state of the encode queue.
+ /// </summary>
+ public ReadOnlyCollection<Job> CurrentQueue
+ {
+ get { return this.queue.AsReadOnly(); }
+ }
+
+ /// <summary>
+ /// Gets the number of items in the queue.
+ /// </summary>
+ public int Count
+ {
+ get { return this.queue.Count; }
+ }
+ #endregion
+
+ #region Queue
+
+ /// <summary>
+ /// Gets and removes the next job in the queue.
+ /// </summary>
+ /// <returns>The job that was removed from the queue.</returns>
+ private Job GetNextJob()
+ {
+ Job job = this.queue[0];
+ this.LastEncode = job;
+ this.Remove(0); // Remove the item which we are about to pass out.
+
+ this.WriteQueueStateToFile("hb_queue_recovery.xml");
+
+ return job;
+ }
+
+ /// <summary>
+ /// Adds an item to the queue.
+ /// </summary>
+ /// <param name="query">
+ /// The query that will be passed to the HandBrake CLI.
+ /// </param>
+ /// <param name="title">
+ /// The title.
+ /// </param>
+ /// <param name="source">
+ /// The location of the source video.
+ /// </param>
+ /// <param name="destination">
+ /// The location where the encoded video will be.
+ /// </param>
+ /// <param name="customJob">
+ /// Custom job
+ /// </param>
+ public void Add(string query, int title, string source, string destination, bool customJob)
+ {
+ Job newJob = new Job
+ {
+ Id = this.nextJobId++,
+ Title = title,
+ Query = query,
+ Source = source,
+ Destination = destination,
+ CustomQuery = customJob
+ };
+
+ this.queue.Add(newJob);
+ this.WriteQueueStateToFile("hb_queue_recovery.xml");
+
+ if (this.QueueListChanged != null)
+ this.QueueListChanged(this, new EventArgs());
+ }
+
+ /// <summary>
+ /// Removes an item from the queue.
+ /// </summary>
+ /// <param name="index">The zero-based location of the job in the queue.</param>
+ public void Remove(int index)
+ {
+ this.queue.RemoveAt(index);
+ this.WriteQueueStateToFile("hb_queue_recovery.xml");
+
+ if (this.QueueListChanged != null)
+ this.QueueListChanged(this, new EventArgs());
+ }
+
+ /// <summary>
+ /// Retrieve a job from the queue
+ /// </summary>
+ /// <param name="index">the job id</param>
+ /// <returns>A job for the given index or blank job object</returns>
+ public Job GetJob(int index)
+ {
+ if (this.queue.Count >= (index + 1))
+ return this.queue[index];
+
+ return new Job();
+ }
+
+ /// <summary>
+ /// Moves an item up one position in the queue.
+ /// </summary>
+ /// <param name="index">The zero-based location of the job in the queue.</param>
+ public void MoveUp(int index)
+ {
+ if (index > 0)
+ {
+ Job item = queue[index];
+
+ queue.RemoveAt(index);
+ queue.Insert((index - 1), item);
+ }
+
+ WriteQueueStateToFile("hb_queue_recovery.xml"); // Update the queue recovery file
+
+ if (this.QueueListChanged != null)
+ this.QueueListChanged(this, new EventArgs());
+ }
+
+ /// <summary>
+ /// Moves an item down one position in the queue.
+ /// </summary>
+ /// <param name="index">The zero-based location of the job in the queue.</param>
+ public void MoveDown(int index)
+ {
+ if (index < this.queue.Count - 1)
+ {
+ Job item = this.queue[index];
+
+ this.queue.RemoveAt(index);
+ this.queue.Insert((index + 1), item);
+ }
+
+ this.WriteQueueStateToFile("hb_queue_recovery.xml"); // Update the queue recovery file
+
+ if (this.QueueListChanged != null)
+ this.QueueListChanged(this, new EventArgs());
+ }
+
+ /// <summary>
+ /// Writes the current state of the queue to a file.
+ /// </summary>
+ /// <param name="file">The location of the file to write the queue to.</param>
+ public void WriteQueueStateToFile(string file)
+ {
+ string appDataPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
+ @"HandBrake\hb_queue_recovery.xml");
+ string tempPath = file == "hb_queue_recovery.xml" ? appDataPath : file;
+
+ try
+ {
+ using (FileStream strm = new FileStream(tempPath, FileMode.Create, FileAccess.Write))
+ {
+ if (serializer == null)
+ serializer = new XmlSerializer(typeof(List<Job>));
+ serializer.Serialize(strm, queue);
+ strm.Close();
+ strm.Dispose();
+ }
+ }
+ catch (Exception)
+ {
+ return;
+ }
+ }
+
+ /// <summary>
+ /// Writes the current state of the queue in the form of a batch (.bat) file.
+ /// </summary>
+ /// <param name="file">The location of the file to write the batch file to.</param>
+ public bool WriteBatchScriptToFile(string file)
+ {
+ string queries = string.Empty;
+ foreach (Job queueItem in this.queue)
+ {
+ string qItem = queueItem.Query;
+ string fullQuery = '"' + Application.StartupPath + "\\HandBrakeCLI.exe" + '"' + qItem;
+
+ if (queries == string.Empty)
+ queries = queries + fullQuery;
+ else
+ queries = queries + " && " + fullQuery;
+ }
+ string strCmdLine = queries;
+
+ if (file != string.Empty)
+ {
+ try
+ {
+ // Create a StreamWriter and open the file, Write the batch file query to the file and
+ // Close the stream
+ using (StreamWriter line = new StreamWriter(file))
+ {
+ line.WriteLine(strCmdLine);
+ }
+
+ return true;
+ }
+ catch (Exception exc)
+ {
+ Main.ShowExceptiowWindow("Unable to write to the file. Please make sure that the location has the correct permissions for file writing.", exc.ToString());
+ }
+ }
+ return false;
+ }
+
+ /// <summary>
+ /// Reads a serialized XML file that represents a queue of encoding jobs.
+ /// </summary>
+ /// <param name="file">The location of the file to read the queue from.</param>
+ public void LoadQueueFromFile(string file)
+ {
+ string appDataPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
+ @"HandBrake\hb_queue_recovery.xml");
+ string tempPath = file == "hb_queue_recovery.xml" ? appDataPath : file;
+
+ if (File.Exists(tempPath))
+ {
+ using (FileStream strm = new FileStream(tempPath, FileMode.Open, FileAccess.Read))
+ {
+ if (strm.Length != 0)
+ {
+ if (serializer == null)
+ serializer = new XmlSerializer(typeof(List<Job>));
+
+ List<Job> list = serializer.Deserialize(strm) as List<Job>;
+
+ if (list != null)
+ foreach (Job item in list)
+ this.queue.Add(item);
+
+ if (file != "hb_queue_recovery.xml")
+ this.WriteQueueStateToFile("hb_queue_recovery.xml");
+ }
+ }
+ }
+ }
+
+ /// <summary>
+ /// Checks the current queue for an existing instance of the specified destination.
+ /// </summary>
+ /// <param name="destination">The destination of the encode.</param>
+ /// <returns>Whether or not the supplied destination is already in the queue.</returns>
+ public bool CheckForDestinationDuplicate(string destination)
+ {
+ return this.queue.Any(checkItem => checkItem.Destination.Contains(destination.Replace("\\\\", "\\")));
+ }
+
+ #endregion
+
+ #region Encoding
+
+ /// <summary>
+ /// Starts encoding the first job in the queue and continues encoding until all jobs
+ /// have been encoded.
+ /// </summary>
+ public void Start()
+ {
+ if (this.Count != 0)
+ {
+ if (this.Paused)
+ this.Paused = false;
+ else
+ {
+ this.Paused = false;
+ try
+ {
+ Thread theQueue = new Thread(this.StartQueue) { IsBackground = true };
+ theQueue.Start();
+
+ if (this.QueueStarted != null)
+ this.QueueStarted(this, new EventArgs());
+ }
+ catch (Exception exc)
+ {
+ Main.ShowExceptiowWindow("Unable to Start Queue", exc.ToString());
+ }
+ }
+ }
+ }
+
+ /// <summary>
+ /// Requests a pause of the encode queue.
+ /// </summary>
+ public void Pause()
+ {
+ this.Paused = true;
+
+ if (this.QueuePauseRequested != null)
+ this.QueuePauseRequested(this, new EventArgs());
+ }
+
+ /// <summary>
+ /// Run through all the jobs on the queue.
+ /// </summary>
+ /// <param name="state">Object State</param>
+ private void StartQueue(object state)
+ {
+ // Run through each item on the queue
+ while (this.Count != 0)
+ {
+ Job encJob = this.GetNextJob();
+ this.WriteQueueStateToFile("hb_queue_recovery.xml"); // Update the queue recovery file
+
+ Run(encJob, false);
+
+ if (HbProcess == null)
+ {
+ return;
+ }
+ HbProcess.WaitForExit();
+
+ AddCLIQueryToLog(encJob);
+ this.CopyLog(this.LastEncode.Destination);
+
+ HbProcess.Close();
+ HbProcess.Dispose();
+
+ // Growl
+ if (Properties.Settings.Default.growlEncode)
+ GrowlCommunicator.Notify("Encode Completed",
+ "Put down that cocktail...\nyour Handbrake encode is done.");
+
+ while (this.Paused) // Need to find a better way of doing this.
+ {
+ Thread.Sleep(2000);
+ }
+ }
+ this.LastEncode = new Job();
+
+ if (this.QueueCompleted != null)
+ this.QueueCompleted(this, new EventArgs());
+
+ // After the encode is done, we may want to shutdown, suspend etc.
+ Finish();
+ }
+
+ #endregion
+ }
+}
\ No newline at end of file diff --git a/win/C#/HandBrake.ApplicationServices/Services/Scan.cs b/win/C#/HandBrake.ApplicationServices/Services/Scan.cs new file mode 100644 index 000000000..1f54fa904 --- /dev/null +++ b/win/C#/HandBrake.ApplicationServices/Services/Scan.cs @@ -0,0 +1,285 @@ +/* Scan.cs $
+ This file is part of the HandBrake source code.
+ Homepage: <http://handbrake.fr>.
+ It may be used under the terms of the GNU General Public License. */
+
+namespace HandBrake.ApplicationServices.Services
+{
+ using System;
+ using System.Diagnostics;
+ using System.IO;
+ using System.Text;
+ using System.Threading;
+ using System.Windows.Forms;
+
+ using HandBrake.ApplicationServices.Functions;
+ using HandBrake.ApplicationServices.Parsing;
+
+ /// <summary>
+ /// Scan a Source
+ /// </summary>
+ public class ScanService
+ {
+ /* Private Variables */
+
+ /// <summary>
+ /// A Lock object
+ /// </summary>
+ private static readonly object locker = new object();
+
+ /// <summary>
+ /// The CLI data parser
+ /// </summary>
+ private Parser readData;
+
+ /// <summary>
+ /// The Log Buffer
+ /// </summary>
+ private StringBuilder logBuffer;
+
+ /// <summary>
+ /// The line number thats been read to in the log file
+ /// </summary>
+ private int logFilePosition;
+
+ /// <summary>
+ /// The Process belonging to the CLI
+ /// </summary>
+ private Process hbProc;
+
+ /* Event Handlers */
+
+ /// <summary>
+ /// Scan has Started
+ /// </summary>
+ public event EventHandler ScanStared;
+
+ /// <summary>
+ /// Scan has completed
+ /// </summary>
+ public event EventHandler ScanCompleted;
+
+ /// <summary>
+ /// Scan process has changed to a new title
+ /// </summary>
+ public event EventHandler ScanStatusChanged;
+
+ /* Properties */
+
+ /// <summary>
+ /// Gets a value indicating whether IsScanning.
+ /// </summary>
+ public bool IsScanning { get; private set; }
+
+ /// <summary>
+ /// Gets the Scan Status.
+ /// </summary>
+ public string ScanStatus { get; private set; }
+
+ /// <summary>
+ /// Gets the Souce Data.
+ /// </summary>
+ public DVD SouceData { get; private set; }
+
+ /// <summary>
+ /// Gets ActivityLog.
+ /// </summary>
+ public string ActivityLog
+ {
+ get
+ {
+ if (IsScanning)
+ return readData.Buffer.ToString();
+
+ if (logBuffer == null)
+ {
+ ResetLogReader();
+ ReadLastScanFile();
+ }
+
+ return logBuffer != null ? logBuffer.ToString() : string.Empty;
+ }
+ }
+
+ /* Public Methods */
+
+ /// <summary>
+ /// Scan a Source Path.
+ /// Title 0: scan all
+ /// </summary>
+ /// <param name="sourcePath">Path to the file to scan</param>
+ /// <param name="title">int title number. 0 for scan all</param>
+ public void Scan(string sourcePath, int title)
+ {
+ Thread t = new Thread(unused => this.ScanSource(sourcePath, title));
+ t.Start();
+ }
+
+ /// <summary>
+ /// Kill the scan
+ /// </summary>
+ public void Stop()
+ {
+ try
+ {
+ if (hbProc != null)
+ hbProc.Kill();
+ }
+ catch (Exception ex)
+ {
+ Main.ShowExceptiowWindow("Unable to kill HandBrakeCLI.exe \n" +
+ "You may need to manually kill HandBrakeCLI.exe using the Windows Task Manager if it does not close automatically" +
+ " within the next few minutes. ", ex.ToString());
+ }
+ }
+
+ /* Private Methods */
+
+ /// <summary>
+ /// Start a scan for a given source path and title
+ /// </summary>
+ /// <param name="sourcePath">Path to the source file</param>
+ /// <param name="title">the title number to look at</param>
+ private void ScanSource(object sourcePath, int title)
+ {
+ try
+ {
+ IsScanning = true;
+ if (this.ScanStared != null)
+ this.ScanStared(this, new EventArgs());
+
+ ResetLogReader();
+
+ string handbrakeCLIPath = Path.Combine(Application.StartupPath, "HandBrakeCLI.exe");
+ string logDir = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) +
+ "\\HandBrake\\logs";
+ string dvdInfoPath = Path.Combine(logDir, "last_scan_log.txt");
+
+ // Make we don't pick up a stale last_encode_log.txt (and that we have rights to the file)
+ if (File.Exists(dvdInfoPath))
+ File.Delete(dvdInfoPath);
+
+ string extraArguments = string.Empty;
+ if (Properties.Settings.Default.disableDvdNav)
+ extraArguments = " --no-dvdnav";
+
+ if (title > 0)
+ extraArguments += " --scan ";
+
+ this.hbProc = new Process
+ {
+ StartInfo =
+ {
+ FileName = handbrakeCLIPath,
+ Arguments =
+ String.Format(@" -i ""{0}"" -t{1} {2} -v ", sourcePath, title,
+ extraArguments),
+ RedirectStandardOutput = true,
+ RedirectStandardError = true,
+ UseShellExecute = false,
+ CreateNoWindow = true
+ }
+ };
+
+ // Start the Scan
+ this.hbProc.Start();
+
+ this.readData = new Parser(this.hbProc.StandardError.BaseStream);
+ this.readData.OnScanProgress += new ScanProgressEventHandler(this.OnScanProgress);
+ this.SouceData = DVD.Parse(this.readData);
+
+ // Write the Buffer out to file.
+ StreamWriter scanLog = new StreamWriter(dvdInfoPath);
+ scanLog.Write(this.readData.Buffer);
+ scanLog.Flush();
+ scanLog.Close();
+ logBuffer = readData.Buffer;
+
+ IsScanning = false;
+
+ if (this.ScanCompleted != null)
+ this.ScanCompleted(this, new EventArgs());
+ }
+ catch (Exception exc)
+ {
+ Main.ShowExceptiowWindow("frmMain.cs - scanProcess() Error", exc.ToString());
+ }
+ }
+
+ /// <summary>
+ /// Read the log file
+ /// </summary>
+ private void ReadLastScanFile()
+ {
+ lock (locker)
+ {
+ // last_encode_log.txt is the primary log file. Since .NET can't read this file whilst the CLI is outputing to it (Not even in read only mode),
+ // we'll need to make a copy of it.
+ string logDir = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) +
+ "\\HandBrake\\logs";
+ string logFile = Path.Combine(logDir, "last_scan_log.txt");
+ string logFile2 = Path.Combine(logDir, "tmp_appReadable_log.txt");
+
+ try
+ {
+ // Make sure the application readable log file does not already exist. FileCopy fill fail if it does.
+ if (File.Exists(logFile2))
+ File.Delete(logFile2);
+
+ // Copy the log file.
+ if (File.Exists(logFile))
+ File.Copy(logFile, logFile2, true);
+ else
+ {
+ ResetLogReader();
+ return;
+ }
+
+ // Start the Reader
+ // Only use text which continues on from the last read line
+ StreamReader sr = new StreamReader(logFile2);
+ string line;
+ int i = 1;
+ while ((line = sr.ReadLine()) != null)
+ {
+ if (i > logFilePosition)
+ {
+ logBuffer.AppendLine(line);
+ logFilePosition++;
+ }
+ i++;
+ }
+ sr.Close();
+ sr.Dispose();
+ }
+ catch (Exception exc)
+ {
+ Console.WriteLine(exc.ToString());
+ ResetLogReader();
+ }
+ }
+ }
+
+ /// <summary>
+ /// Reset the Log Reader
+ /// </summary>
+ private void ResetLogReader()
+ {
+ logFilePosition = 0;
+ logBuffer = new StringBuilder();
+ }
+
+ /// <summary>
+ /// Fire an event when the scan process progresses
+ /// </summary>
+ /// <param name="sender">the sender</param>
+ /// <param name="currentTitle">the current title being scanned</param>
+ /// <param name="titleCount">the total number of titles</param>
+ private void OnScanProgress(object sender, int currentTitle, int titleCount)
+ {
+ this.ScanStatus = string.Format("Processing Title: {0} of {1}", currentTitle, titleCount);
+ if (this.ScanStatusChanged != null)
+ this.ScanStatusChanged(this, new EventArgs());
+ }
+ }
+}
\ No newline at end of file diff --git a/win/C#/HandBrake.ApplicationServices/app.config b/win/C#/HandBrake.ApplicationServices/app.config new file mode 100644 index 000000000..5b9648fa1 --- /dev/null +++ b/win/C#/HandBrake.ApplicationServices/app.config @@ -0,0 +1,45 @@ +<?xml version="1.0" encoding="utf-8" ?>
+<configuration>
+ <configSections>
+ <sectionGroup name="userSettings" type="System.Configuration.UserSettingsGroup, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" >
+ <section name="HandBrake.ApplicationServices.Properties.Settings" type="System.Configuration.ClientSettingsSection, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" allowExeDefinition="MachineToLocalUser" requirePermission="false" />
+ </sectionGroup>
+ </configSections>
+ <userSettings>
+ <HandBrake.ApplicationServices.Properties.Settings>
+ <setting name="disableDvdNav" serializeAs="String">
+ <value>False</value>
+ </setting>
+ <setting name="saveLogWithVideo" serializeAs="String">
+ <value>False</value>
+ </setting>
+ <setting name="saveLogPath" serializeAs="String">
+ <value />
+ </setting>
+ <setting name="saveLogToSpecifiedPath" serializeAs="String">
+ <value>False</value>
+ </setting>
+ <setting name="growlQueue" serializeAs="String">
+ <value>False</value>
+ </setting>
+ <setting name="CompletionOption" serializeAs="String">
+ <value />
+ </setting>
+ <setting name="processPriority" serializeAs="String">
+ <value />
+ </setting>
+ <setting name="cli_minimized" serializeAs="String">
+ <value>False</value>
+ </setting>
+ <setting name="showCliForInGuiEncodeStatus" serializeAs="String">
+ <value>False</value>
+ </setting>
+ <setting name="enocdeStatusInGui" serializeAs="String">
+ <value>False</value>
+ </setting>
+ <setting name="growlEncode" serializeAs="String">
+ <value>False</value>
+ </setting>
+ </HandBrake.ApplicationServices.Properties.Settings>
+ </userSettings>
+</configuration>
\ No newline at end of file diff --git a/win/C#/HandBrake.ApplicationServices/frmExceptionWindow.Designer.cs b/win/C#/HandBrake.ApplicationServices/frmExceptionWindow.Designer.cs new file mode 100644 index 000000000..bdc47995e --- /dev/null +++ b/win/C#/HandBrake.ApplicationServices/frmExceptionWindow.Designer.cs @@ -0,0 +1,249 @@ +namespace HandBrake.ApplicationServices
+{
+ partial class frmExceptionWindow
+ {
+ /// <summary>
+ /// Required designer variable.
+ /// </summary>
+ private System.ComponentModel.IContainer components = null;
+
+ /// <summary>
+ /// Clean up any resources being used.
+ /// </summary>
+ /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing && (components != null))
+ {
+ components.Dispose();
+ }
+ base.Dispose(disposing);
+ }
+
+ #region Windows Form Designer generated code
+
+ /// <summary>
+ /// Required method for Designer support - do not modify
+ /// the contents of this method with the code editor.
+ /// </summary>
+ private void InitializeComponent()
+ {
+ this.components = new System.ComponentModel.Container();
+ System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(frmExceptionWindow));
+ this.panel1 = new System.Windows.Forms.Panel();
+ this.panel2 = new System.Windows.Forms.Panel();
+ this.PictureBox1 = new System.Windows.Forms.PictureBox();
+ this.label3 = new System.Windows.Forms.Label();
+ this.label1 = new System.Windows.Forms.Label();
+ this.lbl_shortError = new System.Windows.Forms.Label();
+ this.panel3 = new System.Windows.Forms.Panel();
+ this.panel5 = new System.Windows.Forms.Panel();
+ this.panel4 = new System.Windows.Forms.Panel();
+ this.panel6 = new System.Windows.Forms.Panel();
+ this.btn_close = new System.Windows.Forms.Button();
+ this.btn_copy = new System.Windows.Forms.Button();
+ this.rtf_exceptionFull = new System.Windows.Forms.RichTextBox();
+ this.rightClickMenu = new System.Windows.Forms.ContextMenuStrip(this.components);
+ this.mnu_copy_log = new System.Windows.Forms.ToolStripMenuItem();
+ this.panel1.SuspendLayout();
+ ((System.ComponentModel.ISupportInitialize)(this.PictureBox1)).BeginInit();
+ this.panel4.SuspendLayout();
+ this.panel6.SuspendLayout();
+ this.rightClickMenu.SuspendLayout();
+ this.SuspendLayout();
+ //
+ // panel1
+ //
+ this.panel1.BackColor = System.Drawing.Color.White;
+ this.panel1.Controls.Add(this.panel2);
+ this.panel1.Controls.Add(this.PictureBox1);
+ this.panel1.Controls.Add(this.label3);
+ this.panel1.Controls.Add(this.label1);
+ this.panel1.Controls.Add(this.lbl_shortError);
+ this.panel1.Dock = System.Windows.Forms.DockStyle.Top;
+ this.panel1.Location = new System.Drawing.Point(0, 0);
+ this.panel1.Name = "panel1";
+ this.panel1.Size = new System.Drawing.Size(669, 97);
+ this.panel1.TabIndex = 61;
+ //
+ // panel2
+ //
+ this.panel2.BackColor = System.Drawing.SystemColors.Control;
+ this.panel2.Dock = System.Windows.Forms.DockStyle.Bottom;
+ this.panel2.Location = new System.Drawing.Point(0, 87);
+ this.panel2.MaximumSize = new System.Drawing.Size(0, 10);
+ this.panel2.MinimumSize = new System.Drawing.Size(0, 10);
+ this.panel2.Name = "panel2";
+ this.panel2.Size = new System.Drawing.Size(669, 10);
+ this.panel2.TabIndex = 59;
+ //
+ // PictureBox1
+ //
+ this.PictureBox1.Image = global::HandBrake.ApplicationServices.Properties.Resources.ErrorX;
+ this.PictureBox1.InitialImage = null;
+ this.PictureBox1.Location = new System.Drawing.Point(12, 12);
+ this.PictureBox1.Name = "PictureBox1";
+ this.PictureBox1.Size = new System.Drawing.Size(64, 64);
+ this.PictureBox1.TabIndex = 24;
+ this.PictureBox1.TabStop = false;
+ //
+ // label3
+ //
+ this.label3.AutoSize = true;
+ this.label3.Font = new System.Drawing.Font("Tahoma", 8.25F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
+ this.label3.Location = new System.Drawing.Point(84, 63);
+ this.label3.Name = "label3";
+ this.label3.Size = new System.Drawing.Size(80, 13);
+ this.label3.TabIndex = 57;
+ this.label3.Text = "Error Details:";
+ //
+ // label1
+ //
+ this.label1.AutoSize = true;
+ this.label1.Font = new System.Drawing.Font("Tahoma", 9.75F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
+ this.label1.Location = new System.Drawing.Point(84, 12);
+ this.label1.Name = "label1";
+ this.label1.Size = new System.Drawing.Size(150, 16);
+ this.label1.TabIndex = 25;
+ this.label1.Text = "An Error has occured.";
+ //
+ // lbl_shortError
+ //
+ this.lbl_shortError.Location = new System.Drawing.Point(84, 33);
+ this.lbl_shortError.Name = "lbl_shortError";
+ this.lbl_shortError.Size = new System.Drawing.Size(573, 30);
+ this.lbl_shortError.TabIndex = 58;
+ this.lbl_shortError.Text = "An Unknown Error has occured.";
+ //
+ // panel3
+ //
+ this.panel3.Dock = System.Windows.Forms.DockStyle.Left;
+ this.panel3.Location = new System.Drawing.Point(0, 97);
+ this.panel3.Name = "panel3";
+ this.panel3.Size = new System.Drawing.Size(76, 216);
+ this.panel3.TabIndex = 68;
+ //
+ // panel5
+ //
+ this.panel5.Dock = System.Windows.Forms.DockStyle.Right;
+ this.panel5.Location = new System.Drawing.Point(640, 97);
+ this.panel5.Name = "panel5";
+ this.panel5.Size = new System.Drawing.Size(29, 216);
+ this.panel5.TabIndex = 69;
+ //
+ // panel4
+ //
+ this.panel4.Controls.Add(this.panel6);
+ this.panel4.Dock = System.Windows.Forms.DockStyle.Bottom;
+ this.panel4.Location = new System.Drawing.Point(0, 313);
+ this.panel4.Name = "panel4";
+ this.panel4.Size = new System.Drawing.Size(669, 42);
+ this.panel4.TabIndex = 69;
+ //
+ // panel6
+ //
+ this.panel6.Controls.Add(this.btn_close);
+ this.panel6.Controls.Add(this.btn_copy);
+ this.panel6.Dock = System.Windows.Forms.DockStyle.Right;
+ this.panel6.Location = new System.Drawing.Point(491, 0);
+ this.panel6.Name = "panel6";
+ this.panel6.Size = new System.Drawing.Size(178, 42);
+ this.panel6.TabIndex = 58;
+ //
+ // btn_close
+ //
+ this.btn_close.BackColor = System.Drawing.Color.Transparent;
+ this.btn_close.FlatAppearance.BorderColor = System.Drawing.Color.Black;
+ this.btn_close.Font = new System.Drawing.Font("Verdana", 8.25F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
+ this.btn_close.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(255)))), ((int)(((byte)(128)))), ((int)(((byte)(0)))));
+ this.btn_close.Location = new System.Drawing.Point(105, 8);
+ this.btn_close.Name = "btn_close";
+ this.btn_close.Size = new System.Drawing.Size(70, 25);
+ this.btn_close.TabIndex = 56;
+ this.btn_close.Text = "OK";
+ this.btn_close.UseVisualStyleBackColor = false;
+ this.btn_close.Click += new System.EventHandler(this.btn_close_Click);
+ //
+ // btn_copy
+ //
+ this.btn_copy.BackColor = System.Drawing.Color.Transparent;
+ this.btn_copy.FlatAppearance.BorderColor = System.Drawing.Color.Black;
+ this.btn_copy.Font = new System.Drawing.Font("Verdana", 8.25F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
+ this.btn_copy.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(255)))), ((int)(((byte)(128)))), ((int)(((byte)(0)))));
+ this.btn_copy.Image = global::HandBrake.ApplicationServices.Properties.Resources.copy;
+ this.btn_copy.ImageAlign = System.Drawing.ContentAlignment.MiddleLeft;
+ this.btn_copy.Location = new System.Drawing.Point(14, 8);
+ this.btn_copy.Name = "btn_copy";
+ this.btn_copy.Size = new System.Drawing.Size(85, 25);
+ this.btn_copy.TabIndex = 57;
+ this.btn_copy.Text = "Copy";
+ this.btn_copy.UseVisualStyleBackColor = false;
+ this.btn_copy.Click += new System.EventHandler(this.btn_copy_Click);
+ //
+ // rtf_exceptionFull
+ //
+ this.rtf_exceptionFull.ContextMenuStrip = this.rightClickMenu;
+ this.rtf_exceptionFull.Dock = System.Windows.Forms.DockStyle.Fill;
+ this.rtf_exceptionFull.Location = new System.Drawing.Point(76, 97);
+ this.rtf_exceptionFull.Name = "rtf_exceptionFull";
+ this.rtf_exceptionFull.Size = new System.Drawing.Size(564, 216);
+ this.rtf_exceptionFull.TabIndex = 70;
+ this.rtf_exceptionFull.Text = "";
+ //
+ // rightClickMenu
+ //
+ this.rightClickMenu.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
+ this.mnu_copy_log});
+ this.rightClickMenu.Name = "rightClickMenu";
+ this.rightClickMenu.Size = new System.Drawing.Size(153, 48);
+ //
+ // mnu_copy_log
+ //
+ this.mnu_copy_log.Image = global::HandBrake.ApplicationServices.Properties.Resources.copy;
+ this.mnu_copy_log.Name = "mnu_copy_log";
+ this.mnu_copy_log.Size = new System.Drawing.Size(152, 22);
+ this.mnu_copy_log.Text = "Copy";
+ this.mnu_copy_log.Click += new System.EventHandler(this.mnu_copy_log_Click);
+ //
+ // frmExceptionWindow
+ //
+ this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
+ this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
+ this.ClientSize = new System.Drawing.Size(669, 355);
+ this.Controls.Add(this.rtf_exceptionFull);
+ this.Controls.Add(this.panel3);
+ this.Controls.Add(this.panel5);
+ this.Controls.Add(this.panel4);
+ this.Controls.Add(this.panel1);
+ this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon")));
+ this.Name = "frmExceptionWindow";
+ this.Text = "Error";
+ this.panel1.ResumeLayout(false);
+ this.panel1.PerformLayout();
+ ((System.ComponentModel.ISupportInitialize)(this.PictureBox1)).EndInit();
+ this.panel4.ResumeLayout(false);
+ this.panel6.ResumeLayout(false);
+ this.rightClickMenu.ResumeLayout(false);
+ this.ResumeLayout(false);
+
+ }
+
+ #endregion
+
+ private System.Windows.Forms.Panel panel1;
+ private System.Windows.Forms.Panel panel2;
+ internal System.Windows.Forms.PictureBox PictureBox1;
+ private System.Windows.Forms.Label label3;
+ private System.Windows.Forms.Label label1;
+ private System.Windows.Forms.Label lbl_shortError;
+ private System.Windows.Forms.Panel panel3;
+ private System.Windows.Forms.Panel panel5;
+ private System.Windows.Forms.Panel panel4;
+ private System.Windows.Forms.RichTextBox rtf_exceptionFull;
+ internal System.Windows.Forms.Button btn_close;
+ private System.Windows.Forms.Panel panel6;
+ internal System.Windows.Forms.Button btn_copy;
+ private System.Windows.Forms.ContextMenuStrip rightClickMenu;
+ private System.Windows.Forms.ToolStripMenuItem mnu_copy_log;
+ }
+}
\ No newline at end of file diff --git a/win/C#/HandBrake.ApplicationServices/frmExceptionWindow.cs b/win/C#/HandBrake.ApplicationServices/frmExceptionWindow.cs new file mode 100644 index 000000000..3be1cf219 --- /dev/null +++ b/win/C#/HandBrake.ApplicationServices/frmExceptionWindow.cs @@ -0,0 +1,81 @@ +/* frmExceptionWindow.cs $
+ This file is part of the HandBrake source code.
+ Homepage: <http://handbrake.fr>.
+ It may be used under the terms of the GNU General Public License. */
+
+namespace HandBrake.ApplicationServices
+{
+ using System;
+ using System.Windows.Forms;
+
+ /// <summary>
+ /// A window to display Exceptions in a form which can be easily copied and reported by users.
+ /// </summary>
+ public partial class frmExceptionWindow : Form
+ {
+ /// <summary>
+ /// Initializes a new instance of the <see cref="frmExceptionWindow"/> class.
+ /// </summary>
+ public frmExceptionWindow()
+ {
+ InitializeComponent();
+ }
+
+ /// <summary>
+ /// Setup the window with the error message.
+ /// </summary>
+ /// <param name="shortError">
+ /// The short error.
+ /// </param>
+ /// <param name="longError">
+ /// The long error.
+ /// </param>
+ public void Setup(string shortError, string longError)
+ {
+ lbl_shortError.Text = shortError;
+ rtf_exceptionFull.Text = shortError + Environment.NewLine + longError;
+ }
+
+ /// <summary>
+ /// Copy the Exception Information to the Clipboard.
+ /// </summary>
+ /// <param name="sender">
+ /// The sender.
+ /// </param>
+ /// <param name="e">
+ /// The e.
+ /// </param>
+ private void btn_copy_Click(object sender, EventArgs e)
+ {
+ Clipboard.SetDataObject(rtf_exceptionFull.SelectedText != string.Empty ? rtf_exceptionFull.SelectedText : rtf_exceptionFull.Text, true);
+ }
+
+ /// <summary>
+ /// Copy from the right click menu
+ /// </summary>
+ /// <param name="sender">
+ /// The sender.
+ /// </param>
+ /// <param name="e">
+ /// The e.
+ /// </param>
+ private void mnu_copy_log_Click(object sender, EventArgs e)
+ {
+ Clipboard.SetDataObject(rtf_exceptionFull.SelectedText != string.Empty ? rtf_exceptionFull.SelectedText : rtf_exceptionFull.Text, true);
+ }
+
+ /// <summary>
+ /// Close the window
+ /// </summary>
+ /// <param name="sender">
+ /// The sender.
+ /// </param>
+ /// <param name="e">
+ /// The e.
+ /// </param>
+ private void btn_close_Click(object sender, EventArgs e)
+ {
+ this.Close();
+ }
+ }
+}
diff --git a/win/C#/HandBrake.ApplicationServices/frmExceptionWindow.resx b/win/C#/HandBrake.ApplicationServices/frmExceptionWindow.resx new file mode 100644 index 000000000..6df001c71 --- /dev/null +++ b/win/C#/HandBrake.ApplicationServices/frmExceptionWindow.resx @@ -0,0 +1,503 @@ +<?xml version="1.0" encoding="utf-8"?>
+<root>
+ <!--
+ Microsoft ResX Schema
+
+ Version 2.0
+
+ The primary goals of this format is to allow a simple XML format
+ that is mostly human readable. The generation and parsing of the
+ various data types are done through the TypeConverter classes
+ associated with the data types.
+
+ Example:
+
+ ... ado.net/XML headers & schema ...
+ <resheader name="resmimetype">text/microsoft-resx</resheader>
+ <resheader name="version">2.0</resheader>
+ <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+ <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+ <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+ <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+ <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+ <value>[base64 mime encoded serialized .NET Framework object]</value>
+ </data>
+ <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+ <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+ <comment>This is a comment</comment>
+ </data>
+
+ There are any number of "resheader" rows that contain simple
+ name/value pairs.
+
+ Each data row contains a name, and value. The row also contains a
+ type or mimetype. Type corresponds to a .NET class that support
+ text/value conversion through the TypeConverter architecture.
+ Classes that don't support this are serialized and stored with the
+ mimetype set.
+
+ The mimetype is used for serialized objects, and tells the
+ ResXResourceReader how to depersist the object. This is currently not
+ extensible. For a given mimetype the value must be set accordingly:
+
+ Note - application/x-microsoft.net.object.binary.base64 is the format
+ that the ResXResourceWriter will generate, however the reader can
+ read any of the formats listed below.
+
+ mimetype: application/x-microsoft.net.object.binary.base64
+ value : The object must be serialized with
+ : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
+ : and then encoded with base64 encoding.
+
+ mimetype: application/x-microsoft.net.object.soap.base64
+ value : The object must be serialized with
+ : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+ : and then encoded with base64 encoding.
+
+ mimetype: application/x-microsoft.net.object.bytearray.base64
+ value : The object must be serialized into a byte array
+ : using a System.ComponentModel.TypeConverter
+ : and then encoded with base64 encoding.
+ -->
+ <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+ <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
+ <xsd:element name="root" msdata:IsDataSet="true">
+ <xsd:complexType>
+ <xsd:choice maxOccurs="unbounded">
+ <xsd:element name="metadata">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" />
+ </xsd:sequence>
+ <xsd:attribute name="name" use="required" type="xsd:string" />
+ <xsd:attribute name="type" type="xsd:string" />
+ <xsd:attribute name="mimetype" type="xsd:string" />
+ <xsd:attribute ref="xml:space" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="assembly">
+ <xsd:complexType>
+ <xsd:attribute name="alias" type="xsd:string" />
+ <xsd:attribute name="name" type="xsd:string" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="data">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+ <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
+ <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+ <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+ <xsd:attribute ref="xml:space" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="resheader">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required" />
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:choice>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:schema>
+ <resheader name="resmimetype">
+ <value>text/microsoft-resx</value>
+ </resheader>
+ <resheader name="version">
+ <value>2.0</value>
+ </resheader>
+ <resheader name="reader">
+ <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+ </resheader>
+ <resheader name="writer">
+ <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+ </resheader>
+ <metadata name="rightClickMenu.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
+ <value>17, 17</value>
+ </metadata>
+ <assembly alias="System.Drawing" name="System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
+ <data name="$this.Icon" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+ <value>
+ AAABAAYAMDAAAAEACACoDgAAZgAAACAgAAABAAgAqAgAAA4PAAAQEAAAAQAIAGgFAAC2FwAAMDAAAAEA
+ IACoJQAAHh0AACAgAAABACAAqBAAAMZCAAAQEAAAAQAgAGgEAABuUwAAKAAAADAAAABgAAAAAQAIAAAA
+ AACACgAAAAAAAAAAAAAAAQAAAAAAAAAAAAD///8A/wAAAAD/AAAAAIAAgICAAICAAAAAgAAAAICAAIAA
+ AACAAIAAgP//ACAgIADg4OAAIAAAAAAgAABg4CAAQOBAAGBgYAAgYAAAQKDgAAAAIABAwCAAIEAAAECg
+ AABg4AAAIGCAAECAoABgoMAAYECgAGDAAABgICAAoKCgAOAAAAAgIAAAAGAAAEDgAABgAAAAIAAgAEAg
+ IAAgQGAAIIDAAADg4ABgAGAAgOD/ACCA/wCgAAAAQGAAACCAAAAAoAAAYCAAAAAgIAAgIEAAYGBAAEBg
+ YAAAIIAAAGCAAGCAgABAAKAAAICgAACgoACAoKAAIKDAAECgwAAAAOAAQADgAADA4ABgwOAAIODgAADg
+ AADA4AAAAEAgAKDgIAAA4EAAYABAAABAYACAAGAAgCBgAGAggABA4KAAAECgAGBAwADgIOAAIEDgAACA
+ 4ADgoOAAYAD/ACBg/wAAoP8A4KD/AGD//wAICAgACAAAAAgIAAAAAJgACAAIAAAACAAACAAACBAQACA4
+ SAAgYIgAOHCIADhw+AAIGAAAEBAIACg4QAAwYHgAAACIACA4QAAoQFAAKFh4AHh4eAAwaIAAIGiQADh4
+ mAAACAgAEAgIABAYGAAgGBgASEhIABhIYAAoUGAAIFBoAChQaAAoWGgAMFhoAChoiAAweJgAKHioACiA
+ sAAIEAAACAgQAAgQGAAQGCAAGCAoABhAUAAoSFgAaGhoABhQcAAgWHAAKFhwADhgcAAYWIAAOGiAAIiI
+ iAAoaJAAKHCYACh4oAA4gKAAMICoAKioqAAwmNAAEDgAAChYAAAweAAAMIgQAAgYGAAYGBgACBggABAg
+ KAAgKCgAKCgoACAwOAA4ODgAKDhIADBQWABYWFgAGEhoADBYcAAYUHgAGFh4ACBYeAAoYHgAKGCAABhY
+ iAAgaJgAKICoACiIuAC4uLgAMJDIADiw6AAQCAAAABgAAAggAAAAOAAAMGgAABgQCAAwgAgAEAgQABgQ
+ EAAwmBgAGBggAAgYKAAAICgACCAoABgoMAAgKDAAGDBAABg4QAAYOFAAEEBYACBIWAAwSFgAOEhYACBI
+ YAAQSGgAOFhoABhIcAAoUHAAQFhwACBgeABAaIAAIGiIADBwiABAcIgAGGCQADhwkABYeJAACBCgAChw
+ oAAweKAAKIC4ACiQwAAwmMgAOKjgADBg6ABAsOgAELD4AAgoAAAIMAAAGDAAABhIAAAYUAAAKHgAAACY
+ AAAwmAAAAMgAABAACAAIEAgAEBgIABA4CAAYOAgAMHgIABAYEAAYGBAAIBgQACh4EAAwmBAAEBAYABgg
+ GAAoIBgAGCAgAAgIKAAgICgAGAgwAAggMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAWwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAn2KfdXV1XAAAAAAAAAAAAAAAAAAAAAAAAAAAAABoo2+QIJCJb28Sd3em
+ nQAAAFuKqW0aqsd6Y5/DXAAAAAAAAAAAAAAAAAAAAAAAAAB3kAWQkG8SpqaJb28gsncAbIeSroyii21x
+ kczIwwAAAAAAAAAAAAAAAAAAAAAAAABoo4mJkLKylm9vb5BvdwwAcZO/fox7z2NjqsOss2MAWwAAAAAA
+ AAAAAAAAAAAAAAAAvaGmo5ANlgUFEiBvo1xjq3p6qMTJroBkxXt9cGzFnAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAL2ylgV3vQAAAGOvxMXXq41uh6yVjxqp1YhknwAAAAAAAAAAAAAAAAAAAAAAAAAAAABvsolbAAAA
+ +5KneouS2Kx4pZF9ndywsXuvkocAAAAAAAAAAAAAAAAAAAAAAAAAAAB3sncAAAAAdayHca95bH9+cKmv
+ fMVucG2B4MYAAAAAAAAAAAAAAAAAAAAAAAAAAAChsqMAAAAAe3VkyHF5kW59cN3eZc/XyXutyot7AAAA
+ AAAAAAAAAAAAAAAAAAAAAACjIKEAAACgfv94gX+PituLDI0/aoBxqxqOY8PgbQAAAAAAAAAAAAAAAAAA
+ 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
+ OAAABwAA//wYAAADAAD/+BgAAAMAAP/AAAAAAwAA/wAAAAADAAD8AAAAAAMAAPwAAAAAAwAA+AAAAAAD
+ AADwAAAAAAMAAPAAAAAAAwAA8AAAAAADAADwAAAAAAcAAOAAAAAABwAA8AAAAAAPAADwAAAQAA8AAPAA
+ ABgADwAA+AAAHgAHAAD8AAAfgAMAAP4QAA+ADwAA/gAADgADAAD8AAAfAAMAAPAfxD/ABwAA4B/n/+AB
+ AADgB+B/wB8AAOADgB+ADwAA8AOAHhAPAAD4BwB/8kcAAPz/gf/nLwAA+P/5/+8/AADwf/n//z8AAPh/
+ /P//vwAA+P////+fAAAoAAAAIAAAAEAAAAABAAgAAAAAAIAEAAAAAAAAAAAAAAABAAAAAAAAAAAAAP//
+ /wAAAP8AAP//AAAAgACAgIAAgAAAAACAAAAAgIAAgIAAAECg4ABAgKAAYOAgAEDgQAAAIAAAACAgACAg
+ IABgIIAAIAAAACBAAAAAYAAAIIAAAGAgIABAYGAAAACgAGCAoACgoKAA4ODgAGDAAAAAACAAYABgAIAg
+ YAAAYMAAICAAACBgAABgYAAAQKAAAKDAAAAA4AAAYOAAAABAIAAgQCAAYAAgACBAQABgYEAAIABgACAg
+ YAAgQGAAYGBgACBggABgYIAAgACgAKCAoABgIMAAQKDAAGCgwAAgwMAAAADgAIDA4ACgwOAAAODgAIDg
+ 4ACA4AAAgIAgAEDgIACgACAAQABgAIAggAAgYP8AAKD/AAgIAAAICAgACAAAABhAWAAoUGAAaGhoADBg
+ eAAoaIgAMICoAChggAAACAgAEAgIABgYGAAoUGgAKFhwAChwkAAIGAgACBAQABAgKAAQKDgAIEhgACBQ
+ aAAAAHgAIFh4AChgeAAAeHgAeHh4AChogAAwaIAAIGCIADBoiAA4cIgAIGiQADhwkAAoeKgAKICwAAgQ
+ AAAIIAAACAAIABhICAAICBAAMIgQABAYGAAYICgAGCgwAEhISAAwSFgAGFBoAChYaAAICHgAOGiAACh4
+ oAAweKAAGAAAAAAIAAAAGAAACBgAABAoAAAYUAAAKGAAAChoAAAAeAAAAAAIAAgQCAAQEAgAGP8IACAY
+ EAAIEBgAMJAYABggIAAAACgACBAoACgwMAAAKDgAEDA4ADg4OAAoOEAAGDhIACA4SAAAQEgAMEBIAEhQ
+ UAAISFgAIEhYAChIWAAwUFgAIEhoAEhYaAAYUHAAMFhwABhYeAAoWHgAIFiAAEhwgACIiIgAGGCQAAAA
+ mAAgcJgAKHCYADB4mACYmJgAACjIAEBw+ACo//8ASAAAAFgAAABoAAAAeAAAABAIAABICAAAGCgAAAA4
+ AAAYOAAACEAAAAhIAAAoUAAAAFgAACBYAAAAaAAAIGgAADB4AAAAiAAAMIgAAGiIAACAmAAAGAAIADAI
+ CABgCAgAEBgIAAggCAAQIAgAECgIAAgwCAAQMAgAODAIABA4CAAYOAgAEEAIABhACAAgQAgAIFAIAChg
+ CAAwgAgAMJAIADCoCAAACBAAGBAQABggEAAoIBAAKGAQAChoEAAgeBAAKHgQAEh4EAAY+BAACAAYAAAI
+ GAAICBgAEAgYAAAQGAAQEBgAABgYAAgYGAAYMBgAKHgYADCYGAAwoBgAMKgYAGj/GABgCCAACBAgAAgY
+ IAAQGCAAGBggACggIABIcCAACAAoABAAKAAAECgAEBAoAAggKAAACDAASAgwAAgQMAAIKDAAECgwAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAARxBSUlJHAAAAAABYWFlY8kYAAAAAAAAAAAAAAAAAAJFgqqRLSzAwBXMAkKJbW1pdSVmE
+ AAAAAAAAAAAAAAAAEJdLqhpgYKQFkf9NmVRanHVJVZRQAAAAAAAAAAAAAAAAUY6RO6qXMHNYW5MxdFpm
+ TVtPSpAAAAAAAAAAAAAAAAAAAAAFpIYAAFSeW09PU15KU05MaEkAAAAAAAAAAAAAAAAAAEtgAAAAk55j
+ ml5Mp2haXUqpW3IAAAAAAAAAAAAAAAAAS0sAAEpJoE5PpZpMZVWnMZyZVQAAAAAAAAAAAAAAAABLMAAA
+ cqCeSnppZGZKWzFNaV2ecAAAAAAAAAAAAAAAAEtLAACLVF6iZHROp2eiW1paeWnxAAAAAAAAAAAAAABI
+ GmAAAElJSmh6SWNVSk5hZqJ1VXUAAAAAAAAAAAAAlpmjeJAAk12eZXZ5p3WndnZpaUlbZgAAAAAAAAAA
+ m2lFRTw8PDZeWV1OlE0xeWlNeVVdZmmZUAAAAAAAjzYKRaysRUVFRa02SXSnYaFNTUxpTFSoTmKTAAAA
+ AI8KOkSrrKysrKxErDullF6fVWhiVakLaVWbVZoAAAAAqK2sRKZEqyCsRKummKBZT0xPTHppZVMLqVN4
+ cQAAAAA6O6sYGKYYGKsYphgELjejY09KTTZNaWdMNpkAAAAA+zc6XFxcd3d3XASmXEw6PZZiZ6g2ZUw2
+ T2QLnwAAAAAdoTtcLcV3pndfLi2jraMtcmNkT05np0xnaXhUAAAAAAD5NKL87xgYd1+eLZ06+1wAVE6o
+ p2cKZ2WjjgAAAAAAAAAQo67EcuZuj5jkACue9gAAkTEKeJfbioptRgAAAAAAAACIr0dLVwBXmEeL5Qg4
+ AAAAAOnqwNbVb9O5AAAAAAAAAP38RmDae3tfkDqIX0UAAABq3W9v6+2BtssAAAAAAAD9q6v7AFl1dV9f
+ mpgIXwAAAABrtG9v7O2BagAAAACSF52fL/AAAAAAAHUAAAAAAAAAAACC1NS+3s/ZzAAAAJ03MjqjdJ0A
+ AAC3FIMUtwAAAAAAzr27goK5awAAAAAAWaNgGho3dAAAyr+/v4ODtQAAAMmAAM+2zxMTawAAAAAAAK6z
+ kZ1xAAC1un3HVwAAAAAAAAAAawB/ftAAAAAAAABCNUIAAAAAAAAAAF8AAAAAAAAAAEcAAGpGAAAAAAAA
+ AEKsEQAAAAAAAAAAlZUAAAAAAAAAAAAAAEgAAAAAAAAAAC3hAAAAAAAAAAAAAAAAAAAAAAAAAAAARgAA
+ AAD///////////4BwH/8AAAf/AAAD/4AAA//xgAP/84AB//MAAf/zAAD/8wAA/+MAAP/BAAD/AAAAfAA
+ AAHgAAAB4AAAAeAAAAPAAAADwAAAA+AAIAfgADAD8AA8A/gAOAPwgDwDwPv+AcBwfAfAYDkD8GD/Q+P9
+ /s/j/P/v8///7ygAAAAQAAAAIAAAAAEACAAAAAAAQAEAAAAAAAAAAAAAAAEAAAAAAAAAAAAA////AFEl
+ swBJDW0ABEMwAAJHSQAFDwIAXl9ZAHJtagBwfYQAQVNqAAFtAAAAZgAAAz0CABQ/AQAXQwAAGkAAAA8g
+ AQAhSJQAM1SRABw6fgAFVk0ADE48AAplaQAgWgIAMYcMACl7CwAOJwMAAAMtABwPNwAhIQAABxACAA9S
+ UAAnQUgAAISfACRoDAAxmwQAK4MHABMpAAA8P1kATBMbACQSVwAKL1EADS5CADFMVQABIF4AJ3CGADB2
+ kAAzdlEAMnY1ABMnEwBARooAVVq5ABgATwAWBn0ABh56AAULaQBOeKYAT3WSACpNZwAucpgAMHifADFx
+ nwA1b5YAMF93AEBalgBvjecADCLAAAkPpQAbJr4AFiLGAAAJjgAzbIEANGh8AClhgAAzfKEAMHWWADJz
+ kwAsW3MAQ4mvADyY9AAtgf8AIpr/ACuk/wBdk9UAG0tkACladQAqbI4AK3GUAC5vjwArbpEALWJ7ACdl
+ aAAOaoQAJJ20ABx0gQAeTF8AJ116ACViggAgZosAJmeJAChvlAAhZIgAH1ZyAHVmYwAaPVMAJ19+ACto
+ hgAmaIwAKGB9ACFdfAAgY4YAGU5qAFtgYQAYQVgAIVx7ACtrjgAtZYIAJmKAACNhgQAmX3wAIUpdAHp6
+ ewARN0wAH1R0ACdXcgApXnoAJ2B+ACligQAdSmAAKiopAGZlZQCRkZEAbm9vAFFSUwAWO08AJFRuACFP
+ agAhUm0AHD9RAAMKDgA3NjUAWlpaADk5OQA3NzcAPDk3AAYSGQAVNUgAFjFAAA8jLwABAgQAVQAAAP//
+ /wBWAAAA/f//AFcAAAD8//8AWAAAAPz//wBZAAAA/P//AFoAAAD9//8AWwAAAP7//wBcAAAA////AF4A
+ AAABAAAAXwAAAAEAAABgAAAAAQAAAGEAAAABAAAAYgAAAAEAAAB3IFIAbWFuAAAAAAAAAAAAAAAAAAAA
+ AAC0VWMAtFVjALwEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAEAAAAAADgDAACfAQAAPwAAAAEA
+ AABAAAAAAQAAAEEAAAABAAAAQgAAAAEAAABFAAAA////AEYAAAD+//8ARwAAAP3//wBIAAAA/P//AEkA
+ AAD8//8ASgAAAPz//wBLAAAA/f//AEwAAAD///8ATQAAAAMAAABOAAAABwAAAE8AAAALAAAAUAAAABAA
+ AABRAAAAFQAAAFIAAAAZAAAAUwAAABwAAABUAAAAHgAAAFUAAAAeAAAAVgAAAB0AAABXAAAAGgAAAFgA
+ AAAWAAAAWQAAABIAAABaAAAADQAAAFsAAAAIAAAAXAAAAAQAAABeAAAA/v//AF8AAAD8//8AAAAAAAAA
+ AAAAAAAAAAAAAAAAAI2Oj5CRkpOUlZYAAAAAAACCg4SFhoeIiYqLjAAAAAAAAAB6AHt8fX5/gIEAAAAA
+ AAAAcQByc3R1dnd4eQAAAAAAAGgAaWprbG1ub3AAAAAAXF1eX2BhYmNkZWZnAAAAT1BRUlNUVVZXWFla
+ WwAAQUJDREVGR0hJSktMTU4AADM0NTY3ODk6Ozw9Pj9AAAAAJygpKissLQAuLzAxMgAAABwdHh8gISIA
+ ACMkJSYAABITFAAAFRYXAAAYGRobAAAHCAkKAAsMDQAADg8QEQAAAAMAAAAEBQAAAAAABgAAAAACAAAA
+ AAAAAAAAAAAAAP//AADgBwAA4AMAAPoDAAD6AQAA+gEAAOABAADAAQAAgAEAAIABAADAQQAAwGEAAIxh
+ AACEYQAA3PsAAN//AAAoAAAAMAAAAGAAAAABACAAAAAAAIAlAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAMAAAAGAAAACAAA
+ AAcAAAAHAAAABgAAAAIAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAxIOAwgFBAEOAQAAEgAA
+ ABgAAAAbAAAAHQAAAB0AAAAcAQEAGQAAABYAAAAQAAAADQAAAAwAAAAKAAAACgAAAAsAAAAQAAAAFwMH
+ CRwBAgMhAAMEIwEEBSUAAgMmAQICIwEBAR8AAAAYAAAAEQAAAAkAAAADAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQICAAgBAgATAQEBIAcI
+ CCwFBgc2BQUGQAYGBkgGBgVOBAQEVQUFBVUEBARTAQICTQcHB0UGBgY+BwcHNgICAi4AAAAoAAAAJAAA
+ ACcBAgIsAAABKQAAADAAAAA7AAAARwAAAE0BAABOAAAATAAAAEgAAAJCAQUHOAEAACwAAAAeAAAAEgAA
+ AAcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEGRIMEgAA
+ AB8AAAAxAAAAOwAAAFUAAAB2AAAAjQAAAKgAAAC1AAAAsQAAALIAAACrAAAAmQAAAIcAAABuAAAAWgAA
+ AEoAAABAAAAAQQABAzwAAAA8AAAAfA4eJZoGDA/AEx8m2A8YHNoSFxjaEBgbxwcAAJgDAACDAAAAagAA
+ AFUAAABHAAAAMwAAACAAAAAQAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAIAAAAQBQMCIAAAADMBAABMEA4Nkjc2Ndh9e3r4kI+P/J+fn/+IiIn/b29v/3Jycv9xcnL/Xlxb+0lK
+ SvJGRkbaUVBQzBoaGqIAAAB4AAAAUwEAAF4MCwu/G1Fw/xtTdf8iQVD/ImCB/xtXdv8YN0n/HE1s/x46
+ S/8QIy35EiUw4QoDAKMAAABwAAAAXAIAAEIAAAArAAAAGAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAQAAAASAAAAIwUFBjIAAABkQ0ND/46Ojv9/gID8hoaG/YmJif1ycnL8YWJh/VVV
+ Vf1bW1v+ZGVl/nNzc/94eHj/np+f/7Ozs/9HSEn6AAECmR41QuYaP1L/KW2V+xZbh/spVm38Gi85/B1X
+ dPwpQE78H2OO/CZoj/0eSmP+Dz1a/w8oN/0AAACtAAABdgAAAGAAAABEAAAAKwAAABcAAAAGAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAMAAAAGwEBAC4AAAA9Dg0LtDUzMv9nZ2b/bm5u/oWF
+ hfu7u7v9v7+//qioqP5xcXH+cnJy/nl5efuQkJD6cXFx/FBQUP8kJCLuAwAAvyJpj/8mdJ77FRsd/ipp
+ iv4sW3L+KFNs/hZLbf4kOUb/HDlJ/htae/4XKjX+KF17/DCTxf4jPEj5AQAAyQUEBHEAAABRAAAAOgAA
+ ACIAAAAPAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAACgAAABgAAAAlAAAANhMQ
+ DoYqKCfbVVNT/zIxMf+Li4v/4uPi/qurq/99fX3+fX19/2FhYf+UlZX/fHt8/zIzNNEFAQCqHjdF8yNZ
+ dvwkUGj+Hk1q/y9adP8hKi3/IERa/xhah/8kdKT/IGOG/xEwQP4lUWj/MlVo/zJmfP4hNT3+Ey88/wkV
+ G9MAAABZAAIDPAAAACYAAAATAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAIAAAAJAAAAEQAAABAAAAAlAAAAUAAAAH4TExPdtbW1/qysrPx+fn7+QUBA9RUUE48AAABzSFNQRQAA
+ AEseOkvoI2iW/yArMf0TM0P/G2SS/h5VeP41XXH/J1d0/h0+UP8rYXz/NISq/zxrf/8hXH/+FFB4/ixt
+ jP8oSFn+IWKJ+w0iK/8DBAScAAMFOQAAASEAAAARAAAAAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAABMREANaW1oDDQsJAQcICAAEAwNqdHR0/8PDxPhmZmf/DAwMmHt6
+ egAiLzUAbImSABsgIZQoc5z/G0hn+iNQaP8fU27+Jm+W/zFwkP8qXXv/GUJc/y9QXv8papD/MFZq/xca
+ Gv8zfKD/KH6q/zSMtv4sUmj/G2WX+ydxlf4aPE3MAAAAHAoYHxcAAAAIMSgjAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAQEAAgIBAgAAAAMAAAArSEhI+8TE
+ xP1LTEz7AAAAMwAAAAIbLDUHJj9NABAVGK8oYHz+F0BV+xxkkv4kapr/Jkte/yA1Pv8veJv/KWmH/zRo
+ g/8TTXb/ImyW/ydVaf8ZMkP/Klx4/zFmfv8nQ07+L4ex/jap4P0WNUP1AAAAUgAAAQAAAAACfmteAAoJ
+ CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOrr
+ 6gD+/v4ELzAw0rS0tP8xMTHkAgECDl5eXgARBwEADwcBLiZRZ/EOGBz+H2KH/RM/W/8eYpH/LU9f/yZk
+ jf8kWHf/MlRh/zFmf/8mg7r/K43C/zZviP8VSG//G2KR/x1FV/8mU2z/LWGE/y5LV/0jWXL+K1Rm9ggA
+ AEofEA4AJx8aAQoFAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAKSlpQGvsLAAMDExqJubm/4oJya3eHp7AGRsbwUrRVEAHSYpkClqjP8KJzf6GEpk/yeB
+ r/8sdZz/OGV6/xVMbv8icKP/I1Zt/x0cHP83YHD/OpC2/zNjef8idaT/IGmP/yJcdf8kWn//FlSA/iM6
+ Rv8RKjb8Oqnb/yRCUOMnHBgMIBsYAAgBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAENDQwJFRUUALi4uh42Ojv8cHR6hRkhJAEFVXgWh3uQAHy0zuiZL
+ Xv4YVXv7F0Ri/iR6ov80aYH/MYCl/xxijP8hcqb/MXma/y5edv82hK3/MU9b/yUtMv8eZof/M6vj/x9O
+ Yv8ufqf/IXOh/y90lf4XP1T+IV+A/C1hef8AAAI7AAECAA4REQIAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKampgGysrIALS4uopGRkf4VFRWhNTc5ACQm
+ JwYsIBcADAQBqh9Sbf8TSGr7H16H/xsyPP8iMjn/MW+L/zCazf81ndH/OV5t/yVwnP8TUnv/MY28/yM5
+ Qf8iU23/HlFp/xhIXv8pZoH/Oa/l/zBjev8cXYX+IGaZ+Ro2RP4IBQZgCQYGAAwMCwMbEgwAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAgIAAAAAAG5ubgDIyMgIPTw83KWl
+ pv80MzPEw8TFAXt7egQBAAAACAQDZx84Rv8tfaj7JFp1/ydef/8reKf/NG2I/y9UZP83TFX/NoGj/yR9
+ sv8QOVr/K4Ox/zRVZf8eXIX/JmeR/xgyPf8UO07/Gz5O/yBQZv8hdKH/JHKj+iZ8pv4AAACbAgoOAAYT
+ GgIQCgYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALCgkAERAPBAAA
+ AAAAAABWZ2Zm/7Ozs/46OzvvCwsLFrq9vwEuQT8AGRscdypNX/8kOkT7J0hW/yV5qf8LME//NZ/X/yVE
+ Uf8UJzH/KWmG/zCXyf8zmNH/K2B4/ztwiv8ZYI7/J3al/yVOYv8gcp7/IWqT/w4vPv8og67+Nq/o/R1c
+ ef4JGyPuCQ4SHQYDBAAIBAIBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAA
+ AAQNEBABHSIjACAkJQovLi7YwcDA/bS0tPpeXl7/CQkJZAAAAADA9f8CCR4txhxeif8cOEX7JTpD/zdz
+ kP8lhbX/NoCj/yRWbv8bZI7/Hl6D/yJPZP8kUWT/HEda/x9BT/89q+D/NXKN/yNZc/8bXYn/HmKQ/x9l
+ if8JGB//H1lz/hlVcPwldpz/AQAAZQEAAAAECgwHAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAA
+ AAH/AAEDERgdAAAAAAAKAAABAAAANwAAAKw1ODr/cnR1/F1fYf1OUVP9FRMT6wkAAEA+eaMAFCAnqyFk
+ j/8TO1L7H2OP/y1PZ/84eZb/K2V//yBliP8YUHX/Jnyx/xpTbv8ZUnD/JXyn/yVzmP8lQk7/KDY8/xxW
+ cv8wnNL/L5LH/yRxlf8XPVH/JlFv/iFadvonf6j+BQECgwYFBQAMFBoIAAAAAAAAAAAAAAAAAAAAAABs
+ ogACAAAAAAIDA/8CAgCBAAAAAAAAJBQRFGstTlnBBnOK/wCYtv8ArtX9AKLM/wCkz/8ArNX+CJa6/x5Y
+ aPwAJS6sBwICty1gff8VO1D8Fkdn/x1uof8xbIj/HSMm/yRbdf8rkMT/LZPF/xtbd/8ne6v/F0lz/yqH
+ t/8XPEz/M57V/zBui/8maIb/NKvi/x9cef8dX4f/GlmE/ytkhPsfYHz/CBslmhVIYQAMICoIAAAAAAAA
+ AAAAAAAAJNT/AABilAACAAECAgABAAQAABcADRSWPHWH9Fu00P8dxPT/BNT//RG8//wOvf//B9D//gHX
+ //4C0///BNb//SrU//5tzO//L5Kr/wNBU/8XIyr/KElb/yuOv/84eZb/J1Z0/yROZ/8lWXL/GlFq/xtL
+ Yf8qjb//K4zE/yd0mf8vb47/GViE/ymDtv81b4r/GUlg/yBEU/8rhrX/IXKh/jiVwvwbKzP/AgwRxxeB
+ tgEHGyUFAAAAAAAAAAADBgoAJ+b/AAA6VwEAAAAACAAAUwBBVuBbr8z/ddn//w66//sVnfn9JJD0/jps
+ 9f4jkvT+EK/2/xan9/8Psvn+D7P4/wmm9f4luP38xOX//WnG7v8AUHb+HjVB/yFKXv8xVWX/HV2F/xZX
+ gv8yXnT/J1Jn/zNwkv8kTWD/LZjJ/yFRaP8udZn/GFmB/yZ5p/8nVWv/ID9O/y6BsP8qVmr/MZ/S/zZx
+ i/09eJP/GzZC8gAAABwJAgAAAAAAAAAAAAAAAAAABSAtAQE0TgAKAQE7AEty/2/P//+Bz//6B6D0/TCA
+ +P48cvn+O2z5/jtw+v87dvv/LIP1/ztt9/8lj/b/HJz4/i+J+P8XiPb+ZY32/vnv//9lyf/+AGeR/zI7
+ PP8bIyb/NG+N/zSUxP83aH3/KoCt/xZPev8sgbL/HThE/zJngP8yW2z/Mp3Q/zCf1P81Wmr/OYGo/xVW
+ g/8sdJ3/LWJ5/ytbcf0neqD/KmN/9QAAACIAAAAAAAAAAAYFBwAAAAAABBkqAQggKxIAS3DeUsb//s3M
+ +vsrefP/Lob8/ilE1v4ULbz/PXr//zRm8f8vXun/PnL9/zls9/82c/f/L4b6/0Bz//8uU9//ESq7/2+O
+ 8v+Qo/D/IHPN/xc0Tf8nYYT/Ikpf/ypJVv8oPEX/LGuM/x5wov8tfaj/OWyD/ymFtP8vdJf/NV5u/zFl
+ fP89cIf/O7Lv/xdEX/8vk8b/KDtC/yxQX/0mdpz/KVFi6WJRShIAAAAAAAAAAAYFBgAyWHQCD0h0AAIH
+ Ioc6fOD/4ev/+3ma+f8uZ/r+RoD//xYuvf8AAJP/OXL5/yxV4P8DCZr/MWbv/z15//87cvv/MVzp/zdx
+ +P8sWOH/AACV/wAAiP8ANF3/AnSB/wUmOf8VHiv/GlyB/zBid/8yY3z/L2F5/y5xkP86epb/RYyw/xJG
+ bP8rgrb/Q5zE/x82Qf80a4f/O3mV/0G17P82cIn/LWOE/z9nffxNjan/JjtCwqHT5gBhf4oGAAAAAAYE
+ AwAvS0wAE1NlFwwsd++PrP/91dbz/Txx9P4lUd7/FCu5/wwYrf8AAJX/Bw6l/wkTqP8AAJH/Chaq/xUt
+ u/8vX+j/HT3J/wcPoP8KFqn/AACg/wIBZP8MPFr/Enyp/wEuQf9CW1T/NHaW/zxxiP8lYob/G2KQ/zFX
+ af8eJCX/MHGP/zOb0/80q+n/O4Cf/yRlif8XVn3/PG2J/ztvhP8zZoH/Ez1d/iJhh/stSFT+BgcHeAoa
+ IAAFBwcHAAAAAAkVLgIAAAAAAAAAOSNTuv/C1//9oKDd/gUNov4CBJr/AACQ/wAAlf8AAJr/AACZ/wAA
+ mP8AAJ7/AACb/wAAkv8FDKH/DRus/wAAkf8AAJT/AACY/wECp/8CAKL/CgiI/y1Ja/+V6uX/PVxf/zNd
+ cf8eZ47/EkBd/ztwiv8sao7/J1l2/zhofP9Jrtn/L1ls/yyHu/8cZpf/PrDp/yU4P/83cIr/MpzT/jqq
+ 4/sgNj//AwMEZgMAAAAGBgUHAAAAAAAEJwIAAQgAAAEDQRIrof7I2f38l5rd/QAAmP8AAKP/AACd/wAA
+ oP8AAJf/AQGJ/wEBi/8AAIr/AACL/wAAlP8AAJr/AACb/wAAof8AAKX/AACc/wAAYf8XJk7/UIOF/p77
+ +P+g/Pj+OlhZ/iQ4Q/41gqb+QZ7K/jl0kf8iZpP/E0lr/0Cx6P80SlT/JT5K/zB/pP9Dwfb/OX6e/yta
+ ef87dZn+SY2r/Td3lPw1V2T8BAMDSgUFAwALDQ0FAAAAAAQCNAMFBCkACAcXVQAAhf6zuvP6rbLl/QAA
+ cv8EA1D+BwlD/xoBLf8aBDX/Dw1r/w0LZf8QDmT/FhBX/wcjO/8BFUP/CgRF/wgIV/8EA2L/CxJJ/2Og
+ lf+a+vD/q////3W3sf8iOUf+LDVB/jFTYPwhUW3/Ol5y/h45Rv4vmM//NpvR/0G79v82aYD/NZLD/zJw
+ lf8uTFj/Mltv/xdIav4hcKH/MU1a+kCJqv8aRVi+JFlxACtPXwEAAAAAAAAAAAICHgIBARMAAAAUPwIB
+ Q/52ebj72OD5/QcHdf0CAYT9BhV3/pgEAP9RDCj+FRqM/yYdkP8GB6f+GQmP/xRncv4CmIf+HQZS/xMW
+ h/4QB1r/OldT/6r///+V8/X/ME1J/gAARP8AAIT+Gxwm/zNpgP8ORG37NIrA/jJZaf8oVmv+LXKR/iVK
+ Wv83fZ7/E0Zn/x9vov85dpL/NkxT/zqs5f8zh7T9OGR1/kCMr/g/Ozk5Q0lLACAzOwIAAAAAAAAAAAAA
+ AAAHCAYAUlQJBxIPD+AeG3X/wMbw/0ZIvP8PCpr/JyBV/6EAAv4qE1P/CQuy/xUQwv8KCbL/DQiO/hYZ
+ T/8Atar/GSVS/xAJff8gF4T+LDdK/qL//v52wLn/AAA2/gEDYv8CBE37AAAATRs2Qb8weKH/Obbt/z+F
+ pfsvdJv+HGiU/zWJs/41bYX/Na3o/y+Wyv88d4//Royw/0N8nf5CfZz8L0tX+RYfI4xXVFsZRD9FAwAA
+ AAAAAAAAAAAAAAAAAAAMDAwCJCUQAAoLCHMKCQffS05etpedu9MICyrpQA8X+YoAAP4NFzH6FhB++xcT
+ c/ocF177GRlt/BwGR/sAfnr9D2Rt/x4LR/4gH17/DwcV/zRYVv9wppf+BQBc+wAAl/4AAxGxAAFhAGgA
+ ABEcGhyXK1hn6yRMXv8xmM7+F05y+jOj3/0nQ03/SYen/jyDs/4qLTL/M2dj/zlMKv1AbDf+IToL3gEH
+ AG4bXAARLGsAAAEDAgIBAAEAAAAAAAAAAAAAAAAAAAAEAQQDBAkDAwOmAAAArJWcspIoR0ueRwAA7nAA
+ AP8ABgDPRUEqxCkpGckAAADVAAIA2gAAANcAQkL5AHl4/gYAAMgECQCsCgwJrwAAAOgIS1X/AkVd+gIK
+ Nf8LAABoEQsKAB4YGwAHAAAAAAAAGggFBnwwcZD9OabY/z+y6P9BZoD+OVND/ydMGP8icQD/LJoC/y+u
+ Gv0pnBP+G24V1QoeA2gcPQZQDhIJJgEAAgABAAEDAAAAAAAAAAAAAAAABgYFAQsLCwANDQ0kAgICwiEi
+ K8EnMkLGPQAC/iEMDN0BAwWPS0pQixsbH5EAAAKWAgMGngMCA5sCCAjJAFBQ/wkND+IKCQvEAAAArAAA
+ AKIFISrbAZmv/wDH2vsDXnLyCAsKPQ8YGAAEIScFFRQQABEAAAADAAApAQMOcQ8SHakgSRD+LY8G/i6S
+ AP4zqQn/MZEN/zCZGv8ylxv/M4kO/yNSAf8WLQD/Dh0BpgAAATEBAAACAAAAAAAAAAAAAAAACAcIAAkJ
+ BAEDAwQAAwMCFwIBAMARAADKmAAA/xQMC5IAAABJ4uHgXGpqaVwAAABcAgMDXgADA1MQAQGBA5qa+wBD
+ Q9AKAQGyQWpq/yI4OOAFAADIAiAo7QDX//sAwO3/AyUsgAQ1QAAFUmQDFw4HAQ8AAAUOIggsHjEAgCZ6
+ Ddkwux/9MnwE/TGREP4wnhT/MJoP/zFjAP4udQD8I1AA/REVBKgWHQc0GicKIgAAAAYAAAAAAAAAAAwT
+ GgAHDxYABQUGAQkIEAUFAQAFDAoGKwUQOtQFGGL+GxNB/w8UErkMDAqFbm9wfGJiYmwEBARrCgoKawgL
+ DGkOAABrDmxs9gB5efUfGxvrsP///2qqqv0AAACRAyEoqgDN+/wDl7j/CRYWUgsWGAADHSQEAAAAAAAA
+ AF0DBgD4JW4U/ymvJv8tfQf/LmoA/zGvIf4weQT/MMIp/zGWE/8rWwD+GEYA/REzA/UAAAKjAQAGWggG
+ BjUKCAcAAAAAAA0VHQAIDxcAAAAAAAMPPAAEDTYAAgYbkQArw/8AOe75ADnr/gEWYv8GBgWfCgwQfBMU
+ FJwdHh+tJSYnrh4oKbAcHyCpDjc31ACdnv0BAAC+QE9O/CxJS/8AOETLAaC+/wGlx/8MLTWeF3d3ARNM
+ XAIAAAAAAAAAAAAAABURLAFbDjUIXAkQAKkYMwDyK2IA/DG2Jv4vfgn/MI8N/jDXN/4tiA7+LmEA/CRF
+ A/wIDwHVBAMBngkOAj8JBwYAAAAAAAwTGgAECQ0AAAAAHwsTKGsJDyJbDQwQmwold/0AHpD7ACCv/QAa
+ df0HAwQ1BwEAAAAAAAcAAAAHAAAACAgIBwsICgoHBQYFUQGVk/8ASkh3AG1pPwA2OaUBbYDfAWZ6yAEB
+ A2YAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAANGwAAER8AKxEkAG0XRwWpL20A/jGmHvwwhxH/MHEA/zGX
+ F/8txy3+JHoG/xxNAf0IGAKPAAADNgQACAQCAQEAAAAAARopNwASIjACIC05yVZ0kP9KZHz/UWyH/1t4
+ jf9adYr8PlVu/w8VHeMAAAAaAAAAACEaEwAAAAEABgwOAAwBAAAKAAAABwAADAJwc+0BXGXeA5moFQB4
+ hi8AAAACAAAAAAADAAAAAwIADQAAAA0AAAAAAAAACwgIAAAAAAAZPgIICRUDFAAABSAkdALsM30A/S+I
+ E/4wdwr+MYMA/jJuAP0ldA39DjgS1xExAuAHHADrCQUHvQgCC30HBQUtBAMDBQAAAAAAAAAhKjlG+Vt4
+ kfpDWG36KzxM/VNyj/89U2r+RmB3/jZKW/lJY3vgKTZEuRIQECoUFhgACg8RAyAAIAABAAMIABcASAYR
+ CeEBQgn/BSwA5ABEAP0BLAPEAhIGnwIAAh8DAAINEwAMACQCGAAAAAAAIRkXABgdDQJHcxIADAAQDhc1
+ BtYwmwD/LF0A/yZvBfwybwH+KnoA/y10AP4rVQD/CA0CpSx5AQ4teAAdBwAGPwQFADwAAAAPAwEBAzRH
+ VwA7XWAXM0VT567W9P+2xdf4XnCA/sjd7/652vv+epWr/h8oL/89Umf/YoWn/xciLI8uQ1UAJTc/BgcA
+ BAUDDAKDAWAA/wDJAP4AgwD+AMsD/wCQAv8AhwD/AJQA/gE6AfcEKgNpBjIEAAgiBQMAAAAAHhYVACxr
+ CwAAABIrFDgF0SN2AP8SPQDQCRQAuiFtAPMpWQD+GlAA/BxVAO8qVQDzIVIA/wYaAmUOMQMACBgDAAYF
+ AgAAAAAABQMCACg2QQIlODsACBIbTxoqM/RYcH7/dnR0/2daV/61ur//g4GB/6CnsvyApMT7Hys2/wkM
+ D1gLDRIADxoRAQ4ACQkFMgPgAJMA+gCrAPgAlgD8AKMA/QBeAf0AkwP/AIAD/wZxBOQDEgJRARAAAAAN
+ AAISDQ0BCQIJAAMACFkMGgTYHWQA4QgeAXwAAAAjDzUAmBlRAv8PHgCuEjsAxxRLAM8PEgBcLmkA/xAt
+ APoABQI7Ag0DAAQDAwMAAAAAAwAAAGiNrAAAAAABCxMbACIAAC1UIiyVSyco8YAAAP5MWmT7V2Bv/4ii
+ wP91mrf+ISs0yAAAABURIBcBAgIAAAkABlMEVAP8AIIA/wBWA/8AUAP/ADYA/QAMAP8BCgDnAAAAeRlj
+ EgwJPgYABgAEAAAwAABXQj8AAAAAAAAOABYAAAAUAQADBQUEAwAJFAISDzUB9wAOAIIGCwA8CRoBchA1
+ Af4AAAAYChgAYBEiAdUBAAKvAgAEMwUEAwACAQEDAAAAAENabQAAAAAAAwAAAlIIBQBvAAAlKQAA508B
+ AO0AAAAxAAAAMgAAAEIAAAAyAAAAAgQFBgAWIhkBCQAGAAAEADcAGwBzATwBjQkBAK0CAADIATA0/wQ3
+ Or0VmJwUBLCzAAAAAAAQVwoBBAQCAAAYAAAyJiQAAQABAAAAAAADAwIAEBIKAAwNBwAKCgWKBAQDqDkq
+ JwMACgARETQGIwokAf8FBwNlAgMCAAAAAFQAAgA2AAMACQAAAAAKBwcAAAAAAAAAAAABAAAAAgICAAAC
+ ACAcA23yYBTX/zcMmPsNBxFLFgkmABwKMwAAAAAAAAAAAAAAAAAVIxkACgAHAAEEAQAAKwAAAdMBABHn
+ 8gAJ19YFA3Jy5wGhodMA+/kAAtXUAgAdAAAOUAkAAwQCAAAAAAAAAAAAAAAAAAAAAAATDw0CEAwLAAoH
+ ByUHBQWnVUU8BRANCwABDwACEEkDAAQNAcsCBgGhAwsBAgECAAIAAAA7AAAACQAAAAAAAAAAAAAAAAAA
+ AAAPCRECCgAWAAMAAFVfDdn/rH7/+IhI//8cAEmtRAC1AkgIpwYDAwMCAAAAAAAAAAAKCwcADQMJAAcF
+ BQEBCgEDAP8AAwZbWQUGeXgACCwrggF+f/8CAAA6AwAAAAQFBAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAABAAAAAAAAADwAAAAbBQQDAAAAAAEKCQcCBAAEAAYEBGIEBAPJAwICAAAAAAAAAAAKAAAAFAAA
+ AAAAAAAAAAAAAAAAAAAJBQoBBwQPAAQCByc7B43pfDbo/E8bp/kFAhJiDwM2ABwFQAIAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAEGAAAABAAAGwQaGdkEFBNWBRwcAAUNDQIAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAABAQEAAAAAAAAAAAAAAAAADAoJAAAAAAAeGBQABgcEAAYFBAcGBQSpAQAAIgEB
+ AQAEAwMAAAAAAAAAAAAAAAAAAAAAAAAAAAAOCA8AGwszABcHLQABAQJ8HQBT4gAABbUAHAAAAH4AAAA4
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA0AAAANAAAAFQwJAAoAABgBAAAMAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAADAAAAAAAAAAAdFhMACQcGAwgG
+ BQAFBANhBgUEUQgGBQAKCAYDAAAAAQAAAAAAAAAA////////AAD///////8AAP/+P////wAA/8AD4AP/
+ AAD/gAAAAP8AAP8AAAAAfwAA/4AAAAA/AAD/wAAAAD8AAP/wBgAAPwAA//wcAAA/AAD//jwAAB8AAP/+
+ PAAAHwAA//44AAAPAAD//jgAAA8AAP/+OAAABwAA//44AAAHAAD//DgAAAcAAP/8GAAAAwAA//gYAAAD
+ AAD/wAAAAAMAAP8AAAAAAwAA/AAAAAADAAD8AAAAAAMAAPgAAAAAAwAA8AAAAAADAADwAAAAAAMAAPAA
+ AAAAAwAA8AAAAAAHAADgAAAAAAcAAPAAAAAADwAA8AAAEAAPAADwAAAYAA8AAPgAAB4ABwAA/AAAH4AD
+ AAD+EAAPgA8AAP4AAA4AAwAA/AAAHwADAADwH8Q/wAcAAOAf5//gAQAA4Afgf8AfAADgA4AfgA8AAPAD
+ gB4QDwAA+AcAf/JHAAD8/4H/5y8AAPj/+f/vPwAA8H/5//8/AAD4f/z//78AAPj/////nwAAKAAAACAA
+ AABAAAAAAQAgAAAAAACAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAQEBAAAAAAEAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABlDWggSKjYNDyIsDQsc
+ JAwoQlEJFB4jAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMD
+ AgAMDQAGDA0OEQAAABoAAAAmAAAAMAAAADYAAAA1AAAALwsLCycYGRkfDAwMGwAAABkEDBAbAAAAHAAA
+ ACgAAAA0AAAAOAAAADMAAAAqAAYKHwABAhIAAAAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAACCwkGEgEAACkAAABDCAYFeR0cHJ0bGxy1FxcXtxcXF7MMCwqhAAAAggAAAGEAAABCAAAANwAA
+ AEgMICqQDyAotxMrN84QHybQEBkeuAcFA5IBAABsAQAARwEAAC0AAAASAAAAAgAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAsHBgYkAAAASTk4ONV6eXn/lpaW/4uMjP9mZmb/ZmZm/2NjYv9iYmL4gICA60tL
+ S8gBAgSFFy453x1bgv8iT2n/H1Bp/x9GXP8iWHn/GkBW/w4nOPEBAwacAAAAWwAAADwAAAAaAAAAAwAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAABgICAhsAAAAzIiAftFJRUf9oaGj/lpaW/7i4uP16enr9c3Nz/42N
+ jv+AgYH/ODEt7g8lMOAlaIv/IEVZ+ipWbfwdSWP9IEpl/RtMZ/0bQFT8LWyO/x0/TvcDBQauAAABUAAA
+ AC8GFBoQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwEBAA4AAAAaDQsJXS8tLKI7OjrixcXF/pKT
+ k/5TUlLzWlpbu0dISI0OHyqxIk1o+hw9TfwfXYT+LEtc/x9HYP4laZD+LGeE/yNSbf8pY4L9KVBk/hIw
+ Qf8DBASNBQoNLAUAABQGDBABAAAAAAAAAAAAAAAAAAAAAAAAAAABAQAAAgIBAAAAAAAAAAAAAAAAAAAA
+ AD+EhIP7iYmJ/w0NDHAGAAAAAAAACyRXdPAfUnL/IFNw/SVhgf8rZIP+JVBn/ypde/8oTF7+JlNq/y6A
+ qv8tYHv7JHil/hlBU8mk//8MCjZOCFSt1wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAF7e3sBjY2NB2pqauB2dnb7AAAAHAALEwEVJS1NHDpI/xlPbfweX4v+KEpd/iZdev8yYXX/ImuZ/yp7
+ pP8eRmH+IVRy/iZNYf4ydpn8I1Rq+RkqMXQzU1cAJkxeAj5vggAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAO3t7QH09PQAaGhouGVlZeLd3d0DGoW6AClOYbYYQlj+GVd4+y9+pf8qYH3/G2OS/yZK
+ Wf8zY3j/N2+J/yRsk/8icZj/Il2A/x5Laf4fSFn+LnOS/yYyMTAmNz8AHSwyAwAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAA3NzcAuTk5ABlZWa0Xl5f1gAAAAA8j74AGSkwxRtUd/4aUXL8Kk9d/i14
+ nP8ui73/MmuI/yNpk/8qUWT/IlJo/yFlg/8lZob+M4u1/x5UdfofUnL9ERcXZw8UFAAWIycIAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAQDAsAEgAAAP///wKFhoYEmJucB2dqauZsb2/mS05PCwAJFQMXHyKVJ1dw/yVc
+ evsgW4D/MW2K/y9LVP8xfqT/HGyg/zFwj/8kWn7/HlBu/xpIXf8eTWL+JHWh+yd/sP4IFx2iP3WMAA4s
+ OwgAAAAAAAAAAAAAAAABKDQAAAAAAA4JBwIAAAACSk1MABoWFgAGAABrpp2b/3xycfwAAAA8M36tABk/
+ V8EfQFP/K09f/CV3pP8veJz/GEFZ/yJkhv8rb5D/JU5g/zCAq/8rZYP/H2WP/xtbgf8aTWT9I26P/xdL
+ ZPI3PUAcBA0SAAAAAAAAAAAAAAAAAAEUGgAAvPsCGRISAAAAAABOODYfL0NFch9RWfBRgI38O2t2/hk0
+ O85JhZw1FjNIwR1TdP8dUXP8NG2L/yZVaf8hcqH/I3Kd/xlPa/8gbJb/JlZr/ylXa/8qg7D/LY6+/xhC
+ W/4hUW/+IWqM/wAAADoAAAAAAAAAAAAAAAAAPlsAAQ4TAQAQFAAAAAA8MFNfsimZs/MAr93/Bbz7/wDH
+ +v4A0P7+C8Px/zydt/8hZXfwCyw6/yJXeP4wfaT/Ij1O/yZoif8gXnr/JXag/yeAtP8kZIX/JnSj/y5x
+ kf8hW3T/I2uS/yuBs/wfQ1P+AgYJXAAAAAAMEyYAAAAAAAA2SAEAPEgAACg5hUWZuP9Oy///Hqv//y+E
+ /fwygvv+HKD3/xqm+v8Pr/z9JrH//6je//9CkrL/FENW/y5KVv8ka5f/LGWB/yhbd/8nZoj/J2uJ/y1i
+ fP8if7P/LGF7/yRVcf8qcJX+MoOn+zBmf/8XM0CSGjpJAAsSJQAZbI0DBl+EAAAtSl9PodT/grz//B1/
+ +PspQtP+PW77/zVn8f49cPv+NnT3/jSK/v8nY+/+T2LZ/pG7/v8WXJP+IDhH/ypeeP8xW2z/JGyT/yl4
+ pf8vZX//L2+P/zR3lP89e5f/K4Cw/yVsk/8uTlv7K3KT/yVKWowtVmgACw8aAlOIjgAPZW4TMl2q6rvV
+ //1Hd/r8Llnm/wMHm/4kStb/Dh2v/yNI0v84cPb/LVjj/yRF0f8AAJH/BS9s/wdieP8RJDT/KmSB/y9e
+ df8pZIP/NGN1/zB3nv8qjMT/NHCL/yZSa/9AjbH/M3WV/ihRbv07aX3+GCImUxIZGgAUKlkJAAAoAAAA
+ IE94l+P/naLn/AgctP8CBKH+AAGh/wAAmf8AAJz/AACc/wsZtP8KFrD/AACX/wAAnP8AAIH+Cih2/2uq
+ p/5Fcn3+IWGG/yVihP4rT2L/K2WE/0GXv/8saor/I4G4/zdxjv4yYHf+LpDD/yJBUP4AAAAoAAAAAAUO
+ egoBBFAAAANBYXOD2f2Lj9f6AAB4/gACe/4AAHn+BQR6/wYFeP8GBnT/AABw/wAAfP8BAY3/AwV2/zhW
+ f/+GztH/hNHL/jFDSP4tZYD9OnSP/yVslf4vkMP+OnKK/y1hfP87krb/K2B+/y5nifxChqL/Lltv4Ud7
+ hxFAk6QEAwFFCgAAKAAAAB5cQkKR/6iv4v4AAHf/Jw9L/2EGCf4VGID9Ew6V/RQWcv0Ib2L+FRRc/ggA
+ Wf1TgYT9q////kVxfP8CA1L/Hi44/iNihf8ya4z6K2N+/DGBpf4zdJL+IHCf/zNieP80cY7+LH2t+zlr
+ g/4pV25kVpy5ADlpfQcJCAADAQAAAAcIBRsTEC3ri47B7CcxnftKCy/9Ywsc/wkRp/8WEqj/Fwd7/wt3
+ f/4UQXL/GAVq/0Jgbv99x779AABM/gAAWN8/SHBHJ1dy2DKIsf8ucJX/InCd/jRzj/83ltD+N2yQ/kJy
+ i/1CbXL+HzAuxxMfEgxia2sAKCIeAg0LAwAHBwgCBwgLAAAAAGgjISC9U3F5p0gAAPMxBgfuHyo3zxIR
+ GN0KBBTgAjA38wVQT/kRBhTIAAAAxxVBQvgHPHj/BQIlpBUTZwAVOkcQPzw8YSRffN4ymdD7OGaF/Dhc
+ RP8lYQ/+Lo4W/S+PFv4VRgawBwwAYwAPABgAAAAACgkPAAcGBgADAQACBAMABwMEA3sdFRDHVgEA8wwH
+ Botna2xsCgwNeQAAAHYDEhCaAVdX9wcHBcATISHAAg4TzQCInvkBoL/0AAwCPQg1RQBLamcACQAAGRgy
+ GYondBryLooA/jCkCf4wjgr+MIoP/iZdBvQhTwCwFzQFRGqJAAAAAAAAAAAAAAQSRwAHDy8ABgwZOgYR
+ TPAwFD72BQYCjYN5YXgoIhJ4EgAAdRgAAH0FeHjyEjo75H28vf8fFQ6/AGyH1wOy3P8HKS05ADpMAExK
+ QBMJDgSqI3cS9CyND/8whAz+MJgW/zGnGv4rYgD9GDgA8RAtBIUHGAZBBgIGAwAAAAAAAAAAAB2KAwAf
+ jRYCD0SbACvE/wApx/8BDTezBhxnQwwaTGkOYGFuDmRlZwZlZcYAZ2XMJ05NrwpJVeAAiKTqBmp0eQaI
+ mQABcnkBABMAAwIQAC4HIQRjFScAyS6KEP0xjxH9MJ4X/i6vIP0nXQD/BhEAyjAsA00LGAUAi4+UBI+V
+ nQAnND+hO1Jj8TtQZu09V3j8JTtm/goRI30ABCwABAwqAAdaWwAEYGIABDpDTQFdaeMAPEo3AA8nTwAA
+ ABgIV18ABEWBAAaKZAAQGAYATWADAIGZAgwkaQHAMoMG/zCCDfozeQD+J3sR+RE/CcsWIhG8OjMFcwge
+ Ay7X4uoAi56jB0ZZa+iDnLf/XXOF/4qrwvxUaXr6MUJR9UVbatEAAAAcLDJABBYACCQFRACvAWQB/wF3
+ AP0AYwDmAkAAsBAHBEwEHwAACC4JAhtEBwAiTAgJHDwHoyJpAP8iVgDnLGwB/yVqAPsoUwD/DRwChERu
+ IAIYTwEkKGoMDUxbaAYtQVAAESw6ZlRzgvh3bHL9o52k/4+eqv99la//MURW/SsuLRcAQQAAAy0CmwCR
+ AP8ArQH9AIsA/gB0AP8AcgD/ATIAdgNjAwAHQwMDFkUEDg4lBZMXUQC6AxQASBJDAM4SMgDHET4A0RxB
+ AZcdQQD/DSIAUxIrAQAGCgMAvdz2AAAAAAFkAAAAewAALEMAAOVFBwDFSDU7o0xgbq0aIylWUENTAAUR
+ BQkDOAKkAlQA3AEWAPIAIg7+AhMNnQQLDCQDBgYAAwADABr8BQBq/xUDCRUCDgoUBAAKFgRDChwCq0Z6
+ ERQNKgG2BxgBXxdABFIAAAKIAAACCgUFAwOUrcEAQAmRAmAEjgE+AlJ6Vxuw+zEMY5xnFIcASwCEACw7
+ RwAAAAAAAP//ABT2DQAKSVkGCYaPIAJ0d+0DeXo/A4iIAAN0dQMEAAMAGv8IAAMIAABIRkgAAwACDAcE
+ BXcBAAESDBwEAAUOAlsECwGjETkGAAACACEAAgAEAAAAAQAAAABzE+QCiQ/0ADYGk7GISv//Sh2XyspG
+ +wWqLu0EJzM9AwAAAAAAAAAAAAAAAAB2dQECgYAABD8+kQJDQ5YCXFwCAm9vAwIgAgAAAAAAAAAAAA4R
+ DwAAAAAKAAAADgAAAAADAAMBBQMEDwUDBI4AAAAKAAAABgUEAwQAAAEAAAAAABABMwMNATkAEwImSyoA
+ besJABliNglYAC0JUgMAAAAAAAAAAAAAAAAAAAAAAAAAAwkAAAQDAAAXBwAAQAoAAAAaAAABAAAAAAAA
+ AAAAAAAAFxgZAAAAAAAAAAAAAAAAAwoJBgEGBQMABQQDVAYKAyQFBAMADwwKAAAAAAD///////////4B
+ wH/8AAAf/AAAD/4AAA//xgAP/84AB//MAAf/zAAD/8wAA/+MAAP/BAAD/AAAAfAAAAHgAAAB4AAAAeAA
+ AAPAAAADwAAAA+AAIAfgADAD8AA8A/gAOAPwgDwDwPv+AcBwfAfAYDkD8GD/Q+P9/s/j/P/v8///7ygA
+ AAAQAAAAIAAAAAEAIAAAAAAAQAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoJuXACEgIQBvbm4AAAAAAP//
+ /wMAAAAAyMPAABAuPwDY//8A////CQAAAAAAAAAAAAAAAKoKCgARKDMAAAAAAIJ9eAAuMTUWNzY1c1pa
+ Wqo5OTmzNzc3oDw5N28GEhlZFTVIqRYxQL8PIy+lAQIEXgAAABeOCwwCBB8uAAAAAABqaGcAGBgYFCoq
+ KZ9mZWX8kZGR/25vb/9RUlPfFjtP8CRUbv8hT2r/IVJt/xw/UfIDCg5vbQ0PDQkcJgAAAAAAAAAAABIS
+ EwBISUoAOTg4H3p6e+xXRTpCETdMfB9UdP8nV3L4KV56/CdgfvgpYoH/HUpgynTAwQk4dZICAwwQAAAA
+ AAAjIyQAtra4Bevu7wBbYGGpTSscEhhBWMMhXHv+K2uO/C1lgv4mYoD/I2GB+iZffP8hSl1gL2J9ABBS
+ aAA4bHQAKygpACwpKQBkV1MIdWZj2DwOBBkaPVO/J19+/ytohvwmaIz/KGB9/iFdfPsgY4b+GU5qryqJ
+ tQAWYnsBO3B1AC1CQwEnZWhWDmqEvCSdtP8cdIG2Hkxf3iddev8lYoL9IGaL/yZnif4ob5T9IWSI/h9W
+ ct5+vckEQXiGA1R/iAdDia+zPJj0/y2B//8imv/9K6T//12T1f8bS2T+KVp1/ipsjv8rcZT+Lm+P/itu
+ kf8tYnvuAAAAIG2MwgBAWpZgb43n/wwiwPgJD6X7Gya+/hYixvsACY79M2yB/zRofP8pYYD8M3yh/zB1
+ lvwyc5P+LFtz30jA2gR0gMUAQEaKf1Vauf8YAE/9FgZ9/wYeev8FC2n/Tnim/091kvoqTWf/LnKY/zB4
+ n/8xcZ/7NW+W/zBfd41EhKMACQIAAA0EADA8P1nFTBMb6iQSV9sKL1HoDS5C6zFMVeoBIF7WFFpuSydw
+ hrswdpD2M3ZR/TJ2NfgTJxNYSm8RAwAIPQgJFFcAAAMtbxwPN9shIQBkBxAChQ9SUNknQUjcAISf6A+o
+ 0QYeRhc1JGgM3TGbBPorgwf9EykAs0JpBxM7bbwCIUiUdDNUkeIcOn7oRE1QOwxjSxsFVk2ZDE48qgpl
+ aVUZfEQDX5EmDyBaAq8xhwz/KXsL+w4nA5onaQkwiHt1AF5fWYBybWrzcH2E+kFTaoAFKQofAW0A+gBm
+ APQDPQJ5CE0AABJBAjQUPwGYF0MAxhpAAMgPIAFWFzkDCE0AUABQFFYRSQ1t0SsXOz8eEDYKAj4rFgRD
+ MFwCR0mXBU9PBAJJSwIGBQMCBQUEJQQHAyUFDwJcBAYCIxM9BAEtDpYAHAByLFEls+ERAC4EEgIuAAkf
+ IwAGUV0ABDQzSwkyMgQBNTQAFCIPAAYCBQUGBAQABAAEOwUEBAAAAAEB//8AAOAHAADgAwAA+gMAAPoB
+ AAD6AQAA4AEAAMABAACAAQAAgAEAAMBBAADAYQAAjGEAAIRhAADc+wAA3/8AAA==
+</value>
+ </data>
+</root>
\ No newline at end of file |