From 73cecd27229ded35353e4364a92987ddef06b89a Mon Sep 17 00:00:00 2001 From: Tichau Date: Wed, 25 Nov 2015 20:30:26 +0100 Subject: [PATCH] Add CD Audio extraction job. --- .../ConversionJobs/ConversionJob.cs | 50 ++++- .../ConversionJobs/ConversionJobFactory.cs | 15 +- .../ConversionJob_ExtractCDA.cs | 205 ++++++++++++++++++ .../ConversionJob_FFMPEG.Converters.cs | 1 - .../ConversionJobs/ConversionJob_FFMPEG.cs | 11 +- .../FileConverter/FileConverter.csproj | 8 + Application/FileConverter/PathHelpers.cs | 14 +- .../FileConverter/Windows/MainWindow.xaml | 2 +- FileConverter.sln | 16 +- Installer/Product.wxs | 2 +- Middleware/Ripper.dll | Bin 0 -> 28672 bytes Middleware/yeti.mmedia.dll | Bin 0 -> 45056 bytes 12 files changed, 303 insertions(+), 21 deletions(-) create mode 100644 Application/FileConverter/ConversionJobs/ConversionJob_ExtractCDA.cs create mode 100644 Middleware/Ripper.dll create mode 100644 Middleware/yeti.mmedia.dll 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 0000000000000000000000000000000000000000..104975509f1f570d00f257b3b4fdee55d6070427 GIT binary patch literal 28672 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 literal 0 HcmV?d00001 diff --git a/Middleware/yeti.mmedia.dll b/Middleware/yeti.mmedia.dll new file mode 100644 index 0000000000000000000000000000000000000000..f216eef5869d5a4ba59fe9c09f54f07538492973 GIT binary patch literal 45056 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