diff --git a/Application/FileConverter/ConversionJobs/ConversionJob.cs b/Application/FileConverter/ConversionJobs/ConversionJob.cs
index f2d0151..baf214a 100644
--- a/Application/FileConverter/ConversionJobs/ConversionJob.cs
+++ b/Application/FileConverter/ConversionJobs/ConversionJob.cs
@@ -13,6 +13,7 @@ namespace FileConverter.ConversionJobs
private ConversionState state = ConversionState.Unknown;
private string errorMessage = string.Empty;
private string initialInputPath = string.Empty;
+ private string userState = string.Empty;
public ConversionJob()
{
@@ -75,6 +76,20 @@ namespace FileConverter.ConversionJobs
}
}
+ public string UserState
+ {
+ get
+ {
+ return this.userState;
+ }
+
+ protected set
+ {
+ this.userState = value;
+ this.NotifyPropertyChanged();
+ }
+ }
+
public float Progress
{
get
@@ -103,7 +118,20 @@ namespace FileConverter.ConversionJobs
}
}
- public void PrepareConversion(string inputFilePath)
+ protected virtual InputPostConversionAction InputPostConversionAction
+ {
+ get
+ {
+ if (this.ConversionPreset == null)
+ {
+ return InputPostConversionAction.None;
+ }
+
+ return this.ConversionPreset.InputPostConversionAction;
+ }
+ }
+
+ public void PrepareConversion(string inputFilePath, string outputFilePath = null)
{
if (string.IsNullOrEmpty(inputFilePath))
{
@@ -120,17 +148,17 @@ namespace FileConverter.ConversionJobs
string extensionCategory = PathHelpers.GetExtensionCategory(extension);
if (!PathHelpers.IsOutputTypeCompatibleWithCategory(this.ConversionPreset.OutputType, extensionCategory))
{
- this.ConvertionFailed("The input file type is not compatible with the output file type.");
+ this.ConversionFailed("The input file type is not compatible with the output file type.");
return;
}
this.initialInputPath = inputFilePath;
this.InputFilePath = inputFilePath;
- this.OutputFilePath = this.ConversionPreset.GenerateOutputFilePath(inputFilePath);
+ this.OutputFilePath = outputFilePath ?? this.ConversionPreset.GenerateOutputFilePath(inputFilePath);
if (!PathHelpers.IsPathValid(this.OutputFilePath))
{
- this.ConvertionFailed("Invalid output path generated by output file path template.");
+ this.ConversionFailed("Invalid output path generated by output file path template.");
Diagnostics.Log(string.Format("Invalid output path generated: {0} from input: {1}.", this.OutputFilePath, this.InputFilePath));
return;
}
@@ -164,7 +192,7 @@ namespace FileConverter.ConversionJobs
}
catch (Exception)
{
- this.ConvertionFailed("Invalid output path generated by output file path template.");
+ this.ConversionFailed("Invalid output path generated by output file path template.");
Diagnostics.Log(string.Format("Can't create directories for path {0}", this.OutputFilePath));
return;
}
@@ -179,7 +207,7 @@ namespace FileConverter.ConversionJobs
}
catch (Exception exception)
{
- this.ConvertionFailed("Can't generate a valid output filename.");
+ this.ConversionFailed("Can't generate a valid output filename.");
Diagnostics.Log(exception.Message);
return;
}
@@ -190,6 +218,8 @@ namespace FileConverter.ConversionJobs
{
this.State = ConversionState.Ready;
}
+
+ this.UserState = "In Queue";
}
public void StartConvertion()
@@ -207,7 +237,7 @@ namespace FileConverter.ConversionJobs
Diagnostics.Log("Convert file {0} to {1}.", this.InputFilePath, this.OutputFilePath);
this.State = ConversionState.InProgress;
-
+
this.Convert();
if (this.State != ConversionState.Failed)
@@ -229,7 +259,7 @@ namespace FileConverter.ConversionJobs
Diagnostics.Log("Conversion Succeed!");
// Apply the input post conversion action.
- switch (this.ConversionPreset.InputPostConversionAction)
+ switch (this.InputPostConversionAction)
{
case InputPostConversionAction.None:
break;
@@ -258,12 +288,14 @@ namespace FileConverter.ConversionJobs
this.Progress = 1f;
this.State = ConversionState.Done;
+ this.UserState = "Done";
Diagnostics.Log("Conversion Done!");
}
- protected void ConvertionFailed(string exitingMessage)
+ protected void ConversionFailed(string exitingMessage)
{
this.State = ConversionState.Failed;
+ this.UserState = "Failed";
this.ErrorMessage = exitingMessage;
}
diff --git a/Application/FileConverter/ConversionJobs/ConversionJobFactory.cs b/Application/FileConverter/ConversionJobs/ConversionJobFactory.cs
index c919ca4..b63601c 100644
--- a/Application/FileConverter/ConversionJobs/ConversionJobFactory.cs
+++ b/Application/FileConverter/ConversionJobs/ConversionJobFactory.cs
@@ -4,9 +4,20 @@ namespace FileConverter.ConversionJobs
{
public static class ConversionJobFactory
{
- public static ConversionJob Create(ConversionPreset conversionPreset)
+ public static ConversionJob Create(ConversionPreset conversionPreset, string inputFilePath)
{
- ConversionJob conversionJob = new ConversionJob_FFMPEG(conversionPreset);
+ ConversionJob conversionJob = null;
+
+ string extension = System.IO.Path.GetExtension(inputFilePath);
+ extension = extension.ToLowerInvariant().Substring(1, extension.Length - 1);
+ if (extension == "cda")
+ {
+ conversionJob = new ConversionJob_ExtractCDA(conversionPreset);
+ }
+ else
+ {
+ conversionJob = new ConversionJob_FFMPEG(conversionPreset);
+ }
return conversionJob;
}
diff --git a/Application/FileConverter/ConversionJobs/ConversionJob_ExtractCDA.cs b/Application/FileConverter/ConversionJobs/ConversionJob_ExtractCDA.cs
new file mode 100644
index 0000000..9479eb2
--- /dev/null
+++ b/Application/FileConverter/ConversionJobs/ConversionJob_ExtractCDA.cs
@@ -0,0 +1,205 @@
+// License: http://www.gnu.org/licenses/gpl.html GPL version 3.
+
+namespace FileConverter.ConversionJobs
+{
+ using System;
+ using System.IO;
+ using System.Threading;
+
+ using Ripper;
+
+ using WaveLib;
+ using Yeti.MMedia;
+
+ public class ConversionJob_ExtractCDA : ConversionJob
+ {
+ private Ripper.CDDrive cdDrive;
+ private int cdaTrackNumber = -1;
+ private WaveWriter waveWriter;
+ private string intermediateFilePath;
+ private ConversionJob compressionConversionJob;
+ private System.Threading.Thread compressionThread;
+
+ public ConversionJob_ExtractCDA() : base()
+ {
+ }
+
+ public ConversionJob_ExtractCDA(ConversionPreset conversionPreset) : base(conversionPreset)
+ {
+ }
+
+ protected override InputPostConversionAction InputPostConversionAction
+ {
+ get
+ {
+ return InputPostConversionAction.None;
+ }
+ }
+
+ protected override void Initialize()
+ {
+ base.Initialize();
+
+ if (this.ConversionPreset == null)
+ {
+ throw new Exception("The conversion preset must be valid.");
+ }
+
+ // Retrieve and check drive letter.
+ string pathDriveLetter = PathHelpers.GetPathDriveLetter(this.InputFilePath);
+ if (pathDriveLetter.Length == 0)
+ {
+ this.ConversionFailed("Can't retrieve input path drive letter.");
+ return;
+ }
+
+ char driveLetter = pathDriveLetter[0];
+
+ this.cdDrive = new Ripper.CDDrive();
+ this.cdDrive.CDRemoved += new EventHandler(this.CdDriveCdRemoved);
+
+ bool driveLetterFound = false;
+ char[] driveLetters = Ripper.CDDrive.GetCDDriveLetters();
+ for (int index = 0; index < driveLetters.Length; index++)
+ {
+ driveLetterFound |= driveLetters[index] == driveLetter;
+ }
+
+ if (!driveLetterFound)
+ {
+ this.ConversionFailed(string.Format("Invalid drive letter {0}.", driveLetter));
+ return;
+ }
+
+ // Retrieve and track number.
+ try
+ {
+ this.cdaTrackNumber = PathHelpers.GetCDATrackNumber(this.InputFilePath);
+ }
+ catch (Exception)
+ {
+ this.ConversionFailed(string.Format("Can't retrieve the track number from input path '{0}'.", this.InputFilePath));
+ return;
+ }
+
+ if (this.cdDrive.IsOpened)
+ {
+ this.ConversionFailed(string.Format("CD drive already used."));
+ return;
+ }
+
+ if (!this.cdDrive.Open(driveLetter))
+ {
+ this.ConversionFailed(string.Format("Fail to open cd drive {0}.", driveLetter));
+ return;
+ }
+
+ // Generate intermediate file path.
+ string fileName = Path.GetFileName(this.OutputFilePath);
+ string tempPath = Path.GetTempPath();
+ this.intermediateFilePath = PathHelpers.GenerateUniquePath(tempPath + fileName + ".wav");
+
+ // Sub conversion job (for compression).
+ this.compressionConversionJob = ConversionJobFactory.Create(this.ConversionPreset, this.intermediateFilePath);
+ this.compressionConversionJob.PrepareConversion(this.intermediateFilePath, this.OutputFilePath);
+ this.compressionThread = new Thread(this.CompressAsync);
+ }
+
+ protected override void Convert()
+ {
+ if (this.ConversionPreset == null)
+ {
+ throw new Exception("The conversion preset must be valid.");
+ }
+
+ Diagnostics.Log("Starting CDA extraction.");
+
+ this.UserState = "Extraction";
+
+ if (!this.cdDrive.IsCDReady())
+ {
+ this.ConversionFailed(string.Format("CD drive is not ready."));
+ return;
+ }
+
+ if (!this.cdDrive.Refresh())
+ {
+ this.ConversionFailed(string.Format("Can't refresh CD drive data."));
+ return;
+ }
+
+ if (!this.cdDrive.LockCD())
+ {
+ this.ConversionFailed(string.Format("Can't lock cd."));
+ return;
+ }
+
+ WaveFormat waveFormat = new WaveFormat(44100, 16, 2);
+
+ using (Stream waveStream = new FileStream(this.intermediateFilePath, FileMode.Create, FileAccess.Write))
+ using (this.waveWriter = new WaveWriter(waveStream, waveFormat, this.cdDrive.TrackSize(this.cdaTrackNumber)))
+ {
+ this.cdDrive.ReadTrack(this.cdaTrackNumber, this.WriteWaveData, this.CdReadProgress);
+ }
+
+ this.waveWriter = null;
+
+ this.cdDrive.UnLockCD();
+
+ this.cdDrive.Close();
+
+ if (!File.Exists(this.intermediateFilePath))
+ {
+ this.ConversionFailed("Extraction failed.");
+ return;
+ }
+
+ Diagnostics.Log("CDA extracted to {0}.", this.intermediateFilePath);
+ Diagnostics.Log(string.Empty);
+ Diagnostics.Log("Start compression.");
+
+ this.UserState = "Conversion";
+
+ this.compressionThread.Start();
+
+ while (this.compressionConversionJob.State != ConversionState.Done &&
+ this.compressionConversionJob.State != ConversionState.Failed)
+ {
+ this.Progress = this.compressionConversionJob.Progress;
+ }
+
+ if (this.compressionConversionJob.State == ConversionState.Failed)
+ {
+ this.ConversionFailed(this.compressionConversionJob.ErrorMessage);
+ return;
+ }
+
+ Diagnostics.Log(string.Empty);
+ Diagnostics.Log("Delete intermediate file {0}.", this.intermediateFilePath);
+
+ File.Delete(this.intermediateFilePath);
+ }
+
+ private void WriteWaveData(object sender, DataReadEventArgs eventArgs)
+ {
+ this.waveWriter?.Write(eventArgs.Data, 0, (int)eventArgs.DataSize);
+ }
+
+ private void CdReadProgress(object sender, ReadProgressEventArgs eventArgs)
+ {
+ this.Progress = (float)eventArgs.BytesRead / (float)eventArgs.Bytes2Read;
+
+ eventArgs.CancelRead |= this.State != ConversionState.InProgress;
+ }
+
+ private void CdDriveCdRemoved(object sender, System.EventArgs eventArgs)
+ {
+ this.ConversionFailed("The CD has been ejected.");
+ }
+
+ private void CompressAsync()
+ {
+ this.compressionConversionJob.StartConvertion();
+ }
+ }
+}
diff --git a/Application/FileConverter/ConversionJobs/ConversionJob_FFMPEG.Converters.cs b/Application/FileConverter/ConversionJobs/ConversionJob_FFMPEG.Converters.cs
index 5e351fb..dc649d6 100644
--- a/Application/FileConverter/ConversionJobs/ConversionJob_FFMPEG.Converters.cs
+++ b/Application/FileConverter/ConversionJobs/ConversionJob_FFMPEG.Converters.cs
@@ -249,6 +249,5 @@ namespace FileConverter.ConversionJobs
throw new Exception("Unknown VBR bitrate.");
}
-
}
}
diff --git a/Application/FileConverter/ConversionJobs/ConversionJob_FFMPEG.cs b/Application/FileConverter/ConversionJobs/ConversionJob_FFMPEG.cs
index 06c54ac..fe3ba3e 100644
--- a/Application/FileConverter/ConversionJobs/ConversionJob_FFMPEG.cs
+++ b/Application/FileConverter/ConversionJobs/ConversionJob_FFMPEG.cs
@@ -40,7 +40,7 @@ namespace FileConverter.ConversionJobs
string ffmpegPath = string.Format("{0}\\ffmpeg.exe", applicationDirectory);
if (!System.IO.File.Exists(ffmpegPath))
{
- this.ConvertionFailed("Can't find ffmpeg executable. You should try to reinstall the application.");
+ this.ConversionFailed("Can't find ffmpeg executable. You should try to reinstall the application.");
Diagnostics.Log("Can't find ffmpeg executable ({0}). Try to reinstall the application.", ffmpegPath);
return;
}
@@ -164,9 +164,10 @@ namespace FileConverter.ConversionJobs
throw new Exception("The conversion preset must be valid.");
}
- Diagnostics.Log("Convert file {0} to {1}.", this.InputFilePath, this.OutputFilePath);
- Diagnostics.Log(string.Empty);
+ this.UserState = "Conversion";
+
Diagnostics.Log("Execute command: {0} {1}.", this.ffmpegProcessStartInfo.FileName, this.ffmpegProcessStartInfo.Arguments);
+ Diagnostics.Log(string.Empty);
try
{
@@ -189,7 +190,7 @@ namespace FileConverter.ConversionJobs
}
catch
{
- this.ConvertionFailed("Failed to launch FFMPEG process.");
+ this.ConversionFailed("Failed to launch FFMPEG process.");
throw;
}
}
@@ -227,7 +228,7 @@ namespace FileConverter.ConversionJobs
if (input.Contains("Exiting."))
{
- this.ConvertionFailed(input);
+ this.ConversionFailed(input);
}
}
}
diff --git a/Application/FileConverter/FileConverter.csproj b/Application/FileConverter/FileConverter.csproj
index 138a8fe..64a6a23 100644
--- a/Application/FileConverter/FileConverter.csproj
+++ b/Application/FileConverter/FileConverter.csproj
@@ -48,6 +48,10 @@
+
+ False
+ ..\..\Middleware\Ripper.dll
+
@@ -67,6 +71,9 @@
+
+ ..\..\Middleware\yeti.mmedia.dll
+
@@ -76,6 +83,7 @@
EncodingQualitySliderControl.xaml
+
diff --git a/Application/FileConverter/PathHelpers.cs b/Application/FileConverter/PathHelpers.cs
index c92b6e9..b5e6110 100644
--- a/Application/FileConverter/PathHelpers.cs
+++ b/Application/FileConverter/PathHelpers.cs
@@ -8,6 +8,7 @@ namespace FileConverter
public static class PathHelpers
{
private static Regex driveLetterRegex = new Regex(@"[a-zA-Z]:\\");
+ private static Regex cdaTrackNumberRegex = new Regex(@"[a-zA-Z]:\\Track([0-9]+)\.cda");
private static Regex pathRegex = new Regex(@"^[a-zA-Z]:\\(?:[^\\/:*?""<>|\r\n]+\\)*[^\.\\/:*?""<>|\r\n][^\\/:*?""<>|\r\n]*$");
private static Regex filenameRegex = new Regex(@"[^\\]*", RegexOptions.RightToLeft);
private static Regex directoryRegex = new Regex(@"(?:([^\\]*)\\)*");
@@ -17,6 +18,18 @@ namespace FileConverter
return PathHelpers.driveLetterRegex.IsMatch(path);
}
+ public static string GetPathDriveLetter(string path)
+ {
+ return PathHelpers.driveLetterRegex.Match(path).Groups[0].Value;
+ }
+
+ public static int GetCDATrackNumber(string path)
+ {
+ Match match = PathHelpers.cdaTrackNumberRegex.Match(path);
+ string stringNumber = match.Groups[1].Value;
+ return int.Parse(stringNumber);
+ }
+
public static bool IsPathValid(string path)
{
return PathHelpers.pathRegex.IsMatch(path);
@@ -88,7 +101,6 @@ namespace FileConverter
case "png":
case "tiff":
return InputCategoryNames.Image;
-
}
return InputCategoryNames.Misc;
diff --git a/Application/FileConverter/Windows/MainWindow.xaml b/Application/FileConverter/Windows/MainWindow.xaml
index 536f3ce..006ba1a 100644
--- a/Application/FileConverter/Windows/MainWindow.xaml
+++ b/Application/FileConverter/Windows/MainWindow.xaml
@@ -57,7 +57,7 @@
-
+
diff --git a/FileConverter.sln b/FileConverter.sln
index 1697b2d..af7cc2b 100644
--- a/FileConverter.sln
+++ b/FileConverter.sln
@@ -1,6 +1,8 @@
Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio 2012
+# Visual Studio 14
+VisualStudioVersion = 14.0.23107.0
+MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FileConverter", "Application\FileConverter\FileConverter.csproj", "{D27A76D2-43E4-43CC-9DA3-334B0B46F4E5}"
EndProject
Project("{930C7802-8A8C-48F9-8165-68863BCCD9DD}") = "Installer", "Installer\Installer.wixproj", "{F14673DF-DF38-44B4-AB1D-99A59182C24C}"
@@ -9,32 +11,44 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FileConverterExtension", "A
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
Debug|x64 = Debug|x64
Debug|x86 = Debug|x86
+ Release|Any CPU = Release|Any CPU
Release|x64 = Release|x64
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {D27A76D2-43E4-43CC-9DA3-334B0B46F4E5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {D27A76D2-43E4-43CC-9DA3-334B0B46F4E5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D27A76D2-43E4-43CC-9DA3-334B0B46F4E5}.Debug|x64.ActiveCfg = Debug|Any CPU
{D27A76D2-43E4-43CC-9DA3-334B0B46F4E5}.Debug|x64.Build.0 = Debug|Any CPU
{D27A76D2-43E4-43CC-9DA3-334B0B46F4E5}.Debug|x86.ActiveCfg = Debug|Any CPU
{D27A76D2-43E4-43CC-9DA3-334B0B46F4E5}.Debug|x86.Build.0 = Debug|Any CPU
+ {D27A76D2-43E4-43CC-9DA3-334B0B46F4E5}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {D27A76D2-43E4-43CC-9DA3-334B0B46F4E5}.Release|Any CPU.Build.0 = Release|Any CPU
{D27A76D2-43E4-43CC-9DA3-334B0B46F4E5}.Release|x64.ActiveCfg = Release|Any CPU
{D27A76D2-43E4-43CC-9DA3-334B0B46F4E5}.Release|x64.Build.0 = Release|Any CPU
{D27A76D2-43E4-43CC-9DA3-334B0B46F4E5}.Release|x86.ActiveCfg = Release|Any CPU
{D27A76D2-43E4-43CC-9DA3-334B0B46F4E5}.Release|x86.Build.0 = Release|Any CPU
+ {F14673DF-DF38-44B4-AB1D-99A59182C24C}.Debug|Any CPU.ActiveCfg = Debug|x86
{F14673DF-DF38-44B4-AB1D-99A59182C24C}.Debug|x64.ActiveCfg = Debug|x64
{F14673DF-DF38-44B4-AB1D-99A59182C24C}.Debug|x64.Build.0 = Debug|x64
{F14673DF-DF38-44B4-AB1D-99A59182C24C}.Debug|x86.ActiveCfg = Debug|x86
{F14673DF-DF38-44B4-AB1D-99A59182C24C}.Debug|x86.Build.0 = Debug|x86
+ {F14673DF-DF38-44B4-AB1D-99A59182C24C}.Release|Any CPU.ActiveCfg = Release|x86
{F14673DF-DF38-44B4-AB1D-99A59182C24C}.Release|x64.ActiveCfg = Release|x64
{F14673DF-DF38-44B4-AB1D-99A59182C24C}.Release|x64.Build.0 = Release|x64
{F14673DF-DF38-44B4-AB1D-99A59182C24C}.Release|x86.ActiveCfg = Release|x86
{F14673DF-DF38-44B4-AB1D-99A59182C24C}.Release|x86.Build.0 = Release|x86
+ {0C44CA69-42D6-4357-BDFD-83069D1ABA2F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {0C44CA69-42D6-4357-BDFD-83069D1ABA2F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0C44CA69-42D6-4357-BDFD-83069D1ABA2F}.Debug|x64.ActiveCfg = Debug|Any CPU
{0C44CA69-42D6-4357-BDFD-83069D1ABA2F}.Debug|x64.Build.0 = Debug|Any CPU
{0C44CA69-42D6-4357-BDFD-83069D1ABA2F}.Debug|x86.ActiveCfg = Debug|Any CPU
{0C44CA69-42D6-4357-BDFD-83069D1ABA2F}.Debug|x86.Build.0 = Debug|Any CPU
+ {0C44CA69-42D6-4357-BDFD-83069D1ABA2F}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {0C44CA69-42D6-4357-BDFD-83069D1ABA2F}.Release|Any CPU.Build.0 = Release|Any CPU
{0C44CA69-42D6-4357-BDFD-83069D1ABA2F}.Release|x64.ActiveCfg = Release|Any CPU
{0C44CA69-42D6-4357-BDFD-83069D1ABA2F}.Release|x64.Build.0 = Release|Any CPU
{0C44CA69-42D6-4357-BDFD-83069D1ABA2F}.Release|x86.ActiveCfg = Release|Any CPU
diff --git a/Installer/Product.wxs b/Installer/Product.wxs
index e680539..10a4bf2 100644
--- a/Installer/Product.wxs
+++ b/Installer/Product.wxs
@@ -71,7 +71,7 @@
-
+
diff --git a/Middleware/Ripper.dll b/Middleware/Ripper.dll
new file mode 100644
index 0000000..1049755
Binary files /dev/null and b/Middleware/Ripper.dll differ
diff --git a/Middleware/yeti.mmedia.dll b/Middleware/yeti.mmedia.dll
new file mode 100644
index 0000000..f216eef
Binary files /dev/null and b/Middleware/yeti.mmedia.dll differ