Add CD Audio extraction job.

This commit is contained in:
Tichau 2015-11-25 20:30:26 +01:00
parent 8ca9734298
commit 73cecd2722
12 changed files with 303 additions and 21 deletions

View File

@ -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;
}

View File

@ -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;
}

View File

@ -0,0 +1,205 @@
// <copyright file="ConversionJob_ExtractCDA.cs" company="AAllard">License: http://www.gnu.org/licenses/gpl.html GPL version 3.</copyright>
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();
}
}
}

View File

@ -249,6 +249,5 @@ namespace FileConverter.ConversionJobs
throw new Exception("Unknown VBR bitrate.");
}
}
}

View File

@ -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);
}
}
}

View File

@ -48,6 +48,10 @@
<Reference Include="Accessibility" />
<Reference Include="PresentationUI, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL" />
<Reference Include="ReachFramework" />
<Reference Include="Ripper, Version=1.0.5806.33932, Culture=neutral, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\..\Middleware\Ripper.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Data" />
<Reference Include="System.Deployment" />
@ -67,6 +71,9 @@
<Reference Include="WindowsBase" />
<Reference Include="PresentationCore" />
<Reference Include="PresentationFramework" />
<Reference Include="yeti.mmedia">
<HintPath>..\..\Middleware\yeti.mmedia.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<ApplicationDefinition Include="Application.xaml">
@ -76,6 +83,7 @@
<Compile Include="Controls\EncodingQualitySliderControl.xaml.cs">
<DependentUpon>EncodingQualitySliderControl.xaml</DependentUpon>
</Compile>
<Compile Include="ConversionJobs\ConversionJob_ExtractCDA.cs" />
<Compile Include="ConversionJobs\ConversionJobFactory.cs" />
<Compile Include="ConversionJobs\ConversionJob_FFMPEG.Converters.cs" />
<Compile Include="ConversionJobs\ConversionJob_FFMPEG.cs" />

View File

@ -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;

View File

@ -57,7 +57,7 @@
<TextBlock Text="{Binding ErrorMessage}" Foreground="{Binding State, ConverterParameter=Foreground, Converter={StaticResource ConversionStateToColor}}" />
</StackPanel>
<TextBlock Grid.Row="0" Grid.Column="1" Text="{Binding State}" FontWeight="Bold" Foreground="{Binding State, ConverterParameter=Foreground, Converter={StaticResource ConversionStateToColor}}" />
<TextBlock Grid.Row="0" Grid.Column="1" Text="{Binding UserState}" FontWeight="Bold" Foreground="{Binding State, ConverterParameter=Foreground, Converter={StaticResource ConversionStateToColor}}" />
<ProgressBar Grid.Row="1" Grid.Column="1" Height="18" Width="Auto" Minimum="0" Maximum="1" SmallChange="0.001" Value="{Binding Progress, Mode=OneWay}" Foreground="{Binding State, ConverterParameter=Foreground, Converter={StaticResource ConversionStateToColor}}" HorizontalAlignment="Stretch" HorizontalContentAlignment="Stretch" MinWidth="100" LargeChange="0.1" />
</Grid>

View File

@ -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

View File

@ -71,7 +71,7 @@
<Component Id="RegistryEntries" Guid="{C3EF3D67-0206-4DBD-B2EA-78FF2E290093}">
<RegistryKey Root="HKCU" Key="Software\FileConverter">
<RegistryValue Name="Path" Type="string" Value="[INSTALLFOLDER]FileConverter.exe"/>
<RegistryValue Name="CompatibleInputExtensions" Type="string" Value="aac;aiff;ape;avi;bmp;flac;flv;jpg;jpeg;m4a;mkv;mov;mp3;mp4;ogg;png;tiff;wav;wma"/>
<RegistryValue Name="CompatibleInputExtensions" Type="string" Value="aac;aiff;ape;avi;bmp;cda;flac;flv;jpg;jpeg;m4a;mkv;mov;mp3;mp4;ogg;png;tiff;wav;wma"/>
</RegistryKey>
<RegistryKey Root="HKCU" Key="Software\FileConverter\aiff">

BIN
Middleware/Ripper.dll Normal file

Binary file not shown.

BIN
Middleware/yeti.mmedia.dll Normal file

Binary file not shown.