Initial commit — M3U playlist tool with MP3/AAC encoding
This commit is contained in:
@@ -0,0 +1,365 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
|
||||
namespace m3uTool
|
||||
{
|
||||
/// <summary>
|
||||
/// A generic encoder. Creates an external process, sends it argument options,
|
||||
/// and acts as a wrapper for streaming reads and writes
|
||||
/// </summary>
|
||||
public abstract class ProcessStreamWrapper : IDisposable
|
||||
{
|
||||
#region Protected Variables
|
||||
/// <summary>
|
||||
/// The arguments to use for the process
|
||||
/// </summary>
|
||||
protected ProcessArguments mProcessArguments;
|
||||
|
||||
/// <summary>
|
||||
/// The process
|
||||
/// </summary>
|
||||
protected Process mProcess;
|
||||
|
||||
/// <summary>
|
||||
/// How much data to buffer when reading and writing
|
||||
/// </summary>
|
||||
protected const int mBufferLength = 1000;
|
||||
|
||||
/// <summary>
|
||||
/// The output stream writer. If it is null, it is assumed
|
||||
/// that the process will take care of output itself.
|
||||
/// </summary>
|
||||
protected StreamWriter mOutputStreamWriter = null;
|
||||
|
||||
/// <summary>
|
||||
/// Progress call-back, for status updates outside this class.
|
||||
/// </summary>
|
||||
protected IProgressCallback mProgressCallback = new DoNothingProgressCallback();
|
||||
|
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the progress callback.
|
||||
/// </summary>
|
||||
/// <value>The progress callback.</value>
|
||||
public IProgressCallback ProgressCallback
|
||||
{
|
||||
get { return mProgressCallback; }
|
||||
set { mProgressCallback = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the process executable filename, such as "LAME.EXE", "FAAC.EXE", etc.
|
||||
/// </summary>
|
||||
/// <value>The process executable filename.</value>
|
||||
protected abstract string ProcessExecutableFilename
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructor
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ProcessStreamWrapper"/> class.
|
||||
/// </summary>
|
||||
/// <param name="encOpt">The enc opt.</param>
|
||||
public ProcessStreamWrapper(ProcessArguments encOpt) : this (encOpt, null)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ProcessStreamWrapper"/> class.
|
||||
/// </summary>
|
||||
/// <param name="encOpt">The enc opt.</param>
|
||||
/// <param name="outputStream">Output to this stream</param>
|
||||
public ProcessStreamWrapper(ProcessArguments encOpt, Stream outputStream)
|
||||
{
|
||||
mProcessArguments = encOpt;
|
||||
|
||||
mProcess = CreateProcess(mProcessArguments, ProcessExecutableFilename);
|
||||
if (outputStream != null)
|
||||
{
|
||||
mOutputStreamWriter = new StreamWriter(outputStream, Encoding.Default);
|
||||
mProcess.StartInfo.RedirectStandardOutput = true;
|
||||
mProcess.StartInfo.StandardOutputEncoding = Encoding.Default;
|
||||
}
|
||||
mProcess.StartInfo.RedirectStandardError = true;
|
||||
|
||||
mProcess.Exited += new EventHandler(Process_Exited);
|
||||
mProcess.Start();
|
||||
if (mOutputStreamWriter != null)
|
||||
{
|
||||
Thread thread = new Thread(new ThreadStart(GetStandardOutput));
|
||||
thread.Start();
|
||||
}
|
||||
|
||||
Thread standardErrorThread = new Thread(new ThreadStart(GetStandardError));
|
||||
standardErrorThread.Start();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Static methods
|
||||
|
||||
/// <summary>
|
||||
/// Creates a process given an executable and some process arguments
|
||||
/// </summary>
|
||||
/// <param name="processArguments">The enc opt.</param>
|
||||
/// <param name="executable">The executable filename.</param>
|
||||
/// <returns></returns>
|
||||
private static Process CreateProcess(ProcessArguments processArguments, string executable)
|
||||
{
|
||||
Process process = new Process();
|
||||
process.StartInfo.RedirectStandardInput = true;
|
||||
process.StartInfo.UseShellExecute = false;
|
||||
process.StartInfo.FileName = executable;
|
||||
process.StartInfo.Arguments = processArguments.GetCommandLineArguments();
|
||||
process.StartInfo.WorkingDirectory = Environment.CurrentDirectory;
|
||||
process.StartInfo.CreateNoWindow = true;
|
||||
|
||||
process.EnableRaisingEvents = true;
|
||||
return process;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Event handlers
|
||||
|
||||
/// <summary>
|
||||
/// Handles the Exited event of the Process.
|
||||
/// </summary>
|
||||
/// <param name="sender">The source of the event.</param>
|
||||
/// <param name="e">The <see cref="T:System.EventArgs"/> instance containing the event data.</param>
|
||||
void Process_Exited(object sender, EventArgs e)
|
||||
{
|
||||
mProgressCallback.SetText("Process_Exited");
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Utility methods
|
||||
|
||||
private static int GetPercentDone(long previousLeft, long currentLeft, long currentDone, int lastDone)
|
||||
{
|
||||
// if currentLeft > previousLeft - lastDone,
|
||||
// addedLeft = previousLeft - lastDone - currentLeft
|
||||
// totalLength = previousLeft + currentDone + addedLeft
|
||||
|
||||
long totalLength = currentLeft;
|
||||
if (currentLeft != previousLeft)
|
||||
{
|
||||
long leftDelta = previousLeft - lastDone - currentLeft;
|
||||
totalLength = previousLeft + currentDone + leftDelta;
|
||||
}
|
||||
try
|
||||
{
|
||||
return Convert.ToInt32((currentDone / (double)totalLength) * 100);
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
Debug.WriteLine(ex.ToString());
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public methods
|
||||
|
||||
/// <summary>
|
||||
/// Sends the data to the underlying process through its input stream.
|
||||
/// </summary>
|
||||
/// <param name="buffer">The data to encode</param>
|
||||
/// <param name="count">How many characters to write</param>
|
||||
/// <param name="startIndex">The index to begin writing from</param>
|
||||
public void ProcessInput(char[] buffer, int startIndex, int count)
|
||||
{
|
||||
mProcess.StandardInput.Write(buffer, startIndex, count);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends the data from the input stream to the process
|
||||
/// </summary>
|
||||
/// <param name="inputStream"></param>
|
||||
public void ProcessInput(Stream inputStream)
|
||||
{
|
||||
if (!inputStream.CanRead)
|
||||
throw new ArgumentException("Can't read from inputStream");
|
||||
|
||||
mProgressCallback.SetText("ProcessInput: StreamReader sr");
|
||||
|
||||
long inputCount = inputStream.Length;
|
||||
long previousInputCount = inputStream.Length;
|
||||
long wroteCount = 0;
|
||||
int percentageComplete = 0;
|
||||
|
||||
StreamReader sr = new StreamReader(inputStream, Encoding.Default);
|
||||
//using (StreamReader sr = new StreamReader(inputStream, Encoding.Default))
|
||||
{
|
||||
mProgressCallback.Begin(0, 100);
|
||||
mProgressCallback.SetText(mProcessArguments.GetCommandLineArguments());
|
||||
|
||||
char[] buffer = new char[mBufferLength];
|
||||
|
||||
while (!sr.EndOfStream)
|
||||
{
|
||||
int readLength = sr.Read(buffer, 0, mBufferLength);
|
||||
ProcessInput(buffer, 0, readLength);
|
||||
|
||||
// update progress
|
||||
wroteCount += readLength;
|
||||
previousInputCount = inputCount;
|
||||
inputCount = inputStream.Length;
|
||||
int currentPercentageComplete = GetPercentDone(previousInputCount, inputCount, wroteCount, readLength);
|
||||
if (currentPercentageComplete != percentageComplete)
|
||||
{
|
||||
percentageComplete = currentPercentageComplete;
|
||||
mProgressCallback.StepTo(percentageComplete);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
mInputDone = true;
|
||||
mProgressCallback.SetText("ProcessInput: Done. " + String.Format("Wrote: {0}", wroteCount));
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Open this file and encode it.
|
||||
/// </summary>
|
||||
/// <param name="filename"></param>
|
||||
public void ProcessInput(string filename)
|
||||
{
|
||||
using (FileStream inputStream = File.OpenRead(filename))
|
||||
{
|
||||
ProcessInput(inputStream);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Threaded methods
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// TEMP: this should be removed once GetStandardOutput is working
|
||||
/// </summary>
|
||||
bool mInputDone = false;
|
||||
|
||||
/// <summary>
|
||||
/// Collects all the data from Process.StandardOutput and sends it to the outputstream writer
|
||||
/// </summary>
|
||||
private void GetStandardOutput()
|
||||
{
|
||||
int readTotal = 0;
|
||||
char[] writeBuffer = new char[mBufferLength];
|
||||
|
||||
try
|
||||
{
|
||||
while (!mProcess.StandardOutput.EndOfStream)
|
||||
//while (mProcess.StandardOutput.Peek() > -1)
|
||||
{
|
||||
mProgressCallback.SetText("- Before read");
|
||||
int readLength = mProcess.StandardOutput.Read(writeBuffer, 0, writeBuffer.Length);
|
||||
mProgressCallback.SetText("- After read: " + readLength);
|
||||
mOutputStreamWriter.Write(writeBuffer, 0, readLength);
|
||||
if (mInputDone)
|
||||
mProgressCallback.SetText(" StandardOutput: " + new string(writeBuffer, 0, readLength));
|
||||
readTotal += readLength;
|
||||
mProgressCallback.SetText("Read total = " + readTotal);
|
||||
|
||||
//mProcess.StandardInput.Flush();
|
||||
//mProcess.StandardOutput.BaseStream.Flush();
|
||||
}
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
mProgressCallback.SetText(ex.ToString());
|
||||
}
|
||||
// mOutputStreamWriter.Flush();
|
||||
mProgressCallback.SetText("GetStandardOutput() " + "Done!" + " StandardOutput.EndOfStream = " + mProcess.StandardOutput.EndOfStream);
|
||||
}
|
||||
|
||||
/* Asynchronous implementation of GetStandardOutput
|
||||
//int mReadTotal = 0;
|
||||
//private byte[] mBuffer = new byte[mBufferLength];
|
||||
//private void GetStandardOutputAsync()
|
||||
//{
|
||||
// mProcess.StandardOutput.BaseStream.BeginRead(mBuffer, 0, mBufferLength, GetStandardOutputCallback, 0);
|
||||
//}
|
||||
|
||||
//private void GetStandardOutputCallback(IAsyncResult asyncResult)
|
||||
//{
|
||||
// mProgressCallback.SetText("- Before read:");
|
||||
// int readLength = mProcess.StandardOutput.BaseStream.EndRead(asyncResult);
|
||||
// mProgressCallback.SetText("- After read:" + readLength);
|
||||
// mOutputStreamWriter.BaseStream.Write(mBuffer, 0, readLength);
|
||||
// mReadTotal += readLength;
|
||||
// mProgressCallback.SetText("Read total = " + mReadTotal);
|
||||
// mProcess.StandardOutput.BaseStream.BeginRead(mBuffer, 0, mBufferLength, GetStandardOutputCallback, 0);
|
||||
//}
|
||||
*/
|
||||
|
||||
/// <summary>
|
||||
/// Collects all the data from Process.StandardError and sends it to the progress callback
|
||||
/// </summary>
|
||||
private void GetStandardError()
|
||||
{
|
||||
try
|
||||
{
|
||||
while (!mProcess.StandardError.EndOfStream)
|
||||
{
|
||||
string line = mProcess.StandardError.ReadLine();
|
||||
mProgressCallback.SetText("GetStandardError() " + line);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
mProgressCallback.SetText(ex.ToString());
|
||||
}
|
||||
mProgressCallback.SetText("GetStandardError() " + "Done!");
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region IDisposable Members
|
||||
|
||||
/// <summary>
|
||||
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
int count = 0;
|
||||
mProgressCallback.SetText("Dispose()");
|
||||
while (mProcess != null && !mProcess.HasExited)
|
||||
{
|
||||
mProcess.WaitForExit(2);
|
||||
if (count++ > mBufferLength && !mProcess.HasExited)
|
||||
{
|
||||
mProcess.Kill();
|
||||
mProgressCallback.SetText("!!! Process.Kill()");
|
||||
}
|
||||
else
|
||||
{
|
||||
// mProgressCallback.SetText("Writing EOF");
|
||||
mProcess.StandardInput.Write(new string(new char[] { (char)0x1A, '\n' }));
|
||||
}
|
||||
}
|
||||
|
||||
mProgressCallback.SetText("Dispose() with exit code " + mProcess.ExitCode);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user