2009-01-28 04:53:13 +00:00
using System ;
2018-02-10 17:33:22 -05:00
using System.ComponentModel ;
2013-06-11 21:19:48 -04:00
using System.Collections.Generic ;
2009-08-21 03:26:12 +00:00
using System.IO ;
2009-01-28 04:53:13 +00:00
using CUETools.Codecs ;
using CUETools.Processor ;
namespace CUETools.Converter
{
2013-04-11 00:05:48 -04:00
class Program
{
static void Usage ( )
{
Console . Error . WriteLine ( "Usage : CUETools.Converter.exe [options] <infile> <outfile>" ) ;
Console . Error . WriteLine ( ) ;
Console . Error . WriteLine ( "Options:" ) ;
Console . Error . WriteLine ( ) ;
2013-06-05 03:02:43 -04:00
Console . Error . WriteLine ( " --decoder <name> Use non-default decoder." ) ;
2018-04-07 13:55:01 -04:00
Console . Error . WriteLine ( " --decoder-option <name> <value>" ) ;
2013-06-05 03:02:43 -04:00
Console . Error . WriteLine ( " --encoder <name> Use non-default encoder." ) ;
Console . Error . WriteLine ( " --encoder-format <ext> Use encoder format different from file extension." ) ;
Console . Error . WriteLine ( " --lossy Use lossy encoder/mode." ) ;
Console . Error . WriteLine ( " --lossless Use lossless encoder/mode (default)." ) ;
2013-07-07 21:10:57 -04:00
Console . Error . WriteLine ( " --ignore-chunk-sizes Ignore WAV length (for pipe input)" ) ;
2013-06-05 03:02:43 -04:00
Console . Error . WriteLine ( " -p # Padding bytes." ) ;
Console . Error . WriteLine ( " -m <mode> Encoder mode (0..8 for flac, V0..V9 for mp3, etc)" ) ;
2013-04-11 00:05:48 -04:00
Console . Error . WriteLine ( ) ;
}
2009-01-28 04:53:13 +00:00
2018-03-23 19:26:26 -04:00
public static AudioEncoderSettingsViewModel GetEncoder ( CUEToolsCodecsConfig config , CUEToolsFormat fmt , bool lossless , string chosenEncoder )
2013-05-27 22:55:42 -04:00
{
2018-03-23 19:26:26 -04:00
AudioEncoderSettingsViewModel tmpEncoder ;
2013-06-04 20:44:50 -04:00
return chosenEncoder ! = null ?
2018-03-24 12:15:49 -04:00
( config . encodersViewModel . TryGetValue ( fmt . extension , lossless , chosenEncoder , out tmpEncoder ) ? tmpEncoder : null ) :
2013-06-04 20:44:50 -04:00
( lossless ? fmt . encoderLossless : fmt . encoderLossy ) ;
2013-05-27 22:55:42 -04:00
}
2018-04-07 13:55:01 -04:00
public static IAudioSource GetAudioSource ( CUEToolsCodecsConfig config , string path , string chosenDecoder , bool ignore_chunk_sizes , Dictionary < string , string > decoderOptions )
2013-05-27 22:55:42 -04:00
{
if ( path = = "-" )
2018-03-23 19:26:26 -04:00
return new Codecs . WAV . AudioDecoder ( new Codecs . WAV . DecoderSettings ( ) { IgnoreChunkSizes = true } , "" , Console . OpenStandardInput ( ) ) ;
2013-05-27 22:55:42 -04:00
string extension = Path . GetExtension ( path ) . ToLower ( ) ;
Stream IO = null ;
if ( extension = = ".bin" )
2018-03-23 19:26:26 -04:00
return new Codecs . WAV . AudioDecoder ( new Codecs . WAV . DecoderSettings ( ) , path , IO , AudioPCMConfig . RedBook ) ;
2013-05-27 22:55:42 -04:00
CUEToolsFormat fmt ;
if ( ! extension . StartsWith ( "." ) | | ! config . formats . TryGetValue ( extension . Substring ( 1 ) , out fmt ) )
throw new Exception ( "Unsupported audio type: " + path ) ;
var decoder = fmt . decoder ;
2018-03-25 17:24:27 -04:00
if ( chosenDecoder ! = null & & ! config . decodersViewModel . TryGetValue ( fmt . extension , chosenDecoder , out decoder ) )
2013-05-27 22:55:42 -04:00
throw new Exception ( "Unknown audio decoder " + chosenDecoder + " or unsupported audio type " + fmt . extension ) ;
if ( decoder = = null )
throw new Exception ( "Unsupported audio type: " + path ) ;
2018-03-26 20:11:49 -04:00
var settings = decoder . Settings . Clone ( ) ;
2018-04-07 13:55:01 -04:00
foreach ( var decOpt in decoderOptions )
{
var property = TypeDescriptor . GetProperties ( settings ) . Find ( decOpt . Key , true ) ;
if ( property = = null )
throw new Exception ( $"{settings.Name} {settings.Extension} decoder settings object (of type {settings.GetType().FullName}) doesn't have a property named {decOpt.Key}." ) ;
property . SetValue ( settings ,
TypeDescriptor . GetConverter ( property . PropertyType ) . ConvertFromString ( decOpt . Value ) ) ;
}
2013-05-27 22:55:42 -04:00
try
{
2018-03-24 12:15:49 -04:00
object src = Activator . CreateInstance ( decoder . Settings . DecoderType , settings , path , IO ) ;
2013-05-27 22:55:42 -04:00
if ( src = = null | | ! ( src is IAudioSource ) )
2018-03-24 12:15:49 -04:00
throw new Exception ( "Unsupported audio type: " + path + ": " + decoder . Settings . DecoderType . FullName ) ;
2013-05-27 22:55:42 -04:00
return src as IAudioSource ;
}
catch ( System . Reflection . TargetInvocationException ex )
{
if ( ex . InnerException = = null )
throw ex ;
throw ex . InnerException ;
}
}
2013-04-11 00:05:48 -04:00
static int Main ( string [ ] args )
{
bool ok = true ;
string sourceFile = null , destFile = null ;
int padding = 8192 ;
string encoderMode = null ;
2013-05-27 22:55:42 -04:00
string decoderName = null ;
string encoderName = null ;
string encoderFormat = null ;
2013-07-07 21:10:57 -04:00
bool ignore_chunk_sizes = false ;
2013-04-11 00:05:48 -04:00
AudioEncoderType audioEncoderType = AudioEncoderType . NoAudio ;
2018-02-10 17:33:22 -05:00
var decoderOptions = new Dictionary < string , string > ( ) ;
2013-07-07 21:10:57 -04:00
2013-04-11 00:05:48 -04:00
for ( int arg = 0 ; arg < args . Length ; arg + + )
{
if ( args [ arg ] . Length = = 0 )
ok = false ;
2013-07-07 21:10:57 -04:00
else if ( args [ arg ] = = "--ignore-chunk-sizes" )
ignore_chunk_sizes = true ;
2013-05-27 22:55:42 -04:00
else if ( args [ arg ] = = "--decoder" & & + + arg < args . Length )
decoderName = args [ arg ] ;
2018-02-10 17:33:22 -05:00
else if ( args [ arg ] = = "--decoder-option" & & arg + 2 < args . Length )
{
var optionName = args [ + + arg ] ;
var optionValue = args [ + + arg ] ;
decoderOptions . Add ( optionName , optionValue ) ;
}
2013-05-27 22:55:42 -04:00
else if ( args [ arg ] = = "--encoder" & & + + arg < args . Length )
encoderName = args [ arg ] ;
else if ( args [ arg ] = = "--encoder-format" & & + + arg < args . Length )
encoderFormat = args [ arg ] ;
2013-04-11 00:05:48 -04:00
else if ( ( args [ arg ] = = "-p" | | args [ arg ] = = "--padding" ) & & + + arg < args . Length )
ok = int . TryParse ( args [ arg ] , out padding ) ;
else if ( ( args [ arg ] = = "-m" | | args [ arg ] = = "--mode" ) & & + + arg < args . Length )
encoderMode = args [ arg ] ;
else if ( args [ arg ] = = "--lossy" )
audioEncoderType = AudioEncoderType . Lossy ;
else if ( args [ arg ] = = "--lossless" )
audioEncoderType = AudioEncoderType . Lossless ;
2013-04-18 23:20:18 -04:00
else if ( ( args [ arg ] [ 0 ] ! = '-' | | args [ arg ] = = "-" ) & & sourceFile = = null )
2013-04-11 00:05:48 -04:00
sourceFile = args [ arg ] ;
2013-04-18 23:20:18 -04:00
else if ( ( args [ arg ] [ 0 ] ! = '-' | | args [ arg ] = = "-" ) & & sourceFile ! = null & & destFile = = null )
2013-04-11 00:05:48 -04:00
destFile = args [ arg ] ;
else
ok = false ;
if ( ! ok )
break ;
}
2009-01-28 04:53:13 +00:00
2020-01-30 18:13:46 +01:00
Console . Error . WriteLine ( "CUETools.Converter, Copyright (C) 2009-2020 Grigory Chudov." ) ;
2013-04-11 00:05:48 -04:00
Console . Error . WriteLine ( "This is free software under the GNU GPLv3+ license; There is NO WARRANTY, to" ) ;
Console . Error . WriteLine ( "the extent permitted by law. <http://www.gnu.org/licenses/> for details." ) ;
if ( ! ok | | sourceFile = = null | | destFile = = null )
{
Usage ( ) ;
return 22 ;
}
2009-02-19 04:09:59 +00:00
2013-05-27 22:55:42 -04:00
if ( destFile ! = "-" & & destFile ! = "nul" & & File . Exists ( destFile ) )
2013-04-11 00:05:48 -04:00
{
Console . Error . WriteLine ( "Error: file already exists." ) ;
return 17 ;
}
DateTime start = DateTime . Now ;
TimeSpan lastPrint = TimeSpan . FromMilliseconds ( 0 ) ;
2018-03-24 12:15:49 -04:00
var config = new CUEConfigAdvanced ( ) ;
config . Init ( ) ;
2013-04-11 00:05:48 -04:00
2009-01-28 04:53:13 +00:00
#if ! DEBUG
2013-04-11 00:05:48 -04:00
try
2009-01-28 04:53:13 +00:00
#endif
2013-04-11 00:05:48 -04:00
{
IAudioSource audioSource = null ;
IAudioDest audioDest = null ;
2013-04-18 23:20:18 -04:00
TagLib . UserDefined . AdditionalFileTypes . Config = config ;
TagLib . File sourceInfo = sourceFile = = "-" ? null : TagLib . File . Create ( new TagLib . File . LocalFileAbstraction ( sourceFile ) ) ;
2013-05-27 22:55:42 -04:00
2018-03-11 17:07:48 -04:00
#if ! DEBUG
2013-04-11 00:05:48 -04:00
try
2018-03-11 17:07:48 -04:00
#endif
2013-04-11 00:05:48 -04:00
{
2018-04-07 13:55:01 -04:00
audioSource = Program . GetAudioSource ( config , sourceFile , decoderName , ignore_chunk_sizes , decoderOptions ) ;
2018-02-10 17:33:22 -05:00
2013-04-11 00:05:48 -04:00
AudioBuffer buff = new AudioBuffer ( audioSource , 0x10000 ) ;
Console . Error . WriteLine ( "Filename : {0}" , sourceFile ) ;
2018-04-07 23:02:01 -04:00
Console . Error . WriteLine ( "File Info : {0}kHz; {1} channel; {2} bit; {3}" , audioSource . PCM . SampleRate , audioSource . PCM . ChannelCount , audioSource . PCM . BitsPerSample , audioSource . Duration ) ;
2013-04-11 00:05:48 -04:00
CUEToolsFormat fmt ;
2013-05-27 22:55:42 -04:00
if ( encoderFormat = = null )
{
if ( destFile = = "-" | | destFile = = "nul" )
encoderFormat = "wav" ;
else
{
string extension = Path . GetExtension ( destFile ) . ToLower ( ) ;
if ( ! extension . StartsWith ( "." ) )
throw new Exception ( "Unknown encoder format: " + destFile ) ;
encoderFormat = extension . Substring ( 1 ) ;
}
}
if ( ! config . formats . TryGetValue ( encoderFormat , out fmt ) )
throw new Exception ( "Unsupported encoder format: " + encoderFormat ) ;
2018-03-23 19:26:26 -04:00
AudioEncoderSettingsViewModel encoder =
2013-05-27 22:55:42 -04:00
audioEncoderType = = AudioEncoderType . Lossless ? Program . GetEncoder ( config , fmt , true , encoderName ) :
audioEncoderType = = AudioEncoderType . Lossy ? Program . GetEncoder ( config , fmt , false , encoderName ) :
Program . GetEncoder ( config , fmt , true , encoderName ) ? ? Program . GetEncoder ( config , fmt , false , encoderName ) ;
2013-04-11 00:05:48 -04:00
if ( encoder = = null )
2013-06-04 20:44:50 -04:00
{
2018-03-24 12:15:49 -04:00
var lst = new List < AudioEncoderSettingsViewModel > ( config . encodersViewModel ) . FindAll (
2018-03-23 19:26:26 -04:00
e = > e . Extension = = fmt . extension & & ( audioEncoderType = = AudioEncoderType . NoAudio | | audioEncoderType = = ( e . Lossless ? AudioEncoderType . Lossless : AudioEncoderType . Lossy ) ) ) .
2013-06-04 20:44:50 -04:00
ConvertAll ( e = > e . Name + ( e . Lossless ? " (lossless)" : " (lossy)" ) ) ;
throw new Exception ( "Encoders available for format " + fmt . extension + ": " + ( lst . Count = = 0 ? "none" : string . Join ( ", " , lst . ToArray ( ) ) ) ) ;
}
2018-03-24 12:15:49 -04:00
var settings = encoder . Settings . Clone ( ) ;
2013-04-11 00:05:48 -04:00
settings . PCM = audioSource . PCM ;
settings . Padding = padding ;
settings . EncoderMode = encoderMode ? ? settings . EncoderMode ;
object o = null ;
try
2013-04-18 23:20:18 -04:00
{
2018-03-23 19:26:26 -04:00
o = destFile = = "-" ? Activator . CreateInstance ( settings . EncoderType , settings , "" , Console . OpenStandardOutput ( ) ) :
destFile = = "nul" ? Activator . CreateInstance ( settings . EncoderType , settings , "" , new NullStream ( ) ) :
Activator . CreateInstance ( settings . EncoderType , settings , destFile , null ) ;
2013-04-11 00:05:48 -04:00
}
catch ( System . Reflection . TargetInvocationException ex )
{
throw ex . InnerException ;
}
if ( o = = null | | ! ( o is IAudioDest ) )
2018-03-23 19:26:26 -04:00
throw new Exception ( "Unsupported audio type: " + destFile + ": " + settings . EncoderType . FullName ) ;
2013-04-11 00:05:48 -04:00
audioDest = o as IAudioDest ;
audioDest . FinalSampleCount = audioSource . Length ;
2009-01-28 04:53:13 +00:00
2013-04-11 00:05:48 -04:00
bool keepRunning = true ;
Console . CancelKeyPress + = delegate ( object sender , ConsoleCancelEventArgs e )
{
keepRunning = false ;
if ( e . SpecialKey = = ConsoleSpecialKey . ControlC )
e . Cancel = true ;
else
audioDest . Delete ( ) ;
} ;
2009-01-28 04:53:13 +00:00
2013-04-11 00:05:48 -04:00
while ( audioSource . Read ( buff , - 1 ) ! = 0 )
{
audioDest . Write ( buff ) ;
TimeSpan elapsed = DateTime . Now - start ;
if ( ( elapsed - lastPrint ) . TotalMilliseconds > 60 )
{
2018-04-07 23:02:01 -04:00
var duration = audioSource . Duration ;
var position = TimeSpan . FromSeconds ( ( double ) audioSource . Position / audioSource . PCM . SampleRate ) ;
if ( duration = = TimeSpan . Zero & & sourceInfo ! = null ) duration = sourceInfo . Properties . Duration ;
if ( duration < position ) duration = position ;
if ( duration < TimeSpan . FromSeconds ( 1 ) ) duration = TimeSpan . FromSeconds ( 1 ) ;
2013-04-11 00:05:48 -04:00
Console . Error . Write ( "\rProgress : {0:00}%; {1:0.00}x; {2}/{3}" ,
2018-04-07 23:02:01 -04:00
100.0 * position . TotalSeconds / duration . TotalSeconds ,
position . TotalSeconds / elapsed . TotalSeconds ,
2013-04-11 00:05:48 -04:00
elapsed ,
2018-04-07 23:02:01 -04:00
TimeSpan . FromSeconds ( elapsed . TotalSeconds / position . TotalSeconds * duration . TotalSeconds )
2013-04-11 00:05:48 -04:00
) ;
lastPrint = elapsed ;
}
if ( ! keepRunning )
throw new Exception ( "Aborted" ) ;
}
2009-01-28 04:53:13 +00:00
2013-04-11 00:05:48 -04:00
TimeSpan totalElapsed = DateTime . Now - start ;
Console . Error . Write ( "\r \r" ) ;
Console . Error . WriteLine ( "Results : {0:0.00}x; {1}" ,
audioSource . Position / totalElapsed . TotalSeconds / audioSource . PCM . SampleRate ,
totalElapsed
) ;
}
2018-03-11 17:07:48 -04:00
#if ! DEBUG
2013-04-11 00:05:48 -04:00
catch ( Exception ex )
{
if ( audioSource ! = null ) audioSource . Close ( ) ;
if ( audioDest ! = null ) audioDest . Delete ( ) ;
throw ex ;
}
2018-03-11 17:07:48 -04:00
#endif
2013-04-11 00:05:48 -04:00
audioSource . Close ( ) ;
audioDest . Close ( ) ;
2009-02-19 04:09:59 +00:00
2013-05-27 22:55:42 -04:00
if ( sourceFile ! = "-" & & destFile ! = "-" & & destFile ! = "nul" )
2013-04-11 00:05:48 -04:00
{
2013-05-27 22:55:42 -04:00
TagLib . File destInfo = TagLib . File . Create ( new TagLib . File . LocalFileAbstraction ( destFile ) ) ;
2013-06-11 21:19:48 -04:00
if ( Tagging . UpdateTags ( destInfo , Tagging . Analyze ( sourceInfo ) , config , false ) )
2013-04-18 23:20:18 -04:00
{
sourceInfo . Tag . CopyTo ( destInfo . Tag , true ) ;
destInfo . Tag . Pictures = sourceInfo . Tag . Pictures ;
destInfo . Save ( ) ;
}
2013-04-11 00:05:48 -04:00
}
}
2009-01-28 04:53:13 +00:00
#if ! DEBUG
2013-04-11 00:05:48 -04:00
catch ( Exception ex )
{
Console . Error . Write ( "\r \r" ) ;
Console . Error . WriteLine ( "Error : {0}" , ex . Message ) ;
return 1 ;
//Console.WriteLine("{0}", ex.StackTrace);
}
2009-01-28 04:53:13 +00:00
#endif
2013-04-11 00:05:48 -04:00
return 0 ;
}
}
2009-01-28 04:53:13 +00:00
}