commit b14531362b453a139962973be231dfdd75044a5d Author: poprhythm Date: Sun May 10 03:02:53 2026 +0000 Initial commit — M3U playlist tool with MP3/AAC encoding diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2fec11a --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +bin/ +obj/ +_ReSharper*/ +_UpgradeReport_Files/ +*.suo +*.user +.vs/ diff --git a/App.ico b/App.ico new file mode 100644 index 0000000..3a5525f Binary files /dev/null and b/App.ico differ diff --git a/AssemblyInfo.cs b/AssemblyInfo.cs new file mode 100644 index 0000000..7636e89 --- /dev/null +++ b/AssemblyInfo.cs @@ -0,0 +1,59 @@ +using System.Reflection; + +// +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +// + +[assembly: AssemblyTitle("")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("")] +[assembly: AssemblyCopyright("")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Revision and Build Numbers +// by using the '*' as shown below: + +[assembly: AssemblyVersion("1.0.*")] + +// +// In order to sign your assembly you must specify a key to use. Refer to the +// Microsoft .NET Framework documentation for more information on assembly signing. +// +// Use the attributes below to control which key is used for signing. +// +// Notes: +// (*) If no key is specified, the assembly is not signed. +// (*) KeyName refers to a key that has been installed in the Crypto Service +// Provider (CSP) on your machine. KeyFile refers to a file which contains +// a key. +// (*) If the KeyFile and the KeyName values are both specified, the +// following processing occurs: +// (1) If the KeyName can be found in the CSP, that key is used. +// (2) If the KeyName does not exist and the KeyFile does exist, the key +// in the KeyFile is installed into the CSP and used. +// (*) In order to create a KeyFile, you can use the sn.exe (Strong Name) utility. +// When specifying the KeyFile, the location of the KeyFile should be +// relative to the project output directory which is +// %Project Directory%\obj\. For example, if your KeyFile is +// located in the project directory, you would specify the AssemblyKeyFile +// attribute as [assembly: AssemblyKeyFile("..\\..\\mykey.snk")] +// (*) Delay Signing is an advanced option - see the Microsoft .NET Framework +// documentation for more information on this. +// + +[assembly: AssemblyDelaySign(false)] +[assembly: AssemblyKeyFile("")] +[assembly: AssemblyKeyName("")] \ No newline at end of file diff --git a/Backup/App.ico b/Backup/App.ico new file mode 100644 index 0000000..3a5525f Binary files /dev/null and b/Backup/App.ico differ diff --git a/Backup/AssemblyInfo.cs b/Backup/AssemblyInfo.cs new file mode 100644 index 0000000..177a4f0 --- /dev/null +++ b/Backup/AssemblyInfo.cs @@ -0,0 +1,58 @@ +using System.Reflection; +using System.Runtime.CompilerServices; + +// +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +// +[assembly: AssemblyTitle("")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("")] +[assembly: AssemblyCopyright("")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Revision and Build Numbers +// by using the '*' as shown below: + +[assembly: AssemblyVersion("1.0.*")] + +// +// In order to sign your assembly you must specify a key to use. Refer to the +// Microsoft .NET Framework documentation for more information on assembly signing. +// +// Use the attributes below to control which key is used for signing. +// +// Notes: +// (*) If no key is specified, the assembly is not signed. +// (*) KeyName refers to a key that has been installed in the Crypto Service +// Provider (CSP) on your machine. KeyFile refers to a file which contains +// a key. +// (*) If the KeyFile and the KeyName values are both specified, the +// following processing occurs: +// (1) If the KeyName can be found in the CSP, that key is used. +// (2) If the KeyName does not exist and the KeyFile does exist, the key +// in the KeyFile is installed into the CSP and used. +// (*) In order to create a KeyFile, you can use the sn.exe (Strong Name) utility. +// When specifying the KeyFile, the location of the KeyFile should be +// relative to the project output directory which is +// %Project Directory%\obj\. For example, if your KeyFile is +// located in the project directory, you would specify the AssemblyKeyFile +// attribute as [assembly: AssemblyKeyFile("..\\..\\mykey.snk")] +// (*) Delay Signing is an advanced option - see the Microsoft .NET Framework +// documentation for more information on this. +// +[assembly: AssemblyDelaySign(false)] +[assembly: AssemblyKeyFile("")] +[assembly: AssemblyKeyName("")] diff --git a/Backup/DoNothingProgressCallback.cs b/Backup/DoNothingProgressCallback.cs new file mode 100644 index 0000000..7e5f633 --- /dev/null +++ b/Backup/DoNothingProgressCallback.cs @@ -0,0 +1,81 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace m3uTool +{ + public class DoNothingProgressCallback : IProgressCallback + { + /// + /// Call this method from the worker thread to initialize + /// the progress callback. + /// + /// The minimum. + /// The maximum. + public void Begin(int minimum, int maximum) + { + } + + /// + /// Call this method from the worker thread to initialize + /// the progress callback, without setting the range + /// + public void Begin() + { + } + + /// + /// Call this method from the worker thread to reset the range in the + /// progress callback + /// + /// The minimum. + /// The maximum. + public void SetRange(int minimum, int maximum) + { + } + + /// + /// Call this method from the worker thread to update the progress text. + /// + /// The text. + public void SetText(string text) + { + } + + /// + /// Call this method from the worker thread to increase the progress + /// counter by a specified value. + /// + /// The val. + public void StepTo(int val) + { + } + + /// + /// Call this method from the worker thread to step the progress meter to a + /// particular value. + /// + /// The val. + public void Increment(int val) + { + } + + /// + /// If this property is true, then you should abort work + /// + /// + /// true if this instance is aborting; otherwise, false. + /// + public bool IsAborting + { + get { return false; } + } + + /// + /// Call this method from the worker thread to finalize the progress meter + /// + public void End() + { + } + } +} diff --git a/Backup/IProgressCallback.cs b/Backup/IProgressCallback.cs new file mode 100644 index 0000000..43648ee --- /dev/null +++ b/Backup/IProgressCallback.cs @@ -0,0 +1,68 @@ +using System; + +/// +/// This defines an interface which can be implemented by UI elements +/// which indicate the progress of a long operation. +/// (See ProgressWindow for a typical implementation) +/// +/// Based on http://www.codeproject.com/cs/miscctrl/progressdialog.asp +public interface IProgressCallback +{ + /// + /// Call this method from the worker thread to initialize + /// the progress callback. + /// + /// The minimum. + /// The maximum. + void Begin(int minimum, int maximum); + + /// + /// Call this method from the worker thread to initialize + /// the progress callback, without setting the range + /// + void Begin(); + + /// + /// Call this method from the worker thread to reset the range in the + /// progress callback + /// + /// The minimum. + /// The maximum. + void SetRange(int minimum, int maximum); + + /// + /// Call this method from the worker thread to update the progress text. + /// + /// The text. + void SetText(String text); + + /// + /// Call this method from the worker thread to increase the progress + /// counter by a specified value. + /// + /// The val. + void StepTo(int val); + + /// + /// Call this method from the worker thread to step the progress meter to a + /// particular value. + /// + /// The val. + void Increment(int val); + + /// + /// If this property is true, then you should abort work + /// + /// + /// true if this instance is aborting; otherwise, false. + /// + bool IsAborting + { + get; + } + + /// + /// Call this method from the worker thread to finalize the progress meter + /// + void End(); +} \ No newline at end of file diff --git a/Backup/KeyedList.cs b/Backup/KeyedList.cs new file mode 100644 index 0000000..60cf772 --- /dev/null +++ b/Backup/KeyedList.cs @@ -0,0 +1,399 @@ +/* +Copyright (c) 2005, Marc Clifton +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this list + of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, this + list of conditions and the following disclaimer in the documentation and/or other + materials provided with the distribution. + +* Neither the name of MyXaml nor the names of its contributors may be + used to endorse or promote products derived from this software without specific + prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ + +using System; +using System.Collections; +using System.Collections.Generic; + +namespace Clifton.Collections.Generic +{ + [Serializable] + public class KeyedList : IDictionary, IList> + { + private Dictionary objectTable = new Dictionary(); + private List> objectList = new List>(); + + /// + /// Returns false. + /// + public bool IsReadOnly + { + get { return false; } + } + + /// + /// Returns the number of entries in the KeyedList. + /// + public int Count + { + get { return objectList.Count; } + } + + /// + /// Get/Set the value at the specified index. + /// + /// The index. + /// The value. + public KeyValuePair this[int idx] + { + get + { + if (idx < 0 || idx >= Count) + { + throw new ArgumentOutOfRangeException("index"); + } + + return objectList[idx]; + } + set + { + if (idx < 0 || idx >= Count) + { + throw new ArgumentOutOfRangeException("index"); + } + + objectList[idx] = value; + objectTable[value.Key] = value.Value; + } + } + + /// + /// Get/Set the value associated with the specified key. + /// + /// The key. + /// The associated value. + public virtual V this[K key] + { + get { return objectTable[key]; } + set + { + if (objectTable.ContainsKey(key)) + { + objectTable[key] = value; + objectList[IndexOf(key)] = new KeyValuePair(key, value); + } + else + { + Add(key, value); + } + } + } + + /// + /// Get an unordered list of keys. + /// This collection refers back to the keys in the original Dictionary. + /// + public ICollection Keys + { + get { return objectTable.Keys; } + } + + /// + /// Get an unordered list of values. + /// This collection refers back to the values in the original Dictionary. + /// + public ICollection Values + { + get { return objectTable.Values; } + } + + /// + /// Get the ordered list of keys. + /// This is a copy of the keys in the original Dictionary. + /// + public List OrderedKeys + { + get + { + List retList = new List(); + + foreach (KeyValuePair kvp in objectList) + { + retList.Add(kvp.Key); + } + + return retList; + } + } + + /// + /// Get the ordered list of values. + /// This is a copy of the values in the original Dictionary. + /// + public List OrderedValues + { + get + { + List retList = new List(); + + foreach (KeyValuePair kvp in objectList) + { + retList.Add(kvp.Value); + } + + return retList; + } + } + + /// + /// Returns the key at the specified index. + /// + /// The index. + /// The key at the index. + public K GetKey(int idx) + { + if (idx < 0 || idx >= Count) + { + throw new ArgumentOutOfRangeException("index"); + } + + return objectList[idx].Key; + } + + /// + /// Returns the value at the specified index. + /// + /// The index. + /// The value at the index. + public V GetValue(int idx) + { + if (idx < 0 || idx >= Count) + { + throw new ArgumentOutOfRangeException("index"); + } + + return objectList[idx].Value; + } + + /// + /// Get the index of a particular key. + /// + /// The key to find the index of. + /// The index of the key, or -1 if not found. + public int IndexOf(K key) + { + int ret = -1; + + for (int i = 0; i < objectList.Count; i++) + { + if (objectList[i].Key.Equals(key)) + { + ret = i; + break; + } + } + + return ret; + } + + /// + /// Given the key-value pair, find the index. + /// + /// The key-value pair. + /// The index, or -1 if not found. + public int IndexOf(KeyValuePair kvp) + { + return IndexOf(kvp.Key); + } + + /// + /// Gets the Dictionary class backing the KeyedList. + /// + public Dictionary ObjectTable + { + get { return objectTable; } + } + + /// + /// Clears all entries in the KeyedList. + /// + public void Clear() + { + objectTable.Clear(); + objectList.Clear(); + } + + /// + /// Test if the KeyedList contains the key. + /// + /// The key. + /// True if the key is found. + public bool ContainsKey(K key) + { + return objectTable.ContainsKey(key); + } + + /// + /// Test if the KeyedList contains the key in the key-value pair. + /// + /// The key-value pair. + /// True if the key is found. + public bool Contains(KeyValuePair kvp) + { + return objectTable.ContainsKey(kvp.Key); + } + + /// + /// Adds a key-value pair to the KeyedList. + /// + /// The key. + /// The associated value. + public void Add(K key, V value) + { + objectTable.Add(key, value); + objectList.Add(new KeyValuePair(key, value)); + } + + /// + /// Adds a key-value pair to the KeyedList. + /// + /// The KeyValuePair instance. + public void Add(KeyValuePair kvp) + { + Add(kvp.Key, kvp.Value); + } + + /// + /// Copy the entire key-value pairs to the KeyValuePair array, starting + /// at the specified index of the target array. The array is populated + /// as an ordered list. + /// + /// The KeyValuePair array. + /// The position to start the copy. + public void CopyTo(KeyValuePair[] kvpa, int idx) + { + objectList.CopyTo(kvpa, idx); + } + + /// + /// Insert the key-value at the specified index. + /// + /// The zero-based insert point. + /// The key. + /// The value. + public void Insert(int idx, K key, V value) + { + if ((idx < 0) || (idx > Count)) + { + throw new ArgumentOutOfRangeException("index"); + } + + objectTable.Add(key, value); + objectList.Insert(idx, new KeyValuePair(key, value)); + } + + /// + /// Insert the key-value pair at the specified index location. + /// + /// The key. + /// The value. + public void Insert(int idx, KeyValuePair kvp) + { + if ((idx < 0) || (idx > Count)) + { + throw new ArgumentOutOfRangeException("index"); + } + + objectTable.Add(kvp.Key, kvp.Value); + objectList.Insert(idx, kvp); + } + + /// + /// Remove the entry. + /// + /// The key identifying the key-value pair. + /// True if removed. + public bool Remove(K key) + { + bool found = objectTable.Remove(key); + + if (found) + { + objectList.RemoveAt(IndexOf(key)); + } + + return found; + } + + /// + /// Remove the key in the specified KeyValuePair instance. The Value + /// property is ignored. + /// + /// The key-value identifying the entry. + /// True if removed. + public bool Remove(KeyValuePair kvp) + { + return Remove(kvp.Key); + } + + /// + /// Remove the entry at the specified index. + /// + /// The index to the entry to be removed. + public void RemoveAt(int idx) + { + if ((idx < 0) || (idx >= Count)) + { + throw new ArgumentOutOfRangeException("index"); + } + + objectTable.Remove(objectList[idx].Key); + objectList.RemoveAt(idx); + } + + /// + /// Attempt to get the value, given the key, without throwing an exception if not found. + /// + /// The key indentifying the entry. + /// The value, if found. + /// True if found. + public bool TryGetValue(K key, out V val) + { + return objectTable.TryGetValue(key, out val); + } + + /// + /// Returns an ordered System.Collections KeyValuePair objects. + /// + IEnumerator IEnumerable.GetEnumerator() + { + return objectList.GetEnumerator(); + } + + /// + /// Returns an ordered KeyValuePair enumerator. + /// + IEnumerator> IEnumerable>.GetEnumerator() + { + return objectList.GetEnumerator(); + } + } +} diff --git a/Backup/LongestCommonSequence.cs b/Backup/LongestCommonSequence.cs new file mode 100644 index 0000000..dea6f26 --- /dev/null +++ b/Backup/LongestCommonSequence.cs @@ -0,0 +1,175 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; +using System.Text; + +namespace m3uTool +{ + public class LongestCommonSubstrings + { + //def longest_common_substrings(S, T): + // maks = 0 # length of longest match + // ret = Set([""]) # the longest matches we have found + // last = {} # length of matches ending at S[i-1] and + // next = {} # at S[i] for T[0]...T[n-1] + // for i in xrange(len(S)): + // for j in xrange(len(T)): + // if S[i] == T[j]: + // if j-1 in last: + // next[j] = last[j-1] + 1 + // else: + // next[j] = 1 + // if next[j] > maks: + // maks = next[j] + // ret = Set() + // if next[j] == maks: + // ret.add(S[i-maks+1:i+1]) # equals T[j-maks+1:j+1] + // last = next + // next = {} + // return ret + + public static List GetLongestSubstring(string s, string t) + { + int maks = 0; + List ret = new List(); + Dictionary last = new Dictionary(); + Dictionary next = new Dictionary(); + for (int i = 0; i < s.Length; i++) + { + for (int j = 0; j < t.Length; j++) + { + if (s[i] == t[j]) + { + if (last.ContainsKey(j-1)) + next[j] = last[j - 1] + 1; + else + next[j] = 1; + + if (next[j] > maks) + { + maks = next[j]; + ret.Clear(); + } + if (next[j] == maks) + ret.Add(s.Substring(i - maks + 1, maks)); + } + } + last = next; + next = new Dictionary(); + } + + return ret; + } + } + + public class LongestCommonSequence where T : IEnumerable + { + public enum BackTracking { + NEITHER, + UP, + LEFT, + UP_AND_LEFT + } + + private static int ConsecutiveMeasure(int k) + { + //f(k)=k*a - b; + return k*k; + } + + //public T DoLCS(T list1, T list2) + //{ + + //} + + private object[] LCS(object[] list1, object[] list2) + { + int m=list1.Length ; + int n=list2.Length ; + + int[,] lcs=new int[m+1, n+1]; + BackTracking[ , ] backTracer=new BackTracking[m+1, n+1]; + int[,] w=new int[m+1, n+1]; + int i, j; + + for (i = 0; i <= m; i++) + { + lcs[i,0] = 0; + backTracer[i,0]=BackTracking.UP; + + } + for (j = 0; j <= n; j++) + { + lcs[0,j]=0; + backTracer[0,j]=BackTracking.LEFT; + } + + for (i = 1; i <= m; i++) + { + for (j = 1; j <= n; j++) + { + if(list1[i-1] == list2[j-1]) + { + int k = w[i-1, j-1]; + //lcs[i,j] = lcs[i-1,j-1] + 1; + lcs[i,j]=lcs[i-1,j-1] + ConsecutiveMeasure(k+1) - ConsecutiveMeasure(k); + backTracer[i,j] = BackTracking.UP_AND_LEFT; + w[i,j] = k+1; + } + else + { + lcs[i,j] = lcs[i-1,j-1]; + backTracer[i,j] = BackTracking.NEITHER; + } + + if(lcs[i-1,j] >= lcs[i,j]) + { + lcs[i,j] = lcs[i-1,j]; + backTracer[i,j] = BackTracking.UP; + w[i,j] = 0; + } + + if(lcs[i,j-1] >= lcs[i,j]) + { + lcs[i,j] = lcs[i,j-1]; + backTracer [i,j] = BackTracking.LEFT; + w[i,j] = 0; + } + } + } + + i=m; + j=n; + + Stack subseq = new Stack(); + // int p=lcs[i,j]; + + //trace the backtracking matrix. + while( i > 0 || j > 0 ) + { + if( backTracer[i,j] == BackTracking.UP_AND_LEFT ) + { + i--; + j--; + subseq.Push(list1[i]); + // subseq = list1[i] + subseq; + Trace.WriteLine(i + " " + list1[i] + " " + j) ; + } + + else if( backTracer[i,j] == BackTracking.UP ) + { + i--; + } + + else if( backTracer[i,j] == BackTracking.LEFT ) + { + j--; + } + } + + + return subseq.ToArray(); + } + } +} diff --git a/Backup/M3uCreate.cs b/Backup/M3uCreate.cs new file mode 100644 index 0000000..417bfc3 --- /dev/null +++ b/Backup/M3uCreate.cs @@ -0,0 +1,155 @@ +using System; +using System.Diagnostics; +using System.IO; + +namespace m3uTool +{ + /// + /// Summary description for m3uFromDirectory. + /// + public class M3uCreate + { + /// + /// Use the parent's directory name for the m3u name + /// + private bool mUseGeneratedOuputFilename = true; + + /// + /// Directory to grab the files from + /// + private DirectoryInfo mInputDir; + + /// + /// Ouput m3u filename, if it isn't generated + /// + private string mOutputFilename = null; + + /// + /// Filter for input filenames + /// + private string mFileFilter = "*.mp3"; + + public bool UseGeneratedOuputFilename + { + get + { + return mUseGeneratedOuputFilename; + } + set + { + mUseGeneratedOuputFilename = value; + } + } + + public string OutputFilename + { + get + { + if (mUseGeneratedOuputFilename) + { + Debug.WriteLine("OutputFilename:" + mInputDir.FullName + "\\" + mInputDir.Name + ".m3u"); + return mInputDir.FullName + "\\" + mInputDir.Name + ".m3u"; + } + return mOutputFilename; + } + set + { + mOutputFilename = value; + mUseGeneratedOuputFilename = false; + } + } + + public M3uCreate(string dir) : this (new DirectoryInfo(dir)) + { + } + + public M3uCreate(DirectoryInfo inputDir) + { + mInputDir = inputDir; + } + + /// + /// + /// + /// + /// dir: "\foo\bar\" file: "\foo\bar\myfile" output: "" + /// dir: "\foo\" file: "\foo\bar\myfile" output: ".." + /// dir: "\" file: "\foo\bar\myfile" output: "..\.." + /// dir: "\har" file: "\foo\bar\myfile" output: "..\..\har" + /// dir: "\foo\bar\hey\" file: "\foo\bar\myfile" output: "hey\" + /// + /// + /// + /// + private string GetPath(DirectoryInfo dir, FileInfo file) + { + string source = dir.FullName; + string target = file.Directory.FullName; + + return GetPath(source, target); + } + + /// + /// Gets the path. + /// + /// The source. + /// The target. + /// + private string GetPath(string source, string target) + { + int lastCommonIndex = GetLastCommonIndex(source, target); + Debug.WriteLine(String.Format("s:{0} , t:{1} lastCommon:{2} ", source, target, lastCommonIndex)); + + int slashCount = CountCharacters(source, lastCommonIndex + 1, '\\'); + + string path = ""; + for (int i = 0; i < slashCount; i++) + { + path += "..\\"; + } + path += target.Substring(lastCommonIndex); + if (path.Length > 0 && path[path.Length - 1] != '\\') + path += "\\"; + Debug.WriteLine(path); + return path; + } + + private int CountCharacters(string str, int startIdx, char c) + { + int charsCount = 0; + int currIdx = startIdx; + while (currIdx < str.Length && (currIdx = str.IndexOf(c, currIdx) + 1) != 0) + charsCount++; + return charsCount; + } + + private int GetLastCommonIndex(string str1, string str2) + { + int i = 0; + while (i < str1.Length && i < str2.Length && Char.ToLower(str1[i]) == Char.ToLower(str2[i])) + i++; + return i; + } + + public void Create() + { + string fileList = ""; + DirectoryInfo ouputDirectory = (new FileInfo(OutputFilename)).Directory; + foreach (FileInfo info in mInputDir.GetFiles(mFileFilter)) + { + string fileName = GetPath(ouputDirectory, info) + info.Name; + Debug.WriteLine(fileName); + fileList += fileName + Environment.NewLine; + } + SaveToFile(fileList, OutputFilename); + } + + private void SaveToFile(string contents, string outputFilename) + { + using (TextWriter tw = new StreamWriter(outputFilename)) + { + tw.Write(contents); + } + } + } +} diff --git a/Backup/M3uMakeByDirectory.cs b/Backup/M3uMakeByDirectory.cs new file mode 100644 index 0000000..4248827 --- /dev/null +++ b/Backup/M3uMakeByDirectory.cs @@ -0,0 +1,171 @@ +using System; +using System.ComponentModel; +using System.IO; +using System.Threading; +using System.Windows.Forms; + +namespace m3uTool +{ + /// + /// Summary description for Form1. + /// + public class M3uMakeByDirectory : Form + { + private Button selectDirectoryButton; + private TextBox m3uDirectoryTextBox; + private TextBox outputTextBox; + private Button m3uMakeButton; + /// + /// Required designer variable. + /// + private Container components = null; + + public M3uMakeByDirectory() + { + // + // Required for Windows Form Designer support + // + InitializeComponent(); + + // + // TODO: Add any constructor code after InitializeComponent call + // + } + + /// + /// Clean up any resources being used. + /// + protected override void Dispose( bool disposing ) + { + if( disposing ) + { + if (components != null) + { + components.Dispose(); + } + } + base.Dispose( disposing ); + } + + #region Windows Form Designer generated code + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.m3uDirectoryTextBox = new System.Windows.Forms.TextBox(); + this.outputTextBox = new System.Windows.Forms.TextBox(); + this.selectDirectoryButton = new System.Windows.Forms.Button(); + this.m3uMakeButton = new System.Windows.Forms.Button(); + this.SuspendLayout(); + // + // m3uDirectoryTextBox + // + this.m3uDirectoryTextBox.Enabled = false; + this.m3uDirectoryTextBox.Location = new System.Drawing.Point(8, 8); + this.m3uDirectoryTextBox.Name = "m3uDirectoryTextBox"; + this.m3uDirectoryTextBox.Size = new System.Drawing.Size(176, 20); + this.m3uDirectoryTextBox.TabIndex = 0; + this.m3uDirectoryTextBox.Text = ""; + // + // outputTextBox + // + this.outputTextBox.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.outputTextBox.Location = new System.Drawing.Point(8, 88); + this.outputTextBox.Multiline = true; + this.outputTextBox.Name = "outputTextBox"; + this.outputTextBox.ScrollBars = System.Windows.Forms.ScrollBars.Vertical; + this.outputTextBox.Size = new System.Drawing.Size(272, 168); + this.outputTextBox.TabIndex = 1; + this.outputTextBox.Text = ""; + // + // selectDirectoryButton + // + this.selectDirectoryButton.Location = new System.Drawing.Point(208, 8); + this.selectDirectoryButton.Name = "selectDirectoryButton"; + this.selectDirectoryButton.TabIndex = 2; + this.selectDirectoryButton.Text = "Select Dir..."; + this.selectDirectoryButton.Click += new System.EventHandler(this.selectDirectoryButton_Click); + // + // m3uMakeButton + // + this.m3uMakeButton.Enabled = false; + this.m3uMakeButton.Location = new System.Drawing.Point(208, 40); + this.m3uMakeButton.Name = "m3uMakeButton"; + this.m3uMakeButton.TabIndex = 3; + this.m3uMakeButton.Text = "Make M3u"; + this.m3uMakeButton.Click += new System.EventHandler(this.m3uMakeButton_Click); + // + // MakeSubdir + // + this.AutoScaleBaseSize = new System.Drawing.Size(5, 13); + this.ClientSize = new System.Drawing.Size(292, 266); + this.Controls.Add(this.m3uMakeButton); + this.Controls.Add(this.selectDirectoryButton); + this.Controls.Add(this.outputTextBox); + this.Controls.Add(this.m3uDirectoryTextBox); + this.Name = "MakeSubdir"; + this.Text = "M3u Make"; + this.ResumeLayout(false); + + } + #endregion + + /// + /// The main entry point for the application. + /// + [STAThread] + static void Main() + { + Application.Run(new M3uMakeByDirectory()); + } + + private void selectDirectoryButton_Click(object sender, EventArgs e) + { + FolderBrowserDialog fbd = new FolderBrowserDialog(); + DialogResult dialogResult = fbd.ShowDialog(this); + + if (dialogResult == DialogResult.OK) + { + m3uDirectoryTextBox.Text = fbd.SelectedPath; + } + UpdateRunButton(); + + } + + private void UpdateRunButton() + { + if (m3uDirectoryTextBox.Text != null && m3uDirectoryTextBox.Text!="" && Directory.Exists(m3uDirectoryTextBox.Text)) + { + m3uMakeButton.Enabled = true; + } + else + m3uMakeButton.Enabled = false; + } + + private void CreateM3uThread() + { + CreateM3uRecursive(new DirectoryInfo(m3uDirectoryTextBox.Text)); + } + + private void CreateM3uRecursive(DirectoryInfo info) + { + M3uCreate cm3u = new M3uCreate(info); + cm3u.Create(); + outputTextBox.AppendText("Created " + cm3u.OutputFilename + Environment.NewLine); + foreach (DirectoryInfo subDir in info.GetDirectories()) + { + CreateM3uRecursive(subDir); + } + } + + private void m3uMakeButton_Click(object sender, EventArgs e) + { + Thread t = new Thread(new ThreadStart(CreateM3uThread)); + t.Start(); + } + } +} diff --git a/Backup/M3uMakeByDirectory.resx b/Backup/M3uMakeByDirectory.resx new file mode 100644 index 0000000..3108915 --- /dev/null +++ b/Backup/M3uMakeByDirectory.resx @@ -0,0 +1,166 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 1.3 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Private + + + False + + + Private + + + Private + + + False + + + Private + + + False + + + Private + + + Private + + + False + + + Private + + + Private + + + False + + + (Default) + + + False + + + MakeSubdir + + + False + + + 8, 8 + + + True + + + 80 + + + True + + + Private + + \ No newline at end of file diff --git a/Backup/M3uToAac.Designer.cs b/Backup/M3uToAac.Designer.cs new file mode 100644 index 0000000..a8da24d --- /dev/null +++ b/Backup/M3uToAac.Designer.cs @@ -0,0 +1,46 @@ +namespace m3uTool +{ + partial class M3uToAac + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.SuspendLayout(); + // + // M3uToAac + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(434, 138); + this.Name = "M3uToAac"; + this.Text = "M3uToAac"; + this.ResumeLayout(false); + + } + + #endregion + } +} \ No newline at end of file diff --git a/Backup/M3uToAac.cs b/Backup/M3uToAac.cs new file mode 100644 index 0000000..dfd8d73 --- /dev/null +++ b/Backup/M3uToAac.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Data; +using System.Drawing; +using System.Text; +using System.Windows.Forms; + +namespace m3uTool +{ + public partial class M3uToAac : Form + { + public class Mp3ToAacConversionOptions + { + string[] mInputFilenames; + string[] mOutputFilenames; + + + } + public M3uToAac() + { + InitializeComponent(); + } + } +} \ No newline at end of file diff --git a/Backup/M3uToAac.resx b/Backup/M3uToAac.resx new file mode 100644 index 0000000..ff31a6d --- /dev/null +++ b/Backup/M3uToAac.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/Backup/Mp3Encoder.cs b/Backup/Mp3Encoder.cs new file mode 100644 index 0000000..1c55038 --- /dev/null +++ b/Backup/Mp3Encoder.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; + +namespace m3uTool +{ + public class Mp3Encoder : ProcessStreamWrapper + { + public Mp3Encoder(Mp3EncodingOptions encOpt) + : base(encOpt) + { + } + + public Mp3Encoder(Mp3EncodingOptions encOpt, Stream outputStream) + : base(encOpt, outputStream) + { + } + + protected override string ProcessExecutableFilename + { + get { return @"c:\console\audio\lame.exe"; } + } + } +} diff --git a/Backup/Mp3EncodingOptions.cs b/Backup/Mp3EncodingOptions.cs new file mode 100644 index 0000000..8910088 --- /dev/null +++ b/Backup/Mp3EncodingOptions.cs @@ -0,0 +1,442 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace m3uTool +{ + /// + /// LAME 32bits version 3.97 (beta 1, Oct 16 2005) (http://www.mp3dev.org/) + /// + /// usage: lame [options] [outfile] + /// + /// and/or can be "-", which means stdin/stdout. + /// + /// RECOMMENDED: + /// lame -V2 input.wav output.mp3 + /// + /// OPTIONS: + /// Input options: + /// -r input is raw pcm + /// -x force byte-swapping of input + /// -s sfreq sampling frequency of input file (kHz) - default 44.1 kHz + /// --bitwidth w input bit width is w (default 16) + /// --scale scale input (multiply PCM data) by + /// --scale-l scale channel 0 (left) input (multiply PCM data) by + /// --scale-r scale channel 1 (right) input (multiply PCM data) by + /// --mp1input input file is a MPEG Layer I file + /// --mp2input input file is a MPEG Layer II file + /// --mp3input input file is a MPEG Layer III file + /// --nogap <...> + /// gapless encoding for a set of contiguous files + /// --nogapout + /// output dir for gapless encoding (must precede --nogap) + /// --nogaptags allow the use of VBR tags in gapless encoding + /// + /// Operational options: + /// -a downmix from stereo to mono file for mono encoding + /// -m (j)oint, (s)imple, (f)orce, (d)dual-mono, (m)ono + /// default is (j) or (s) depending on bitrate + /// joint = joins the best possible of MS and LR stereo + /// simple = force LR stereo on all frames + /// force = force MS stereo on all frames. + /// --preset type type must be "medium", "standard", "extreme", "insane", + /// or a value for an average desired bitrate and depending + /// on the value specified, appropriate quality settings will + /// be used. + /// "--preset help" gives more info on these + /// --comp choose bitrate to achive a compression ratio of + /// --replaygain-fast compute RG fast but slightly inaccurately (default) + /// --replaygain-accurate compute RG more accurately and find the peak sample + /// --noreplaygain disable ReplayGain analysis + /// --clipdetect enable --replaygain-accurate and print a message whether + /// clipping occurs and how far the waveform is from full scale + /// --freeformat produce a free format bitstream + /// --decode input=mp3 file, output=wav + /// -t disable writing wav header when using --decode + /// + /// + /// Verbosity: + /// --disptime print progress report every arg seconds + /// -S don't print progress report, VBR histograms + /// --nohist disable VBR histogram display + /// --silent don't print anything on screen + /// --quiet don't print anything on screen + /// --brief print more useful information + /// --verbose print a lot of useful information + /// + /// Noise shaping & psycho acoustic algorithms: + /// -q = 0...9. Default -q 5 + /// -q 0: Highest quality, very slow + /// -q 9: Poor quality, but fast + /// -h Same as -q 2. Recommended. + /// -f Same as -q 7. Fast, ok quality + /// + /// + /// CBR (constant bitrate, the default) options: + /// -b set the bitrate in kbps, default 128 kbps + /// --cbr enforce use of constant bitrate + /// + /// ABR options: + /// --abr specify average bitrate desired (instead of quality) + /// + /// VBR options: + /// -v use variable bitrate (VBR) (--vbr-old) + /// --vbr-old use old variable bitrate (VBR) routine + /// --vbr-new use new variable bitrate (VBR) routine + /// -V n quality setting for VBR. default n=4 + /// 0=high quality,bigger files. 9=smaller files + /// -b specify minimum allowed bitrate, default 32 kbps + /// -B specify maximum allowed bitrate, default 320 kbps + /// -F strictly enforce the -b option, for use with players that + /// do not support low bitrate mp3 + /// -t disable writing LAME Tag + /// -T enable and force writing LAME Tag + /// + /// + /// PSY related: + /// --short use short blocks when appropriate + /// --noshort do not use short blocks + /// --allshort use only short blocks + /// --notemp disable temporal masking effect + /// --nssafejoint M/S switching criterion + /// --nsmsfix M/S switching tuning [effective 0-3.5] + /// --interch x adjust inter-channel masking ratio + /// --ns-bass x adjust masking for sfbs 0 - 6 (long) 0 - 5 (short) + /// --ns-alto x adjust masking for sfbs 7 - 13 (long) 6 - 10 (short) + /// --ns-treble x adjust masking for sfbs 14 - 21 (long) 11 - 12 (short) + /// --ns-sfb21 x change ns-treble by x dB for sfb21 + /// + /// + /// experimental switches: + /// -X n[,m] selects between different noise measurements + /// n for long block, m for short. if m is omitted, m = n + /// -Y lets LAME ignore noise in sfb21, like in CBR + /// -Z [n] currently no effects + /// + /// + /// MP3 header/stream options: + /// -e de-emphasis n/5/c (obsolete) + /// -c mark as copyright + /// -o mark as non-original + /// -p error protection. adds 16 bit checksum to every frame + /// (the checksum is computed correctly) + /// --nores disable the bit reservoir + /// --strictly-enforce-ISO comply as much as possible to ISO MPEG spec + /// + /// Filter options: + /// -k keep ALL frequencies (disables all filters), + /// Can cause ringing and twinkling + /// --lowpass frequency(kHz), lowpass filter cutoff above freq + /// --lowpass-width frequency(kHz) - default 15% of lowpass freq + /// --highpass frequency(kHz), highpass filter cutoff below freq + /// --highpass-width frequency(kHz) - default 15% of highpass freq + /// --resample sampling frequency of output file(kHz)- default=automatic + /// + /// + /// ID3 tag options: + /// --tt audio/song title (max 30 chars for version 1 tag) + /// --ta <artist> audio/song artist (max 30 chars for version 1 tag) + /// --tl <album> audio/song album (max 30 chars for version 1 tag) + /// --ty <year> audio/song year of issue (1 to 9999) + /// --tc <comment> user-defined text (max 30 chars for v1 tag, 28 for v1.1) + /// --tn <track> audio/song track number (1 to 255, creates v1.1 tag) + /// --tg <genre> audio/song genre (name or number in list) + /// --add-id3v2 force addition of version 2 tag + /// --id3v1-only add only a version 1 tag + /// --id3v2-only add only a version 2 tag + /// --space-id3v1 pad version 1 tag with spaces instead of nulls + /// --pad-id3v2 pad version 2 tag with extra 128 bytes + /// --genre-list print alphabetically sorted ID3 genre list and exit + /// --ignore-tag-errors ignore errors in values passed for tags + /// + /// Note: A version 2 tag will NOT be added unless one of the input fields + /// won't fit in a version 1 tag (e.g. the title string is longer than 30 + /// characters), or the '--add-id3v2' or '--id3v2-only' options are used, + /// or output is redirected to stdout. + /// + /// + /// MS-Windows-specific options: + /// --priority <type> sets the process priority: + /// 0,1 = Low priority (IDLE_PRIORITY_CLASS) + /// 2 = normal priority (NORMAL_PRIORITY_CLASS, defau + /// lt) + /// 3,4 = High priority (HIGH_PRIORITY_CLASS)) + /// Note: Calling '--priority' without a parameter will select priority 0. + /// + /// + /// Platform specific: + /// --noasm <instructions> disable assembly optimizations for mmx/3dnow/sse + /// + /// + /// + /// MPEG-1 layer III sample frequencies (kHz): 32 48 44.1 + /// bitrates (kbps): 32 40 48 56 64 80 96 112 128 160 192 224 256 320 + /// + /// MPEG-2 layer III sample frequencies (kHz): 16 24 22.05 + /// bitrates (kbps): 8 16 24 32 40 48 56 64 80 96 112 128 144 160 + /// + /// MPEG-2.5 layer III sample frequencies (kHz): 8 12 11.025 + /// bitrates (kbps): 8 16 24 32 40 48 56 64 80 96 112 128 144 160 + /// </summary> + public class Mp3EncodingOptions : ProcessArguments + { + #region Private variables + /// <summary> + /// Input filename. Can be "-", which means stdin. + /// </summary> + private string mInfile = null; + + /// <summary> + /// Output filename. Can be "-", which means stdout. + /// </summary> + private string mOutfile = null; + + /// <summary> + /// input is raw pcm (-r) + /// </summary> + private bool mRawPCMInputMode = false; + + /// <summary> + /// force byte-swapping of input (-x) + /// </summary> + private bool mForceByteSwappingOfInput = false; + + /// <summary> + /// sampling frequency of input file (kHz) - default 44.1 kHz (-s sfreq) + /// </summary> + private SampleRateFrequency mRawPCMInputSampleSize = SampleRateFrequency.Hz_44100; + + /// <summary> + /// input bit width is w (default 16) (--bitwidth w) + /// </summary> + private int mRawPCMInputSampleRate = 16; + + /// <summary> + /// input file is a MPEG Layer III file (--mp3input) + /// </summary> + private bool mMp3Input = false; + + /// <summary> + /// downmix from stereo to mono file for mono encoding (-a) + /// </summary> + private bool mMonoDownmixing = false; + + /// <summary> + /// (j)oint, (s)imple, (f)orce, (d)dual-mono, (m)ono (-m) + /// </summary> + private ChannelMode mChannelMode = ChannelMode.JointStereo; + + /// <summary> + /// produce a free format bitstream (--freeformat) + /// </summary> + private bool mFreeFormat = false; + + /// <summary> + /// input=mp3 file, output=wav (--decode) + /// </summary> + private bool mDecodeWav = false; + + /// <summary> + /// disable writing wav header when using --decode (-t) + /// </summary> + private bool mDisableWavHeader = false; + + /// <summary> + /// Set the bitrate in kbps, default 128 kbps -b <bitrate> + /// </summary> + private BitRate mBitRate = BitRate.KBPS_0; + + #endregion + + #region Public properties + /// <summary> + /// Input filename. Can be "-", which means stdin. + /// </summary> + public string Infile + { + get { return mInfile; } + set { mInfile = value; } + } + + /// <summary> + /// Output filename. Can be "-", which means stdout. + /// </summary> + public string Outfile + { + get { return mOutfile; } + set { mOutfile = value; } + } + + /// <summary> + /// input is raw pcm (-r) + /// </summary> + public bool RawPCMInputMode + { + get { return mRawPCMInputMode; } + set + { + mRawPCMInputMode = value; + } + } + + /// <summary> + /// force byte-swapping of input (-x) + /// </summary> + public bool ForceByteSwappingOfInput + { + get { return mForceByteSwappingOfInput; } + set + { + mForceByteSwappingOfInput = value; + mRawPCMInputMode = true; + + } + } + + /// <summary> + /// sampling frequency of input file (kHz) - default 44.1 kHz (-s sfreq) + /// </summary> + public SampleRateFrequency RawPCMInputSampleSize + { + get { return mRawPCMInputSampleSize; } + set + { + mRawPCMInputSampleSize = value; + mRawPCMInputMode = true; + } + } + + /// <summary> + /// input bit width is w (default 16) (--bitwidth w) + /// </summary> + public int RawPCMInputSampleRate + { + get { return mRawPCMInputSampleRate; } + set + { + mRawPCMInputSampleRate = value; + mRawPCMInputMode = true; + } + } + + /// <summary> + /// input file is a MPEG Layer III file (--mp3input) + /// </summary> + public bool Mp3Input + { + get { return mMp3Input; } + set + { + mMp3Input = value; + if (mMp3Input) + mRawPCMInputMode = false; + } + } + + /// <summary> + /// downmix from stereo to mono file for mono encoding (-a) + /// </summary> + public bool MonoDownmixing + { + get { return mMonoDownmixing; } + set { mMonoDownmixing = value; } + } + + /// <summary> + /// (j)oint, (s)imple, (f)orce, (d)dual-mono, (m)ono (-m) + /// </summary> + public ChannelMode ChannelMode + { + get { return mChannelMode; } + set { mChannelMode = value; } + } + + /// <summary> + /// produce a free format bitstream (--freeformat) + /// </summary> + public bool Freeformat + { + get { return mFreeFormat; } + set { mFreeFormat = value; } + } + + /// <summary> + /// input=mp3 file, output=wav (--decode) + /// </summary> + public bool DecodeWav + { + get { return mDecodeWav; } + set { mDecodeWav = value; } + } + + /// <summary> + /// disable writing wav header when using --decode (-t) + /// </summary> + public bool DisableWavHeader + { + get { return mDisableWavHeader; } + set + { + mDisableWavHeader = value; + if (mDisableWavHeader) + DecodeWav = true; + } + } + + public BitRate BitRate + { + get { return mBitRate; } + set + { + mBitRate = value; + } + } + #endregion + + /// <summary> + /// Gets a string representing the command line arguments specified by the properties in the object. + /// </summary> + /// <returns></returns> + public override string GetCommandLineArguments() + { + StringBuilder stringBuilder = new StringBuilder(); + if (mRawPCMInputMode) + { + stringBuilder.Append(" -r"); + if (mForceByteSwappingOfInput) + stringBuilder.Append(" -x"); + if (mRawPCMInputSampleSize != SampleRateFrequency.Hz_44100) + stringBuilder.Append(" -s " + SampleRateFrequencyUtility.GetSampleRateFrequencyInt(mRawPCMInputSampleSize)); + if (mRawPCMInputSampleRate != 16) + stringBuilder.Append(" --bandwidth " + mRawPCMInputSampleRate); + } + if (mMp3Input) + stringBuilder.Append(" --mp3input"); + if (mMonoDownmixing) + stringBuilder.Append(" -a"); + if (mChannelMode != ChannelMode.JointStereo) + stringBuilder.Append(" -m " + ChannelModeUtility.GetChannelModeChar(mChannelMode)); + + if (mFreeFormat) + { + stringBuilder.Append(" --freeformat"); + stringBuilder.Append(" -b " + BitRateFrequencyUtility.GetBitRateInt(BitRate)); + } + + if (mDecodeWav) + { + stringBuilder.Append(" --decode"); + if (mDisableWavHeader) + stringBuilder.Append(" -t"); + } + + if (!string.IsNullOrEmpty(mInfile)) + stringBuilder.Append(" " + GetQuotedCommandLineArgument(mInfile)); + if (!string.IsNullOrEmpty(mOutfile)) + stringBuilder.Append(" " + (mOutfile)); + + + return stringBuilder.ToString(); + } + } +} diff --git a/Backup/Mp3FileProperties.cs b/Backup/Mp3FileProperties.cs new file mode 100644 index 0000000..a625679 --- /dev/null +++ b/Backup/Mp3FileProperties.cs @@ -0,0 +1,1211 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Reflection; +using System.Text; +using System.Text.RegularExpressions; +using Clifton.Collections.Generic; + +namespace m3uTool +{ + /// <summary> + /// Indicates that a problem existed when parsing an mp3 file + /// </summary> + public class Mp3FormatException : Exception + { + string mFilename; + + string mFormatExceptionString; + + public string Filename + { + get { return mFilename; } + } + + public string FormatExceptionString + { + get { return mFormatExceptionString; } + } + + /// <summary> + /// Constructor. + /// </summary> + /// <param name="filename"></param> + /// <param name="formatExceptionString"></param> + public Mp3FormatException(string filename, string formatExceptionString) + { + mFilename = filename; + mFormatExceptionString = formatExceptionString; + } + } + + /// <summary> + /// Tool for accessing the SampleRateFreqency Enum + /// </summary> + public class ChannelModeUtility + { + /// <summary> + /// Converts a ChannelMode into an Int value + /// </summary> + /// <param name="s"></param> + /// <returns></returns> + public static int GetChannelModeInt(ChannelMode s) + { + switch (s) + { + case ChannelMode.Stereo: + return 2; + case ChannelMode.JointStereo: + return 2; + case ChannelMode.DualChannel: + return 2; + case ChannelMode.SingleChannel: + return 1; + default: + return 2; + } + } + + /// <summary> + /// (j)oint, (s)imple, (f)orce, (d)dual-mono, (m)ono (-m) + /// </summary> + /// <param name="s"></param> + public static char GetChannelModeChar(ChannelMode s) + { + switch (s) + { + case ChannelMode.Stereo: + return 's'; + case ChannelMode.JointStereo: + return 'j'; + case ChannelMode.DualChannel: + return 'd'; + case ChannelMode.SingleChannel: + return 'm'; + default: + return 'j'; + } + } + } + + /// <summary> + /// The channel mode an mp3 is encoded in + /// </summary> + public enum ChannelMode + { + /// <summary> + /// + /// </summary> + Stereo, + + /// <summary> + /// Merge a given frequency range of multiple sound channels together so that the + /// resulting encoding will perceive the sound information of that range not as a + /// bundle of separate channels but as one homogenous lump + /// </summary> + JointStereo, + + /// <summary> + /// made of two independant mono channel. Each one uses exactly half the bitrate of the file + /// </summary> + DualChannel, + + /// <summary> + /// Mono + /// </summary> + SingleChannel + } + + /// <summary> + /// Tool for accessing the SampleRateFreqency Enum + /// </summary> + public class SampleRateFrequencyUtility + { + /// <summary> + /// Converts a SampleRateFrequency into an Int value + /// </summary> + /// <param name="s"></param> + /// <returns></returns> + public static int GetSampleRateFrequencyInt(SampleRateFrequency s) + { + return Convert.ToInt32(s.ToString().Substring(s.ToString().IndexOf("_") + 1)); + } + } + + /// <summary> + /// Sample rate frequency of an audio file + /// </summary> + public enum SampleRateFrequency + { + /// <summary> + /// reserved + /// </summary> + Hz_0, + /// <summary> + /// 8000 + /// </summary> + Hz_8000, + /// <summary> + /// 11025 + /// </summary> + Hz_11025, + /// <summary> + /// 12000 + /// </summary> + Hz_12000, + /// <summary> + /// 16000 + /// </summary> + Hz_16000, + /// <summary> + /// 22050 + /// </summary> + Hz_22050, + /// <summary> + /// 24000 + /// </summary> + Hz_24000, + /// <summary> + /// 32000 + /// </summary> + Hz_32000, + /// <summary> + /// 41000 + /// </summary> + Hz_44100, + /// <summary> + /// 48000 + /// </summary> + Hz_48000 + } + + /// <summary> + /// Bitrates + /// 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256 and 320 + /// </summary> + public enum BitRate + { + /// <summary> + /// reserved + /// </summary> + KBPS_0, + /// <summary> + /// 32 + /// </summary> + KBPS_32, + /// <summary> + /// 40 + /// </summary> + KBPS_40, + /// <summary> + /// 48 + /// </summary> + KBPS_48, + /// <summary> + /// 48 + /// </summary> + KBPS_56, + /// <summary> + /// 64 + /// </summary> + KBPS_64, + /// <summary> + /// 80 + /// </summary> + KBPS_80, + /// <summary> + /// 96 + /// </summary> + KBPS_96, + /// <summary> + /// 112 + /// </summary> + KBPS_112, + /// <summary> + /// 128 + /// </summary> + KBPS_128, + /// <summary> + /// 160 + /// </summary> + KBPS_160, + /// <summary> + /// 192 + /// </summary> + KBPS_192, + /// <summary> + /// 224 + /// </summary> + KBPS_224, + /// <summary> + /// 256 + /// </summary> + KBPS_256, + /// <summary> + /// 320 + /// </summary> + KBPS_320, + } + + /// <summary> + /// Tool for accessing the BitRate Enum + /// </summary> + public class BitRateFrequencyUtility + { + /// <summary> + /// Converts a BitRate into an Int value + /// </summary> + /// <param name="b"></param> + /// <returns></returns> + public static int GetBitRateInt(BitRate b) + { + return Convert.ToInt32(b.ToString().Substring(b.ToString().IndexOf("_") + 1)); + } + /// <summary> + /// Converts a BitRate into an Int value + /// </summary> + /// <param name="b"></param> + /// <returns></returns> + public static int GetBitRateBitsInt(BitRate b) + { + return GetBitRateInt(b) * 1024; + } + } + + /// <summary> + /// Given an mp3 file, this extracts all the encoding details and id3 fields + /// </summary> + public class Mp3FileProperties + { + #region private variables + /// <summary> + /// path to the mp3 + /// </summary> + private string mMp3Path; + /// <summary> + /// mp3's filename, without path + /// </summary> + private string mMp3Filename; + + /// <summary> + /// The mp3 bit rate + /// </summary> + private int mBitRate; + /// <summary> + /// the file size, in bytes + /// </summary> + private long mFileSize; + /// <summary> + /// The sample rate frequency integer + /// </summary> + private int mSampleRateIntegerFrequency; + + /// <summary> + /// The sample rate frequency + /// </summary> + private SampleRateFrequency mSampleRateFrequency; + + /// <summary> + /// the channel mode + /// </summary> + private ChannelMode mChannelMode; + /// <summary> + /// the mp3 length in seconds + /// </summary> + private int mLengthInSeconds; + + + /// <summary> + /// True if the id3 tag exists in the mp3 + /// </summary> + private bool mId3TagExists; + /// <summary> + /// The title field + /// </summary> + private string mId3Title; + /// <summary> + /// The artist field + /// </summary> + public string mId3Artist; + /// <summary> + /// The album field + /// </summary> + private string mId3Album; + /// <summary> + /// The year field + /// </summary> + private string mId3Year; + /// <summary> + /// The comment field + /// </summary> + private string mId3Comment; + /// <summary> + /// The track number field + /// </summary> + private byte mId3TrackNumber; + /// <summary> + /// The genre field byte + /// </summary> + private byte mId3Genre; + /// <summary> + /// The genre field name + /// </summary> + private string mId3GenreName; + + // Private variables used in the process of reading in the MP3 files + private ulong bithdr; + private bool boolVBitRate; + private int intVFrames; + + #endregion + + #region Public Properties + /// <summary> + /// path to the mp3 + /// </summary> + public string Mp3Path + { + get { return mMp3Path; } + } + /// <summary> + /// mp3's filename, without path + /// </summary> + public string Mp3Filename + { + get { return mMp3Filename; } + } + /// <summary> + /// mp3's path and filename + /// </summary> + public string Mp3PathAndFilename + { + get + { + return mMp3Path + (mMp3Path.EndsWith("\\") ? "" : "\\") + mMp3Filename; + } + } + + /// <summary> + /// The mp3 bit rate + /// </summary> + public int BitRate + { + get { return mBitRate; } + } + + /// <summary> + /// the file size, in bytes + /// </summary> + public long FileSize + { + get { return mFileSize; } + } + + /// <summary> + /// The sample rate frequency integer + /// </summary> + public int SampleRateIntegerFrequency + { + get { return mSampleRateIntegerFrequency; } + } + + /// <summary> + /// The sample rate frequency + /// </summary> + public SampleRateFrequency SampleRateFrequency + { + get { return mSampleRateFrequency; } + } + + /// <summary> + /// the channel mode + /// </summary> + public ChannelMode ChannelMode + { + get { return mChannelMode; } + } + + /// <summary> + /// the mp3 length in seconds + /// </summary> + public int LengthInSeconds + { + get { return mLengthInSeconds; } + } + + /// <summary> + /// the length formatted + /// </summary> + public string LengthFormatted + { + get + { + // Complete number of seconds + int s = GetLengthInSeconds(); + + // Seconds to display + int ss = s % 60; + + // Complete number of minutes + int m = (s - ss) / 60; + + // Minutes to display + int mm = m % 60; + + // Complete number of hours + int h = (m - mm) / 60; + + // Make "hh:mm:ss" + return h.ToString("D2") + ":" + mm.ToString("D2") + ":" + ss.ToString("D2"); + } + } + + /// <summary> + /// the mp3 length in timespan + /// </summary> + public TimeSpan FileLength + { + get { return new TimeSpan(0, 0, 0, 0, mLengthInSeconds); } + } + + /// <summary> + /// True if the id3 tag exists in the mp3 + /// </summary> + public bool Id3TagExists + { + get { return mId3TagExists; } + } + + /// <summary> + /// The title field + /// </summary> + public string Id3Title + { + get { return mId3Title; } + } + + /// <summary> + /// The artist field + /// </summary> + public string Id3Artist + { + get { return mId3Artist; } + } + + /// <summary> + /// The album field + /// </summary> + public string Id3Album + { + get { return mId3Album; } + } + + /// <summary> + /// The year field + /// </summary> + public string Id3Year + { + get { return mId3Year; } + } + + /// <summary> + /// The comment field + /// </summary> + public string Id3Comment + { + get { return mId3Comment; } + } + + /// <summary> + /// The track number field + /// </summary> + public byte Id3TrackNumber + { + get { return mId3TrackNumber; } + } + + /// <summary> + /// The genre field byte + /// </summary> + public byte Id3Genre + { + get { return mId3Genre; } + } + + /// <summary> + /// The genre field name + /// </summary> + public string Id3GenreName + { + get { return mId3GenreName; } + } + + #endregion + + #region Private Readonly Static Lookup + /// <summary> + /// list of genres defined by id3 1.1 + /// </summary> + private readonly static string[] mGenres = + { + "Blues","Classic Rock","Country","Dance","Disco","Funk","Grunge","Hip-Hop","Jazz","Metal", + "New Age","Oldies","Other","Pop","R&B","Rap","Reggae","Rock","Techno","Industrial", + "Alternative","Ska","Death Metal","Pranks","Soundtrack","Euro-Techno","Ambient","Trip-Hop", + "Vocal","Jazz+Funk","Fusion","Trance","Classical","Instrumental","Acid","House", + "Game","Sound Clip","Gospel","Noise","Alternative Rock","Bass","Soul","Punk","Space", + "Meditative","Instrumental Pop","Instrumental Rock","Ethnic","Gothic", + "Darkwave","Techno-Industrial","Electronic","Pop-Folk","Eurodance","Dream", + "Southern Rock","Comedy","Cult","Gangsta","Top 40","Christian Rap","Pop/Funk","Jungle", + "Native American","Cabaret","New Wave","Psychadelic","Rave","Showtunes","Trailer","Lo-Fi", + "Tribal","Acid Punk","Acid Jazz","Polka","Retro","Musical","Rock & Roll","Hard Rock","Folk", + "Folk/Rock","National Folk","Swing","Fast-Fusion","Bebob","Latin","Revival","Celtic","Bluegrass", + "Avantgarde","Gothic Rock","Progressive Rock","Psychedelic Rock","Symphonic Rock","Slow Rock", + "Big Band","Chorus","Easy Listening","Acoustic","Humour","Speech","Chanson","Opera","Chamber Music", + "Sonata","Symphony","Booty Bass","Primus","Porn Groove","Satire","Slow Jam","Club", + "Tango","Samba","Folklore","Ballad","Power Ballad","Rhytmic Soul","Freestyle","Duet", + "Punk Rock","Drum Solo","Acapella","Euro-House","Dance Hall","Goa","Drum & Bass","Club-House", + "Hardcore","Terror","Indie","BritPop","Negerpunk","Polsk Punk","Beat","Christian Gangsta Rap", + "Heavy Metal","Black Metal","Crossover","Contemporary Christian", + "Christian Rock","Merengue","Salsa","Trash Metal","Anime","JPop","SynthPop" + }; + + /// <summary> + /// bit rates available to mp3 + /// </summary> + private readonly static int[, ,] mBitrateTable = { + { // MPEG 2 & 2.5 + {0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96,112,128,144,160,0}, // Layer III + {0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96,112,128,144,160,0}, // Layer II + {0, 32, 48, 56, 64, 80, 96,112,128,144,160,176,192,224,256,0} // Layer I + }, + { // MPEG 1 + {0, 32, 40, 48, 56, 64, 80, 96,112,128,160,192,224,256,320,0}, // Layer III + {0, 32, 48, 56, 64, 80, 96,112,128,160,192,224,256,320,384,0}, // Layer II + {0, 32, 64, 96,128,160,192,224,256,288,320,352,384,416,448,0} // Layer I + } + }; + + /// <summary> + /// integer sample rates avaiable for mp3 + /// </summary> + private readonly static int[,] mSampleFrequencyIntegerTable = { + {32000, 16000, 8000}, // MPEG 2.5 + { 0, 0, 0}, // reserved + {22050, 24000, 16000}, // MPEG 2 + {44100, 48000, 32000} // MPEG 1 + }; + + /// <summary> + /// Lookup for + /// </summary> + private readonly static SampleRateFrequency[,] mSampleFrequencyTable = { + { + SampleRateFrequency.Hz_32000, SampleRateFrequency.Hz_16000, SampleRateFrequency.Hz_8000 // MPEG 2.5 + }, + { + m3uTool.SampleRateFrequency.Hz_0, m3uTool.SampleRateFrequency.Hz_0, SampleRateFrequency.Hz_0 // reserved + }, + { + SampleRateFrequency.Hz_22050, SampleRateFrequency.Hz_24000, SampleRateFrequency.Hz_16000 // MPEG 2 + }, + { + SampleRateFrequency.Hz_44100, SampleRateFrequency.Hz_48000, SampleRateFrequency.Hz_32000 // MPEG 1 + } + }; + + #endregion + + #region Constructor + + // Required struct constructor + public Mp3FileProperties(string name) + { + FileInfo fFileInfo = new FileInfo(name); + mMp3Path = fFileInfo.DirectoryName; + mMp3Filename = fFileInfo.Name; + + ReadMP3Information(); + ReadMP3Tag(); + } + + #endregion + + private Mp3FileProperties() + { + } + + #region private methods + + private void ReadMP3Information() + { + FileStream fs = new FileStream(Mp3PathAndFilename, FileMode.Open, FileAccess.Read); + // Set the filename not including the path information + char[] chrSeparators = new char[] { '\\', '/' }; + string[] strSeparator = Mp3PathAndFilename.Split(chrSeparators); + int intUpper = strSeparator.GetUpperBound(0); + //mMp3PathAndFilename = strSeparator[intUpper]; + + //// Replace ' with '' for the SQL INSERT statement + //mMp3PathAndFilename = mMp3PathAndFilename.Replace("'", "''"); + + // Set the file size + mFileSize = fs.Length; + + byte[] bytHeader = new byte[4]; + byte[] bytVBitRate = new byte[12]; + int intPos = 0; + + // Keep reading 4 bytes from the header until we know for sure that in + // fact it's an MP3 + do + { + fs.Position = intPos; + fs.Read(bytHeader, 0, 4); + intPos++; + LoadMP3Header(bytHeader); + } + while (!IsValidHeader() && (fs.Position != fs.Length)); + + // If the current file stream position is equal to the length, + // that means that we've read the entire file and it's not a valid MP3 file + if (fs.Position == fs.Length) + throw new Mp3FormatException(Mp3PathAndFilename, "Invalid mp3 file"); + + intPos += 3; + + if (getVersionIndex() == 3) // MPEG Version 1 + { + if (getModeIndex() == 3) // Single Channel + { + intPos += 17; + } + else + { + intPos += 32; + } + } + else // MPEG Version 2.0 or 2.5 + { + if (getModeIndex() == 3) // Single Channel + { + intPos += 9; + } + else + { + intPos += 17; + } + } + + // Check to see if the MP3 has a variable bitrate + fs.Position = intPos; + fs.Read(bytVBitRate, 0, 12); + boolVBitRate = LoadVBRHeader(bytVBitRate); + + // Once the file's read in, then assign the properties of the file to the public variables + mBitRate = GetBitrate(); + mSampleRateIntegerFrequency = GetSampleRateFrequencyInt(); + mSampleRateFrequency = GetSampleRateFrequency(); + mChannelMode = GetChannelMode(); + mLengthInSeconds = GetLengthInSeconds(); + fs.Close(); + } + + private void LoadMP3Header(byte[] c) + { + // this thing is quite interesting, it works like the following + // c[0] = 00000011 + // c[1] = 00001100 + // c[2] = 00110000 + // c[3] = 11000000 + // the operator << means that we'll move the bits in that direction + // 00000011 << 24 = 00000011000000000000000000000000 + // 00001100 << 16 = 000011000000000000000000 + // 00110000 << 24 = 0011000000000000 + // 11000000 = 11000000 + // +_________________________________ + // 00000011000011000011000011000000 + bithdr = (ulong)(((c[0] & 255) << 24) | ((c[1] & 255) << 16) | ((c[2] & 255) << 8) | ((c[3] & 255))); + } + + private bool LoadVBRHeader(byte[] inputheader) + { + // If it's a variable bitrate MP3, the first 4 bytes will read 'Xing' + // since they're the ones who added variable bitrate-edness to MP3s + if (inputheader[0] == 88 && inputheader[1] == 105 && + inputheader[2] == 110 && inputheader[3] == 103) + { + int flags = (((inputheader[4] & 255) << 24) | ((inputheader[5] & 255) << 16) | ((inputheader[6] & 255) << 8) | ((inputheader[7] & 255))); + if ((flags & 0x0001) == 1) + { + intVFrames = (((inputheader[8] & 255) << 24) | ((inputheader[9] & 255) << 16) | ((inputheader[10] & 255) << 8) | ((inputheader[11] & 255))); + return true; + } + else + { + intVFrames = -1; + return true; + } + } + return false; + } + + private bool IsValidHeader() + { + return (((getFrameSync() & 2047) == 2047) && + ((getVersionIndex() & 3) != 1) && + ((getLayerIndex() & 3) != 0) && + ((getBitrateIndex() & 15) != 0) && + ((getBitrateIndex() & 15) != 15) && + ((getFrequencyIndex() & 3) != 3) && + ((getEmphasisIndex() & 3) != 2)); + } + + private int getFrameSync() + { + return (int)((bithdr >> 21) & 2047); + } + + private int getVersionIndex() + { + return (int)((bithdr >> 19) & 3); + } + + private int getLayerIndex() + { + return (int)((bithdr >> 17) & 3); + } + + //private int getProtectionBit() + //{ + // return (int)((bithdr>>16) & 1); + //} + + private int getBitrateIndex() + { + return (int)((bithdr >> 12) & 15); + } + + private int getFrequencyIndex() + { + return (int)((bithdr >> 10) & 3); + } + + //private int getPaddingBit() + //{ + // return (int)((bithdr>>9) & 1); + //} + + //private int getPrivateBit() + //{ + // return (int)((bithdr>>8) & 1); + //} + + private int getModeIndex() + { + return (int)((bithdr >> 6) & 3); + } + + //private int getModeExtIndex() + //{ + // return (int)((bithdr>>4) & 3); + //} + + //private int getCoprightBit() + //{ + // return (int)((bithdr>>3) & 1); + //} + + //private int getOrginalBit() + //{ + // return (int)((bithdr>>2) & 1); + //} + + private int getEmphasisIndex() + { + return (int)(bithdr & 3); + } + + //private double getVersion() + //{ + // double[] table = {2.5, 0.0, 2.0, 1.0}; + // return table[getVersionIndex()]; + //} + + //private int getLayer() + //{ + // return (int)(4 - getLayerIndex()); + //} + + private int GetBitrate() + { + // If the file has a variable bitrate, then we return an integer average bitrate, + // otherwise, we use a lookup table to return the bitrate + int layerIndex = getLayerIndex(); + if (!boolVBitRate) + { + try + { + int versionIndex = getVersionIndex(); + int bitrateIndex = getBitrateIndex(); + return mBitrateTable[versionIndex & 1, layerIndex - 1, bitrateIndex]; + } + catch(Exception) // must have been variable afterall + { + boolVBitRate = true; + } + + } + + double medFrameSize = (double)mFileSize / (double)getNumberOfFrames(); + return (int)((medFrameSize * GetSampleRateFrequencyInt()) / (1000.0 * ((layerIndex == 3) ? 12.0 : 144.0))); + + } + + private int GetSampleRateFrequencyInt() + { + return mSampleFrequencyIntegerTable[getVersionIndex(), getFrequencyIndex()]; + } + + private SampleRateFrequency GetSampleRateFrequency() + { + return mSampleFrequencyTable[getVersionIndex(), getFrequencyIndex()]; + } + + private ChannelMode GetChannelMode() + { + switch (getModeIndex()) + { + default: + return ChannelMode.Stereo; + case 1: + return ChannelMode.JointStereo; + case 2: + return ChannelMode.DualChannel; + case 3: + return m3uTool.ChannelMode.SingleChannel; + } + } + + private int GetLengthInSeconds() + { + // "intKilBitFileSize" made by dividing by 1000 in order to match the "Kilobits/second" + int intKiloBitFileSize = ((8 * (int)mFileSize) / 1000); + return (intKiloBitFileSize / GetBitrate()); + } + + private int getNumberOfFrames() + { + // Again, the number of MPEG frames is dependant on whether it's a variable bitrate MP3 or not + if (!boolVBitRate) + { + double medFrameSize = (((getLayerIndex() == 3) ? 12 : 144) * ((1000.0 * GetBitrate()) / GetSampleRateFrequencyInt())); + return (int)(mFileSize / medFrameSize); + } + else + return intVFrames; + } + + private static string TruncateStringAtNull(string sourceString) + { + ArrayList targetCharList = new ArrayList(sourceString.Length); + for (int i = 0; i < sourceString.Length && sourceString[i] != 0; i++) + { + // Debug.WriteLine("c : " + (int)sourceString[i] + " " + sourceString[i]); + + targetCharList.Add(sourceString[i] < 32 ? ' ' : sourceString[i]); + } + return new string((char[]) targetCharList.ToArray(typeof (char))); // Regex.Replace(new string((char [])targetCharList.ToArray(typeof (char))), @"\W*", ""); + } + + private void ReadMP3Tag() + { + // Read the 128 byte ID3 tag into a byte array + FileStream oFileStream; + oFileStream = new FileStream(Mp3PathAndFilename, FileMode.Open); + byte[] bBuffer = new byte[128]; + oFileStream.Seek(-128, SeekOrigin.End); + oFileStream.Read(bBuffer, 0, 128); + oFileStream.Close(); + + // Convert the Byte Array to a String + Encoding instEncoding = new ASCIIEncoding(); // NB: Encoding is an Abstract class + string id3Tag = instEncoding.GetString(bBuffer); + + // If there is an attched ID3 v1.x TAG then read it + if (id3Tag.Substring(0, 3) == "TAG") + { + mId3Title = TruncateStringAtNull(id3Tag.Substring(3, 30)); + mId3Artist = TruncateStringAtNull(id3Tag.Substring(33, 30)); + mId3Album = TruncateStringAtNull(id3Tag.Substring(63, 30)); + mId3Year = TruncateStringAtNull(id3Tag.Substring(93, 4)); + mId3Comment = TruncateStringAtNull(id3Tag.Substring(97, 28)); + + // Get the track number if TAG conforms to ID3 v1.1 + if (id3Tag[125] == 0) + mId3TrackNumber = bBuffer[126]; + else + mId3TrackNumber = 0; + mId3Genre = bBuffer[127]; + if (mGenres.Length > mId3Genre) + mId3GenreName = mGenres[mId3Genre]; + mId3TagExists = true; + // ********* IF USED IN ANGER: ENSURE to test for non-numeric year + } + else + { + // ID3 Tag not found so create an empty TAG in case the user saces later + mId3Title = ""; + mId3Artist = ""; + mId3Album = ""; + mId3Year = ""; + mId3Comment = ""; + mId3TrackNumber = 0; + mId3Genre = 0; + mId3GenreName = ""; + mId3TagExists = false; + } + } + + private static void UpdateMP3ID3Tag(ref Mp3FileProperties paramMP3) + { + // Trim any whitespace + paramMP3.mId3Title = paramMP3.mId3Title.Trim(); + paramMP3.mId3Artist = paramMP3.mId3Artist.Trim(); + paramMP3.mId3Album = paramMP3.mId3Album.Trim(); + paramMP3.mId3Year = paramMP3.mId3Year.Trim(); + paramMP3.mId3Comment = paramMP3.mId3Comment.Trim(); + + // Ensure all properties are correct size + if (paramMP3.mId3Title.Length > 30) paramMP3.mId3Title = paramMP3.mId3Title.Substring(0, 30); + if (paramMP3.mId3Artist.Length > 30) paramMP3.mId3Artist = paramMP3.mId3Artist.Substring(0, 30); + if (paramMP3.mId3Album.Length > 30) paramMP3.mId3Album = paramMP3.mId3Album.Substring(0, 30); + if (paramMP3.mId3Year.Length > 4) paramMP3.mId3Year = paramMP3.mId3Year.Substring(0, 4); + if (paramMP3.mId3Comment.Length > 28) paramMP3.mId3Comment = paramMP3.mId3Comment.Substring(0, 28); + + // Build a new ID3 Tag (128 Bytes) + byte[] tagByteArray = new byte[128]; + for (int i = 0; i < tagByteArray.Length; i++) + tagByteArray[i] = 0; // Initialise array to nulls + + // Convert the Byte Array to a String + Encoding instEncoding = new ASCIIEncoding(); // NB: Encoding is an Abstract class // ************ To DO: Make a shared instance of ASCIIEncoding so we don't keep creating/destroying it + // Copy "TAG" to Array + byte[] workingByteArray = instEncoding.GetBytes("TAG"); + Array.Copy(workingByteArray, 0, tagByteArray, 0, workingByteArray.Length); + // Copy Title to Array + workingByteArray = instEncoding.GetBytes(paramMP3.mId3Title); + Array.Copy(workingByteArray, 0, tagByteArray, 3, workingByteArray.Length); + // Copy Artist to Array + workingByteArray = instEncoding.GetBytes(paramMP3.mId3Artist); + Array.Copy(workingByteArray, 0, tagByteArray, 33, workingByteArray.Length); + // Copy Album to Array + workingByteArray = instEncoding.GetBytes(paramMP3.mId3Album); + Array.Copy(workingByteArray, 0, tagByteArray, 63, workingByteArray.Length); + // Copy Year to Array + workingByteArray = instEncoding.GetBytes(paramMP3.mId3Year); + Array.Copy(workingByteArray, 0, tagByteArray, 93, workingByteArray.Length); + // Copy Comment to Array + workingByteArray = instEncoding.GetBytes(paramMP3.mId3Comment); + Array.Copy(workingByteArray, 0, tagByteArray, 97, workingByteArray.Length); + // Copy Track and Genre to Array + tagByteArray[126] = paramMP3.mId3TrackNumber; + tagByteArray[127] = paramMP3.mId3Genre; + + // SAVE TO DISK: Replace the final 128 Bytes with our new ID3 tag + FileStream oFileStream = new FileStream(paramMP3.Mp3PathAndFilename, FileMode.Open); + if (paramMP3.mId3TagExists) + oFileStream.Seek(-128, SeekOrigin.End); + else + oFileStream.Seek(0, SeekOrigin.End); + oFileStream.Write(tagByteArray, 0, 128); + oFileStream.Close(); + paramMP3.mId3TagExists = true; + } + + #endregion + + /// <summary> + /// Returns a <see cref="T:System.String"></see> that represents the current <see cref="T:System.Object"></see>. + /// </summary> + /// <returns> + /// A <see cref="T:System.String"></see> that represents the current <see cref="T:System.Object"></see>. + /// </returns> + public override string ToString() + { + return string.Format("{0}, BR:{1}, SR:{2}, CH:{3}", mMp3Filename, mBitRate, mSampleRateFrequency, mChannelMode); + } + + public static Mp3FileProperties GetCommonProperties(List<Mp3FileProperties> properties) + { + Mp3FileProperties targetProperty = new Mp3FileProperties(); + + Type mp3FilePropertiesType = typeof(Mp3FileProperties); + foreach(PropertyInfo propertyInfo in mp3FilePropertiesType.GetProperties()) + { + FieldInfo field = mp3FilePropertiesType.GetField("m" + propertyInfo.Name, BindingFlags.NonPublic | BindingFlags.Instance); + if (field == null) + { + // Debug.WriteLine("Couldn't find " + "m" + propertyInfo.Name); + continue; + } + if (propertyInfo.PropertyType == typeof(string)) + { + string commonString = GetCommonString(properties, propertyInfo); + if (commonString != null) + field.SetValue(targetProperty, commonString); + Debug.WriteLine(propertyInfo.Name + " " + commonString); + } + else if (propertyInfo.PropertyType == typeof(int)) + { + field.SetValue(targetProperty, GetCommonThing<int>(properties, propertyInfo)); + } + else if (propertyInfo.PropertyType == typeof(long)) + { + field.SetValue(targetProperty, GetCommonThing<long>(properties, propertyInfo)); + } + else if (propertyInfo.PropertyType == typeof(ChannelMode)) + { + field.SetValue(targetProperty, GetCommonThing<ChannelMode>(properties, propertyInfo)); + } + else if (propertyInfo.PropertyType == typeof(SampleRateFrequency)) + { + field.SetValue(targetProperty, GetCommonThing<SampleRateFrequency>(properties, propertyInfo)); + } + + else if (propertyInfo.PropertyType == typeof(byte)) + { + field.SetValue(targetProperty, GetCommonThing<byte>(properties, propertyInfo)); + } + else if (propertyInfo.PropertyType == typeof(bool)) + { + field.SetValue(targetProperty, GetCommonThing<bool>(properties, propertyInfo)); + } + else + throw new ApplicationException("Unknown type " + propertyInfo.PropertyType); + + + + // Debug.WriteLine(propertyInfo); + } + return targetProperty; + } + + /// <summary> + /// Gets the common thing. + /// </summary> + /// <param name="properties">The properties.</param> + /// <param name="propertyInfo">The property info.</param> + /// <returns></returns> + public static T GetCommonThing<T>(List<Mp3FileProperties> properties, PropertyInfo propertyInfo) where T : new() + { + if (typeof(T) != propertyInfo.PropertyType) + throw new ArgumentException("Incorrect type. Expecting " + typeof (T) + ", got " + propertyInfo.PropertyType); + Dictionary<T,int> tCounts = new Dictionary<T,int>(); + + foreach (Mp3FileProperties property in properties) + { + T value = (T) propertyInfo.GetValue(property, null); + if (tCounts.ContainsKey(value)) + tCounts[value] = tCounts[value] + 1; + else + tCounts.Add(value, 1); + } + + List<T> keys = new List<T>(tCounts.Keys); + keys.Sort(delegate(T x, T y) + { + return tCounts[x].CompareTo(tCounts[y]); + } + ); + if (keys.Count > 0) + return keys[0]; + return new T(); + } + + + /// <summary> + /// Gets the common string. + /// </summary> + /// <param name="properties">The properties.</param> + /// <param name="propertyInfo">The property info.</param> + /// <returns></returns> + public static string GetCommonString(List<Mp3FileProperties> properties, PropertyInfo propertyInfo) + { + Dictionary<string,int> stringCounts = new Dictionary<string,int>(); + Dictionary<string,string> originalStrings = new Dictionary<string, string>(); + foreach (Mp3FileProperties property in properties) + { + string originalString = (string)propertyInfo.GetValue(property, null); + if (originalString == null) + continue; + + // remove whitespace + while (originalString.Contains(" ")) + originalString.Replace(" ", " "); + while (originalString.Contains("\t")) + originalString.Replace("\t", " "); + + string value = originalString.ToLower(); + + if (!originalStrings.ContainsKey(value)) + originalStrings.Add(value, originalString); + + if (stringCounts.ContainsKey(value)) + stringCounts[value] = stringCounts[value] + 1; + else + stringCounts[value] = 1; + } + if (stringCounts.Count == 0) + return null; + + List<string> keys = new List<string>(originalStrings.Keys); + keys.Sort(delegate(string x, string y) { + return stringCounts[x].CompareTo(stringCounts[y]); + } + ); + + if (stringCounts.Count > 1 && stringCounts.Count > (properties.Count / 2) ) + { + // Dictionary<string, int> substringLengths = new Dictionary<string, int>(); + Dictionary<string, int> substringCounts = new Dictionary<string, int>(); + + foreach (string keyLHS in keys) + foreach (string keyRHS in keys) + foreach (string substring in LongestCommonSubstrings.GetLongestSubstring(keyLHS, keyRHS)) + substringCounts[substring] = (substringCounts.ContainsKey(substring) ? substringCounts[substring] : 0) + 1; + + List<string> allSubstringsSortedByCount = new List<string>(substringCounts.Keys); + List<string> allSubstringsSortedByLength = new List<string>(substringCounts.Keys); + + allSubstringsSortedByCount.Sort(delegate(string x, string y) + { + return substringCounts[x].CompareTo(substringCounts[y]); + } + ); + + allSubstringsSortedByLength.Sort(delegate(string x, string y) + { + return x.Length.CompareTo(y.Length); + } + ); + + List<string> allSubstringsSortedRankCombination = new List<string>(allSubstringsSortedByLength); + + allSubstringsSortedRankCombination.Sort(delegate(string x, string y) + { + return + (allSubstringsSortedByCount.IndexOf(x) + allSubstringsSortedByLength.IndexOf(x)).CompareTo( + allSubstringsSortedByCount.IndexOf(y) + allSubstringsSortedByLength.IndexOf(y) + ); + } + ); + + int startIndex = keys[0].IndexOf(allSubstringsSortedRankCombination[0]); + return originalStrings[keys[0]].Substring(startIndex, allSubstringsSortedRankCombination[0].Length); + } + return originalStrings[keys[0]]; + } + } +} diff --git a/Backup/Mp3ToAacBatch.Designer.cs b/Backup/Mp3ToAacBatch.Designer.cs new file mode 100644 index 0000000..a32c139 --- /dev/null +++ b/Backup/Mp3ToAacBatch.Designer.cs @@ -0,0 +1,306 @@ +namespace m3uTool +{ + partial class Mp3ToAacBatch + { + /// <summary> + /// Required designer variable. + /// </summary> + private System.ComponentModel.IContainer components = null; + + /// <summary> + /// Clean up any resources being used. + /// </summary> + /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param> + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// <summary> + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// </summary> + private void InitializeComponent() + { + System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(Mp3ToAacBatch)); + this.inputListBox = new System.Windows.Forms.ListBox(); + this.addFolderButton = new System.Windows.Forms.Button(); + this.addFileButton = new System.Windows.Forms.Button(); + this.moveUpButton = new System.Windows.Forms.Button(); + this.moveDownButton = new System.Windows.Forms.Button(); + this.sortButton = new System.Windows.Forms.Button(); + this.updownSplitContainer = new System.Windows.Forms.SplitContainer(); + this.inputLabel = new System.Windows.Forms.Label(); + this.inputPanel = new System.Windows.Forms.Panel(); + this.clearButton = new System.Windows.Forms.Button(); + this.optionsPanel = new System.Windows.Forms.Panel(); + this.singleFileCheckBox = new System.Windows.Forms.CheckBox(); + this.outputDirectoryTextBox = new System.Windows.Forms.TextBox(); + this.optionsLabel = new System.Windows.Forms.Label(); + this.savePanel = new System.Windows.Forms.Panel(); + this.saveLabel = new System.Windows.Forms.Label(); + this.updownSplitContainer.Panel1.SuspendLayout(); + this.updownSplitContainer.Panel2.SuspendLayout(); + this.updownSplitContainer.SuspendLayout(); + this.inputPanel.SuspendLayout(); + this.optionsPanel.SuspendLayout(); + this.savePanel.SuspendLayout(); + this.SuspendLayout(); + // + // inputListBox + // + this.inputListBox.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.inputListBox.FormattingEnabled = true; + this.inputListBox.Location = new System.Drawing.Point(6, 35); + this.inputListBox.Name = "inputListBox"; + this.inputListBox.Size = new System.Drawing.Size(551, 264); + this.inputListBox.TabIndex = 0; + // + // addFolderButton + // + this.addFolderButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.addFolderButton.BackgroundImageLayout = System.Windows.Forms.ImageLayout.Center; + this.addFolderButton.FlatStyle = System.Windows.Forms.FlatStyle.Flat; + this.addFolderButton.Image = ((System.Drawing.Image)(resources.GetObject("addFolderButton.Image"))); + this.addFolderButton.ImageAlign = System.Drawing.ContentAlignment.TopLeft; + this.addFolderButton.Location = new System.Drawing.Point(470, 3); + this.addFolderButton.Name = "addFolderButton"; + this.addFolderButton.Size = new System.Drawing.Size(87, 30); + this.addFolderButton.TabIndex = 2; + this.addFolderButton.Text = "Add Folder"; + this.addFolderButton.TextImageRelation = System.Windows.Forms.TextImageRelation.ImageBeforeText; + this.addFolderButton.UseVisualStyleBackColor = true; + // + // addFileButton + // + this.addFileButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.addFileButton.BackgroundImageLayout = System.Windows.Forms.ImageLayout.Center; + this.addFileButton.FlatStyle = System.Windows.Forms.FlatStyle.Flat; + this.addFileButton.Image = ((System.Drawing.Image)(resources.GetObject("addFileButton.Image"))); + this.addFileButton.ImageAlign = System.Drawing.ContentAlignment.TopLeft; + this.addFileButton.Location = new System.Drawing.Point(390, 3); + this.addFileButton.Name = "addFileButton"; + this.addFileButton.Size = new System.Drawing.Size(74, 30); + this.addFileButton.TabIndex = 3; + this.addFileButton.Text = "Add File"; + this.addFileButton.TextImageRelation = System.Windows.Forms.TextImageRelation.ImageBeforeText; + this.addFileButton.UseVisualStyleBackColor = true; + // + // moveUpButton + // + this.moveUpButton.BackgroundImageLayout = System.Windows.Forms.ImageLayout.Center; + this.moveUpButton.Dock = System.Windows.Forms.DockStyle.Fill; + this.moveUpButton.FlatStyle = System.Windows.Forms.FlatStyle.Flat; + this.moveUpButton.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.moveUpButton.ImageAlign = System.Drawing.ContentAlignment.TopLeft; + this.moveUpButton.Location = new System.Drawing.Point(0, 0); + this.moveUpButton.Name = "moveUpButton"; + this.moveUpButton.Size = new System.Drawing.Size(18, 107); + this.moveUpButton.TabIndex = 4; + this.moveUpButton.Text = "^|"; + this.moveUpButton.TextImageRelation = System.Windows.Forms.TextImageRelation.ImageBeforeText; + this.moveUpButton.UseVisualStyleBackColor = true; + // + // moveDownButton + // + this.moveDownButton.BackgroundImageLayout = System.Windows.Forms.ImageLayout.Center; + this.moveDownButton.Dock = System.Windows.Forms.DockStyle.Fill; + this.moveDownButton.FlatStyle = System.Windows.Forms.FlatStyle.Flat; + this.moveDownButton.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.moveDownButton.ImageAlign = System.Drawing.ContentAlignment.TopLeft; + this.moveDownButton.Location = new System.Drawing.Point(0, 0); + this.moveDownButton.Name = "moveDownButton"; + this.moveDownButton.Size = new System.Drawing.Size(18, 157); + this.moveDownButton.TabIndex = 5; + this.moveDownButton.Text = "|v"; + this.moveDownButton.TextImageRelation = System.Windows.Forms.TextImageRelation.ImageBeforeText; + this.moveDownButton.UseVisualStyleBackColor = true; + // + // sortButton + // + this.sortButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.sortButton.BackgroundImageLayout = System.Windows.Forms.ImageLayout.Center; + this.sortButton.FlatStyle = System.Windows.Forms.FlatStyle.Flat; + this.sortButton.Image = ((System.Drawing.Image)(resources.GetObject("sortButton.Image"))); + this.sortButton.ImageAlign = System.Drawing.ContentAlignment.TopLeft; + this.sortButton.Location = new System.Drawing.Point(0, 307); + this.sortButton.Name = "sortButton"; + this.sortButton.Size = new System.Drawing.Size(61, 30); + this.sortButton.TabIndex = 6; + this.sortButton.Text = "Sort"; + this.sortButton.TextImageRelation = System.Windows.Forms.TextImageRelation.ImageBeforeText; + this.sortButton.UseVisualStyleBackColor = true; + // + // updownSplitContainer + // + this.updownSplitContainer.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Right))); + this.updownSplitContainer.IsSplitterFixed = true; + this.updownSplitContainer.Location = new System.Drawing.Point(563, 35); + this.updownSplitContainer.Name = "updownSplitContainer"; + this.updownSplitContainer.Orientation = System.Windows.Forms.Orientation.Horizontal; + // + // updownSplitContainer.Panel1 + // + this.updownSplitContainer.Panel1.Controls.Add(this.moveUpButton); + // + // updownSplitContainer.Panel2 + // + this.updownSplitContainer.Panel2.Controls.Add(this.moveDownButton); + this.updownSplitContainer.Size = new System.Drawing.Size(18, 266); + this.updownSplitContainer.SplitterDistance = 107; + this.updownSplitContainer.SplitterWidth = 2; + this.updownSplitContainer.TabIndex = 8; + // + // inputLabel + // + this.inputLabel.AutoSize = true; + this.inputLabel.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.inputLabel.Location = new System.Drawing.Point(3, 3); + this.inputLabel.Name = "inputLabel"; + this.inputLabel.Size = new System.Drawing.Size(126, 13); + this.inputLabel.TabIndex = 9; + this.inputLabel.Text = "1) Select Input MP3s"; + // + // inputPanel + // + this.inputPanel.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.inputPanel.Controls.Add(this.clearButton); + this.inputPanel.Controls.Add(this.inputLabel); + this.inputPanel.Controls.Add(this.inputListBox); + this.inputPanel.Controls.Add(this.updownSplitContainer); + this.inputPanel.Controls.Add(this.addFolderButton); + this.inputPanel.Controls.Add(this.addFileButton); + this.inputPanel.Controls.Add(this.sortButton); + this.inputPanel.Location = new System.Drawing.Point(12, 12); + this.inputPanel.Name = "inputPanel"; + this.inputPanel.Size = new System.Drawing.Size(584, 344); + this.inputPanel.TabIndex = 10; + // + // clearButton + // + this.clearButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.clearButton.BackgroundImageLayout = System.Windows.Forms.ImageLayout.Center; + this.clearButton.FlatStyle = System.Windows.Forms.FlatStyle.Flat; + this.clearButton.Image = ((System.Drawing.Image)(resources.GetObject("clearButton.Image"))); + this.clearButton.ImageAlign = System.Drawing.ContentAlignment.TopLeft; + this.clearButton.Location = new System.Drawing.Point(491, 307); + this.clearButton.Name = "clearButton"; + this.clearButton.Size = new System.Drawing.Size(66, 30); + this.clearButton.TabIndex = 10; + this.clearButton.Text = "Clear"; + this.clearButton.TextImageRelation = System.Windows.Forms.TextImageRelation.ImageBeforeText; + this.clearButton.UseVisualStyleBackColor = true; + // + // optionsPanel + // + this.optionsPanel.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.optionsPanel.Controls.Add(this.singleFileCheckBox); + this.optionsPanel.Controls.Add(this.outputDirectoryTextBox); + this.optionsPanel.Controls.Add(this.optionsLabel); + this.optionsPanel.Location = new System.Drawing.Point(12, 362); + this.optionsPanel.Name = "optionsPanel"; + this.optionsPanel.Size = new System.Drawing.Size(584, 135); + this.optionsPanel.TabIndex = 11; + // + // singleFileCheckBox + // + this.singleFileCheckBox.AutoSize = true; + this.singleFileCheckBox.Location = new System.Drawing.Point(448, 12); + this.singleFileCheckBox.Name = "singleFileCheckBox"; + this.singleFileCheckBox.Size = new System.Drawing.Size(109, 17); + this.singleFileCheckBox.TabIndex = 12; + this.singleFileCheckBox.Text = "Output Single File"; + this.singleFileCheckBox.UseVisualStyleBackColor = true; + // + // outputDirectoryTextBox + // + this.outputDirectoryTextBox.Location = new System.Drawing.Point(8, 112); + this.outputDirectoryTextBox.Name = "outputDirectoryTextBox"; + this.outputDirectoryTextBox.Size = new System.Drawing.Size(100, 20); + this.outputDirectoryTextBox.TabIndex = 11; + // + // optionsLabel + // + this.optionsLabel.AutoSize = true; + this.optionsLabel.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.optionsLabel.Location = new System.Drawing.Point(3, 0); + this.optionsLabel.Name = "optionsLabel"; + this.optionsLabel.Size = new System.Drawing.Size(105, 13); + this.optionsLabel.TabIndex = 10; + this.optionsLabel.Text = "2) Select Options"; + // + // savePanel + // + this.savePanel.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.savePanel.Controls.Add(this.saveLabel); + this.savePanel.Location = new System.Drawing.Point(12, 503); + this.savePanel.Name = "savePanel"; + this.savePanel.Size = new System.Drawing.Size(584, 93); + this.savePanel.TabIndex = 12; + // + // saveLabel + // + this.saveLabel.AutoSize = true; + this.saveLabel.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.saveLabel.Location = new System.Drawing.Point(3, 0); + this.saveLabel.Name = "saveLabel"; + this.saveLabel.Size = new System.Drawing.Size(79, 13); + this.saveLabel.TabIndex = 10; + this.saveLabel.Text = "3) Save AAC"; + // + // Mp3ToAacBatch + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(608, 608); + this.Controls.Add(this.savePanel); + this.Controls.Add(this.optionsPanel); + this.Controls.Add(this.inputPanel); + this.Name = "Mp3ToAacBatch"; + this.Text = "Mp3 To AAC Batch"; + this.updownSplitContainer.Panel1.ResumeLayout(false); + this.updownSplitContainer.Panel2.ResumeLayout(false); + this.updownSplitContainer.ResumeLayout(false); + this.inputPanel.ResumeLayout(false); + this.inputPanel.PerformLayout(); + this.optionsPanel.ResumeLayout(false); + this.optionsPanel.PerformLayout(); + this.savePanel.ResumeLayout(false); + this.savePanel.PerformLayout(); + this.ResumeLayout(false); + + } + + #endregion + + private System.Windows.Forms.ListBox inputListBox; + private System.Windows.Forms.Button addFolderButton; + private System.Windows.Forms.Button addFileButton; + private System.Windows.Forms.Button moveUpButton; + private System.Windows.Forms.Button moveDownButton; + private System.Windows.Forms.Button sortButton; + private System.Windows.Forms.SplitContainer updownSplitContainer; + private System.Windows.Forms.Label inputLabel; + private System.Windows.Forms.Panel inputPanel; + private System.Windows.Forms.Panel optionsPanel; + private System.Windows.Forms.Label optionsLabel; + private System.Windows.Forms.Button clearButton; + private System.Windows.Forms.Panel savePanel; + private System.Windows.Forms.Label saveLabel; + private System.Windows.Forms.TextBox outputDirectoryTextBox; + private System.Windows.Forms.CheckBox singleFileCheckBox; + } +} \ No newline at end of file diff --git a/Backup/Mp3ToAacBatch.cs b/Backup/Mp3ToAacBatch.cs new file mode 100644 index 0000000..813825c --- /dev/null +++ b/Backup/Mp3ToAacBatch.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Data; +using System.Drawing; +using System.Text; +using System.Windows.Forms; + +namespace m3uTool +{ + public partial class Mp3ToAacBatch : Form + { + public Mp3ToAacBatch() + { + InitializeComponent(); + } + } +} \ No newline at end of file diff --git a/Backup/Mp3ToAacBatch.resx b/Backup/Mp3ToAacBatch.resx new file mode 100644 index 0000000..079f5b8 --- /dev/null +++ b/Backup/Mp3ToAacBatch.resx @@ -0,0 +1,174 @@ +<?xml version="1.0" encoding="utf-8"?> +<root> + <!-- + Microsoft ResX Schema + + Version 2.0 + + The primary goals of this format is to allow a simple XML format + that is mostly human readable. The generation and parsing of the + various data types are done through the TypeConverter classes + associated with the data types. + + Example: + + ... ado.net/XML headers & schema ... + <resheader name="resmimetype">text/microsoft-resx</resheader> + <resheader name="version">2.0</resheader> + <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader> + <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader> + <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data> + <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data> + <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64"> + <value>[base64 mime encoded serialized .NET Framework object]</value> + </data> + <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64"> + <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value> + <comment>This is a comment</comment> + </data> + + There are any number of "resheader" rows that contain simple + name/value pairs. + + Each data row contains a name, and value. The row also contains a + type or mimetype. Type corresponds to a .NET class that support + text/value conversion through the TypeConverter architecture. + Classes that don't support this are serialized and stored with the + mimetype set. + + The mimetype is used for serialized objects, and tells the + ResXResourceReader how to depersist the object. This is currently not + extensible. For a given mimetype the value must be set accordingly: + + Note - application/x-microsoft.net.object.binary.base64 is the format + that the ResXResourceWriter will generate, however the reader can + read any of the formats listed below. + + mimetype: application/x-microsoft.net.object.binary.base64 + value : The object must be serialized with + : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter + : and then encoded with base64 encoding. + + mimetype: application/x-microsoft.net.object.soap.base64 + value : The object must be serialized with + : System.Runtime.Serialization.Formatters.Soap.SoapFormatter + : and then encoded with base64 encoding. + + mimetype: application/x-microsoft.net.object.bytearray.base64 + value : The object must be serialized into a byte array + : using a System.ComponentModel.TypeConverter + : and then encoded with base64 encoding. + --> + <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"> + <xsd:import namespace="http://www.w3.org/XML/1998/namespace" /> + <xsd:element name="root" msdata:IsDataSet="true"> + <xsd:complexType> + <xsd:choice maxOccurs="unbounded"> + <xsd:element name="metadata"> + <xsd:complexType> + <xsd:sequence> + <xsd:element name="value" type="xsd:string" minOccurs="0" /> + </xsd:sequence> + <xsd:attribute name="name" use="required" type="xsd:string" /> + <xsd:attribute name="type" type="xsd:string" /> + <xsd:attribute name="mimetype" type="xsd:string" /> + <xsd:attribute ref="xml:space" /> + </xsd:complexType> + </xsd:element> + <xsd:element name="assembly"> + <xsd:complexType> + <xsd:attribute name="alias" type="xsd:string" /> + <xsd:attribute name="name" type="xsd:string" /> + </xsd:complexType> + </xsd:element> + <xsd:element name="data"> + <xsd:complexType> + <xsd:sequence> + <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /> + <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" /> + </xsd:sequence> + <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" /> + <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" /> + <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" /> + <xsd:attribute ref="xml:space" /> + </xsd:complexType> + </xsd:element> + <xsd:element name="resheader"> + <xsd:complexType> + <xsd:sequence> + <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /> + </xsd:sequence> + <xsd:attribute name="name" type="xsd:string" use="required" /> + </xsd:complexType> + </xsd:element> + </xsd:choice> + </xsd:complexType> + </xsd:element> + </xsd:schema> + <resheader name="resmimetype"> + <value>text/microsoft-resx</value> + </resheader> + <resheader name="version"> + <value>2.0</value> + </resheader> + <resheader name="reader"> + <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> + </resheader> + <resheader name="writer"> + <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> + </resheader> + <assembly alias="System.Drawing" name="System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" /> + <data name="addFolderButton.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64"> + <value> + iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8 + YQUAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAO9JREFUOE9jYKAW + OLdI8T8yJslckEZ0cGyWxH+iDAEphIA5aPj/f5AcNoxiMNiAz0DNn3swMYa7IIbOrZJEuA5swOuW/48O + +uO0EZsrlhbxQAwBG3C9Baz51v5shJ2/v2OxHyj0vAUsjmLA36v5YAO+f+jEEhZoYXM86v/vn9NQDXi0 + 2en/yQUKIHP///8+BS9+vi/o/7k90agGnF+k+v/6DnuI7cDwwImBzn+/ywmhGRYGx2ZJ/v/wpBJiwONq + 3Ph+9f97m7AYcHyONETzdWAgEsD7Z2qgugDkigPTJP4Ti+dU4kihIAliMFHJm66KAORP5xRdT7ByAAAA + AElFTkSuQmCC +</value> + </data> + <data name="addFileButton.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64"> + <value> + iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8 + YQUAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAiJJREFUOE+Nk+1L + U1Ecx/WP6v1e9d53azYqIogixAcoR2+UjErQIFjSWgZbLJnkbPMur+7pzm3OPXhRvDp0TndxbKV7cLt7 + cGtf77ntgrVLdeBwD4f7+Xx/P845vT2dEYwdQl7/7buePusZ6rvW2/WPcymEP0e7DTRbP1FvtiA0WqBj + x5haiEjzn4K2SMfjSRgM8zjiczivNfHMuY/rln30vXB0S65WQGCS7PWxePJ4Enduj8Hl20OuUEYyVwLl + 4qDT6X6vQhbIcP2iBbd3Ax+MFEZHLRgZmcUqwyJTrIn7HDQaTbeAwBekZxGu1JvgEjzs9k3M26LQ3nyK + twYr+HwVzBoHtVrdLbgKl6pNFIQGlukd+EK70L+bg83px9GpAH9gR1lQ6yRLcKWBH+UGnMuboD1x7PGn + EnzwvQKff1tZQMomcL4DZ0s1LFIBfPq8glQHTmTL8DBbygIZTqTyoFcTMJkCeHB/AtPTFimZwFymDJeX + VRbIyeFIGsWiAMZ/gMGB5xgefAlKPFIuc47tkxJod1xZQHpmuSysVgbhcBJmswe3tMOYfGXCV3dMglm+ + iG+uqLKA9BzZ4qHtH8K9uzr03xjAo4fjmJlZhJc9lODocQHUyoay4KRQRfpMwNTrOYyPGWB8vwS9/gvM + Ni/Y9C84nCrAQYeVBaH1BNaCu2DEc/4464DRsAA7FZR6JmWTZAK/Ed9H10Uir4tsqlSq/5qy4BL8gXM2 + KgxAeQAAAABJRU5ErkJggg== +</value> + </data> + <data name="sortButton.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64"> + <value> + iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8 + YQUAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAOJJREFUOE+9UrkN + wzAM9Cbps4SnYO1RNIq7FFwirlOnzBDeQLkTSUOGAkhJEQOEbIP38Khp+seja863x56plZJm1le6umom + iYFxLoMEzzuaAVScRmAlIBhywiYCCxggWeQAk+R6kf4oRQkEMlvz9tpAYi5OOYRSHZRZNrsEMIt45/eJ + xAhiNlMoBeXkija7Z8AR6IrrUfws5xEUGj0ocfUgk9mB4T/5imJNEVQ4YFC1XfY388d+iyUPKmx3L8zn + oKoMepfGZkNVszZBdW2ggSQMjmM0QQ0RVHvlOpugRkh+6XkD9f86XlAO9kEAAAAASUVORK5CYII= +</value> + </data> + <data name="clearButton.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64"> + <value> + iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8 + YQUAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAh1JREFUOE+Vk1tP + E1EUhaeY8L+M/6WPPpgYYzTG6INEEo0aQBo00ShBjRhTi1UsUFqx1SLQG/RGL1AK0wu92jJnziz3Hm0k + meHBSU5mkplvrW+fyXEof687r7IYPp++O0ZGFH0wUM6Njip5313F5XI57L5TOIAvYQADAfQ0A62BgcYv + A7WOBL+/4lbhdDpti8wAIYE+wd0TgvsG6j0DVYIrLYkLs31znX9YsA/hgL4GdKi1ya1dA0dtAwcEl44l + IgmY67W7hxtjbmsIB7RZmVprXYnDtkS5SXBDYrcmkVYldg51xA90fA537QPqPQm1Y6BC8D61FutDWMc2 + wTGCN/d1+NY79gEqzcvKewTnCc5WdaSOJJIViVhZxwbBP4pkEDkjoNw0UCTlPClnqqRMcIJatwj+uSfw + neBveYGP4bbV4PbzBAoE5xhW/ylvUet6iWGBNYIDOQFPyCbg5swGcqTMm5Ws/JmXlSMlHeECwbsCq1mB + 5YyG919bVoPrEyFzl1k5airzvAIhgoME+7MallICizsa3gWa1oCr91cRp2ZWZng4b5BaVzICX9IaPm0L + LCQE3vhtAi6PLVqUA6ycJpiavUkNngTpxzTMLR9bDS7d8pi/aKjM8y5RKyt7qflDnODoCeY3Nbz0NawB + F6/N48nbFFxzSTyejWHqRRTTzyKYehrG5MwaJqaDeDTpx/iDBYzf89qfBz5p/7P4WP8G/jqMg6Vn2nUA + AAAASUVORK5CYII= +</value> + </data> +</root> \ No newline at end of file diff --git a/Backup/Mp4Encoder.cs b/Backup/Mp4Encoder.cs new file mode 100644 index 0000000..39d11d5 --- /dev/null +++ b/Backup/Mp4Encoder.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; + +namespace m3uTool +{ + public class Mp4Encoder : ProcessStreamWrapper + { + public Mp4Encoder(Mp4EncodingOptions encOpt) : base(encOpt) + { + } + + public Mp4Encoder(Mp4EncodingOptions encOpt, Stream outputStream) + : base(encOpt, outputStream) + { + } + + protected override string ProcessExecutableFilename + { + get { return "faac.exe"; } + } + } +} diff --git a/Backup/Mp4EncodingOptions.cs b/Backup/Mp4EncodingOptions.cs new file mode 100644 index 0000000..2dc1beb --- /dev/null +++ b/Backup/Mp4EncodingOptions.cs @@ -0,0 +1,527 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace m3uTool +{ + /// <summary> + ///FAAC 1.24.1 (May 17 2005) UNSTABLE + /// + ///Usage: faac [options] infiles ... + ///Options: + /// -q <quality> Set quantizer quality. + /// -b <bitrate> Set average bitrate to x kbps. (ABR, lower quality mode) + /// -c <freq> Set the bandwidth in Hz. (default=automatic) + /// -o X Set output file to X (only for one input file) + /// -r Use RAW AAC output file. + /// -P Raw PCM input mode (default 44100Hz 16bit stereo). + /// -R Raw PCM input rate. + /// -B Raw PCM input sample size (8, 16 (default), 24 or 32bits). + /// -C Raw PCM input channels. + /// -X Raw PCM swap input bytes + /// -I <C,LF> Input channel config, default is 3,4 (Center third, LF fourth) + /// + ///MP4 specific options: + /// -w Wrap AAC data in MP4 container. (default for *.mp4 and *.m4a) + /// --artist X Set artist to X + /// --writer X Set writer to X + /// --title X Set title to X + /// --genre X Set genre to X + /// --album X Set album to X + /// --compilation Set compilation + /// --track X Set track to X (number/total) + /// --disc X Set disc to X (number/total) + /// --year X Set year to X + /// --cover-art X Read cover art from file X + /// --comment X Set comment to X + /// </summary> + public class Mp4EncodingOptions : ProcessArguments + { + #region Private variables + + /// <summary> + /// Files to use for input + /// </summary> + private string[] mInfiles; + + /// <summary> + /// Quantinizer Quality -q [quality] + /// </summary> + private int mQuantinizerQuality = int.MinValue; + /// <summary> + /// BitRate -b [bitrate] + /// </summary> + private int mBitrate = int.MinValue; + /// <summary> + /// The bandwidth in Hz -c [freq] + /// </summary> + private SampleRateFrequency mBandwidth = SampleRateFrequency.Hz_0; + + /// <summary> + /// The output filename (only for one input file) -o [filename] + /// </summary> + private string mOutputFilename = null; + + /// <summary> + /// Use RAW AAC output file -r + /// </summary> + private bool mRawAACOutputFile = false; + + /// <summary> + /// Raw PCM input mode (default 44100Hz 16bit stereo) -P + /// </summary> + private bool mRawPCMInputMode = false; + + /// <summary> + /// Raw PCMInputSampleSize (default 44100Hz 16bit stereo) -R + /// </summary> + private SampleRateFrequency mRawPCMInputSampleSize = SampleRateFrequency.Hz_44100; + + /// <summary> + /// Raw PCM input rate (default 16) -B + /// </summary> + private int mRawPCMInputSampleRate = 16; + + /// <summary> + /// Raw PCM input channels -C + /// </summary> + private ChannelMode mRawInputChannels; + + /// <summary> + /// Raw PCM swap input bytes -X + /// </summary> + private bool mRawPCMSwapInputBytes = false; + + /// <summary> + /// Wrap AAC data in MP4 container. (default for *.mp4 and *.m4a) -w + /// </summary> + private bool mWrapAACDataInMp4 = false; + + /// <summary> + /// Set artist to X --artist X + /// </summary> + private string mArtist = null; + + /// <summary> + /// Set writer to X --writer X + /// </summary> + private string mWriter = null; + + /// <summary> + /// Set title to X --title X + /// </summary> + private string mTitle = null; + + /// <summary> + /// Set genre to X --genre X + /// </summary> + private string mGenre = null; + + /// <summary> + /// Set album to X --album X + /// </summary> + private string mAlbum = null; + + /// <summary> + /// Set compilation --compilation + /// </summary> + private string mCompilation = null; + + /// <summary> + /// Set track to X (number/total) --track X + /// </summary> + private string mTrack = null; + + /// <summary> + /// Set disc to X (number/total) --disc X + /// </summary> + private string mDisc = null; + + /// <summary> + /// Set year to X --year X + /// </summary> + private int mYear = int.MinValue; + + /// <summary> + /// Load cover art from filename --cover-art X + /// </summary> + private string mCoverArtFilename = null; + + /// <summary> + /// Set comment to X --comment X + /// </summary> + private string mComment = null; + + #endregion + + /// <summary> + /// Gets or sets the input files. + /// </summary> + /// <value>The input files.</value> + public string[] InputFiles + { + get + { + if (mInfiles == null) + return new string[]{}; + return mInfiles; + } + set + { + mInfiles = value; + } + } + + /// <summary> + /// Quantinizer Quality -q [quality] + /// </summary> + public int QuantinizerQuality + { + get { return mQuantinizerQuality; } + set { mQuantinizerQuality = value; } + } + + /// <summary> + /// BitRate -b [bitrate] + /// </summary> + public int Bitrate + { + get { return mBitrate; } + set { mBitrate = value; } + } + + /// <summary> + /// The bandwidth in Hz -c [freq] + /// </summary> + public SampleRateFrequency Bandwidth + { + get { return mBandwidth; } + set { mBandwidth = value; } + } + + /// <summary> + /// The output filename (only for one input file) -o [filename] + /// </summary> + public string OutputFilename + { + get { return mOutputFilename; } + set { mOutputFilename = value; } + } + + /// <summary> + /// Use RAW AAC output file -r + /// </summary> + public bool RawAACOutputFile + { + get { return mRawAACOutputFile; } + set { mRawAACOutputFile = value; } + } + + /// <summary> + /// Raw PCM input mode (default 44100Hz 16bit stereo) -P + /// </summary> + public bool RawPCMInputMode + { + get { return mRawPCMInputMode; } + set { mRawPCMInputMode = value; } + } + + /// <summary> + /// Raw PCMInputSampleSize -R + /// </summary> + public SampleRateFrequency RawPCMInputSampleSize + { + get { return mRawPCMInputSampleSize; } + set + { + mRawPCMInputSampleSize = value; + RawPCMInputMode = true; + } + } + + /// <summary> + /// Raw PCM input rate (default 16) -B + /// </summary> + public int RawPCMInputSampleRate + { + get { return mRawPCMInputSampleRate; } + set + { + mRawPCMInputSampleRate = value; + RawPCMInputMode = true; + } + } + + /// <summary> + /// Raw PCM input channels -C + /// </summary> + public ChannelMode RawInputChannels + { + get { return mRawInputChannels; } + set + { + mRawInputChannels = value; + RawPCMInputMode = true; + } + } + + /// <summary> + /// Raw PCM swap input bytes -X + /// </summary> + public bool RawPCMSwapInputBytes + { + get { return mRawPCMSwapInputBytes; } + set + { + mRawPCMSwapInputBytes = value; + RawPCMInputMode = true; + } + } + + /// <summary> + /// Wrap AAC data in MP4 container. (default for *.mp4 and *.m4a) + /// </summary> + public bool WrapAACDataInMp4 + { + get { return mWrapAACDataInMp4; } + set { mWrapAACDataInMp4 = value; } + } + + /// <summary> + /// Set artist to X + /// </summary> + public string Artist + { + get { return mArtist; } + set + { + mArtist = value; + if (!string.IsNullOrEmpty(value)) + WrapAACDataInMp4 = true; + } + } + + /// <summary> + /// Set writer to X + /// </summary> + public string Writer + { + get { return mWriter; } + set + { + mWriter = value; + if (!string.IsNullOrEmpty(value)) + WrapAACDataInMp4 = true; + + } + } + + /// <summary> + /// Set title to X + /// </summary> + public string Title + { + get { return mTitle; } + set + { + mTitle = value; + if (!string.IsNullOrEmpty(value)) + WrapAACDataInMp4 = true; + } + } + + /// <summary> + /// Set genre to X + /// </summary> + public string Genre + { + get { return mGenre; } + set + { + mGenre = value; + if (!string.IsNullOrEmpty(value)) + WrapAACDataInMp4 = true; + } + } + + /// <summary> + /// Set album to X + /// </summary> + public string Album + { + get { return mAlbum; } + set + { + mAlbum = value; + if (!string.IsNullOrEmpty(value)) + WrapAACDataInMp4 = true; + } + } + + /// <summary> + /// Set compilation + /// </summary> + public string Compilation + { + get { return mCompilation; } + set + { + mCompilation = value; + if (!string.IsNullOrEmpty(value)) + WrapAACDataInMp4 = true; + } + } + + /// <summary> + /// Set track to X (number/total) + /// </summary> + public string Track + { + get { return mTrack; } + set + { + mTrack = value; + if (!string.IsNullOrEmpty(value)) + WrapAACDataInMp4 = true; + } + } + + /// <summary> + /// Set disc to X (number/total) + /// </summary> + public string Disc + { + get { return mDisc; } + set + { + mDisc = value; + if (!string.IsNullOrEmpty(value)) + WrapAACDataInMp4 = true; + } + } + + /// <summary> + /// Set year to X + /// </summary> + public int Year + { + get { return mYear; } + set + { + mYear = value; + if (value != int.MinValue) + WrapAACDataInMp4 = true; + } + } + + /// <summary> + /// Load cover art from filename + /// </summary> + public string CoverArtFilename + { + get { return mCoverArtFilename; } + set + { + mCoverArtFilename = value; + if (!string.IsNullOrEmpty(value)) + WrapAACDataInMp4 = true; + } + } + + /// <summary> + /// Quantinizer Quality -q [quality] + /// </summary> + public string Comment + { + get { return mComment; } + set + { + mComment = value; + if (!string.IsNullOrEmpty(value)) + WrapAACDataInMp4 = true; + } + } + + /// <summary> + /// Gets a string representing the command line arguments specified by the properties in the object. + /// </summary> + /// <returns></returns> + public override string GetCommandLineArguments() + { + StringBuilder stringBuilder = new StringBuilder(); + if (mQuantinizerQuality != int.MinValue) + stringBuilder.Append(" -q " + mQuantinizerQuality); + if (mBitrate != int.MinValue) + stringBuilder.Append(" -b " + mBitrate); + if (mBandwidth != SampleRateFrequency.Hz_0) + stringBuilder.Append(" -c " + SampleRateFrequencyUtility.GetSampleRateFrequencyInt(mBandwidth)); + + if (!string.IsNullOrEmpty(mOutputFilename)) + { + if (mOutputFilename.Trim() == "-") + stringBuilder.Append(" -o-"); + else + stringBuilder.Append(" -o " + GetQuotedCommandLineArgument(mOutputFilename)); + } + else + stringBuilder.Append(" -o-"); + + if (mRawPCMInputMode) + { + stringBuilder.Append(" -P "); + if (mRawPCMInputSampleSize != SampleRateFrequency.Hz_44100) + stringBuilder.Append(" -R " + SampleRateFrequencyUtility.GetSampleRateFrequencyInt(mRawPCMInputSampleSize)); + if (mRawPCMInputSampleRate != 16) + stringBuilder.Append(" -B " + mRawPCMInputSampleRate); + int chanelMode = ChannelModeUtility.GetChannelModeInt(mRawInputChannels); + if (chanelMode != 2) + stringBuilder.Append(" -C " + chanelMode); + if (mRawPCMSwapInputBytes) + stringBuilder.Append(" -X "); + } + + if (mWrapAACDataInMp4) + { + stringBuilder.Append(" -w "); + if (!string.IsNullOrEmpty(mArtist)) + stringBuilder.Append(" --artist " + mArtist); + if (!string.IsNullOrEmpty(mWriter)) + stringBuilder.Append(" --writer " + mWriter); + if (!string.IsNullOrEmpty(mTitle)) + stringBuilder.Append(" --title " + mTitle); + if (!string.IsNullOrEmpty(mGenre)) + stringBuilder.Append(" --genre " + mGenre); + if (!string.IsNullOrEmpty(mAlbum)) + stringBuilder.Append(" --album " + mAlbum); + if (!string.IsNullOrEmpty(mCompilation)) + stringBuilder.Append(" --compilation " + mCompilation); + if (!string.IsNullOrEmpty(mTrack)) + stringBuilder.Append(" --track " + mTrack); + if (!string.IsNullOrEmpty(mDisc)) + stringBuilder.Append(" --disc " + mDisc); + if (mYear != int.MinValue) + stringBuilder.Append(" --year " + mYear); + if (!string.IsNullOrEmpty(mCoverArtFilename)) + stringBuilder.Append(" --cover-art " + mCoverArtFilename); + if (!string.IsNullOrEmpty(mComment)) + stringBuilder.Append(" --comment " + mComment); + } + if (mInfiles != null && mInfiles.Length > 0) + { + // use standard input + if (mInfiles[0].Trim() == "-") + stringBuilder.Append(" -"); + else + { + foreach (string infile in mInfiles) + stringBuilder.Append(" " + GetQuotedCommandLineArgument(infile)); + } + } + else + stringBuilder.Append(" -"); + + + return stringBuilder.ToString(); + } + } +} diff --git a/Backup/ProcessArguments.cs b/Backup/ProcessArguments.cs new file mode 100644 index 0000000..017be2e --- /dev/null +++ b/Backup/ProcessArguments.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace m3uTool +{ + public class ProcessArguments + { + /// <summary> + /// Gets a string representing the command line arguments specified by the properties in the object. + /// </summary> + /// <returns></returns> + public virtual string GetCommandLineArguments() + { + return ""; + } + + protected string GetQuotedCommandLineArgument(string argument) + { + if (argument.Contains(" ") || argument.Contains("\t")) + { + return "\"" + argument + "\""; + } + return argument; + } + + } +} diff --git a/Backup/ProcessStreamWrapper.cs b/Backup/ProcessStreamWrapper.cs new file mode 100644 index 0000000..32cfbc1 --- /dev/null +++ b/Backup/ProcessStreamWrapper.cs @@ -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 + } +} diff --git a/Backup/Properties/Resources.Designer.cs b/Backup/Properties/Resources.Designer.cs new file mode 100644 index 0000000..6220cd9 --- /dev/null +++ b/Backup/Properties/Resources.Designer.cs @@ -0,0 +1,63 @@ +//------------------------------------------------------------------------------ +// <auto-generated> +// This code was generated by a tool. +// Runtime Version:2.0.50727.42 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// </auto-generated> +//------------------------------------------------------------------------------ + +namespace m3uTool.Properties { + using System; + + + /// <summary> + /// A strongly-typed resource class, for looking up localized strings, etc. + /// </summary> + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "2.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// <summary> + /// Returns the cached ResourceManager instance used by this class. + /// </summary> + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("m3uTool.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// <summary> + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// </summary> + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + } +} diff --git a/Backup/Properties/Resources.resx b/Backup/Properties/Resources.resx new file mode 100644 index 0000000..5ea0895 --- /dev/null +++ b/Backup/Properties/Resources.resx @@ -0,0 +1,120 @@ +<?xml version="1.0" encoding="utf-8"?> +<root> + <!-- + Microsoft ResX Schema + + Version 2.0 + + The primary goals of this format is to allow a simple XML format + that is mostly human readable. The generation and parsing of the + various data types are done through the TypeConverter classes + associated with the data types. + + Example: + + ... ado.net/XML headers & schema ... + <resheader name="resmimetype">text/microsoft-resx</resheader> + <resheader name="version">2.0</resheader> + <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader> + <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader> + <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data> + <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data> + <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64"> + <value>[base64 mime encoded serialized .NET Framework object]</value> + </data> + <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64"> + <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value> + <comment>This is a comment</comment> + </data> + + There are any number of "resheader" rows that contain simple + name/value pairs. + + Each data row contains a name, and value. The row also contains a + type or mimetype. Type corresponds to a .NET class that support + text/value conversion through the TypeConverter architecture. + Classes that don't support this are serialized and stored with the + mimetype set. + + The mimetype is used for serialized objects, and tells the + ResXResourceReader how to depersist the object. This is currently not + extensible. For a given mimetype the value must be set accordingly: + + Note - application/x-microsoft.net.object.binary.base64 is the format + that the ResXResourceWriter will generate, however the reader can + read any of the formats listed below. + + mimetype: application/x-microsoft.net.object.binary.base64 + value : The object must be serialized with + : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter + : and then encoded with base64 encoding. + + mimetype: application/x-microsoft.net.object.soap.base64 + value : The object must be serialized with + : System.Runtime.Serialization.Formatters.Soap.SoapFormatter + : and then encoded with base64 encoding. + + mimetype: application/x-microsoft.net.object.bytearray.base64 + value : The object must be serialized into a byte array + : using a System.ComponentModel.TypeConverter + : and then encoded with base64 encoding. + --> + <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"> + <xsd:import namespace="http://www.w3.org/XML/1998/namespace" /> + <xsd:element name="root" msdata:IsDataSet="true"> + <xsd:complexType> + <xsd:choice maxOccurs="unbounded"> + <xsd:element name="metadata"> + <xsd:complexType> + <xsd:sequence> + <xsd:element name="value" type="xsd:string" minOccurs="0" /> + </xsd:sequence> + <xsd:attribute name="name" use="required" type="xsd:string" /> + <xsd:attribute name="type" type="xsd:string" /> + <xsd:attribute name="mimetype" type="xsd:string" /> + <xsd:attribute ref="xml:space" /> + </xsd:complexType> + </xsd:element> + <xsd:element name="assembly"> + <xsd:complexType> + <xsd:attribute name="alias" type="xsd:string" /> + <xsd:attribute name="name" type="xsd:string" /> + </xsd:complexType> + </xsd:element> + <xsd:element name="data"> + <xsd:complexType> + <xsd:sequence> + <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /> + <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" /> + </xsd:sequence> + <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" /> + <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" /> + <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" /> + <xsd:attribute ref="xml:space" /> + </xsd:complexType> + </xsd:element> + <xsd:element name="resheader"> + <xsd:complexType> + <xsd:sequence> + <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /> + </xsd:sequence> + <xsd:attribute name="name" type="xsd:string" use="required" /> + </xsd:complexType> + </xsd:element> + </xsd:choice> + </xsd:complexType> + </xsd:element> + </xsd:schema> + <resheader name="resmimetype"> + <value>text/microsoft-resx</value> + </resheader> + <resheader name="version"> + <value>2.0</value> + </resheader> + <resheader name="reader"> + <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> + </resheader> + <resheader name="writer"> + <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> + </resheader> +</root> \ No newline at end of file diff --git a/Backup/Redirect.cs b/Backup/Redirect.cs new file mode 100644 index 0000000..5a8b385 --- /dev/null +++ b/Backup/Redirect.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace m3uTool +{ + [Flags] + public enum Redirect + { + /// <summary> + /// Do not redirect anything + /// </summary> + None = 0, + /// <summary> + /// Redirect standard input + /// </summary> + StandardInput = 1, + /// <summary> + /// Redirect standard output + /// </summary> + StandardOutput = 2, + /// <summary> + /// Redirect standart error + /// </summary> + StandardError = 4 + } +} diff --git a/Backup/StdinToStdout/Program.cs b/Backup/StdinToStdout/Program.cs new file mode 100644 index 0000000..58b9e08 --- /dev/null +++ b/Backup/StdinToStdout/Program.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; + +namespace StdinToStdout +{ + /// <summary> + /// Simply takes anything from standard input and writes it directly to standard output. + /// </summary> + public class Program + { + static void Main(string[] args) + { + Stream inputStream = Console.OpenStandardInput(1); + Stream outputStream = Console.OpenStandardOutput(1); + // Stream errorStream = Console.OpenStandardError(); + using (StreamReader sr = new StreamReader(inputStream, Encoding.Default)) + using (StreamWriter sw = new StreamWriter(outputStream, Encoding.Default)) + { + char[] buffer = new char[1]; + while (!sr.EndOfStream) + { + int readLength = sr.Read(buffer, 0, 1); + sw.Write(buffer, 0, readLength); + } + inputStream.Flush(); + outputStream.Flush(); + } + } + } +} diff --git a/Backup/StdinToStdout/Properties/AssemblyInfo.cs b/Backup/StdinToStdout/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..b1893b6 --- /dev/null +++ b/Backup/StdinToStdout/Properties/AssemblyInfo.cs @@ -0,0 +1,33 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("StdinToStdout")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("StdinToStdout")] +[assembly: AssemblyCopyright("Copyright © 2006")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("63522965-dbbf-40a5-a4d3-5ed1ec25b611")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Backup/StdinToStdout/StdinToStdout.csproj b/Backup/StdinToStdout/StdinToStdout.csproj new file mode 100644 index 0000000..38d0d3b --- /dev/null +++ b/Backup/StdinToStdout/StdinToStdout.csproj @@ -0,0 +1,47 @@ +<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <PropertyGroup> + <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> + <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> + <ProductVersion>8.0.50727</ProductVersion> + <SchemaVersion>2.0</SchemaVersion> + <ProjectGuid>{94C7033C-C8A8-4104-95FE-0D11122080E2}</ProjectGuid> + <OutputType>Exe</OutputType> + <AppDesignerFolder>Properties</AppDesignerFolder> + <RootNamespace>StdinToStdout</RootNamespace> + <AssemblyName>StdinToStdout</AssemblyName> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> + <DebugSymbols>true</DebugSymbols> + <DebugType>full</DebugType> + <Optimize>false</Optimize> + <OutputPath>bin\Debug\</OutputPath> + <DefineConstants>DEBUG;TRACE</DefineConstants> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> + <DebugType>pdbonly</DebugType> + <Optimize>true</Optimize> + <OutputPath>bin\Release\</OutputPath> + <DefineConstants>TRACE</DefineConstants> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + </PropertyGroup> + <ItemGroup> + <Reference Include="System" /> + <Reference Include="System.Data" /> + <Reference Include="System.Xml" /> + </ItemGroup> + <ItemGroup> + <Compile Include="Program.cs" /> + <Compile Include="Properties\AssemblyInfo.cs" /> + </ItemGroup> + <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" /> + <!-- To modify your build process, add your task inside one of the targets below and uncomment it. + Other similar extension points exist, see Microsoft.Common.targets. + <Target Name="BeforeBuild"> + </Target> + <Target Name="AfterBuild"> + </Target> + --> +</Project> \ No newline at end of file diff --git a/Backup/Tests/CreateM3uTests.cs b/Backup/Tests/CreateM3uTests.cs new file mode 100644 index 0000000..2590621 --- /dev/null +++ b/Backup/Tests/CreateM3uTests.cs @@ -0,0 +1,40 @@ +using System; +using System.Diagnostics; +using System.IO; + +namespace m3uTool.Tests +{ + /// <summary> + /// Summary description for CreateM3uTest. + /// </summary> + public class CreateM3uTests + { + public void CreateM3uTest() + { + M3uCreate c = new M3uCreate(new DirectoryInfo(@"C:\mp3\")); + // c.OutputFilename = @"c:\mym3u.m3u"; + Debug.WriteLine(c.OutputFilename); + c.Create(); + } + + public void CreateSubM3uTest() + { + DirectoryInfo rootDir = new DirectoryInfo(@"C:\mp3\"); + + int count = 0; + foreach (DirectoryInfo subDir in rootDir.GetDirectories()) + { + M3uCreate c = new M3uCreate(subDir); + + Debug.WriteLine(c.OutputFilename); + c.Create(); + + if (count++ > 10) + break; + } + + + // c.OutputFilename = @"c:\mym3u.m3u"; + } + } +} diff --git a/Backup/Tests/HiPerfTimer.cs b/Backup/Tests/HiPerfTimer.cs new file mode 100644 index 0000000..35d72f7 --- /dev/null +++ b/Backup/Tests/HiPerfTimer.cs @@ -0,0 +1,58 @@ +using System; +using System.Runtime.InteropServices; +using System.ComponentModel; +using System.Threading; + +namespace Win32 +{ + internal class HiPerfTimer + { + [DllImport("Kernel32.dll")] + private static extern bool QueryPerformanceCounter( + out long lpPerformanceCount); + + [DllImport("Kernel32.dll")] + private static extern bool QueryPerformanceFrequency( + out long lpFrequency); + + private long startTime, stopTime; + private long freq; + + // Constructor + public HiPerfTimer() + { + startTime = 0; + stopTime = 0; + + if (QueryPerformanceFrequency(out freq) == false) + { + // high-performance counter not supported + throw new Win32Exception(); + } + } + + // Start the timer + public void Start() + { + // lets do the waiting threads there work + Thread.Sleep(0); + + QueryPerformanceCounter(out startTime); + } + + // Stop the timer + public void Stop() + { + QueryPerformanceCounter(out stopTime); + } + + // Returns the duration of the timer (in seconds) + public double Duration + { + get + { + return (double)(stopTime - startTime) / (double)freq; + } + } + } +} \ No newline at end of file diff --git a/Backup/Tests/LongestCommonSequenceTests.cs b/Backup/Tests/LongestCommonSequenceTests.cs new file mode 100644 index 0000000..6eb19c2 --- /dev/null +++ b/Backup/Tests/LongestCommonSequenceTests.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Text; +using NUnit.Framework; + +namespace m3uTool.Tests +{ + [TestFixture] + public class LongestCommonSequenceTests + { + [Test] + public void FunctionalityTest() + { + //LongestCommonSequence<string> lcs = new LongestCommonSequence<string>(); + //Debug.WriteLine(new string(lcs.LCS("what do we say", "heya".ToCharArray()))); + + List<string> substring = LongestCommonSubstrings.GetLongestSubstring("ABAB", "BABA"); + Assert.AreEqual(2, substring.Count); + Assert.IsTrue(substring.Contains("ABA")); + Assert.IsTrue(substring.Contains("BAB")); + } + } +} diff --git a/Backup/Tests/Mp3EncoderTests.cs b/Backup/Tests/Mp3EncoderTests.cs new file mode 100644 index 0000000..f91a241 --- /dev/null +++ b/Backup/Tests/Mp3EncoderTests.cs @@ -0,0 +1,187 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Text; +using NUnit.Framework; + +namespace m3uTool.Tests +{ + [TestFixture] + public class Mp3EncoderTests : ProgressCallbackTestBase + { + /// <summary> + /// + /// </summary> + [Test] + public void StreamingVsOutputMp3EncodingTest() + { + //String inputWav = "44_16_s.wav"; + String inputWav = "test1.wav"; + String outputOptionEncodedMp3 = "outputOptionEncodedMp3.mp3"; + String streamingEncodedMp3 = "streamingEncodedMp3.mp3"; + + Mp3EncodingOptions encOpts = new Mp3EncodingOptions(); + encOpts.Outfile = "-"; + encOpts.Infile = "-"; + + using (FileStream fs = new FileStream(streamingEncodedMp3, FileMode.OpenOrCreate)) + { + using (ProcessStreamWrapper enc = new Mp3Encoder(encOpts, fs)) + { + enc.ProgressCallback = this; + enc.ProcessInput(inputWav); + } + } + + encOpts.Outfile = outputOptionEncodedMp3; + + using (ProcessStreamWrapper enc = new Mp3Encoder(encOpts)) + { + enc.ProgressCallback = this; + enc.ProcessInput(inputWav); + } + + Mp3FileProperties outputOptionProperties = new Mp3FileProperties(outputOptionEncodedMp3); + Mp3FileProperties streamingProperties = new Mp3FileProperties(streamingEncodedMp3); + Assert.AreEqual(outputOptionProperties.LengthInSeconds, streamingProperties.LengthInSeconds); + + // FilesAreEqual(outputOptionFileInfo, streamingFileInfo); + } + + /// <summary> + /// + /// </summary> + [Test] + public void StreamingVsOutputMp3DecodingTest() + { + String input = "44_16_s.mp3"; + String outputOptionDecoded = "outputOptionDecoded.wav"; + String streamingDecoded = "streamingDecode.wav"; + + Mp3EncodingOptions encOpts = new Mp3EncodingOptions(); + encOpts.Outfile = "-"; + encOpts.Infile = "-"; + encOpts.Mp3Input = true; + encOpts.DecodeWav = true; + + using (FileStream fileStream = new FileStream(streamingDecoded, FileMode.OpenOrCreate)) + { + using (ProcessStreamWrapper enc = new Mp3Encoder(encOpts, fileStream)) + { + enc.ProgressCallback = this; + enc.ProcessInput(input); + } + } + + encOpts.Outfile = outputOptionDecoded; + + using (ProcessStreamWrapper enc = new Mp3Encoder(encOpts)) + { + enc.ProgressCallback = this; + enc.ProcessInput(input); + } + + FileInfo outputOptionFileInfo = new FileInfo(outputOptionDecoded); + FileInfo streamingFileInfo = new FileInfo(streamingDecoded); + + FilesAreEqual(outputOptionFileInfo, streamingFileInfo); + } + + private void FilesAreEqual(FileInfo file1, FileInfo file2) + { + //Assert.AreEqual(file1.Length, file2.Length); + + int filePosition = 0; + using (FileStream outputOptionFs = file1.OpenRead()) + using (StreamReader outputOptionSr = new StreamReader(outputOptionFs)) + using (FileStream streamingFs = file2.OpenRead()) + using (StreamReader streamingSr = new StreamReader(streamingFs)) + { + char[] outputOptionBuffer = new char[4096]; + char[] streamingBuffer = new char[4096]; + + while (!outputOptionSr.EndOfStream) + { + int outputOptionReadLength = outputOptionSr.Read(outputOptionBuffer, 0, outputOptionBuffer.Length); + int streamingReadLength = streamingSr.Read(streamingBuffer, 0, streamingBuffer.Length); + + Assert.AreEqual(outputOptionReadLength, streamingReadLength); + Assert.AreEqual(outputOptionBuffer, streamingBuffer, "offset : " + filePosition); + filePosition += outputOptionBuffer.Length; + } + } + } + + /// <summary> + /// Tests this instance. + /// </summary> + [Test] + public void StreamingTest() + { + String inputWav = "44_16_s.wav"; + String encodedMp3 = "encodedMp3.mp3"; + String decodedWav = "decodedWav.wav"; + String reencodedMp3 = "reencodedMp3.mp3"; + + using (FileStream fileStream = new FileStream(encodedMp3, FileMode.OpenOrCreate)) + { + Mp3EncodingOptions encOpts = new Mp3EncodingOptions(); + encOpts.Outfile = "-"; + encOpts.Infile = "-"; + + using (ProcessStreamWrapper enc = new Mp3Encoder(encOpts, fileStream)) + { + enc.ProgressCallback = this; + enc.ProcessInput(inputWav); + } + } + Debug.WriteLine("Finished writing " + encodedMp3); + + Debug.WriteLine("Begin writing " + decodedWav); + using (FileStream fileStream = new FileStream(decodedWav, FileMode.OpenOrCreate)) + { + Mp3EncodingOptions wavOpts = new Mp3EncodingOptions(); + wavOpts.Infile = "-"; + wavOpts.Outfile = "-"; + // wavOpts.MonoDownmixing = true; + wavOpts.Mp3Input = true; + wavOpts.DecodeWav = true; + + using (ProcessStreamWrapper enc = new Mp3Encoder(wavOpts, fileStream)) + { + enc.ProgressCallback = this; + enc.ProcessInput(encodedMp3); + } + } + Debug.WriteLine("Finished writing " + decodedWav); + + using (FileStream fileStream = new FileStream(reencodedMp3, FileMode.OpenOrCreate)) + { + Mp3EncodingOptions encOpts = new Mp3EncodingOptions(); + encOpts.Outfile = "-"; + encOpts.Infile = "-"; + + using (ProcessStreamWrapper enc = new Mp3Encoder(encOpts, fileStream)) + { + enc.ProgressCallback = this; + enc.ProcessInput(decodedWav); + } + } + + Mp3FileProperties reencodedProperties = new Mp3FileProperties(reencodedMp3); + Mp3FileProperties encodedProperties = new Mp3FileProperties(encodedMp3); + Assert.AreEqual(encodedProperties.FileLength, reencodedProperties.FileLength); + } + + /// <summary> + /// Call this method from the worker thread to increase the progress + /// counter by a specified value. + /// </summary> + public override void StepTo(int val) + { + if (val % 10 == 0) + base.StepTo(val); + } + } +} diff --git a/Backup/Tests/Mp3EncodingOptionsTests.cs b/Backup/Tests/Mp3EncodingOptionsTests.cs new file mode 100644 index 0000000..08eb8b6 --- /dev/null +++ b/Backup/Tests/Mp3EncodingOptionsTests.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Text; +using NUnit.Framework; + +namespace m3uTool.Tests +{ + /// <summary> + /// + /// </summary> + [TestFixture] + public class Mp3EncodingOptionsTests + { + /// <summary> + /// + /// </summary> + [Test] + public void Mp3EncodingOptionsTest() + { + Mp3EncodingOptions e = new Mp3EncodingOptions(); + e.ChannelMode = ChannelMode.SingleChannel; + e.Infile = "-"; + string arguments = e.GetCommandLineArguments(); + Debug.WriteLine(arguments); + } + } +} diff --git a/Backup/Tests/Mp3FilePropertiesTests.cs b/Backup/Tests/Mp3FilePropertiesTests.cs new file mode 100644 index 0000000..725daaf --- /dev/null +++ b/Backup/Tests/Mp3FilePropertiesTests.cs @@ -0,0 +1,56 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Text; + +namespace m3uTool.Tests +{ + /// <summary> + /// Summary description for MP3HeaderTests. + /// </summary> + public class Mp3FilePropertiesTests + { + + public void Mp3FileDetailsTest() + { + string[] files = Directory.GetFiles(".", "*.mp3"); + List<Mp3FileProperties> properties = new List<Mp3FileProperties>(); + foreach (string mp3Filename in files) + { + Debug.WriteLine(mp3Filename); + Debug.Indent(); + + // bool boolIsMP3 = mp3hdr.ReadMP3Information(mp3Filename); + Mp3FileProperties fileDetails = new Mp3FileProperties(mp3Filename); + properties.Add(fileDetails); + if (fileDetails != null) + { + Debug.WriteLine(GetMp3FilePropertyReport(fileDetails)); + } + Debug.Unindent(); + } + Mp3FileProperties commonProperties = Mp3FileProperties.GetCommonProperties(properties); + Debug.WriteLine(GetMp3FilePropertyReport(commonProperties)); + } + + private static string GetMp3FilePropertyReport(Mp3FileProperties fileDetails) + { + StringBuilder report = new StringBuilder(); + report.AppendLine("Filename: " + fileDetails.Mp3PathAndFilename); + report.AppendLine("mFileSize: " + fileDetails.FileSize.ToString()); + report.AppendLine("mBitRate: " + fileDetails.BitRate.ToString()); + report.AppendLine("mSampleRateIntegerFrequency:" + fileDetails.SampleRateIntegerFrequency.ToString()); + report.AppendLine("strMode: " + fileDetails.ChannelMode); + report.AppendLine("strLengthFo: " + fileDetails.LengthFormatted); + report.AppendLine("mLengthInSeconds: " + fileDetails.LengthInSeconds.ToString()); + + report.AppendLine("title: " + fileDetails.Id3Title); + report.AppendLine("artist: " + fileDetails.Id3Artist); + report.AppendLine("album: " + fileDetails.Id3Album); + report.AppendLine("track#: " + fileDetails.Id3TrackNumber); + report.AppendLine("genre: " + fileDetails.Id3GenreName); + return report.ToString(); + } + } +} diff --git a/Backup/Tests/Mp4EncoderTests.cs b/Backup/Tests/Mp4EncoderTests.cs new file mode 100644 index 0000000..c174b42 --- /dev/null +++ b/Backup/Tests/Mp4EncoderTests.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Media; +using System.Text; +using NUnit.Framework; + +namespace m3uTool.Tests +{ + [TestFixture] + public class Mp4EncoderTests : ProgressCallbackTestBase + { + /// <summary> + /// Tests Encoding an Mp4 file. + /// </summary> + [Test] + public void Test() + { + Mp4EncodingOptions encOpts = new Mp4EncodingOptions(); + encOpts.OutputFilename = "44_16_s_opts.aac"; + + using (ProcessStreamWrapper enc = new Mp4Encoder(encOpts)) + { + enc.ProgressCallback = this; + enc.ProcessInput("44_16_s.wav"); + } + } + + + /// <summary> + /// Call this method from the worker thread to increase the progress + /// counter by a specified value. + /// </summary> + public override void StepTo(int val) + { + if (val%10 == 0) + base.StepTo(val); + } + + } +} diff --git a/Backup/Tests/Mp4EncodingOptionsTests.cs b/Backup/Tests/Mp4EncodingOptionsTests.cs new file mode 100644 index 0000000..0525e1b --- /dev/null +++ b/Backup/Tests/Mp4EncodingOptionsTests.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Text; +using NUnit.Framework; + +namespace m3uTool.Tests +{ + [TestFixture] + public class Mp4EncodingOptionsTests + { + [Test] + public void Mp4EncodingOptionsTest() + { + Mp4EncodingOptions e = new Mp4EncodingOptions(); + e.RawInputChannels = ChannelMode.SingleChannel; + string arguments = e.GetCommandLineArguments(); + Debug.WriteLine(arguments); + + e = new Mp4EncodingOptions(); + e.OutputFilename = "har"; + Debug.WriteLine(e.GetCommandLineArguments()); + + e.InputFiles = new string[]{"input.txt"}; + Debug.WriteLine(e.GetCommandLineArguments()); + } + } +} diff --git a/Backup/Tests/ProcessStreamWrapperTests.cs b/Backup/Tests/ProcessStreamWrapperTests.cs new file mode 100644 index 0000000..3b23008 --- /dev/null +++ b/Backup/Tests/ProcessStreamWrapperTests.cs @@ -0,0 +1,67 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using System.Threading; +using NUnit.Framework; + +namespace m3uTool.Tests +{ + [TestFixture] + public class ProcessStreamWrapperTests : ProgressCallbackTestBase + { + private class StdinToStdoutProcess : ProcessStreamWrapper + { + /// <summary> + /// Gets the process executable filename, such as "LAME.EXE", "FAAC.EXE", etc. + /// </summary> + /// <value>The process executable filename.</value> + protected override string ProcessExecutableFilename + { + get { return "StdinToStdout.exe"; } + } + + public StdinToStdoutProcess(ProcessArguments encOpt, Stream outputStream) + : base(encOpt, outputStream) + { + } + } + + [Test] + public void ProcessStreamWrapperFunctionalityTest() + { + string inputString = File.ReadAllText("testText.txt"); + string writtenString = ""; // string that was written to the process + + Stream inputStream = new MemoryStream(); + + StreamWriter sw = new StreamWriter(inputStream, Encoding.Default); + { + int writeSize = 127; + for (int i = 0; i < inputString.Length; i += writeSize) + { + // select a substring of characters from the input string + string substring = inputString.Substring(i, (i + writeSize < inputString.Length) ? writeSize : inputString.Length - i); + sw.Write(substring.ToCharArray(), 0, substring.Length); + writtenString += substring; + } + } + inputStream.Seek(0, SeekOrigin.Begin); + + Stream outputStream = new MemoryStream(); + using (StdinToStdoutProcess s = new StdinToStdoutProcess(new ProcessArguments(), outputStream)) + { + s.ProgressCallback = this; + s.ProcessInput(inputStream); + } + + outputStream.Seek(0, SeekOrigin.Begin); + string outputString; + using (StreamReader sr = new StreamReader(outputStream, Encoding.Default)) + { + outputString = sr.ReadToEnd(); + } + Assert.AreEqual(writtenString, outputString); + } + } +} diff --git a/Backup/Tests/ProgressCallbackTestBase.cs b/Backup/Tests/ProgressCallbackTestBase.cs new file mode 100644 index 0000000..01b8c1b --- /dev/null +++ b/Backup/Tests/ProgressCallbackTestBase.cs @@ -0,0 +1,102 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Text; + +namespace m3uTool.Tests +{ + /// <summary> + /// Implements the interface progress callback for testing output purposes + /// </summary> + public class ProgressCallbackTestBase : IProgressCallback + { + protected string mPreface = ""; + + #region IProgressCallback Members + + /// <summary> + /// Call this method from the worker thread to initialize + /// the progress callback. + /// </summary> + /// <param name="minimum">The minimum.</param> + /// <param name="maximum">The maximum.</param> + public virtual void Begin(int minimum, int maximum) + { + Debug.WriteLine(mPreface + String.Format("Begin min:{0} max:{1}", minimum, maximum)); + } + + /// <summary> + /// Call this method from the worker thread to initialize + /// the progress callback, without setting the range + /// </summary> + public virtual void Begin() + { + Debug.WriteLine(mPreface + String.Format("Begin")); + } + + /// <summary> + /// Call this method from the worker thread to reset the range in the + /// progress callback + /// </summary> + /// <param name="minimum">The minimum.</param> + /// <param name="maximum">The maximum.</param> + public virtual void SetRange(int minimum, int maximum) + { + Debug.WriteLine(mPreface + String.Format("SetRange min:{0} max:{1}", minimum, maximum)); + } + + /// <summary> + /// Call this method from the worker thread to update the progress text. + /// </summary> + /// <param name="text">The text.</param> + public virtual void SetText(string text) + { + Debug.WriteLine(mPreface + String.Format("SetText : {0}", text)); + } + + /// <summary> + /// Call this method from the worker thread to increase the progress + /// counter by a specified value. + /// </summary> + /// <param name="val">The val.</param> + public virtual void StepTo(int val) + { + Debug.WriteLine(mPreface + String.Format("StepTo : {0}", val)); + } + + /// <summary> + /// Call this method from the worker thread to step the progress meter to a + /// particular value. + /// </summary> + /// <param name="val">The val.</param> + public virtual void Increment(int val) + { + Debug.WriteLine(mPreface + String.Format("Increment : {0}", val)); + } + + /// <summary> + /// If this property is true, then you should abort work + /// </summary> + /// <value> + /// <c>true</c> if this instance is aborting; otherwise, <c>false</c>. + /// </value> + public virtual bool IsAborting + { + get + { + Debug.WriteLine(mPreface + "IsAborting"); + return false; + } + } + + /// <summary> + /// Call this method from the worker thread to finalize the progress meter + /// </summary> + public virtual void End() + { + Debug.WriteLine(mPreface + "End"); + } + + #endregion + } +} diff --git a/Backup/Tests/TranscodeTest.cs b/Backup/Tests/TranscodeTest.cs new file mode 100644 index 0000000..fb0dd3d --- /dev/null +++ b/Backup/Tests/TranscodeTest.cs @@ -0,0 +1,178 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Text; +using System.Threading; +using NUnit.Framework; +using Utility; + +namespace m3uTool.Tests +{ + [TestFixture] + public class TranscodeTest : ProgressCallbackTestBase + { + static string inputMp3 = "Disc 01 - 04.mp3"; + string outputAac = inputMp3.Replace(".mp3", ".aac"); + string outputWav = inputMp3.Replace(".mp3", ".wav"); + private ReaderProgress mReaderProgress = new ReaderProgress(); + private WriterProgress mWriterProgress = new WriterProgress(); + + private Stream mPipeStream; + + [Test] + public void TranscodeSingleThreadTest() + { + Mp3FileProperties mp3Info = new Mp3FileProperties(inputMp3); + Debug.WriteLine(mp3Info); + + mPipeStream = new MemoryStream(); + + Mp3ReadThread(); + if (mPipeStream.CanSeek) + mPipeStream.Seek(0, SeekOrigin.Begin); + WavWriteThread(); + } + + [Test] + public void TranscodeMultiThreadTest() + { + //mPipeStream = new BridgeStream(); + mPipeStream = new PipeStream(); + (mPipeStream as PipeStream).BlockLastReadBuffer = true; + (mPipeStream as PipeStream).MaxBufferLength = 1 * PipeStream.MB; + + // mPipeStream.MaxBufferLength = 100; + + Thread writeThread = new Thread(new ThreadStart(AacWriteThread)); + writeThread.Start(); + + Thread readThread = new Thread(new ThreadStart(Mp3ReadThread)); + readThread.Start(); + + readThread.Join(); + (mPipeStream as PipeStream).BlockLastReadBuffer = false; + writeThread.Join(); + } + + + public void Mp3ReadThread() + { + //int skip = 3; + //int limit = 1; + //string directory = @"C:\tmp\venus bak\media\audiobooks\Bret Easton Ellis - Lunar Park"; + //// string directory = @"C:\tmp\venus bak\media\audiobooks\Clarke, Arthur C. - Space Odyssey 01 - 2001, A Space Odyssey"; + //// string directory = @"C:\tmp\venus bak\media\audiobooks\Guitar Gym\Part 1 - Alternate Picking (Mp3 And Tab)"; + //string[] files = Directory.GetFiles(directory); + string[] files = new string[]{inputMp3}; + foreach (string mp3File in files) + { + //if (skip-- > 0) + // continue; + //if (limit-- == 0) + // break; + Debug.WriteLine(mp3File); + Mp3FileProperties fp = new Mp3FileProperties(mp3File); + Debug.WriteLine(fp.ToString()); + // Mp3 to wav + using (FileStream fileStream = new FileStream(mp3File, FileMode.Open)) + { + Mp3EncodingOptions opts = new Mp3EncodingOptions(); + opts.Infile = "-"; + opts.Outfile = "-"; + // opts.BitRate = BitRate.KBPS_32; + // opts.Freeformat = true; + opts.Mp3Input = true; + opts.DecodeWav = true; + // opts.DisableWavHeader = true; + Debug.WriteLine("MP3 : " + opts.GetCommandLineArguments()); + // opts.Freeformat = true; + // read from the sr, and write to the memory stream + using (Mp3Encoder encoder = new Mp3Encoder(opts, mPipeStream)) + { + encoder.ProgressCallback = mReaderProgress; + encoder.ProcessInput(fileStream); + } + } + } + } + + public void WavWriteThread() + { + using (FileStream fs = new FileStream(outputWav, FileMode.OpenOrCreate)) + using (StreamWriter sw = new StreamWriter(fs, Encoding.Default)) + using (StreamReader sr = new StreamReader(mPipeStream, Encoding.Default)) + { + char[] buffer = new char[1024 * 1024]; + + while (!sr.EndOfStream) + { + int readCount = sr.Read(buffer, 0, buffer.Length); + sw.Write(buffer, 0, readCount); + mWriterProgress.SetText("Wrote " + buffer.Length); + } + + } + } + + public void AacWriteThread() + { + Mp4EncodingOptions mp4Opts = new Mp4EncodingOptions(); + mp4Opts.OutputFilename = outputAac; + mp4Opts.RawPCMSwapInputBytes = true; + mp4Opts.RawPCMInputMode = true; + mp4Opts.RawPCMInputSampleRate = 16; + mp4Opts.RawPCMInputSampleSize = SampleRateFrequency.Hz_44100; + mp4Opts.RawInputChannels = ChannelMode.Stereo; + Debug.WriteLine("AAC : " + mp4Opts.GetCommandLineArguments()); + + using (Mp4Encoder encoder = new Mp4Encoder(mp4Opts)) + { + encoder.ProgressCallback = mWriterProgress; + encoder.ProcessInput(mPipeStream); + } + } + + + private class ReaderProgress : ProgressCallbackTestBase + { + public ReaderProgress() + { + mPreface = "Reader: "; + } + /// <summary> + /// Call this method from the worker thread to increase the progress + /// counter by a specified value. + /// </summary> + public override void StepTo(int val) + { + if (val % 10 == 0) + { + + base.StepTo(val); + } + } + } + + private class WriterProgress : ProgressCallbackTestBase + { + public WriterProgress() + { + + mPreface = "\t\t\t\tWriter: "; + } + /// <summary> + /// Call this method from the worker thread to increase the progress + /// counter by a specified value. + /// </summary> + public override void StepTo(int val) + { + if (val % 10 == 0) + { + + base.StepTo(val); + } + } + } + } +} diff --git a/Backup/m3uTool.csproj b/Backup/m3uTool.csproj new file mode 100644 index 0000000..7aba756 --- /dev/null +++ b/Backup/m3uTool.csproj @@ -0,0 +1,181 @@ +<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <PropertyGroup> + <ProjectType>Local</ProjectType> + <ProductVersion>8.0.50727</ProductVersion> + <SchemaVersion>2.0</SchemaVersion> + <ProjectGuid>{73C5A642-71EF-471C-A82D-70899904C495}</ProjectGuid> + <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> + <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> + <ApplicationIcon>App.ico</ApplicationIcon> + <AssemblyKeyContainerName> + </AssemblyKeyContainerName> + <AssemblyName>m3uTool</AssemblyName> + <AssemblyOriginatorKeyFile> + </AssemblyOriginatorKeyFile> + <DefaultClientScript>JScript</DefaultClientScript> + <DefaultHTMLPageLayout>Grid</DefaultHTMLPageLayout> + <DefaultTargetSchema>IE50</DefaultTargetSchema> + <DelaySign>false</DelaySign> + <OutputType>WinExe</OutputType> + <RootNamespace>m3uTool</RootNamespace> + <RunPostBuildEvent>OnBuildSuccess</RunPostBuildEvent> + <StartupObject> + </StartupObject> + <FileUpgradeFlags> + </FileUpgradeFlags> + <UpgradeBackupLocation> + </UpgradeBackupLocation> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> + <OutputPath>bin\Debug\</OutputPath> + <AllowUnsafeBlocks>false</AllowUnsafeBlocks> + <BaseAddress>285212672</BaseAddress> + <CheckForOverflowUnderflow>false</CheckForOverflowUnderflow> + <ConfigurationOverrideFile> + </ConfigurationOverrideFile> + <DefineConstants>DEBUG;TRACE</DefineConstants> + <DocumentationFile> + </DocumentationFile> + <DebugSymbols>true</DebugSymbols> + <FileAlignment>4096</FileAlignment> + <NoStdLib>false</NoStdLib> + <NoWarn> + </NoWarn> + <Optimize>false</Optimize> + <RegisterForComInterop>false</RegisterForComInterop> + <RemoveIntegerChecks>false</RemoveIntegerChecks> + <TreatWarningsAsErrors>false</TreatWarningsAsErrors> + <WarningLevel>4</WarningLevel> + <DebugType>full</DebugType> + <ErrorReport>prompt</ErrorReport> + <UseVSHostingProcess>true</UseVSHostingProcess> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> + <OutputPath>bin\Release\</OutputPath> + <AllowUnsafeBlocks>false</AllowUnsafeBlocks> + <BaseAddress>285212672</BaseAddress> + <CheckForOverflowUnderflow>false</CheckForOverflowUnderflow> + <ConfigurationOverrideFile> + </ConfigurationOverrideFile> + <DefineConstants>TRACE</DefineConstants> + <DocumentationFile> + </DocumentationFile> + <DebugSymbols>false</DebugSymbols> + <FileAlignment>4096</FileAlignment> + <NoStdLib>false</NoStdLib> + <NoWarn> + </NoWarn> + <Optimize>true</Optimize> + <RegisterForComInterop>false</RegisterForComInterop> + <RemoveIntegerChecks>false</RemoveIntegerChecks> + <TreatWarningsAsErrors>false</TreatWarningsAsErrors> + <WarningLevel>4</WarningLevel> + <DebugType>none</DebugType> + <ErrorReport>prompt</ErrorReport> + </PropertyGroup> + <ItemGroup> + <Reference Include="nunit.framework, Version=2.2.8.0, Culture=neutral, PublicKeyToken=96d09a1eb7f44a77, processorArchitecture=MSIL" /> + <Reference Include="PipeStream, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL"> + <SpecificVersion>False</SpecificVersion> + <HintPath>..\PipeStream\code\bin\Release\PipeStream.dll</HintPath> + </Reference> + <Reference Include="System"> + <Name>System</Name> + </Reference> + <Reference Include="System.Data"> + <Name>System.Data</Name> + </Reference> + <Reference Include="System.Drawing"> + <Name>System.Drawing</Name> + </Reference> + <Reference Include="System.Windows.Forms"> + <Name>System.Windows.Forms</Name> + </Reference> + <Reference Include="System.Xml"> + <Name>System.XML</Name> + </Reference> + </ItemGroup> + <ItemGroup> + <Content Include="App.ico" /> + <Compile Include="DoNothingProgressCallback.cs" /> + <Compile Include="Redirect.cs" /> + <Compile Include="Tests\HiPerfTimer.cs" /> + <Compile Include="KeyedList.cs" /> + <Compile Include="LongestCommonSequence.cs" /> + <Compile Include="ProcessArguments.cs" /> + <Compile Include="IProgressCallback.cs" /> + <Compile Include="Mp3Encoder.cs" /> + <Compile Include="Mp3EncodingOptions.cs" /> + <Compile Include="Mp3FileProperties.cs" /> + <Compile Include="ProcessStreamWrapper.cs" /> + <Compile Include="Mp4Encoder.cs" /> + <Compile Include="Mp4EncodingOptions.cs" /> + <Compile Include="AssemblyInfo.cs"> + <SubType>Code</SubType> + </Compile> + <Compile Include="M3uCreate.cs"> + <SubType>Code</SubType> + </Compile> + <Compile Include="M3uMakeByDirectory.cs"> + <SubType>Form</SubType> + </Compile> + <Compile Include="M3uToAac.cs"> + <SubType>Form</SubType> + </Compile> + <Compile Include="M3uToAac.Designer.cs"> + <DependentUpon>M3uToAac.cs</DependentUpon> + </Compile> + <Compile Include="Mp3ToAacBatch.cs"> + <SubType>Form</SubType> + </Compile> + <Compile Include="Mp3ToAacBatch.Designer.cs"> + <DependentUpon>Mp3ToAacBatch.cs</DependentUpon> + </Compile> + <Compile Include="Properties\Resources.Designer.cs"> + <AutoGen>True</AutoGen> + <DesignTime>True</DesignTime> + <DependentUpon>Resources.resx</DependentUpon> + </Compile> + <Compile Include="Tests\CreateM3uTests.cs"> + <SubType>Code</SubType> + </Compile> + <Compile Include="Tests\LongestCommonSequenceTests.cs" /> + <Compile Include="Tests\Mp3EncoderTests.cs" /> + <Compile Include="Tests\Mp3EncodingOptionsTests.cs" /> + <Compile Include="Tests\Mp3FilePropertiesTests.cs" /> + <Compile Include="Tests\Mp4EncoderTests.cs" /> + <Compile Include="Tests\Mp4EncodingOptionsTests.cs" /> + <Compile Include="Tests\ProgressCallbackTestBase.cs" /> + <Compile Include="Tests\ProcessStreamWrapperTests.cs" /> + <Compile Include="Tests\TranscodeTest.cs" /> + <EmbeddedResource Include="M3uMakeByDirectory.resx"> + <DependentUpon>M3uMakeByDirectory.cs</DependentUpon> + <SubType>Designer</SubType> + </EmbeddedResource> + <EmbeddedResource Include="M3uToAac.resx"> + <SubType>Designer</SubType> + <DependentUpon>M3uToAac.cs</DependentUpon> + </EmbeddedResource> + <EmbeddedResource Include="Mp3ToAacBatch.resx"> + <SubType>Designer</SubType> + <DependentUpon>Mp3ToAacBatch.cs</DependentUpon> + </EmbeddedResource> + <EmbeddedResource Include="Properties\Resources.resx"> + <SubType>Designer</SubType> + <Generator>ResXFileCodeGenerator</Generator> + <LastGenOutput>Resources.Designer.cs</LastGenOutput> + </EmbeddedResource> + </ItemGroup> + <ItemGroup> + <Folder Include="Resources\" /> + </ItemGroup> + <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" /> + <PropertyGroup> + <PreBuildEvent>copy /D /Y "$(ProjectDir)Tests\"*.mp3 "$(TargetDir)" +copy /D /Y "$(ProjectDir)Tests\"*.wav "$(TargetDir)" +copy /D /Y "$(ProjectDir)Tests\"*.exe "$(TargetDir)" +copy /D /Y "$(ProjectDir)Tests\"*.txt "$(TargetDir)"</PreBuildEvent> + <PostBuildEvent> + </PostBuildEvent> + </PropertyGroup> +</Project> \ No newline at end of file diff --git a/Backup/m3uTool.sln b/Backup/m3uTool.sln new file mode 100644 index 0000000..3d2346c --- /dev/null +++ b/Backup/m3uTool.sln @@ -0,0 +1,25 @@ +Microsoft Visual Studio Solution File, Format Version 9.00 +# Visual Studio 2005 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "m3uTool", "m3uTool.csproj", "{73C5A642-71EF-471C-A82D-70899904C495}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StdinToStdout", "..\StdinToStdout\StdinToStdout.csproj", "{94C7033C-C8A8-4104-95FE-0D11122080E2}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {73C5A642-71EF-471C-A82D-70899904C495}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {73C5A642-71EF-471C-A82D-70899904C495}.Debug|Any CPU.Build.0 = Debug|Any CPU + {73C5A642-71EF-471C-A82D-70899904C495}.Release|Any CPU.ActiveCfg = Release|Any CPU + {73C5A642-71EF-471C-A82D-70899904C495}.Release|Any CPU.Build.0 = Release|Any CPU + {94C7033C-C8A8-4104-95FE-0D11122080E2}.Debug|Any CPU.ActiveCfg = Release|Any CPU + {94C7033C-C8A8-4104-95FE-0D11122080E2}.Debug|Any CPU.Build.0 = Release|Any CPU + {94C7033C-C8A8-4104-95FE-0D11122080E2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {94C7033C-C8A8-4104-95FE-0D11122080E2}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/BridgeStream.cs b/BridgeStream.cs new file mode 100644 index 0000000..6485bcc --- /dev/null +++ b/BridgeStream.cs @@ -0,0 +1,382 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Threading; + +namespace m3uTool +{ + /// <summary> + /// BridgeStream provides a console-like "pipe" data stream for use between two threaded processes + /// in a producer-consumer type problem. + /// </summary> + /// <license> + /// Copyright (c) 2006 James Kolpack + /// + /// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + /// associated documentation files (the "Software"), to deal in the Software without restriction, + /// including without limitation the rights to use, copy, modify, merge, publish, distribute, + /// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + /// furnished to do so, subject to the following conditions: + /// + /// The above copyright notice and this permission notice shall be included in all copies or + /// substantial portions of the Software. + /// + /// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + /// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR + /// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + /// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT + /// OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + /// OTHER DEALINGS IN THE SOFTWARE. + /// </license> + public class BridgeStream : Stream + { + #region Private members + /// <summary> + /// Queue of bytes provides the datastructure for transmitting from an + /// input stream to an output stream. + /// </summary> + /// <remarks>Possibly inefficient.</remarks> + private Queue<byte> mBuffer = new Queue<byte>(); + + /// <summary> + /// Event occurs after data is written. + /// </summary> + private ManualResetEvent mWriteEvent; + + /// <summary> + /// Event occurs after data is read. + /// </summary> + private ManualResetEvent mReadEvent; + + /// <summary> + /// Indicates that the input stream has been flushed and that + /// all remaining data should be written to the output stream. + /// </summary> + private bool mFlushed = false; + + /// <summary> + /// Maximum number of bytes to store in the buffer. + /// </summary> + private long mMaxBufferLength = 200 * MB; + + /// <summary> + /// Setting this to true will cause Read() to block if it appears + /// that it will run out of data. + /// </summary> + private bool mBlockLastRead = false; + + #endregion + + #region Public Const members + + /// <summary> + /// Number of bytes in a kilobyte + /// </summary> + public const long KB = 1024; + + /// <summary> + /// Number of bytes in a megabyte + /// </summary> + public const long MB = KB * 1024; + + #endregion + + #region Public properties + + /// <summary> + /// Gets or sets the maximum number of bytes to store in the buffer. + /// </summary> + /// <value>The length of the max buffer.</value> + public long MaxBufferLength + { + get { return mMaxBufferLength; } + set { mMaxBufferLength = value; } + } + + /// <summary> + /// Gets or sets a value indicating whether to block last read method before the buffer is empty. + /// When true, Read() will block until it can fill the passed in buffer and count. + /// When false, Read() will not block, returning all the available buffer data. + /// </summary> + /// <remarks> + /// Setting to true will remove the possibility of ending a stream reader prematurely. + /// </remarks> + /// <value> + /// <c>true</c> if block last read method before the buffer is empty; otherwise, <c>false</c>. + /// </value> + public bool BlockLastReadBuffer + { + get + { + return mBlockLastRead; + } + set + { + mBlockLastRead = value; + + // when turning off the block last read, signal Read() that it may now read the rest of the buffer. + if (!mBlockLastRead) + mWriteEvent.Set(); + } + } + + #endregion + + #region Constructor + + + /// <summary> + /// Initializes a new instance of the <see cref="BridgeStream"/> class. + /// </summary> + public BridgeStream() + { + mWriteEvent = new ManualResetEvent(false); + mReadEvent = new ManualResetEvent(false); + } + + #endregion + + #region Stream overide methods + + ///<summary> + ///Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + ///</summary> + ///<filterpriority>2</filterpriority> + public new void Dispose() + { + // clear the internal buffer + mBuffer.Clear(); + } + + ///<summary> + ///When overridden in a derived class, clears all buffers for this stream and causes any buffered data to be written to the underlying device. + ///</summary> + /// + ///<exception cref="T:System.IO.IOException">An I/O error occurs. </exception><filterpriority>2</filterpriority> + public override void Flush() + { + mFlushed = true; + mWriteEvent.Set(); // signal any waiting read events. + } + + ///<summary> + ///When overridden in a derived class, sets the position within the current stream. + ///</summary> + /// + ///<returns> + ///The new position within the current stream. + ///</returns> + /// + ///<param name="offset">A byte offset relative to the origin parameter. </param> + ///<param name="origin">A value of type <see cref="T:System.IO.SeekOrigin"></see> indicating the reference point used to obtain the new position. </param> + ///<exception cref="T:System.IO.IOException">An I/O error occurs. </exception> + ///<exception cref="T:System.NotSupportedException">The stream does not support seeking, such as if the stream is constructed from a pipe or console output. </exception> + ///<exception cref="T:System.ObjectDisposedException">Methods were called after the stream was closed. </exception><filterpriority>1</filterpriority> + public override long Seek(long offset, SeekOrigin origin) + { + throw new NotImplementedException(); + } + + ///<summary> + ///When overridden in a derived class, sets the length of the current stream. + ///</summary> + /// + ///<param name="value">The desired length of the current stream in bytes. </param> + ///<exception cref="T:System.NotSupportedException">The stream does not support both writing and seeking, such as if the stream is constructed from a pipe or console output. </exception> + ///<exception cref="T:System.IO.IOException">An I/O error occurs. </exception> + ///<exception cref="T:System.ObjectDisposedException">Methods were called after the stream was closed. </exception><filterpriority>2</filterpriority> + public override void SetLength(long value) + { + throw new NotImplementedException(); + } + + + ///<summary> + ///When overridden in a derived class, reads a sequence of bytes from the current stream and advances the position within the stream by the number of bytes read. + ///</summary> + /// + ///<returns> + ///The total number of bytes read into the buffer. This can be less than the number of bytes requested if that many bytes are not currently available, or zero (0) if the end of the stream has been reached. + ///</returns> + /// + ///<param name="offset">The zero-based byte offset in buffer at which to begin storing the data read from the current stream. </param> + ///<param name="count">The maximum number of bytes to be read from the current stream. </param> + ///<param name="buffer">An array of bytes. When this method returns, the buffer contains the specified byte array with the values between offset and (offset + count - 1) replaced by the bytes read from the current source. </param> + ///<exception cref="T:System.ArgumentException">The sum of offset and count is larger than the buffer length. </exception> + ///<exception cref="T:System.ObjectDisposedException">Methods were called after the stream was closed. </exception> + ///<exception cref="T:System.NotSupportedException">The stream does not support reading. </exception> + ///<exception cref="T:System.ArgumentNullException">buffer is null. </exception> + ///<exception cref="T:System.IO.IOException">An I/O error occurs. </exception> + ///<exception cref="T:System.ArgumentOutOfRangeException">offset or count is negative. </exception><filterpriority>1</filterpriority> + public override int Read(byte[] buffer, int offset, int count) + { + if (offset != 0) + throw new NotImplementedException("Offsets with value of non-zero are not supported"); + if (buffer == null) + throw new ArgumentException("Buffer is null"); + if (offset + count > buffer.Length) + throw new ArgumentException("The sum of offset and count is greater than the buffer length. "); + if (offset < 0 || count < 0) + throw new ArgumentOutOfRangeException("offset or count is negative."); + if (BlockLastReadBuffer && count >= mMaxBufferLength) + throw new ArgumentException("count > mMaxBufferLength"); + + if (count == 0) + return 0; + + int readLength = 0; + + while ((Length < count && !mFlushed) || + (Length < (count + 1) && BlockLastReadBuffer)) + { + mWriteEvent.Reset(); // turn off an existing write signal + mReadEvent.Set(); // signal any waiting reads, preventing deadlock + mWriteEvent.WaitOne(); // wait until a write occurs + } + lock (mBuffer) + { + // fill the read buffer + for (; readLength < count && Length > 0; readLength++) + { + buffer[readLength] = mBuffer.Dequeue(); + } + mReadEvent.Set(); + } + return readLength; + } + + ///<summary> + ///When overridden in a derived class, writes a sequence of bytes to the current stream and advances the current position within this stream by the number of bytes written. + ///</summary> + /// + ///<param name="offset">The zero-based byte offset in buffer at which to begin copying bytes to the current stream. </param> + ///<param name="count">The number of bytes to be written to the current stream. </param> + ///<param name="buffer">An array of bytes. This method copies count bytes from buffer to the current stream. </param> + ///<exception cref="T:System.IO.IOException">An I/O error occurs. </exception> + ///<exception cref="T:System.NotSupportedException">The stream does not support writing. </exception> + ///<exception cref="T:System.ObjectDisposedException">Methods were called after the stream was closed. </exception> + ///<exception cref="T:System.ArgumentNullException">buffer is null. </exception> + ///<exception cref="T:System.ArgumentException">The sum of offset and count is greater than the buffer length. </exception> + ///<exception cref="T:System.ArgumentOutOfRangeException">offset or count is negative. </exception><filterpriority>1</filterpriority> + public override void Write(byte[] buffer, int offset, int count) + { + if (buffer == null) + throw new ArgumentException("Buffer is null"); + if (offset + count > buffer.Length) + throw new ArgumentException("The sum of offset and count is greater than the buffer length. "); + if (offset < 0 || count < 0) + throw new ArgumentOutOfRangeException("offset or count is negative."); + if (count == 0) + return; + while (Length >= mMaxBufferLength) + { + mReadEvent.Reset(); + //Debug.WriteLine(String.Format("\t\t\tWrite(): mBuffer.Count({0}) >= mMaxBufferLength({1})", mBuffer.Count, mMaxBufferLength)); + mWriteEvent.Set(); // release any blocked read events + mReadEvent.WaitOne(); + } + lock (mBuffer) + { + mFlushed = false; // if it were flushed before, it soon will not be. + + // queue up the buffer data + for (int i = offset; i < count; i++) + { + mBuffer.Enqueue(buffer[i]); + } + + mWriteEvent.Set(); // signal that write has occured + } + } + + ///<summary> + ///When overridden in a derived class, gets a value indicating whether the current stream supports reading. + ///</summary> + /// + ///<returns> + ///true if the stream supports reading; otherwise, false. + ///</returns> + ///<filterpriority>1</filterpriority> + public override bool CanRead + { + get + { + return true; + } + } + + ///<summary> + ///When overridden in a derived class, gets a value indicating whether the current stream supports seeking. + ///</summary> + /// + ///<returns> + ///true if the stream supports seeking; otherwise, false. + ///</returns> + ///<filterpriority>1</filterpriority> + public override bool CanSeek + { + get + { + return false; + } + } + + ///<summary> + ///When overridden in a derived class, gets a value indicating whether the current stream supports writing. + ///</summary> + /// + ///<returns> + ///true if the stream supports writing; otherwise, false. + ///</returns> + ///<filterpriority>1</filterpriority> + public override bool CanWrite + { + get + { + return true; + } + } + + ///<summary> + ///When overridden in a derived class, gets the length in bytes of the stream. + ///</summary> + /// + ///<returns> + ///A long value representing the length of the stream in bytes. + ///</returns> + /// + ///<exception cref="T:System.NotSupportedException">A class derived from Stream does not support seeking. </exception> + ///<exception cref="T:System.ObjectDisposedException">Methods were called after the stream was closed. </exception><filterpriority>1</filterpriority> + public override long Length + { + get + { + return mBuffer.Count; + } + } + + ///<summary> + ///When overridden in a derived class, gets or sets the position within the current stream. + ///</summary> + /// + ///<returns> + ///The current position within the stream. + ///</returns> + /// + ///<exception cref="T:System.IO.IOException">An I/O error occurs. </exception> + ///<exception cref="T:System.NotSupportedException">The stream does not support seeking. </exception> + ///<exception cref="T:System.ObjectDisposedException">Methods were called after the stream was closed. </exception><filterpriority>1</filterpriority> + public override long Position + { + get + { + return 0; + } + set + { + throw new NotImplementedException(); + } + } + #endregion + } +} diff --git a/CreateM3u.cs b/CreateM3u.cs new file mode 100644 index 0000000..913d3ad --- /dev/null +++ b/CreateM3u.cs @@ -0,0 +1,149 @@ +using System; +using System.Diagnostics; +using System.IO; + +namespace m3uTool +{ + /// <summary> + /// Summary description for m3uFromDirectory. + /// </summary> + public class CreateM3u + { + /// <summary> + /// Use the parent's directory name for the m3u name + /// </summary> + private bool mUseGeneratedOuputFilename = true; + + /// <summary> + /// Directory to grab the files from + /// </summary> + private DirectoryInfo mInputDir; + + /// <summary> + /// Ouput m3u filename, if it isn't generated + /// </summary> + private string mOutputFilename = null; + + /// <summary> + /// Filter for input filenames + /// </summary> + private string mFileFilter = "*.mp3"; + + public bool UseGeneratedOuputFilename + { + get + { + return mUseGeneratedOuputFilename; + } + set + { + mUseGeneratedOuputFilename = value; + } + } + + public string OutputFilename + { + get + { + if (mUseGeneratedOuputFilename) + { + Debug.WriteLine("OutputFilename:" + mInputDir.FullName + "\\" + mInputDir.Name + ".m3u"); + return mInputDir.FullName + "\\" + mInputDir.Name + ".m3u"; + } + return mOutputFilename; + } + set + { + mOutputFilename = value; + mUseGeneratedOuputFilename = false; + } + } + + public CreateM3u(string dir) : this (new DirectoryInfo(dir)) + { + } + + public CreateM3u(DirectoryInfo inputDir) + { + mInputDir = inputDir; + } + + /// <summary> + /// + /// </summary> + /// <remarks> + /// dir: "\foo\bar\" file: "\foo\bar\myfile" output: "" + /// dir: "\foo\" file: "\foo\bar\myfile" output: ".." + /// dir: "\" file: "\foo\bar\myfile" output: "..\.." + /// dir: "\har" file: "\foo\bar\myfile" output: "..\..\har" + /// dir: "\foo\bar\hey\" file: "\foo\bar\myfile" output: "hey\" + /// </remarks> + /// <param name="dir"></param> + /// <param name="file"></param> + /// <returns></returns> + private string GetPath(DirectoryInfo dir, FileInfo file) + { + string source = dir.FullName; + string target = file.Directory.FullName; + + return GetPath(source, target); + } + + private string GetPath(string source, string target) + { + int lastCommonIndex = GetLastCommonIndex(source, target); + Debug.WriteLine(String.Format("s:{0} , t:{1} lastCommon:{2} ", source, target, lastCommonIndex)); + + int slashCount = CountCharacters(source, lastCommonIndex + 1, '\\'); + + string path = ""; + for (int i = 0; i < slashCount; i++) + { + path += "..\\"; + } + path += target.Substring(lastCommonIndex); + if (path.Length > 0 && path[path.Length - 1] != '\\') + path += "\\"; + Debug.WriteLine(path); + return path; + } + + private int CountCharacters(string str, int startIdx, char c) + { + int charsCount = 0; + int currIdx = startIdx; + while (currIdx < str.Length && (currIdx = str.IndexOf(c, currIdx) + 1) != 0) + charsCount++; + return charsCount; + } + + private int GetLastCommonIndex(string str1, string str2) + { + int i = 0; + while (i < str1.Length && i < str2.Length && Char.ToLower(str1[i]) == Char.ToLower(str2[i])) + i++; + return i; + } + + public void Create() + { + string fileList = ""; + DirectoryInfo ouputDirectory = (new FileInfo(OutputFilename)).Directory; + foreach (FileInfo info in mInputDir.GetFiles(mFileFilter)) + { + string fileName = GetPath(ouputDirectory, info) + info.Name; + Debug.WriteLine(fileName); + fileList += fileName + Environment.NewLine; + } + SaveToFile(fileList, OutputFilename); + } + + private void SaveToFile(string contents, string outputFilename) + { + using (TextWriter tw = new StreamWriter(outputFilename)) + { + tw.Write(contents); + } + } + } +} diff --git a/DoNothingProgressCallback.cs b/DoNothingProgressCallback.cs new file mode 100644 index 0000000..165175c --- /dev/null +++ b/DoNothingProgressCallback.cs @@ -0,0 +1,81 @@ +namespace m3uTool +{ + public class DoNothingProgressCallback : IProgressCallback + { + #region IProgressCallback Members + + /// <summary> + /// Call this method from the worker thread to initialize + /// the progress callback. + /// </summary> + /// <param name="minimum">The minimum.</param> + /// <param name="maximum">The maximum.</param> + public void Begin(int minimum, int maximum) + { + } + + /// <summary> + /// Call this method from the worker thread to initialize + /// the progress callback, without setting the range + /// </summary> + public void Begin() + { + } + + /// <summary> + /// Call this method from the worker thread to reset the range in the + /// progress callback + /// </summary> + /// <param name="minimum">The minimum.</param> + /// <param name="maximum">The maximum.</param> + public void SetRange(int minimum, int maximum) + { + } + + /// <summary> + /// Call this method from the worker thread to update the progress text. + /// </summary> + /// <param name="text">The text.</param> + public void SetText(string text) + { + } + + /// <summary> + /// Call this method from the worker thread to increase the progress + /// counter by a specified value. + /// </summary> + /// <param name="val">The val.</param> + public void StepTo(int val) + { + } + + /// <summary> + /// Call this method from the worker thread to step the progress meter to a + /// particular value. + /// </summary> + /// <param name="val">The val.</param> + public void Increment(int val) + { + } + + /// <summary> + /// If this property is true, then you should abort work + /// </summary> + /// <value> + /// <c>true</c> if this instance is aborting; otherwise, <c>false</c>. + /// </value> + public bool IsAborting + { + get { return false; } + } + + /// <summary> + /// Call this method from the worker thread to finalize the progress meter + /// </summary> + public void End() + { + } + + #endregion + } +} \ No newline at end of file diff --git a/Encoder.cs b/Encoder.cs new file mode 100644 index 0000000..d4463ea --- /dev/null +++ b/Encoder.cs @@ -0,0 +1,274 @@ +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 Encoder : IDisposable + { + protected ProcessArguments mEncOpt; + protected Process mEncoderProcess; + protected const int mBufferLength = 16384; + protected StreamWriter mOutputStreamWriter = null; + protected IProgressCallback mProgressCallback = new DoNothingProgressCallback(); + protected const byte mEndOfStream = 26; + + /// <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; + } + + /// <summary> + /// + /// </summary> + /// <param name="encOpt"></param> + public Encoder(ProcessArguments encOpt) : this (encOpt, null) + { + } + + /// <summary> + /// Initializes a new instance of the <see cref="Encoder"/> class. + /// </summary> + /// <param name="encOpt">The enc opt.</param> + /// <param name="outputStream">Output to this stream</param> + public Encoder(ProcessArguments encOpt, Stream outputStream) + { + //if (!outputStream.CanWrite) + // throw new ArgumentException("Can't write to output stream"); + //if (!string.IsNullOrEmpty(encOpt.OutputFilename) && encOpt.OutputFilename.Trim() != "-") + // throw new ArgumentException("Trying to output to both a stream and a file " + encOpt.OutputFilename); + //encOpt.OutputFilename = "-"; + + mEncOpt = encOpt; + + mEncoderProcess = CreateEncoderProcess(mEncOpt, ProcessExecutableFilename); + if (outputStream != null) + { + mOutputStreamWriter = new StreamWriter(outputStream, Encoding.Default); + mEncoderProcess.StartInfo.RedirectStandardOutput = true; + } + mEncoderProcess.StartInfo.RedirectStandardError = true; + + mEncoderProcess.Start(); + if (mOutputStreamWriter != null) + { + Thread thread = new Thread(new ThreadStart(GetStandardOutput)); + thread.Start(); + } + + Thread standardErrorThread = new Thread(new ThreadStart(GetStandardError)); + standardErrorThread.Start(); + } + + private Process CreateEncoderProcess(ProcessArguments encOpt, string encoderFilename) + { + Process encoderProcess = new Process(); + encoderProcess.StartInfo.RedirectStandardInput = true; + encoderProcess.StartInfo.UseShellExecute = false; + encoderProcess.StartInfo.FileName = encoderFilename; + encoderProcess.StartInfo.Arguments = encOpt.GetCommandLineArguments(); + encoderProcess.StartInfo.WorkingDirectory = Environment.CurrentDirectory; + encoderProcess.StartInfo.CreateNoWindow = true; + + encoderProcess.EnableRaisingEvents = true; + encoderProcess.Exited += new EventHandler(encoderProcess_Exited); + return encoderProcess; + } + + void encoderProcess_Exited(object sender, EventArgs e) + { + mProgressCallback.SetText("encoderProcess_Exited"); + } + + /// <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 Encode(char[] buffer, int startIndex, int count) + { + // Debug.WriteLine("-"); + mEncoderProcess.StandardInput.Write(buffer, startIndex, count); + if (count != mBufferLength) + mProgressCallback.SetText("Encode : " + count); + } + + public void WriteEndOfStream() + { + if (!mEncoderProcess.HasExited) + { + mEncoderProcess.StandardInput.BaseStream.Write(new byte[] { mEndOfStream }, 0, 1); + mEncoderProcess.StandardInput.BaseStream.Flush(); + } + } + + 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; + } + + /// <summary> + /// Encodes all of the data in the input stream + /// </summary> + /// <param name="inputStream"></param> + public void Encode(Stream inputStream) + { + if (!inputStream.CanRead) + throw new ArgumentException("Can't read from inputStream"); + + mProgressCallback.SetText("Encode: StreamReader sr"); + + long inputCount = inputStream.Length; + long previousInputCount = inputStream.Length; + long processCount = 0; + int percentageComplete = 0; + + using (StreamReader sr = new StreamReader(inputStream, Encoding.Default)) + { + mProgressCallback.Begin(0, 100); + mProgressCallback.SetText(mEncOpt.GetCommandLineArguments()); + + char[] buffer = new char[mBufferLength]; + + while (!sr.EndOfStream) + { + int readLength = sr.Read(buffer, 0, mBufferLength); + if (buffer[readLength - 1] == mEndOfStream) + readLength--; + Encode(buffer, 0, readLength); + + // update progress + processCount += readLength; + previousInputCount = inputCount; + inputCount = inputStream.Length; + int currentPercentageComplete = GetPercentDone(previousInputCount, inputCount, processCount, readLength); + if (currentPercentageComplete != percentageComplete) + { + percentageComplete = currentPercentageComplete; + mProgressCallback.StepTo(percentageComplete); + } + } + } + mProgressCallback.SetText("Encode: Done!"); + } + + /// <summary> + /// Open this file and encode it. + /// </summary> + /// <param name="filename"></param> + public void Encode(string filename) + { + using (FileStream inputStream = File.OpenRead(filename)) + { + Encode(inputStream); + } + } + private void GetStandardOutput() + { + char[] writeBuffer = new char[mBufferLength]; + try + { + while (!mEncoderProcess.StandardOutput.EndOfStream) + { + int readLength = mEncoderProcess.StandardOutput.Read(writeBuffer, 0, writeBuffer.Length); + mOutputStreamWriter.Write(writeBuffer, 0, readLength); + //Debug.WriteLine("GetStandardOutput() " + "mOutputStreamWriter.Write : " + readLength); + } + } + catch(Exception ex) + { + mProgressCallback.SetText(ex.ToString()); + } + mProgressCallback.SetText("GetStandardOutput() " + "Done!"); + } + + private void GetStandardError() + { + // char[] writeBuffer = new char[10]; + try + { + while (!mEncoderProcess.StandardError.EndOfStream) + { + string line = mEncoderProcess.StandardError.ReadLine(); + //int readLength = mEncoderProcess.StandardError.Read(writeBuffer, 0, writeBuffer.Length); + Debug.WriteLine("GetStandardError() " + line); + } + } + catch (Exception ex) + { + mProgressCallback.SetText(ex.ToString()); + } + mProgressCallback.SetText("GetStandardError() " + "Done!"); + } + #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 (mEncoderProcess != null && !mEncoderProcess.HasExited) + { + //WriteEndOfStream(); + //Debug.Write("."); + if (count++ > 50) + mEncoderProcess.Kill(); + Thread.Sleep(10); + } + mEncoderProcess.WaitForExit(); + if (mOutputStreamWriter != null) + mOutputStreamWriter.Flush(); + //if (mEncoderProcess.ExitCode != 0) + // throw new ApplicationException("Exit code " + mEncoderProcess.ExitCode); + mProgressCallback.SetText("Dispose() with exit code " + mEncoderProcess.ExitCode); + + + //if (mEncoderProcess != null && mEncoderProcess.Responding) + // mEncoderProcess.Close(); + } + + #endregion + } +} diff --git a/IProgressCallback.cs b/IProgressCallback.cs new file mode 100644 index 0000000..f7c35d4 --- /dev/null +++ b/IProgressCallback.cs @@ -0,0 +1,65 @@ +using System; + +/// <summary> +/// This defines an interface which can be implemented by UI elements +/// which indicate the progress of a long operation. +/// (See ProgressWindow for a typical implementation) +/// </summary> +/// <remarks>Based on http://www.codeproject.com/cs/miscctrl/progressdialog.asp </remarks> +public interface IProgressCallback +{ + /// <summary> + /// If this property is true, then you should abort work + /// </summary> + /// <value> + /// <c>true</c> if this instance is aborting; otherwise, <c>false</c>. + /// </value> + bool IsAborting { get; } + + /// <summary> + /// Call this method from the worker thread to initialize + /// the progress callback. + /// </summary> + /// <param name="minimum">The minimum.</param> + /// <param name="maximum">The maximum.</param> + void Begin(int minimum, int maximum); + + /// <summary> + /// Call this method from the worker thread to initialize + /// the progress callback, without setting the range + /// </summary> + void Begin(); + + /// <summary> + /// Call this method from the worker thread to reset the range in the + /// progress callback + /// </summary> + /// <param name="minimum">The minimum.</param> + /// <param name="maximum">The maximum.</param> + void SetRange(int minimum, int maximum); + + /// <summary> + /// Call this method from the worker thread to update the progress text. + /// </summary> + /// <param name="text">The text.</param> + void SetText(String text); + + /// <summary> + /// Call this method from the worker thread to increase the progress + /// counter by a specified value. + /// </summary> + /// <param name="val">The val.</param> + void StepTo(int val); + + /// <summary> + /// Call this method from the worker thread to step the progress meter to a + /// particular value. + /// </summary> + /// <param name="val">The val.</param> + void Increment(int val); + + /// <summary> + /// Call this method from the worker thread to finalize the progress meter + /// </summary> + void End(); +} \ No newline at end of file diff --git a/KeyedList.cs b/KeyedList.cs new file mode 100644 index 0000000..b7c474c --- /dev/null +++ b/KeyedList.cs @@ -0,0 +1,407 @@ +/* +Copyright (c) 2005, Marc Clifton +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this list + of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, this + list of conditions and the following disclaimer in the documentation and/or other + materials provided with the distribution. + +* Neither the name of MyXaml nor the names of its contributors may be + used to endorse or promote products derived from this software without specific + prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ + +using System; +using System.Collections; +using System.Collections.Generic; + +namespace Clifton.Collections.Generic +{ + [Serializable] + public class KeyedList<K, V> : IDictionary<K, V>, IList<KeyValuePair<K, V>> + { + private readonly List<KeyValuePair<K, V>> objectList = new List<KeyValuePair<K, V>>(); + private readonly Dictionary<K, V> objectTable = new Dictionary<K, V>(); + + /// <summary> + /// Get the ordered list of keys. + /// This is a copy of the keys in the original Dictionary. + /// </summary> + public List<K> OrderedKeys + { + get + { + var retList = new List<K>(); + + foreach (var kvp in objectList) + { + retList.Add(kvp.Key); + } + + return retList; + } + } + + /// <summary> + /// Get the ordered list of values. + /// This is a copy of the values in the original Dictionary. + /// </summary> + public List<V> OrderedValues + { + get + { + var retList = new List<V>(); + + foreach (var kvp in objectList) + { + retList.Add(kvp.Value); + } + + return retList; + } + } + + /// <summary> + /// Gets the Dictionary class backing the KeyedList. + /// </summary> + public Dictionary<K, V> ObjectTable + { + get { return objectTable; } + } + + #region IDictionary<K,V> Members + + /// <summary> + /// Returns false. + /// </summary> + public bool IsReadOnly + { + get { return false; } + } + + /// <summary> + /// Returns the number of entries in the KeyedList. + /// </summary> + public int Count + { + get { return objectList.Count; } + } + + /// <summary> + /// Get/Set the value associated with the specified key. + /// </summary> + /// <param name="key">The key.</param> + /// <returns>The associated value.</returns> + public virtual V this[K key] + { + get { return objectTable[key]; } + set + { + if (objectTable.ContainsKey(key)) + { + objectTable[key] = value; + objectList[IndexOf(key)] = new KeyValuePair<K, V>(key, value); + } + else + { + Add(key, value); + } + } + } + + /// <summary> + /// Get an unordered list of keys. + /// This collection refers back to the keys in the original Dictionary. + /// </summary> + public ICollection<K> Keys + { + get { return objectTable.Keys; } + } + + /// <summary> + /// Get an unordered list of values. + /// This collection refers back to the values in the original Dictionary. + /// </summary> + public ICollection<V> Values + { + get { return objectTable.Values; } + } + + /// <summary> + /// Clears all entries in the KeyedList. + /// </summary> + public void Clear() + { + objectTable.Clear(); + objectList.Clear(); + } + + /// <summary> + /// Test if the KeyedList contains the key. + /// </summary> + /// <param name="key">The key.</param> + /// <returns>True if the key is found.</returns> + public bool ContainsKey(K key) + { + return objectTable.ContainsKey(key); + } + + /// <summary> + /// Test if the KeyedList contains the key in the key-value pair. + /// </summary> + /// <param name="kvp">The key-value pair.</param> + /// <returns>True if the key is found.</returns> + public bool Contains(KeyValuePair<K, V> kvp) + { + return objectTable.ContainsKey(kvp.Key); + } + + /// <summary> + /// Adds a key-value pair to the KeyedList. + /// </summary> + /// <param name="key">The key.</param> + /// <param name="value">The associated value.</param> + public void Add(K key, V value) + { + objectTable.Add(key, value); + objectList.Add(new KeyValuePair<K, V>(key, value)); + } + + /// <summary> + /// Adds a key-value pair to the KeyedList. + /// </summary> + /// <param name="kvp">The KeyValuePair instance.</param> + public void Add(KeyValuePair<K, V> kvp) + { + Add(kvp.Key, kvp.Value); + } + + /// <summary> + /// Copy the entire key-value pairs to the KeyValuePair array, starting + /// at the specified index of the target array. The array is populated + /// as an ordered list. + /// </summary> + /// <param name="kvpa">The KeyValuePair array.</param> + /// <param name="idx">The position to start the copy.</param> + public void CopyTo(KeyValuePair<K, V>[] kvpa, int idx) + { + objectList.CopyTo(kvpa, idx); + } + + /// <summary> + /// Remove the entry. + /// </summary> + /// <param name="key">The key identifying the key-value pair.</param> + /// <returns>True if removed.</returns> + public bool Remove(K key) + { + bool found = objectTable.Remove(key); + + if (found) + { + objectList.RemoveAt(IndexOf(key)); + } + + return found; + } + + /// <summary> + /// Remove the key in the specified KeyValuePair instance. The Value + /// property is ignored. + /// </summary> + /// <param name="kvp">The key-value identifying the entry.</param> + /// <returns>True if removed.</returns> + public bool Remove(KeyValuePair<K, V> kvp) + { + return Remove(kvp.Key); + } + + /// <summary> + /// Attempt to get the value, given the key, without throwing an exception if not found. + /// </summary> + /// <param name="key">The key indentifying the entry.</param> + /// <param name="val">The value, if found.</param> + /// <returns>True if found.</returns> + public bool TryGetValue(K key, out V val) + { + return objectTable.TryGetValue(key, out val); + } + + /// <summary> + /// Returns an ordered System.Collections KeyValuePair objects. + /// </summary> + IEnumerator IEnumerable.GetEnumerator() + { + return objectList.GetEnumerator(); + } + + /// <summary> + /// Returns an ordered KeyValuePair enumerator. + /// </summary> + IEnumerator<KeyValuePair<K, V>> IEnumerable<KeyValuePair<K, V>>.GetEnumerator() + { + return objectList.GetEnumerator(); + } + + #endregion + + #region IList<KeyValuePair<K,V>> Members + + /// <summary> + /// Get/Set the value at the specified index. + /// </summary> + /// <param name="idx">The index.</param> + /// <returns>The value.</returns> + public KeyValuePair<K, V> this[int idx] + { + get + { + if (idx < 0 || idx >= Count) + { + throw new ArgumentOutOfRangeException("index"); + } + + return objectList[idx]; + } + set + { + if (idx < 0 || idx >= Count) + { + throw new ArgumentOutOfRangeException("index"); + } + + objectList[idx] = value; + objectTable[value.Key] = value.Value; + } + } + + /// <summary> + /// Given the key-value pair, find the index. + /// </summary> + /// <param name="kvp">The key-value pair.</param> + /// <returns>The index, or -1 if not found.</returns> + public int IndexOf(KeyValuePair<K, V> kvp) + { + return IndexOf(kvp.Key); + } + + /// <summary> + /// Insert the key-value pair at the specified index location. + /// </summary> + /// <param name="idx">The key.</param> + /// <param name="kvp">The value.</param> + public void Insert(int idx, KeyValuePair<K, V> kvp) + { + if ((idx < 0) || (idx > Count)) + { + throw new ArgumentOutOfRangeException("index"); + } + + objectTable.Add(kvp.Key, kvp.Value); + objectList.Insert(idx, kvp); + } + + /// <summary> + /// Remove the entry at the specified index. + /// </summary> + /// <param name="idx">The index to the entry to be removed.</param> + public void RemoveAt(int idx) + { + if ((idx < 0) || (idx >= Count)) + { + throw new ArgumentOutOfRangeException("index"); + } + + objectTable.Remove(objectList[idx].Key); + objectList.RemoveAt(idx); + } + + #endregion + + /// <summary> + /// Returns the key at the specified index. + /// </summary> + /// <param name="idx">The index.</param> + /// <returns>The key at the index.</returns> + public K GetKey(int idx) + { + if (idx < 0 || idx >= Count) + { + throw new ArgumentOutOfRangeException("index"); + } + + return objectList[idx].Key; + } + + /// <summary> + /// Returns the value at the specified index. + /// </summary> + /// <param name="idx">The index.</param> + /// <returns>The value at the index.</returns> + public V GetValue(int idx) + { + if (idx < 0 || idx >= Count) + { + throw new ArgumentOutOfRangeException("index"); + } + + return objectList[idx].Value; + } + + /// <summary> + /// Get the index of a particular key. + /// </summary> + /// <param name="key">The key to find the index of.</param> + /// <returns>The index of the key, or -1 if not found.</returns> + public int IndexOf(K key) + { + int ret = -1; + + for (int i = 0; i < objectList.Count; i++) + { + if (objectList[i].Key.Equals(key)) + { + ret = i; + break; + } + } + + return ret; + } + + /// <summary> + /// Insert the key-value at the specified index. + /// </summary> + /// <param name="idx">The zero-based insert point.</param> + /// <param name="key">The key.</param> + /// <param name="value">The value.</param> + public void Insert(int idx, K key, V value) + { + if ((idx < 0) || (idx > Count)) + { + throw new ArgumentOutOfRangeException("index"); + } + + objectTable.Add(key, value); + objectList.Insert(idx, new KeyValuePair<K, V>(key, value)); + } + } +} \ No newline at end of file diff --git a/LongestCommonSequence.cs b/LongestCommonSequence.cs new file mode 100644 index 0000000..6e1582d --- /dev/null +++ b/LongestCommonSequence.cs @@ -0,0 +1,177 @@ +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; + +namespace m3uTool +{ + public class LongestCommonSubstrings + { + //def longest_common_substrings(S, T): + // maks = 0 # length of longest match + // ret = Set([""]) # the longest matches we have found + // last = {} # length of matches ending at S[i-1] and + // next = {} # at S[i] for T[0]...T[n-1] + // for i in xrange(len(S)): + // for j in xrange(len(T)): + // if S[i] == T[j]: + // if j-1 in last: + // next[j] = last[j-1] + 1 + // else: + // next[j] = 1 + // if next[j] > maks: + // maks = next[j] + // ret = Set() + // if next[j] == maks: + // ret.add(S[i-maks+1:i+1]) # equals T[j-maks+1:j+1] + // last = next + // next = {} + // return ret + + public static List<string> GetLongestSubstring(string s, string t) + { + int maks = 0; + var ret = new List<string>(); + var last = new Dictionary<int, int>(); + var next = new Dictionary<int, int>(); + for (int i = 0; i < s.Length; i++) + { + for (int j = 0; j < t.Length; j++) + { + if (s[i] == t[j]) + { + if (last.ContainsKey(j - 1)) + next[j] = last[j - 1] + 1; + else + next[j] = 1; + + if (next[j] > maks) + { + maks = next[j]; + ret.Clear(); + } + if (next[j] == maks) + ret.Add(s.Substring(i - maks + 1, maks)); + } + } + last = next; + next = new Dictionary<int, int>(); + } + + return ret; + } + } + + public class LongestCommonSequence<T> where T : IEnumerable + { + #region BackTracking enum + + public enum BackTracking + { + NEITHER, + UP, + LEFT, + UP_AND_LEFT + } + + #endregion + + private static int ConsecutiveMeasure(int k) + { + //f(k)=k*a - b; + return k*k; + } + + //public T DoLCS(T list1, T list2) + //{ + + //} + + private object[] LCS(object[] list1, object[] list2) + { + int m = list1.Length; + int n = list2.Length; + + var lcs = new int[m + 1,n + 1]; + var backTracer = new BackTracking[m + 1,n + 1]; + var w = new int[m + 1,n + 1]; + int i, j; + + for (i = 0; i <= m; i++) + { + lcs[i, 0] = 0; + backTracer[i, 0] = BackTracking.UP; + } + for (j = 0; j <= n; j++) + { + lcs[0, j] = 0; + backTracer[0, j] = BackTracking.LEFT; + } + + for (i = 1; i <= m; i++) + { + for (j = 1; j <= n; j++) + { + if (list1[i - 1] == list2[j - 1]) + { + int k = w[i - 1, j - 1]; + //lcs[i,j] = lcs[i-1,j-1] + 1; + lcs[i, j] = lcs[i - 1, j - 1] + ConsecutiveMeasure(k + 1) - ConsecutiveMeasure(k); + backTracer[i, j] = BackTracking.UP_AND_LEFT; + w[i, j] = k + 1; + } + else + { + lcs[i, j] = lcs[i - 1, j - 1]; + backTracer[i, j] = BackTracking.NEITHER; + } + + if (lcs[i - 1, j] >= lcs[i, j]) + { + lcs[i, j] = lcs[i - 1, j]; + backTracer[i, j] = BackTracking.UP; + w[i, j] = 0; + } + + if (lcs[i, j - 1] >= lcs[i, j]) + { + lcs[i, j] = lcs[i, j - 1]; + backTracer[i, j] = BackTracking.LEFT; + w[i, j] = 0; + } + } + } + + i = m; + j = n; + + var subseq = new Stack<object>(); + // int p=lcs[i,j]; + + //trace the backtracking matrix. + while (i > 0 || j > 0) + { + if (backTracer[i, j] == BackTracking.UP_AND_LEFT) + { + i--; + j--; + subseq.Push(list1[i]); + // subseq = list1[i] + subseq; + Trace.WriteLine(i + " " + list1[i] + " " + j); + } + + else if (backTracer[i, j] == BackTracking.UP) + { + i--; + } + + else if (backTracer[i, j] == BackTracking.LEFT) + { + j--; + } + } + + + return subseq.ToArray(); + } + } +} \ No newline at end of file diff --git a/M3uCreate.cs b/M3uCreate.cs new file mode 100644 index 0000000..d2c94f8 --- /dev/null +++ b/M3uCreate.cs @@ -0,0 +1,149 @@ +using System; +using System.Diagnostics; +using System.IO; + +namespace m3uTool +{ + /// <summary> + /// Summary description for m3uFromDirectory. + /// </summary> + public class M3uCreate + { + /// <summary> + /// Directory to grab the files from + /// </summary> + private readonly DirectoryInfo mInputDir; + + /// <summary> + /// Filter for input filenames + /// </summary> + private string mFileFilter = "*.mp3"; + + /// <summary> + /// Ouput m3u filename, if it isn't generated + /// </summary> + private string mOutputFilename; + + /// <summary> + /// Use the parent's directory name for the m3u name + /// </summary> + private bool mUseGeneratedOuputFilename = true; + + public M3uCreate(string dir) : this(new DirectoryInfo(dir)) + { + } + + public M3uCreate(DirectoryInfo inputDir) + { + mInputDir = inputDir; + } + + public bool UseGeneratedOuputFilename + { + get { return mUseGeneratedOuputFilename; } + set { mUseGeneratedOuputFilename = value; } + } + + public string OutputFilename + { + get + { + if (mUseGeneratedOuputFilename) + { + Debug.WriteLine("OutputFilename:" + mInputDir.FullName + "\\" + mInputDir.Name + ".m3u"); + return mInputDir.FullName + "\\" + mInputDir.Name + ".m3u"; + } + return mOutputFilename; + } + set + { + mOutputFilename = value; + mUseGeneratedOuputFilename = false; + } + } + + /// <summary> + /// + /// </summary> + /// <remarks> + /// dir: "\foo\bar\" file: "\foo\bar\myfile" output: "" + /// dir: "\foo\" file: "\foo\bar\myfile" output: ".." + /// dir: "\" file: "\foo\bar\myfile" output: "..\.." + /// dir: "\har" file: "\foo\bar\myfile" output: "..\..\har" + /// dir: "\foo\bar\hey\" file: "\foo\bar\myfile" output: "hey\" + /// </remarks> + /// <param name="dir"></param> + /// <param name="file"></param> + /// <returns></returns> + private string GetPath(DirectoryInfo dir, FileInfo file) + { + string source = dir.FullName; + string target = file.Directory.FullName; + + return GetPath(source, target); + } + + /// <summary> + /// Gets the path. + /// </summary> + /// <param name="source">The source.</param> + /// <param name="target">The target.</param> + /// <returns></returns> + private string GetPath(string source, string target) + { + int lastCommonIndex = GetLastCommonIndex(source, target); + Debug.WriteLine(String.Format("s:{0} , t:{1} lastCommon:{2} ", source, target, lastCommonIndex)); + + int slashCount = CountCharacters(source, lastCommonIndex + 1, '\\'); + + string path = ""; + for (int i = 0; i < slashCount; i++) + { + path += "..\\"; + } + path += target.Substring(lastCommonIndex); + if (path.Length > 0 && path[path.Length - 1] != '\\') + path += "\\"; + Debug.WriteLine(path); + return path; + } + + private int CountCharacters(string str, int startIdx, char c) + { + int charsCount = 0; + int currIdx = startIdx; + while (currIdx < str.Length && (currIdx = str.IndexOf(c, currIdx) + 1) != 0) + charsCount++; + return charsCount; + } + + private int GetLastCommonIndex(string str1, string str2) + { + int i = 0; + while (i < str1.Length && i < str2.Length && Char.ToLower(str1[i]) == Char.ToLower(str2[i])) + i++; + return i; + } + + public void Create() + { + string fileList = ""; + DirectoryInfo ouputDirectory = (new FileInfo(OutputFilename)).Directory; + foreach (FileInfo info in mInputDir.GetFiles(mFileFilter)) + { + string fileName = GetPath(ouputDirectory, info) + info.Name; + Debug.WriteLine(fileName); + fileList += fileName + Environment.NewLine; + } + SaveToFile(fileList, OutputFilename); + } + + private void SaveToFile(string contents, string outputFilename) + { + using (TextWriter tw = new StreamWriter(outputFilename)) + { + tw.Write(contents); + } + } + } +} \ No newline at end of file diff --git a/M3uMakeByDirectory.cs b/M3uMakeByDirectory.cs new file mode 100644 index 0000000..97c8a5c --- /dev/null +++ b/M3uMakeByDirectory.cs @@ -0,0 +1,175 @@ +using System; +using System.ComponentModel; +using System.IO; +using System.Threading; +using System.Windows.Forms; + +namespace m3uTool +{ + /// <summary> + /// Summary description for Form1. + /// </summary> + public class M3uMakeByDirectory : Form + { + /// <summary> + /// Required designer variable. + /// </summary> + private readonly Container components; + + private TextBox m3uDirectoryTextBox; + private Button m3uMakeButton; + private TextBox outputTextBox; + private Button selectDirectoryButton; + + public M3uMakeByDirectory() + { + // + // Required for Windows Form Designer support + // + InitializeComponent(); + + // + // TODO: Add any constructor code after InitializeComponent call + // + } + + /// <summary> + /// Clean up any resources being used. + /// </summary> + protected override void Dispose(bool disposing) + { + if (disposing) + { + if (components != null) + { + components.Dispose(); + } + } + base.Dispose(disposing); + } + + /// <summary> + /// The main entry point for the application. + /// </summary> + [STAThread] + private static void Main() + { + Application.Run(new M3uMakeByDirectory()); + } + + private void selectDirectoryButton_Click(object sender, EventArgs e) + { + var fbd = new FolderBrowserDialog(); + DialogResult dialogResult = fbd.ShowDialog(this); + + if (dialogResult == DialogResult.OK) + { + m3uDirectoryTextBox.Text = fbd.SelectedPath; + } + UpdateRunButton(); + } + + private void UpdateRunButton() + { + if (m3uDirectoryTextBox.Text != null && m3uDirectoryTextBox.Text != "" && + Directory.Exists(m3uDirectoryTextBox.Text)) + { + m3uMakeButton.Enabled = true; + } + else + m3uMakeButton.Enabled = false; + } + + private void CreateM3uThread() + { + CreateM3uRecursive(new DirectoryInfo(m3uDirectoryTextBox.Text)); + } + + private void CreateM3uRecursive(DirectoryInfo info) + { + var cm3u = new M3uCreate(info); + cm3u.Create(); + outputTextBox.AppendText("Created " + cm3u.OutputFilename + Environment.NewLine); + foreach (DirectoryInfo subDir in info.GetDirectories()) + { + CreateM3uRecursive(subDir); + } + } + + private void m3uMakeButton_Click(object sender, EventArgs e) + { + var t = new Thread(CreateM3uThread); + t.Start(); + } + + #region Windows Form Designer generated code + + /// <summary> + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// </summary> + private void InitializeComponent() + { + this.m3uDirectoryTextBox = new System.Windows.Forms.TextBox(); + this.outputTextBox = new System.Windows.Forms.TextBox(); + this.selectDirectoryButton = new System.Windows.Forms.Button(); + this.m3uMakeButton = new System.Windows.Forms.Button(); + this.SuspendLayout(); + // + // m3uDirectoryTextBox + // + this.m3uDirectoryTextBox.Enabled = false; + this.m3uDirectoryTextBox.Location = new System.Drawing.Point(8, 8); + this.m3uDirectoryTextBox.Name = "m3uDirectoryTextBox"; + this.m3uDirectoryTextBox.Size = new System.Drawing.Size(176, 20); + this.m3uDirectoryTextBox.TabIndex = 0; + this.m3uDirectoryTextBox.Text = ""; + // + // outputTextBox + // + this.outputTextBox.Anchor = + ((System.Windows.Forms.AnchorStyles) + ((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.outputTextBox.Location = new System.Drawing.Point(8, 88); + this.outputTextBox.Multiline = true; + this.outputTextBox.Name = "outputTextBox"; + this.outputTextBox.ScrollBars = System.Windows.Forms.ScrollBars.Vertical; + this.outputTextBox.Size = new System.Drawing.Size(272, 168); + this.outputTextBox.TabIndex = 1; + this.outputTextBox.Text = ""; + // + // selectDirectoryButton + // + this.selectDirectoryButton.Location = new System.Drawing.Point(208, 8); + this.selectDirectoryButton.Name = "selectDirectoryButton"; + this.selectDirectoryButton.TabIndex = 2; + this.selectDirectoryButton.Text = "Select Dir..."; + this.selectDirectoryButton.Click += new System.EventHandler(this.selectDirectoryButton_Click); + // + // m3uMakeButton + // + this.m3uMakeButton.Enabled = false; + this.m3uMakeButton.Location = new System.Drawing.Point(208, 40); + this.m3uMakeButton.Name = "m3uMakeButton"; + this.m3uMakeButton.TabIndex = 3; + this.m3uMakeButton.Text = "Make M3u"; + this.m3uMakeButton.Click += new System.EventHandler(this.m3uMakeButton_Click); + // + // MakeSubdir + // + this.AutoScaleBaseSize = new System.Drawing.Size(5, 13); + this.ClientSize = new System.Drawing.Size(292, 266); + this.Controls.Add(this.m3uMakeButton); + this.Controls.Add(this.selectDirectoryButton); + this.Controls.Add(this.outputTextBox); + this.Controls.Add(this.m3uDirectoryTextBox); + this.Name = "MakeSubdir"; + this.Text = "M3u Make"; + this.ResumeLayout(false); + } + + #endregion + } +} \ No newline at end of file diff --git a/M3uMakeByDirectory.resx b/M3uMakeByDirectory.resx new file mode 100644 index 0000000..3108915 --- /dev/null +++ b/M3uMakeByDirectory.resx @@ -0,0 +1,166 @@ +<?xml version="1.0" encoding="utf-8"?> +<root> + <!-- + Microsoft ResX Schema + + Version 1.3 + + The primary goals of this format is to allow a simple XML format + that is mostly human readable. The generation and parsing of the + various data types are done through the TypeConverter classes + associated with the data types. + + Example: + + ... ado.net/XML headers & schema ... + <resheader name="resmimetype">text/microsoft-resx</resheader> + <resheader name="version">1.3</resheader> + <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader> + <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader> + <data name="Name1">this is my long string</data> + <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data> + <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64"> + [base64 mime encoded serialized .NET Framework object] + </data> + <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64"> + [base64 mime encoded string representing a byte array form of the .NET Framework object] + </data> + + There are any number of "resheader" rows that contain simple + name/value pairs. + + Each data row contains a name, and value. The row also contains a + type or mimetype. Type corresponds to a .NET class that support + text/value conversion through the TypeConverter architecture. + Classes that don't support this are serialized and stored with the + mimetype set. + + The mimetype is used forserialized objects, and tells the + ResXResourceReader how to depersist the object. This is currently not + extensible. For a given mimetype the value must be set accordingly: + + Note - application/x-microsoft.net.object.binary.base64 is the format + that the ResXResourceWriter will generate, however the reader can + read any of the formats listed below. + + mimetype: application/x-microsoft.net.object.binary.base64 + value : The object must be serialized with + : System.Serialization.Formatters.Binary.BinaryFormatter + : and then encoded with base64 encoding. + + mimetype: application/x-microsoft.net.object.soap.base64 + value : The object must be serialized with + : System.Runtime.Serialization.Formatters.Soap.SoapFormatter + : and then encoded with base64 encoding. + + mimetype: application/x-microsoft.net.object.bytearray.base64 + value : The object must be serialized into a byte array + : using a System.ComponentModel.TypeConverter + : and then encoded with base64 encoding. + --> + <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"> + <xsd:element name="root" msdata:IsDataSet="true"> + <xsd:complexType> + <xsd:choice maxOccurs="unbounded"> + <xsd:element name="data"> + <xsd:complexType> + <xsd:sequence> + <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /> + <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" /> + </xsd:sequence> + <xsd:attribute name="name" type="xsd:string" msdata:Ordinal="1" /> + <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" /> + <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" /> + </xsd:complexType> + </xsd:element> + <xsd:element name="resheader"> + <xsd:complexType> + <xsd:sequence> + <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /> + </xsd:sequence> + <xsd:attribute name="name" type="xsd:string" use="required" /> + </xsd:complexType> + </xsd:element> + </xsd:choice> + </xsd:complexType> + </xsd:element> + </xsd:schema> + <resheader name="resmimetype"> + <value>text/microsoft-resx</value> + </resheader> + <resheader name="version"> + <value>1.3</value> + </resheader> + <resheader name="reader"> + <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> + </resheader> + <resheader name="writer"> + <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> + </resheader> + <data name="m3uDirectoryTextBox.DefaultModifiers" type="System.CodeDom.MemberAttributes, System, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"> + <value>Private</value> + </data> + <data name="m3uDirectoryTextBox.Locked" type="System.Boolean, mscorlib, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"> + <value>False</value> + </data> + <data name="m3uDirectoryTextBox.Modifiers" type="System.CodeDom.MemberAttributes, System, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"> + <value>Private</value> + </data> + <data name="outputTextBox.DefaultModifiers" type="System.CodeDom.MemberAttributes, System, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"> + <value>Private</value> + </data> + <data name="outputTextBox.Locked" type="System.Boolean, mscorlib, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"> + <value>False</value> + </data> + <data name="outputTextBox.Modifiers" type="System.CodeDom.MemberAttributes, System, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"> + <value>Private</value> + </data> + <data name="selectDirectoryButton.Locked" type="System.Boolean, mscorlib, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"> + <value>False</value> + </data> + <data name="selectDirectoryButton.DefaultModifiers" type="System.CodeDom.MemberAttributes, System, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"> + <value>Private</value> + </data> + <data name="selectDirectoryButton.Modifiers" type="System.CodeDom.MemberAttributes, System, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"> + <value>Private</value> + </data> + <data name="m3uMakeButton.Locked" type="System.Boolean, mscorlib, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"> + <value>False</value> + </data> + <data name="m3uMakeButton.DefaultModifiers" type="System.CodeDom.MemberAttributes, System, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"> + <value>Private</value> + </data> + <data name="m3uMakeButton.Modifiers" type="System.CodeDom.MemberAttributes, System, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"> + <value>Private</value> + </data> + <data name="$this.Locked" type="System.Boolean, mscorlib, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"> + <value>False</value> + </data> + <data name="$this.Language" type="System.Globalization.CultureInfo, mscorlib, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"> + <value>(Default)</value> + </data> + <data name="$this.TrayLargeIcon" type="System.Boolean, mscorlib, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"> + <value>False</value> + </data> + <data name="$this.Name"> + <value>MakeSubdir</value> + </data> + <data name="$this.Localizable" type="System.Boolean, mscorlib, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"> + <value>False</value> + </data> + <data name="$this.GridSize" type="System.Drawing.Size, System.Drawing, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"> + <value>8, 8</value> + </data> + <data name="$this.DrawGrid" type="System.Boolean, mscorlib, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"> + <value>True</value> + </data> + <data name="$this.TrayHeight" type="System.Int32, mscorlib, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"> + <value>80</value> + </data> + <data name="$this.SnapToGrid" type="System.Boolean, mscorlib, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"> + <value>True</value> + </data> + <data name="$this.DefaultModifiers" type="System.CodeDom.MemberAttributes, System, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"> + <value>Private</value> + </data> +</root> \ No newline at end of file diff --git a/M3uToAac.Designer.cs b/M3uToAac.Designer.cs new file mode 100644 index 0000000..a8da24d --- /dev/null +++ b/M3uToAac.Designer.cs @@ -0,0 +1,46 @@ +namespace m3uTool +{ + partial class M3uToAac + { + /// <summary> + /// Required designer variable. + /// </summary> + private System.ComponentModel.IContainer components = null; + + /// <summary> + /// Clean up any resources being used. + /// </summary> + /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param> + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// <summary> + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// </summary> + private void InitializeComponent() + { + this.SuspendLayout(); + // + // M3uToAac + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(434, 138); + this.Name = "M3uToAac"; + this.Text = "M3uToAac"; + this.ResumeLayout(false); + + } + + #endregion + } +} \ No newline at end of file diff --git a/M3uToAac.cs b/M3uToAac.cs new file mode 100644 index 0000000..c93ef46 --- /dev/null +++ b/M3uToAac.cs @@ -0,0 +1,22 @@ +using System.Windows.Forms; + +namespace m3uTool +{ + public partial class M3uToAac : Form + { + public M3uToAac() + { + InitializeComponent(); + } + + #region Nested type: Mp3ToAacConversionOptions + + public class Mp3ToAacConversionOptions + { + private string[] mInputFilenames; + private string[] mOutputFilenames; + } + + #endregion + } +} \ No newline at end of file diff --git a/M3uToAac.resx b/M3uToAac.resx new file mode 100644 index 0000000..ff31a6d --- /dev/null +++ b/M3uToAac.resx @@ -0,0 +1,120 @@ +<?xml version="1.0" encoding="utf-8"?> +<root> + <!-- + Microsoft ResX Schema + + Version 2.0 + + The primary goals of this format is to allow a simple XML format + that is mostly human readable. The generation and parsing of the + various data types are done through the TypeConverter classes + associated with the data types. + + Example: + + ... ado.net/XML headers & schema ... + <resheader name="resmimetype">text/microsoft-resx</resheader> + <resheader name="version">2.0</resheader> + <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader> + <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader> + <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data> + <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data> + <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64"> + <value>[base64 mime encoded serialized .NET Framework object]</value> + </data> + <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64"> + <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value> + <comment>This is a comment</comment> + </data> + + There are any number of "resheader" rows that contain simple + name/value pairs. + + Each data row contains a name, and value. The row also contains a + type or mimetype. Type corresponds to a .NET class that support + text/value conversion through the TypeConverter architecture. + Classes that don't support this are serialized and stored with the + mimetype set. + + The mimetype is used for serialized objects, and tells the + ResXResourceReader how to depersist the object. This is currently not + extensible. For a given mimetype the value must be set accordingly: + + Note - application/x-microsoft.net.object.binary.base64 is the format + that the ResXResourceWriter will generate, however the reader can + read any of the formats listed below. + + mimetype: application/x-microsoft.net.object.binary.base64 + value : The object must be serialized with + : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter + : and then encoded with base64 encoding. + + mimetype: application/x-microsoft.net.object.soap.base64 + value : The object must be serialized with + : System.Runtime.Serialization.Formatters.Soap.SoapFormatter + : and then encoded with base64 encoding. + + mimetype: application/x-microsoft.net.object.bytearray.base64 + value : The object must be serialized into a byte array + : using a System.ComponentModel.TypeConverter + : and then encoded with base64 encoding. + --> + <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"> + <xsd:import namespace="http://www.w3.org/XML/1998/namespace" /> + <xsd:element name="root" msdata:IsDataSet="true"> + <xsd:complexType> + <xsd:choice maxOccurs="unbounded"> + <xsd:element name="metadata"> + <xsd:complexType> + <xsd:sequence> + <xsd:element name="value" type="xsd:string" minOccurs="0" /> + </xsd:sequence> + <xsd:attribute name="name" use="required" type="xsd:string" /> + <xsd:attribute name="type" type="xsd:string" /> + <xsd:attribute name="mimetype" type="xsd:string" /> + <xsd:attribute ref="xml:space" /> + </xsd:complexType> + </xsd:element> + <xsd:element name="assembly"> + <xsd:complexType> + <xsd:attribute name="alias" type="xsd:string" /> + <xsd:attribute name="name" type="xsd:string" /> + </xsd:complexType> + </xsd:element> + <xsd:element name="data"> + <xsd:complexType> + <xsd:sequence> + <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /> + <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" /> + </xsd:sequence> + <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" /> + <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" /> + <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" /> + <xsd:attribute ref="xml:space" /> + </xsd:complexType> + </xsd:element> + <xsd:element name="resheader"> + <xsd:complexType> + <xsd:sequence> + <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /> + </xsd:sequence> + <xsd:attribute name="name" type="xsd:string" use="required" /> + </xsd:complexType> + </xsd:element> + </xsd:choice> + </xsd:complexType> + </xsd:element> + </xsd:schema> + <resheader name="resmimetype"> + <value>text/microsoft-resx</value> + </resheader> + <resheader name="version"> + <value>2.0</value> + </resheader> + <resheader name="reader"> + <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> + </resheader> + <resheader name="writer"> + <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> + </resheader> +</root> \ No newline at end of file diff --git a/MP3Header.cs b/MP3Header.cs new file mode 100644 index 0000000..7ae8a9a --- /dev/null +++ b/MP3Header.cs @@ -0,0 +1,468 @@ +using System; +using System.IO; +using System.Text; + +namespace m3uTool +{ + /// <summary> + /// Indicates that a problem existed when parsing an mp3 file + /// </summary> + public class Mp3FormatException : Exception + { + string mFilename; + + string mFormatExceptionString; + + public string Filename + { + get { return mFilename; } + } + + public string FormatExceptionString + { + get { return mFormatExceptionString; } + } + + /// <summary> + /// Constructor. + /// </summary> + /// <param name="filename"></param> + /// <param name="formatExceptionString"></param> + public Mp3FormatException(string filename, string formatExceptionString) + { + mFilename = filename; + mFormatExceptionString = formatExceptionString; + } + } + + /* ---------------------------------------------------------- + + original C++ code by: + Gustav "Grim Reaper" Munkby + http://floach.pimpin.net/grd/ + grimreaperdesigns@gmx.net + + modified and converted to C# by: + Robert A. Wlodarczyk + http://rob.wincereview.com:8080 + rwlodarc@hotmail.com + ---------------------------------------------------------- */ + public class Mp3Header + { + //// Public variables for storing the information about the MP3 + //public int intBitRate; + //public string strFileName; + //public long lngFileSize; + //public int intFrequency; + //public string strMode; + //public int intLength; + //public string strLengthFormatted; + + + // Private variables used in the process of reading in the MP3 files + private ulong bithdr; + private bool boolVBitRate; + private int intVFrames; + + public Mp3FileDetails ReadMP3Information(string fileName) + { + mFileDetails = new Mp3FileDetails(); + FileStream fs = new FileStream(fileName, FileMode.Open, FileAccess.Read); + // Set the filename not including the path information + mFileDetails.strFileName = @fs.Name; + char[] chrSeparators = new char[]{'\\','/'}; + string[] strSeparator = mFileDetails.strFileName.Split(chrSeparators); + int intUpper = strSeparator.GetUpperBound(0); + mFileDetails.strFileName = strSeparator[intUpper]; + + // Replace ' with '' for the SQL INSERT statement + mFileDetails.strFileName = mFileDetails.strFileName.Replace("'", "''"); + + // Set the file size + mFileDetails.lngFileSize = fs.Length; + + byte[] bytHeader = new byte[4]; + byte[] bytVBitRate = new byte[12]; + int intPos = 0; + + // Keep reading 4 bytes from the header until we know for sure that in + // fact it's an MP3 + do + { + fs.Position = intPos; + fs.Read(bytHeader,0,4); + intPos++; + LoadMP3Header(bytHeader); + } + while(!IsValidHeader() && (fs.Position!=fs.Length)); + + // If the current file stream position is equal to the length, + // that means that we've read the entire file and it's not a valid MP3 file + if(fs.Position != fs.Length) + { + intPos += 3; + + if(getVersionIndex() == 3) // MPEG Version 1 + { + if(getModeIndex() == 3) // Single Channel + { + intPos += 17; + } + else + { + intPos += 32; + } + } + else // MPEG Version 2.0 or 2.5 + { + if(getModeIndex() == 3) // Single Channel + { + intPos += 9; + } + else + { + intPos += 17; + } + } + + // Check to see if the MP3 has a variable bitrate + fs.Position = intPos; + fs.Read(bytVBitRate,0,12); + boolVBitRate = LoadVBRHeader(bytVBitRate); + + // Once the file's read in, then assign the properties of the file to the public variables + mFileDetails.intBitRate = getBitrate(); + mFileDetails.intFrequency = getFrequency(); + mFileDetails.strMode = getMode(); + mFileDetails.intLength = getLengthInSeconds(); + mFileDetails.strLengthFormatted = getFormattedLength(); + fs.Close(); + return mFileDetails; + } + throw new Mp3FormatException(fileName, "Invalid mp3 file"); + } + + private void LoadMP3Header(byte[] c) + { + // this thing is quite interesting, it works like the following + // c[0] = 00000011 + // c[1] = 00001100 + // c[2] = 00110000 + // c[3] = 11000000 + // the operator << means that we'll move the bits in that direction + // 00000011 << 24 = 00000011000000000000000000000000 + // 00001100 << 16 = 000011000000000000000000 + // 00110000 << 24 = 0011000000000000 + // 11000000 = 11000000 + // +_________________________________ + // 00000011000011000011000011000000 + bithdr = (ulong)(((c[0] & 255) << 24) | ((c[1] & 255) << 16) | ((c[2] & 255) << 8) | ((c[3] & 255))); + } + + private bool LoadVBRHeader(byte[] inputheader) + { + // If it's a variable bitrate MP3, the first 4 bytes will read 'Xing' + // since they're the ones who added variable bitrate-edness to MP3s + if(inputheader[0] == 88 && inputheader[1] == 105 && + inputheader[2] == 110 && inputheader[3] == 103) + { + int flags = (((inputheader[4] & 255) << 24) | ((inputheader[5] & 255) << 16) | ((inputheader[6] & 255) << 8) | ((inputheader[7] & 255))); + if((flags & 0x0001) == 1) + { + intVFrames = (((inputheader[8] & 255) << 24) | ((inputheader[9] & 255) << 16) | ((inputheader[10] & 255) << 8) | ((inputheader[11] & 255))); + return true; + } + else + { + intVFrames = -1; + return true; + } + } + return false; + } + + private bool IsValidHeader() + { + return (((getFrameSync() & 2047)==2047) && + ((getVersionIndex() & 3)!= 1) && + ((getLayerIndex() & 3)!= 0) && + ((getBitrateIndex() & 15)!= 0) && + ((getBitrateIndex() & 15)!= 15) && + ((getFrequencyIndex() & 3)!= 3) && + ((getEmphasisIndex() & 3)!= 2) ); + } + + private int getFrameSync() + { + return (int)((bithdr>>21) & 2047); + } + + private int getVersionIndex() + { + return (int)((bithdr>>19) & 3); + } + + private int getLayerIndex() + { + return (int)((bithdr>>17) & 3); + } + + //private int getProtectionBit() + //{ + // return (int)((bithdr>>16) & 1); + //} + + private int getBitrateIndex() + { + return (int)((bithdr>>12) & 15); + } + + private int getFrequencyIndex() + { + return (int)((bithdr>>10) & 3); + } + + //private int getPaddingBit() + //{ + // return (int)((bithdr>>9) & 1); + //} + + //private int getPrivateBit() + //{ + // return (int)((bithdr>>8) & 1); + //} + + private int getModeIndex() + { + return (int)((bithdr>>6) & 3); + } + + //private int getModeExtIndex() + //{ + // return (int)((bithdr>>4) & 3); + //} + + //private int getCoprightBit() + //{ + // return (int)((bithdr>>3) & 1); + //} + + //private int getOrginalBit() + //{ + // return (int)((bithdr>>2) & 1); + //} + + private int getEmphasisIndex() + { + return (int)(bithdr & 3); + } + + //private double getVersion() + //{ + // double[] table = {2.5, 0.0, 2.0, 1.0}; + // return table[getVersionIndex()]; + //} + + //private int getLayer() + //{ + // return (int)(4 - getLayerIndex()); + //} + + private int getBitrate() + { + // If the file has a variable bitrate, then we return an integer average bitrate, + // otherwise, we use a lookup table to return the bitrate + if(boolVBitRate) + { + double medFrameSize = (double)mFileDetails.lngFileSize/ (double)getNumberOfFrames(); + return (int)((medFrameSize * getFrequency()) / (1000.0 * ((getLayerIndex()==3) ? 12.0 : 144.0))); + } + else + { + int[,,] table = { + { // MPEG 2 & 2.5 + {0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96,112,128,144,160,0}, // Layer III + {0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96,112,128,144,160,0}, // Layer II + {0, 32, 48, 56, 64, 80, 96,112,128,144,160,176,192,224,256,0} // Layer I + }, + { // MPEG 1 + {0, 32, 40, 48, 56, 64, 80, 96,112,128,160,192,224,256,320,0}, // Layer III + {0, 32, 48, 56, 64, 80, 96,112,128,160,192,224,256,320,384,0}, // Layer II + {0, 32, 64, 96,128,160,192,224,256,288,320,352,384,416,448,0} // Layer I + } + }; + + return table[getVersionIndex() & 1, getLayerIndex()-1, getBitrateIndex()]; + } + } + + private int getFrequency() + { + int[,] table = { + {32000, 16000, 8000}, // MPEG 2.5 + { 0, 0, 0}, // reserved + {22050, 24000, 16000}, // MPEG 2 + {44100, 48000, 32000} // MPEG 1 + }; + + return table[getVersionIndex(), getFrequencyIndex()]; + } + + private string getMode() + { + switch(getModeIndex()) + { + default: + return "Stereo"; + case 1: + return "Joint Stereo"; + case 2: + return "Dual Channel"; + case 3: + return "Single Channel"; + } + } + + private int getLengthInSeconds() + { + // "intKilBitFileSize" made by dividing by 1000 in order to match the "Kilobits/second" + int intKiloBitFileSize = ((8 * (int)mFileDetails.lngFileSize) / 1000); + return (intKiloBitFileSize/getBitrate()); + } + + private string getFormattedLength() + { + // Complete number of seconds + int s = getLengthInSeconds(); + + // Seconds to display + int ss = s%60; + + // Complete number of minutes + int m = (s-ss)/60; + + // Minutes to display + int mm = m%60; + + // Complete number of hours + int h = (m-mm)/60; + + // Make "hh:mm:ss" + return h.ToString("D2") + ":" + mm.ToString("D2") + ":" + ss.ToString("D2"); + } + + private int getNumberOfFrames() + { + // Again, the number of MPEG frames is dependant on whether it's a variable bitrate MP3 or not + if (!boolVBitRate) + { + double medFrameSize = (((getLayerIndex()==3) ? 12 : 144) *((1000.0 * (float)getBitrate())/(float)getFrequency())); + return (int)(mFileDetails.lngFileSize/medFrameSize); + } + else + return intVFrames; + } + + public static void ReadMP3Tag(ref Mp3FileDetails paramMP3) + { + // Read the 128 byte ID3 tag into a byte array + FileStream oFileStream; + oFileStream = new FileStream(paramMP3.fileComplete, FileMode.Open); + byte[] bBuffer = new byte[128]; + oFileStream.Seek(-128, SeekOrigin.End); + oFileStream.Read(bBuffer, 0, 128); + oFileStream.Close(); + + // Convert the Byte Array to a String + Encoding instEncoding = new ASCIIEncoding(); // NB: Encoding is an Abstract class + string id3Tag = instEncoding.GetString(bBuffer); + + // If there is an attched ID3 v1.x TAG then read it + if (id3Tag.Substring(0, 3) == "TAG") + { + paramMP3.id3Title = id3Tag.Substring(3, 30).Trim(); + paramMP3.id3Artist = id3Tag.Substring(33, 30).Trim(); + paramMP3.id3Album = id3Tag.Substring(63, 30).Trim(); + paramMP3.id3Year = id3Tag.Substring(93, 4).Trim(); + paramMP3.id3Comment = id3Tag.Substring(97, 28).Trim(); + + // Get the track number if TAG conforms to ID3 v1.1 + if (id3Tag[125] == 0) + paramMP3.id3TrackNumber = bBuffer[126]; + else + paramMP3.id3TrackNumber = 0; + paramMP3.id3Genre = bBuffer[127]; + paramMP3.hasID3Tag = true; + // ********* IF USED IN ANGER: ENSURE to test for non-numeric year + } + else + { + // ID3 Tag not found so create an empty TAG in case the user saces later + paramMP3.id3Title = ""; + paramMP3.id3Artist = ""; + paramMP3.id3Album = ""; + paramMP3.id3Year = ""; + paramMP3.id3Comment = ""; + paramMP3.id3TrackNumber = 0; + paramMP3.id3Genre = 0; + paramMP3.hasID3Tag = false; + } + } + + public static void UpdateMP3ID3Tag(ref Mp3FileDetails paramMP3) + { + // Trim any whitespace + paramMP3.id3Title = paramMP3.id3Title.Trim(); + paramMP3.id3Artist = paramMP3.id3Artist.Trim(); + paramMP3.id3Album = paramMP3.id3Album.Trim(); + paramMP3.id3Year = paramMP3.id3Year.Trim(); + paramMP3.id3Comment = paramMP3.id3Comment.Trim(); + + // Ensure all properties are correct size + if (paramMP3.id3Title.Length > 30) paramMP3.id3Title = paramMP3.id3Title.Substring(0, 30); + if (paramMP3.id3Artist.Length > 30) paramMP3.id3Artist = paramMP3.id3Artist.Substring(0, 30); + if (paramMP3.id3Album.Length > 30) paramMP3.id3Album = paramMP3.id3Album.Substring(0, 30); + if (paramMP3.id3Year.Length > 4) paramMP3.id3Year = paramMP3.id3Year.Substring(0, 4); + if (paramMP3.id3Comment.Length > 28) paramMP3.id3Comment = paramMP3.id3Comment.Substring(0, 28); + + // Build a new ID3 Tag (128 Bytes) + byte[] tagByteArray = new byte[128]; + for (int i = 0; i < tagByteArray.Length; i++) + tagByteArray[i] = 0; // Initialise array to nulls + + // Convert the Byte Array to a String + Encoding instEncoding = new ASCIIEncoding(); // NB: Encoding is an Abstract class // ************ To DO: Make a shared instance of ASCIIEncoding so we don't keep creating/destroying it + // Copy "TAG" to Array + byte[] workingByteArray = instEncoding.GetBytes("TAG"); + Array.Copy(workingByteArray, 0, tagByteArray, 0, workingByteArray.Length); + // Copy Title to Array + workingByteArray = instEncoding.GetBytes(paramMP3.id3Title); + Array.Copy(workingByteArray, 0, tagByteArray, 3, workingByteArray.Length); + // Copy Artist to Array + workingByteArray = instEncoding.GetBytes(paramMP3.id3Artist); + Array.Copy(workingByteArray, 0, tagByteArray, 33, workingByteArray.Length); + // Copy Album to Array + workingByteArray = instEncoding.GetBytes(paramMP3.id3Album); + Array.Copy(workingByteArray, 0, tagByteArray, 63, workingByteArray.Length); + // Copy Year to Array + workingByteArray = instEncoding.GetBytes(paramMP3.id3Year); + Array.Copy(workingByteArray, 0, tagByteArray, 93, workingByteArray.Length); + // Copy Comment to Array + workingByteArray = instEncoding.GetBytes(paramMP3.id3Comment); + Array.Copy(workingByteArray, 0, tagByteArray, 97, workingByteArray.Length); + // Copy Track and Genre to Array + tagByteArray[126] = paramMP3.id3TrackNumber; + tagByteArray[127] = paramMP3.id3Genre; + + // SAVE TO DISK: Replace the final 128 Bytes with our new ID3 tag + FileStream oFileStream = new FileStream(paramMP3.fileComplete, FileMode.Open); + if (paramMP3.hasID3Tag) + oFileStream.Seek(-128, SeekOrigin.End); + else + oFileStream.Seek(0, SeekOrigin.End); + oFileStream.Write(tagByteArray, 0, 128); + oFileStream.Close(); + paramMP3.hasID3Tag = true; + } + } +} diff --git a/MakeSubdir.cs b/MakeSubdir.cs new file mode 100644 index 0000000..5906bcb --- /dev/null +++ b/MakeSubdir.cs @@ -0,0 +1,174 @@ +using System; +using System.Drawing; +using System.Collections; +using System.ComponentModel; +using System.IO; +using System.Threading; +using System.Windows.Forms; +using System.Data; + +namespace m3uTool +{ + /// <summary> + /// Summary description for Form1. + /// </summary> + public class MakeSubdir : System.Windows.Forms.Form + { + private System.Windows.Forms.Button selectDirectoryButton; + private System.Windows.Forms.TextBox m3uDirectoryTextBox; + private System.Windows.Forms.TextBox outputTextBox; + private System.Windows.Forms.Button m3uMakeButton; + /// <summary> + /// Required designer variable. + /// </summary> + private System.ComponentModel.Container components = null; + + public MakeSubdir() + { + // + // Required for Windows Form Designer support + // + InitializeComponent(); + + // + // TODO: Add any constructor code after InitializeComponent call + // + } + + /// <summary> + /// Clean up any resources being used. + /// </summary> + protected override void Dispose( bool disposing ) + { + if( disposing ) + { + if (components != null) + { + components.Dispose(); + } + } + base.Dispose( disposing ); + } + + #region Windows Form Designer generated code + /// <summary> + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// </summary> + private void InitializeComponent() + { + this.m3uDirectoryTextBox = new System.Windows.Forms.TextBox(); + this.outputTextBox = new System.Windows.Forms.TextBox(); + this.selectDirectoryButton = new System.Windows.Forms.Button(); + this.m3uMakeButton = new System.Windows.Forms.Button(); + this.SuspendLayout(); + // + // m3uDirectoryTextBox + // + this.m3uDirectoryTextBox.Enabled = false; + this.m3uDirectoryTextBox.Location = new System.Drawing.Point(8, 8); + this.m3uDirectoryTextBox.Name = "m3uDirectoryTextBox"; + this.m3uDirectoryTextBox.Size = new System.Drawing.Size(176, 20); + this.m3uDirectoryTextBox.TabIndex = 0; + this.m3uDirectoryTextBox.Text = ""; + // + // outputTextBox + // + this.outputTextBox.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.outputTextBox.Location = new System.Drawing.Point(8, 88); + this.outputTextBox.Multiline = true; + this.outputTextBox.Name = "outputTextBox"; + this.outputTextBox.ScrollBars = System.Windows.Forms.ScrollBars.Vertical; + this.outputTextBox.Size = new System.Drawing.Size(272, 168); + this.outputTextBox.TabIndex = 1; + this.outputTextBox.Text = ""; + // + // selectDirectoryButton + // + this.selectDirectoryButton.Location = new System.Drawing.Point(208, 8); + this.selectDirectoryButton.Name = "selectDirectoryButton"; + this.selectDirectoryButton.TabIndex = 2; + this.selectDirectoryButton.Text = "Select Dir..."; + this.selectDirectoryButton.Click += new System.EventHandler(this.selectDirectoryButton_Click); + // + // m3uMakeButton + // + this.m3uMakeButton.Enabled = false; + this.m3uMakeButton.Location = new System.Drawing.Point(208, 40); + this.m3uMakeButton.Name = "m3uMakeButton"; + this.m3uMakeButton.TabIndex = 3; + this.m3uMakeButton.Text = "Make M3u"; + this.m3uMakeButton.Click += new System.EventHandler(this.m3uMakeButton_Click); + // + // MakeSubdir + // + this.AutoScaleBaseSize = new System.Drawing.Size(5, 13); + this.ClientSize = new System.Drawing.Size(292, 266); + this.Controls.Add(this.m3uMakeButton); + this.Controls.Add(this.selectDirectoryButton); + this.Controls.Add(this.outputTextBox); + this.Controls.Add(this.m3uDirectoryTextBox); + this.Name = "MakeSubdir"; + this.Text = "M3u Make"; + this.ResumeLayout(false); + + } + #endregion + + /// <summary> + /// The main entry point for the application. + /// </summary> + [STAThread] + static void Main() + { + Application.Run(new MakeSubdir()); + } + + private void selectDirectoryButton_Click(object sender, System.EventArgs e) + { + FolderBrowserDialog fbd = new FolderBrowserDialog(); + DialogResult dialogResult = fbd.ShowDialog(this); + + if (dialogResult == DialogResult.OK) + { + m3uDirectoryTextBox.Text = fbd.SelectedPath; + } + UpdateRunButton(); + + } + + private void UpdateRunButton() + { + if (m3uDirectoryTextBox.Text != null && m3uDirectoryTextBox.Text!="" && Directory.Exists(m3uDirectoryTextBox.Text)) + { + m3uMakeButton.Enabled = true; + } + else + m3uMakeButton.Enabled = false; + } + + private void CreateM3uThread() + { + CreateM3uRecursive(new DirectoryInfo(m3uDirectoryTextBox.Text)); + } + + private void CreateM3uRecursive(DirectoryInfo info) + { + CreateM3u cm3u = new CreateM3u(info); + cm3u.Create(); + outputTextBox.AppendText("Created " + cm3u.OutputFilename + Environment.NewLine); + foreach (DirectoryInfo subDir in info.GetDirectories()) + { + CreateM3uRecursive(subDir); + } + } + + private void m3uMakeButton_Click(object sender, System.EventArgs e) + { + Thread t = new Thread(new ThreadStart(CreateM3uThread)); + t.Start(); + } + } +} diff --git a/MakeSubdir.resx b/MakeSubdir.resx new file mode 100644 index 0000000..3108915 --- /dev/null +++ b/MakeSubdir.resx @@ -0,0 +1,166 @@ +<?xml version="1.0" encoding="utf-8"?> +<root> + <!-- + Microsoft ResX Schema + + Version 1.3 + + The primary goals of this format is to allow a simple XML format + that is mostly human readable. The generation and parsing of the + various data types are done through the TypeConverter classes + associated with the data types. + + Example: + + ... ado.net/XML headers & schema ... + <resheader name="resmimetype">text/microsoft-resx</resheader> + <resheader name="version">1.3</resheader> + <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader> + <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader> + <data name="Name1">this is my long string</data> + <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data> + <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64"> + [base64 mime encoded serialized .NET Framework object] + </data> + <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64"> + [base64 mime encoded string representing a byte array form of the .NET Framework object] + </data> + + There are any number of "resheader" rows that contain simple + name/value pairs. + + Each data row contains a name, and value. The row also contains a + type or mimetype. Type corresponds to a .NET class that support + text/value conversion through the TypeConverter architecture. + Classes that don't support this are serialized and stored with the + mimetype set. + + The mimetype is used forserialized objects, and tells the + ResXResourceReader how to depersist the object. This is currently not + extensible. For a given mimetype the value must be set accordingly: + + Note - application/x-microsoft.net.object.binary.base64 is the format + that the ResXResourceWriter will generate, however the reader can + read any of the formats listed below. + + mimetype: application/x-microsoft.net.object.binary.base64 + value : The object must be serialized with + : System.Serialization.Formatters.Binary.BinaryFormatter + : and then encoded with base64 encoding. + + mimetype: application/x-microsoft.net.object.soap.base64 + value : The object must be serialized with + : System.Runtime.Serialization.Formatters.Soap.SoapFormatter + : and then encoded with base64 encoding. + + mimetype: application/x-microsoft.net.object.bytearray.base64 + value : The object must be serialized into a byte array + : using a System.ComponentModel.TypeConverter + : and then encoded with base64 encoding. + --> + <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"> + <xsd:element name="root" msdata:IsDataSet="true"> + <xsd:complexType> + <xsd:choice maxOccurs="unbounded"> + <xsd:element name="data"> + <xsd:complexType> + <xsd:sequence> + <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /> + <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" /> + </xsd:sequence> + <xsd:attribute name="name" type="xsd:string" msdata:Ordinal="1" /> + <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" /> + <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" /> + </xsd:complexType> + </xsd:element> + <xsd:element name="resheader"> + <xsd:complexType> + <xsd:sequence> + <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /> + </xsd:sequence> + <xsd:attribute name="name" type="xsd:string" use="required" /> + </xsd:complexType> + </xsd:element> + </xsd:choice> + </xsd:complexType> + </xsd:element> + </xsd:schema> + <resheader name="resmimetype"> + <value>text/microsoft-resx</value> + </resheader> + <resheader name="version"> + <value>1.3</value> + </resheader> + <resheader name="reader"> + <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> + </resheader> + <resheader name="writer"> + <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> + </resheader> + <data name="m3uDirectoryTextBox.DefaultModifiers" type="System.CodeDom.MemberAttributes, System, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"> + <value>Private</value> + </data> + <data name="m3uDirectoryTextBox.Locked" type="System.Boolean, mscorlib, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"> + <value>False</value> + </data> + <data name="m3uDirectoryTextBox.Modifiers" type="System.CodeDom.MemberAttributes, System, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"> + <value>Private</value> + </data> + <data name="outputTextBox.DefaultModifiers" type="System.CodeDom.MemberAttributes, System, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"> + <value>Private</value> + </data> + <data name="outputTextBox.Locked" type="System.Boolean, mscorlib, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"> + <value>False</value> + </data> + <data name="outputTextBox.Modifiers" type="System.CodeDom.MemberAttributes, System, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"> + <value>Private</value> + </data> + <data name="selectDirectoryButton.Locked" type="System.Boolean, mscorlib, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"> + <value>False</value> + </data> + <data name="selectDirectoryButton.DefaultModifiers" type="System.CodeDom.MemberAttributes, System, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"> + <value>Private</value> + </data> + <data name="selectDirectoryButton.Modifiers" type="System.CodeDom.MemberAttributes, System, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"> + <value>Private</value> + </data> + <data name="m3uMakeButton.Locked" type="System.Boolean, mscorlib, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"> + <value>False</value> + </data> + <data name="m3uMakeButton.DefaultModifiers" type="System.CodeDom.MemberAttributes, System, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"> + <value>Private</value> + </data> + <data name="m3uMakeButton.Modifiers" type="System.CodeDom.MemberAttributes, System, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"> + <value>Private</value> + </data> + <data name="$this.Locked" type="System.Boolean, mscorlib, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"> + <value>False</value> + </data> + <data name="$this.Language" type="System.Globalization.CultureInfo, mscorlib, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"> + <value>(Default)</value> + </data> + <data name="$this.TrayLargeIcon" type="System.Boolean, mscorlib, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"> + <value>False</value> + </data> + <data name="$this.Name"> + <value>MakeSubdir</value> + </data> + <data name="$this.Localizable" type="System.Boolean, mscorlib, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"> + <value>False</value> + </data> + <data name="$this.GridSize" type="System.Drawing.Size, System.Drawing, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"> + <value>8, 8</value> + </data> + <data name="$this.DrawGrid" type="System.Boolean, mscorlib, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"> + <value>True</value> + </data> + <data name="$this.TrayHeight" type="System.Int32, mscorlib, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"> + <value>80</value> + </data> + <data name="$this.SnapToGrid" type="System.Boolean, mscorlib, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"> + <value>True</value> + </data> + <data name="$this.DefaultModifiers" type="System.CodeDom.MemberAttributes, System, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"> + <value>Private</value> + </data> +</root> \ No newline at end of file diff --git a/Mp3Encoder.cs b/Mp3Encoder.cs new file mode 100644 index 0000000..87c076d --- /dev/null +++ b/Mp3Encoder.cs @@ -0,0 +1,22 @@ +using System.IO; + +namespace m3uTool +{ + public class Mp3Encoder : ProcessStreamWrapper + { + public Mp3Encoder(Mp3EncodingOptions encOpt) + : base(encOpt) + { + } + + public Mp3Encoder(Mp3EncodingOptions encOpt, Stream outputStream) + : base(encOpt, outputStream) + { + } + + protected override string ProcessExecutableFilename + { + get { return @"c:\console\audio\lame.exe"; } + } + } +} \ No newline at end of file diff --git a/Mp3EncodingOptions.cs b/Mp3EncodingOptions.cs new file mode 100644 index 0000000..6c250aa --- /dev/null +++ b/Mp3EncodingOptions.cs @@ -0,0 +1,437 @@ +using System.Text; + +namespace m3uTool +{ + /// <summary> + /// LAME 32bits version 3.97 (beta 1, Oct 16 2005) (http://www.mp3dev.org/) + /// + /// usage: lame [options] <infile> [outfile] + /// + /// <infile> and/or <outfile> can be "-", which means stdin/stdout. + /// + /// RECOMMENDED: + /// lame -V2 input.wav output.mp3 + /// + /// OPTIONS: + /// Input options: + /// -r input is raw pcm + /// -x force byte-swapping of input + /// -s sfreq sampling frequency of input file (kHz) - default 44.1 kHz + /// --bitwidth w input bit width is w (default 16) + /// --scale <arg> scale input (multiply PCM data) by <arg> + /// --scale-l <arg> scale channel 0 (left) input (multiply PCM data) by <arg> + /// --scale-r <arg> scale channel 1 (right) input (multiply PCM data) by <arg> + /// --mp1input input file is a MPEG Layer I file + /// --mp2input input file is a MPEG Layer II file + /// --mp3input input file is a MPEG Layer III file + /// --nogap <file1> <file2> <...> + /// gapless encoding for a set of contiguous files + /// --nogapout <dir> + /// output dir for gapless encoding (must precede --nogap) + /// --nogaptags allow the use of VBR tags in gapless encoding + /// + /// Operational options: + /// -a downmix from stereo to mono file for mono encoding + /// -m <mode> (j)oint, (s)imple, (f)orce, (d)dual-mono, (m)ono + /// default is (j) or (s) depending on bitrate + /// joint = joins the best possible of MS and LR stereo + /// simple = force LR stereo on all frames + /// force = force MS stereo on all frames. + /// --preset type type must be "medium", "standard", "extreme", "insane", + /// or a value for an average desired bitrate and depending + /// on the value specified, appropriate quality settings will + /// be used. + /// "--preset help" gives more info on these + /// --comp <arg> choose bitrate to achive a compression ratio of <arg> + /// --replaygain-fast compute RG fast but slightly inaccurately (default) + /// --replaygain-accurate compute RG more accurately and find the peak sample + /// --noreplaygain disable ReplayGain analysis + /// --clipdetect enable --replaygain-accurate and print a message whether + /// clipping occurs and how far the waveform is from full scale + /// --freeformat produce a free format bitstream + /// --decode input=mp3 file, output=wav + /// -t disable writing wav header when using --decode + /// + /// + /// Verbosity: + /// --disptime <arg>print progress report every arg seconds + /// -S don't print progress report, VBR histograms + /// --nohist disable VBR histogram display + /// --silent don't print anything on screen + /// --quiet don't print anything on screen + /// --brief print more useful information + /// --verbose print a lot of useful information + /// + /// Noise shaping & psycho acoustic algorithms: + /// -q <arg> <arg> = 0...9. Default -q 5 + /// -q 0: Highest quality, very slow + /// -q 9: Poor quality, but fast + /// -h Same as -q 2. Recommended. + /// -f Same as -q 7. Fast, ok quality + /// + /// + /// CBR (constant bitrate, the default) options: + /// -b <bitrate> set the bitrate in kbps, default 128 kbps + /// --cbr enforce use of constant bitrate + /// + /// ABR options: + /// --abr <bitrate> specify average bitrate desired (instead of quality) + /// + /// VBR options: + /// -v use variable bitrate (VBR) (--vbr-old) + /// --vbr-old use old variable bitrate (VBR) routine + /// --vbr-new use new variable bitrate (VBR) routine + /// -V n quality setting for VBR. default n=4 + /// 0=high quality,bigger files. 9=smaller files + /// -b <bitrate> specify minimum allowed bitrate, default 32 kbps + /// -B <bitrate> specify maximum allowed bitrate, default 320 kbps + /// -F strictly enforce the -b option, for use with players that + /// do not support low bitrate mp3 + /// -t disable writing LAME Tag + /// -T enable and force writing LAME Tag + /// + /// + /// PSY related: + /// --short use short blocks when appropriate + /// --noshort do not use short blocks + /// --allshort use only short blocks + /// --notemp disable temporal masking effect + /// --nssafejoint M/S switching criterion + /// --nsmsfix <arg> M/S switching tuning [effective 0-3.5] + /// --interch x adjust inter-channel masking ratio + /// --ns-bass x adjust masking for sfbs 0 - 6 (long) 0 - 5 (short) + /// --ns-alto x adjust masking for sfbs 7 - 13 (long) 6 - 10 (short) + /// --ns-treble x adjust masking for sfbs 14 - 21 (long) 11 - 12 (short) + /// --ns-sfb21 x change ns-treble by x dB for sfb21 + /// + /// + /// experimental switches: + /// -X n[,m] selects between different noise measurements + /// n for long block, m for short. if m is omitted, m = n + /// -Y lets LAME ignore noise in sfb21, like in CBR + /// -Z [n] currently no effects + /// + /// + /// MP3 header/stream options: + /// -e <emp> de-emphasis n/5/c (obsolete) + /// -c mark as copyright + /// -o mark as non-original + /// -p error protection. adds 16 bit checksum to every frame + /// (the checksum is computed correctly) + /// --nores disable the bit reservoir + /// --strictly-enforce-ISO comply as much as possible to ISO MPEG spec + /// + /// Filter options: + /// -k keep ALL frequencies (disables all filters), + /// Can cause ringing and twinkling + /// --lowpass <freq> frequency(kHz), lowpass filter cutoff above freq + /// --lowpass-width <freq> frequency(kHz) - default 15% of lowpass freq + /// --highpass <freq> frequency(kHz), highpass filter cutoff below freq + /// --highpass-width <freq> frequency(kHz) - default 15% of highpass freq + /// --resample <sfreq> sampling frequency of output file(kHz)- default=automatic + /// + /// + /// ID3 tag options: + /// --tt <title> audio/song title (max 30 chars for version 1 tag) + /// --ta <artist> audio/song artist (max 30 chars for version 1 tag) + /// --tl <album> audio/song album (max 30 chars for version 1 tag) + /// --ty <year> audio/song year of issue (1 to 9999) + /// --tc <comment> user-defined text (max 30 chars for v1 tag, 28 for v1.1) + /// --tn <track> audio/song track number (1 to 255, creates v1.1 tag) + /// --tg <genre> audio/song genre (name or number in list) + /// --add-id3v2 force addition of version 2 tag + /// --id3v1-only add only a version 1 tag + /// --id3v2-only add only a version 2 tag + /// --space-id3v1 pad version 1 tag with spaces instead of nulls + /// --pad-id3v2 pad version 2 tag with extra 128 bytes + /// --genre-list print alphabetically sorted ID3 genre list and exit + /// --ignore-tag-errors ignore errors in values passed for tags + /// + /// Note: A version 2 tag will NOT be added unless one of the input fields + /// won't fit in a version 1 tag (e.g. the title string is longer than 30 + /// characters), or the '--add-id3v2' or '--id3v2-only' options are used, + /// or output is redirected to stdout. + /// + /// + /// MS-Windows-specific options: + /// --priority <type> sets the process priority: + /// 0,1 = Low priority (IDLE_PRIORITY_CLASS) + /// 2 = normal priority (NORMAL_PRIORITY_CLASS, defau + /// lt) + /// 3,4 = High priority (HIGH_PRIORITY_CLASS)) + /// Note: Calling '--priority' without a parameter will select priority 0. + /// + /// + /// Platform specific: + /// --noasm <instructions> disable assembly optimizations for mmx/3dnow/sse + /// + /// + /// + /// MPEG-1 layer III sample frequencies (kHz): 32 48 44.1 + /// bitrates (kbps): 32 40 48 56 64 80 96 112 128 160 192 224 256 320 + /// + /// MPEG-2 layer III sample frequencies (kHz): 16 24 22.05 + /// bitrates (kbps): 8 16 24 32 40 48 56 64 80 96 112 128 144 160 + /// + /// MPEG-2.5 layer III sample frequencies (kHz): 8 12 11.025 + /// bitrates (kbps): 8 16 24 32 40 48 56 64 80 96 112 128 144 160 + ///</summary> + public class Mp3EncodingOptions : ProcessArguments + { + #region Private variables + + /// <summary> + /// Set the bitrate in kbps, default 128 kbps -b <bitrate> + /// </summary> + private BitRate mBitRate = BitRate.KBPS_0; + + /// <summary> + /// (j)oint, (s)imple, (f)orce, (d)dual-mono, (m)ono (-m) + /// </summary> + private ChannelMode mChannelMode = ChannelMode.JointStereo; + + /// <summary> + /// input=mp3 file, output=wav (--decode) + /// </summary> + private bool mDecodeWav; + + /// <summary> + /// disable writing wav header when using --decode (-t) + /// </summary> + private bool mDisableWavHeader; + + /// <summary> + /// force byte-swapping of input (-x) + /// </summary> + private bool mForceByteSwappingOfInput; + + /// <summary> + /// produce a free format bitstream (--freeformat) + /// </summary> + private bool mFreeFormat; + + /// <summary> + /// Input filename. Can be "-", which means stdin. + /// </summary> + private string mInfile; + + /// <summary> + /// downmix from stereo to mono file for mono encoding (-a) + /// </summary> + private bool mMonoDownmixing; + + /// <summary> + /// input file is a MPEG Layer III file (--mp3input) + /// </summary> + private bool mMp3Input; + + /// <summary> + /// Output filename. Can be "-", which means stdout. + /// </summary> + private string mOutfile; + + /// <summary> + /// input is raw pcm (-r) + /// </summary> + private bool mRawPCMInputMode; + + /// <summary> + /// input bit width is w (default 16) (--bitwidth w) + /// </summary> + private int mRawPCMInputSampleRate = 16; + + /// <summary> + /// sampling frequency of input file (kHz) - default 44.1 kHz (-s sfreq) + /// </summary> + private SampleRateFrequency mRawPCMInputSampleSize = SampleRateFrequency.Hz_44100; + + #endregion + + #region Public properties + + /// <summary> + /// Input filename. Can be "-", which means stdin. + /// </summary> + public string Infile + { + get { return mInfile; } + set { mInfile = value; } + } + + /// <summary> + /// Output filename. Can be "-", which means stdout. + /// </summary> + public string Outfile + { + get { return mOutfile; } + set { mOutfile = value; } + } + + /// <summary> + /// input is raw pcm (-r) + /// </summary> + public bool RawPCMInputMode + { + get { return mRawPCMInputMode; } + set { mRawPCMInputMode = value; } + } + + /// <summary> + /// force byte-swapping of input (-x) + /// </summary> + public bool ForceByteSwappingOfInput + { + get { return mForceByteSwappingOfInput; } + set + { + mForceByteSwappingOfInput = value; + mRawPCMInputMode = true; + } + } + + /// <summary> + /// sampling frequency of input file (kHz) - default 44.1 kHz (-s sfreq) + /// </summary> + public SampleRateFrequency RawPCMInputSampleSize + { + get { return mRawPCMInputSampleSize; } + set + { + mRawPCMInputSampleSize = value; + mRawPCMInputMode = true; + } + } + + /// <summary> + /// input bit width is w (default 16) (--bitwidth w) + /// </summary> + public int RawPCMInputSampleRate + { + get { return mRawPCMInputSampleRate; } + set + { + mRawPCMInputSampleRate = value; + mRawPCMInputMode = true; + } + } + + /// <summary> + /// input file is a MPEG Layer III file (--mp3input) + /// </summary> + public bool Mp3Input + { + get { return mMp3Input; } + set + { + mMp3Input = value; + if (mMp3Input) + mRawPCMInputMode = false; + } + } + + /// <summary> + /// downmix from stereo to mono file for mono encoding (-a) + /// </summary> + public bool MonoDownmixing + { + get { return mMonoDownmixing; } + set { mMonoDownmixing = value; } + } + + /// <summary> + /// (j)oint, (s)imple, (f)orce, (d)dual-mono, (m)ono (-m) + /// </summary> + public ChannelMode ChannelMode + { + get { return mChannelMode; } + set { mChannelMode = value; } + } + + /// <summary> + /// produce a free format bitstream (--freeformat) + /// </summary> + public bool Freeformat + { + get { return mFreeFormat; } + set { mFreeFormat = value; } + } + + /// <summary> + /// input=mp3 file, output=wav (--decode) + /// </summary> + public bool DecodeWav + { + get { return mDecodeWav; } + set { mDecodeWav = value; } + } + + /// <summary> + /// disable writing wav header when using --decode (-t) + /// </summary> + public bool DisableWavHeader + { + get { return mDisableWavHeader; } + set + { + mDisableWavHeader = value; + if (mDisableWavHeader) + DecodeWav = true; + } + } + + public BitRate BitRate + { + get { return mBitRate; } + set { mBitRate = value; } + } + + #endregion + + /// <summary> + /// Gets a string representing the command line arguments specified by the properties in the object. + /// </summary> + /// <returns></returns> + public override string GetCommandLineArguments() + { + var stringBuilder = new StringBuilder(); + if (mRawPCMInputMode) + { + stringBuilder.Append(" -r"); + if (mForceByteSwappingOfInput) + stringBuilder.Append(" -x"); + if (mRawPCMInputSampleSize != SampleRateFrequency.Hz_44100) + stringBuilder.Append(" -s " + + SampleRateFrequencyUtility.GetSampleRateFrequencyInt(mRawPCMInputSampleSize)); + if (mRawPCMInputSampleRate != 16) + stringBuilder.Append(" --bandwidth " + mRawPCMInputSampleRate); + } + if (mMp3Input) + stringBuilder.Append(" --mp3input"); + if (mMonoDownmixing) + stringBuilder.Append(" -a"); + if (mChannelMode != ChannelMode.JointStereo) + stringBuilder.Append(" -m " + ChannelModeUtility.GetChannelModeChar(mChannelMode)); + + if (mFreeFormat) + { + stringBuilder.Append(" --freeformat"); + stringBuilder.Append(" -b " + BitRateFrequencyUtility.GetBitRateInt(BitRate)); + } + + if (mDecodeWav) + { + stringBuilder.Append(" --decode"); + if (mDisableWavHeader) + stringBuilder.Append(" -t"); + } + + if (!string.IsNullOrEmpty(mInfile)) + stringBuilder.Append(" " + GetQuotedCommandLineArgument(mInfile)); + if (!string.IsNullOrEmpty(mOutfile)) + stringBuilder.Append(" " + (mOutfile)); + + + return stringBuilder.ToString(); + } + } +} \ No newline at end of file diff --git a/Mp3FileProperties.cs b/Mp3FileProperties.cs new file mode 100644 index 0000000..db8f4a0 --- /dev/null +++ b/Mp3FileProperties.cs @@ -0,0 +1,1262 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Reflection; +using System.Text; + +namespace m3uTool +{ + /// <summary> + /// Indicates that a problem existed when parsing an mp3 file + /// </summary> + public class Mp3FormatException : Exception + { + private readonly string mFilename; + + private readonly string mFormatExceptionString; + + /// <summary> + /// Constructor. + /// </summary> + /// <param name="filename"></param> + /// <param name="formatExceptionString"></param> + public Mp3FormatException(string filename, string formatExceptionString) + { + mFilename = filename; + mFormatExceptionString = formatExceptionString; + } + + public string Filename + { + get { return mFilename; } + } + + public string FormatExceptionString + { + get { return mFormatExceptionString; } + } + } + + /// <summary> + /// Tool for accessing the SampleRateFreqency Enum + /// </summary> + public class ChannelModeUtility + { + /// <summary> + /// Converts a ChannelMode into an Int value + /// </summary> + /// <param name="s"></param> + /// <returns></returns> + public static int GetChannelModeInt(ChannelMode s) + { + switch (s) + { + case ChannelMode.Stereo: + return 2; + case ChannelMode.JointStereo: + return 2; + case ChannelMode.DualChannel: + return 2; + case ChannelMode.SingleChannel: + return 1; + default: + return 2; + } + } + + /// <summary> + /// (j)oint, (s)imple, (f)orce, (d)dual-mono, (m)ono (-m) + /// </summary> + /// <param name="s"></param> + public static char GetChannelModeChar(ChannelMode s) + { + switch (s) + { + case ChannelMode.Stereo: + return 's'; + case ChannelMode.JointStereo: + return 'j'; + case ChannelMode.DualChannel: + return 'd'; + case ChannelMode.SingleChannel: + return 'm'; + default: + return 'j'; + } + } + } + + /// <summary> + /// The channel mode an mp3 is encoded in + /// </summary> + public enum ChannelMode + { + /// <summary> + /// + /// </summary> + Stereo, + + /// <summary> + /// Merge a given frequency range of multiple sound channels together so that the + /// resulting encoding will perceive the sound information of that range not as a + /// bundle of separate channels but as one homogenous lump + /// </summary> + JointStereo, + + /// <summary> + /// made of two independant mono channel. Each one uses exactly half the bitrate of the file + /// </summary> + DualChannel, + + /// <summary> + /// Mono + /// </summary> + SingleChannel + } + + /// <summary> + /// Tool for accessing the SampleRateFreqency Enum + /// </summary> + public class SampleRateFrequencyUtility + { + /// <summary> + /// Converts a SampleRateFrequency into an Int value + /// </summary> + /// <param name="s"></param> + /// <returns></returns> + public static int GetSampleRateFrequencyInt(SampleRateFrequency s) + { + return Convert.ToInt32(s.ToString().Substring(s.ToString().IndexOf("_") + 1)); + } + } + + /// <summary> + /// Sample rate frequency of an audio file + /// </summary> + public enum SampleRateFrequency + { + /// <summary> + /// reserved + /// </summary> + Hz_0, + /// <summary> + /// 8000 + /// </summary> + Hz_8000, + /// <summary> + /// 11025 + /// </summary> + Hz_11025, + /// <summary> + /// 12000 + /// </summary> + Hz_12000, + /// <summary> + /// 16000 + /// </summary> + Hz_16000, + /// <summary> + /// 22050 + /// </summary> + Hz_22050, + /// <summary> + /// 24000 + /// </summary> + Hz_24000, + /// <summary> + /// 32000 + /// </summary> + Hz_32000, + /// <summary> + /// 41000 + /// </summary> + Hz_44100, + /// <summary> + /// 48000 + /// </summary> + Hz_48000 + } + + /// <summary> + /// Bitrates + /// 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256 and 320 + /// </summary> + public enum BitRate + { + /// <summary> + /// reserved + /// </summary> + KBPS_0, + /// <summary> + /// 32 + /// </summary> + KBPS_32, + /// <summary> + /// 40 + /// </summary> + KBPS_40, + /// <summary> + /// 48 + /// </summary> + KBPS_48, + /// <summary> + /// 48 + /// </summary> + KBPS_56, + /// <summary> + /// 64 + /// </summary> + KBPS_64, + /// <summary> + /// 80 + /// </summary> + KBPS_80, + /// <summary> + /// 96 + /// </summary> + KBPS_96, + /// <summary> + /// 112 + /// </summary> + KBPS_112, + /// <summary> + /// 128 + /// </summary> + KBPS_128, + /// <summary> + /// 160 + /// </summary> + KBPS_160, + /// <summary> + /// 192 + /// </summary> + KBPS_192, + /// <summary> + /// 224 + /// </summary> + KBPS_224, + /// <summary> + /// 256 + /// </summary> + KBPS_256, + /// <summary> + /// 320 + /// </summary> + KBPS_320, + } + + /// <summary> + /// Tool for accessing the BitRate Enum + /// </summary> + public class BitRateFrequencyUtility + { + /// <summary> + /// Converts a BitRate into an Int value + /// </summary> + /// <param name="b"></param> + /// <returns></returns> + public static int GetBitRateInt(BitRate b) + { + return Convert.ToInt32(b.ToString().Substring(b.ToString().IndexOf("_") + 1)); + } + + /// <summary> + /// Converts a BitRate into an Int value + /// </summary> + /// <param name="b"></param> + /// <returns></returns> + public static int GetBitRateBitsInt(BitRate b) + { + return GetBitRateInt(b)*1024; + } + } + + /// <summary> + /// Given an mp3 file, this extracts all the encoding details and id3 fields + /// </summary> + public class Mp3FileProperties + { + #region private variables + + /// <summary> + /// mp3's filename, without path + /// </summary> + private readonly string mMp3Filename; + + /// <summary> + /// path to the mp3 + /// </summary> + private readonly string mMp3Path; + + private ulong bithdr; + private bool boolVBitRate; + private int intVFrames; + + /// <summary> + /// The mp3 bit rate + /// </summary> + private int mBitRate; + + /// <summary> + /// the channel mode + /// </summary> + private ChannelMode mChannelMode; + + /// <summary> + /// the file size, in bytes + /// </summary> + private long mFileSize; + + /// <summary> + /// The album field + /// </summary> + private string mId3Album; + + /// <summary> + /// The artist field + /// </summary> + public string mId3Artist; + + /// <summary> + /// The comment field + /// </summary> + private string mId3Comment; + + /// <summary> + /// The genre field byte + /// </summary> + private byte mId3Genre; + + /// <summary> + /// The genre field name + /// </summary> + private string mId3GenreName; + + /// <summary> + /// True if the id3 tag exists in the mp3 + /// </summary> + private bool mId3TagExists; + + /// <summary> + /// The title field + /// </summary> + private string mId3Title; + + /// <summary> + /// The track number field + /// </summary> + private byte mId3TrackNumber; + + /// <summary> + /// The year field + /// </summary> + private string mId3Year; + + /// <summary> + /// the mp3 length in seconds + /// </summary> + private int mLengthInSeconds; + + /// <summary> + /// The sample rate frequency + /// </summary> + private SampleRateFrequency mSampleRateFrequency; + + /// <summary> + /// The sample rate frequency integer + /// </summary> + private int mSampleRateIntegerFrequency; + + // Private variables used in the process of reading in the MP3 files + + #endregion + + #region Public Properties + + /// <summary> + /// path to the mp3 + /// </summary> + public string Mp3Path + { + get { return mMp3Path; } + } + + /// <summary> + /// mp3's filename, without path + /// </summary> + public string Mp3Filename + { + get { return mMp3Filename; } + } + + /// <summary> + /// mp3's path and filename + /// </summary> + public string Mp3PathAndFilename + { + get { return mMp3Path + (mMp3Path.EndsWith("\\") ? "" : "\\") + mMp3Filename; } + } + + /// <summary> + /// The mp3 bit rate + /// </summary> + public int BitRate + { + get { return mBitRate; } + } + + /// <summary> + /// the file size, in bytes + /// </summary> + public long FileSize + { + get { return mFileSize; } + } + + /// <summary> + /// The sample rate frequency integer + /// </summary> + public int SampleRateIntegerFrequency + { + get { return mSampleRateIntegerFrequency; } + } + + /// <summary> + /// The sample rate frequency + /// </summary> + public SampleRateFrequency SampleRateFrequency + { + get { return mSampleRateFrequency; } + } + + /// <summary> + /// the channel mode + /// </summary> + public ChannelMode ChannelMode + { + get { return mChannelMode; } + } + + /// <summary> + /// the mp3 length in seconds + /// </summary> + public int LengthInSeconds + { + get { return mLengthInSeconds; } + } + + /// <summary> + /// the length formatted + /// </summary> + public string LengthFormatted + { + get + { + // Complete number of seconds + int s = GetLengthInSeconds(); + + // Seconds to display + int ss = s%60; + + // Complete number of minutes + int m = (s - ss)/60; + + // Minutes to display + int mm = m%60; + + // Complete number of hours + int h = (m - mm)/60; + + // Make "hh:mm:ss" + return h.ToString("D2") + ":" + mm.ToString("D2") + ":" + ss.ToString("D2"); + } + } + + /// <summary> + /// the mp3 length in timespan + /// </summary> + public TimeSpan FileLength + { + get { return new TimeSpan(0, 0, 0, 0, mLengthInSeconds); } + } + + /// <summary> + /// True if the id3 tag exists in the mp3 + /// </summary> + public bool Id3TagExists + { + get { return mId3TagExists; } + } + + /// <summary> + /// The title field + /// </summary> + public string Id3Title + { + get { return mId3Title; } + } + + /// <summary> + /// The artist field + /// </summary> + public string Id3Artist + { + get { return mId3Artist; } + } + + /// <summary> + /// The album field + /// </summary> + public string Id3Album + { + get { return mId3Album; } + } + + /// <summary> + /// The year field + /// </summary> + public string Id3Year + { + get { return mId3Year; } + } + + /// <summary> + /// The comment field + /// </summary> + public string Id3Comment + { + get { return mId3Comment; } + } + + /// <summary> + /// The track number field + /// </summary> + public byte Id3TrackNumber + { + get { return mId3TrackNumber; } + } + + /// <summary> + /// The genre field byte + /// </summary> + public byte Id3Genre + { + get { return mId3Genre; } + } + + /// <summary> + /// The genre field name + /// </summary> + public string Id3GenreName + { + get { return mId3GenreName; } + } + + #endregion + + #region Private Readonly Static Lookup + + /// <summary> + /// list of genres defined by id3 1.1 + /// </summary> + private static readonly string[] mGenres = + { + "Blues", "Classic Rock", "Country", "Dance", "Disco", "Funk", "Grunge", "Hip-Hop", "Jazz", "Metal", + "New Age", "Oldies", "Other", "Pop", "R&B", "Rap", "Reggae", "Rock", "Techno", "Industrial", + "Alternative", "Ska", "Death Metal", "Pranks", "Soundtrack", "Euro-Techno", "Ambient", "Trip-Hop", + "Vocal", "Jazz+Funk", "Fusion", "Trance", "Classical", "Instrumental", "Acid", "House", + "Game", "Sound Clip", "Gospel", "Noise", "Alternative Rock", "Bass", "Soul", "Punk", "Space", + "Meditative", "Instrumental Pop", "Instrumental Rock", "Ethnic", "Gothic", + "Darkwave", "Techno-Industrial", "Electronic", "Pop-Folk", "Eurodance", "Dream", + "Southern Rock", "Comedy", "Cult", "Gangsta", "Top 40", "Christian Rap", "Pop/Funk", "Jungle", + "Native American", "Cabaret", "New Wave", "Psychadelic", "Rave", "Showtunes", "Trailer", "Lo-Fi", + "Tribal", "Acid Punk", "Acid Jazz", "Polka", "Retro", "Musical", "Rock & Roll", "Hard Rock", "Folk", + "Folk/Rock", "National Folk", "Swing", "Fast-Fusion", "Bebob", "Latin", "Revival", "Celtic", "Bluegrass" + , + "Avantgarde", "Gothic Rock", "Progressive Rock", "Psychedelic Rock", "Symphonic Rock", "Slow Rock", + "Big Band", "Chorus", "Easy Listening", "Acoustic", "Humour", "Speech", "Chanson", "Opera", + "Chamber Music", + "Sonata", "Symphony", "Booty Bass", "Primus", "Porn Groove", "Satire", "Slow Jam", "Club", + "Tango", "Samba", "Folklore", "Ballad", "Power Ballad", "Rhytmic Soul", "Freestyle", "Duet", + "Punk Rock", "Drum Solo", "Acapella", "Euro-House", "Dance Hall", "Goa", "Drum & Bass", "Club-House", + "Hardcore", "Terror", "Indie", "BritPop", "Negerpunk", "Polsk Punk", "Beat", "Christian Gangsta Rap", + "Heavy Metal", "Black Metal", "Crossover", "Contemporary Christian", + "Christian Rock", "Merengue", "Salsa", "Trash Metal", "Anime", "JPop", "SynthPop" + }; + + /// <summary> + /// bit rates available to mp3 + /// </summary> + private static readonly int[,,] mBitrateTable = { + { + // MPEG 2 & 2.5 + { + 0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, + 144 + , 160, 0 + }, // Layer III + { + 0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, + 144 + , 160, 0 + }, // Layer II + { + 0, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, + 192 + , 224, 256, 0 + } // Layer I + }, + { + // MPEG 1 + { + 0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, + 224, + 256, 320, 0 + }, // Layer III + { + 0, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, + 256 + , 320, 384, 0 + }, // Layer II + { + 0, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, + 352, + 384, 416, 448, 0 + } // Layer I + } + }; + + /// <summary> + /// integer sample rates avaiable for mp3 + /// </summary> + private static readonly int[,] mSampleFrequencyIntegerTable = { + {32000, 16000, 8000}, // MPEG 2.5 + {0, 0, 0}, // reserved + {22050, 24000, 16000}, // MPEG 2 + {44100, 48000, 32000} // MPEG 1 + }; + + /// <summary> + /// Lookup for + /// </summary> + private static readonly SampleRateFrequency[,] mSampleFrequencyTable = { + { + SampleRateFrequency.Hz_32000, + SampleRateFrequency.Hz_16000, + SampleRateFrequency.Hz_8000 + // MPEG 2.5 + }, + { + SampleRateFrequency.Hz_0, + SampleRateFrequency.Hz_0, + SampleRateFrequency.Hz_0 + // reserved + }, + { + SampleRateFrequency.Hz_22050, + SampleRateFrequency.Hz_24000, + SampleRateFrequency.Hz_16000 + // MPEG 2 + }, + { + SampleRateFrequency.Hz_44100, + SampleRateFrequency.Hz_48000, + SampleRateFrequency.Hz_32000 + // MPEG 1 + } + }; + + #endregion + + #region Constructor + + // Required struct constructor + public Mp3FileProperties(string name) + { + var fFileInfo = new FileInfo(name); + mMp3Path = fFileInfo.DirectoryName; + mMp3Filename = fFileInfo.Name; + + ReadMP3Information(); + ReadMP3Tag(); + } + + #endregion + + private Mp3FileProperties() + { + } + + #region private methods + + private void ReadMP3Information() + { + var fs = new FileStream(Mp3PathAndFilename, FileMode.Open, FileAccess.Read); + // Set the filename not including the path information + var chrSeparators = new[] {'\\', '/'}; + string[] strSeparator = Mp3PathAndFilename.Split(chrSeparators); + int intUpper = strSeparator.GetUpperBound(0); + //mMp3PathAndFilename = strSeparator[intUpper]; + + //// Replace ' with '' for the SQL INSERT statement + //mMp3PathAndFilename = mMp3PathAndFilename.Replace("'", "''"); + + // Set the file size + mFileSize = fs.Length; + + var bytHeader = new byte[4]; + var bytVBitRate = new byte[12]; + int intPos = 0; + + // Keep reading 4 bytes from the header until we know for sure that in + // fact it's an MP3 + do + { + fs.Position = intPos; + fs.Read(bytHeader, 0, 4); + intPos++; + LoadMP3Header(bytHeader); + } while (!IsValidHeader() && (fs.Position != fs.Length)); + + // If the current file stream position is equal to the length, + // that means that we've read the entire file and it's not a valid MP3 file + if (fs.Position == fs.Length) + throw new Mp3FormatException(Mp3PathAndFilename, "Invalid mp3 file"); + + intPos += 3; + + if (getVersionIndex() == 3) // MPEG Version 1 + { + if (getModeIndex() == 3) // Single Channel + { + intPos += 17; + } + else + { + intPos += 32; + } + } + else // MPEG Version 2.0 or 2.5 + { + if (getModeIndex() == 3) // Single Channel + { + intPos += 9; + } + else + { + intPos += 17; + } + } + + // Check to see if the MP3 has a variable bitrate + fs.Position = intPos; + fs.Read(bytVBitRate, 0, 12); + boolVBitRate = LoadVBRHeader(bytVBitRate); + + // Once the file's read in, then assign the properties of the file to the public variables + mBitRate = GetBitrate(); + mSampleRateIntegerFrequency = GetSampleRateFrequencyInt(); + mSampleRateFrequency = GetSampleRateFrequency(); + mChannelMode = GetChannelMode(); + mLengthInSeconds = GetLengthInSeconds(); + fs.Close(); + } + + private void LoadMP3Header(byte[] c) + { + // this thing is quite interesting, it works like the following + // c[0] = 00000011 + // c[1] = 00001100 + // c[2] = 00110000 + // c[3] = 11000000 + // the operator << means that we'll move the bits in that direction + // 00000011 << 24 = 00000011000000000000000000000000 + // 00001100 << 16 = 000011000000000000000000 + // 00110000 << 24 = 0011000000000000 + // 11000000 = 11000000 + // +_________________________________ + // 00000011000011000011000011000000 + bithdr = (ulong) (((c[0] & 255) << 24) | ((c[1] & 255) << 16) | ((c[2] & 255) << 8) | ((c[3] & 255))); + } + + private bool LoadVBRHeader(byte[] inputheader) + { + // If it's a variable bitrate MP3, the first 4 bytes will read 'Xing' + // since they're the ones who added variable bitrate-edness to MP3s + if (inputheader[0] == 88 && inputheader[1] == 105 && + inputheader[2] == 110 && inputheader[3] == 103) + { + int flags = (((inputheader[4] & 255) << 24) | ((inputheader[5] & 255) << 16) | + ((inputheader[6] & 255) << 8) | ((inputheader[7] & 255))); + if ((flags & 0x0001) == 1) + { + intVFrames = (((inputheader[8] & 255) << 24) | ((inputheader[9] & 255) << 16) | + ((inputheader[10] & 255) << 8) | ((inputheader[11] & 255))); + return true; + } + else + { + intVFrames = -1; + return true; + } + } + return false; + } + + private bool IsValidHeader() + { + return (((getFrameSync() & 2047) == 2047) && + ((getVersionIndex() & 3) != 1) && + ((getLayerIndex() & 3) != 0) && + ((getBitrateIndex() & 15) != 0) && + ((getBitrateIndex() & 15) != 15) && + ((getFrequencyIndex() & 3) != 3) && + ((getEmphasisIndex() & 3) != 2)); + } + + private int getFrameSync() + { + return (int) ((bithdr >> 21) & 2047); + } + + private int getVersionIndex() + { + return (int) ((bithdr >> 19) & 3); + } + + private int getLayerIndex() + { + return (int) ((bithdr >> 17) & 3); + } + + //private int getProtectionBit() + //{ + // return (int)((bithdr>>16) & 1); + //} + + private int getBitrateIndex() + { + return (int) ((bithdr >> 12) & 15); + } + + private int getFrequencyIndex() + { + return (int) ((bithdr >> 10) & 3); + } + + //private int getPaddingBit() + //{ + // return (int)((bithdr>>9) & 1); + //} + + //private int getPrivateBit() + //{ + // return (int)((bithdr>>8) & 1); + //} + + private int getModeIndex() + { + return (int) ((bithdr >> 6) & 3); + } + + //private int getModeExtIndex() + //{ + // return (int)((bithdr>>4) & 3); + //} + + //private int getCoprightBit() + //{ + // return (int)((bithdr>>3) & 1); + //} + + //private int getOrginalBit() + //{ + // return (int)((bithdr>>2) & 1); + //} + + private int getEmphasisIndex() + { + return (int) (bithdr & 3); + } + + //private double getVersion() + //{ + // double[] table = {2.5, 0.0, 2.0, 1.0}; + // return table[getVersionIndex()]; + //} + + //private int getLayer() + //{ + // return (int)(4 - getLayerIndex()); + //} + + private int GetBitrate() + { + // If the file has a variable bitrate, then we return an integer average bitrate, + // otherwise, we use a lookup table to return the bitrate + int layerIndex = getLayerIndex(); + if (!boolVBitRate) + { + try + { + int versionIndex = getVersionIndex(); + int bitrateIndex = getBitrateIndex(); + return mBitrateTable[versionIndex & 1, layerIndex - 1, bitrateIndex]; + } + catch (Exception) // must have been variable afterall + { + boolVBitRate = true; + } + } + + double medFrameSize = mFileSize/(double) getNumberOfFrames(); + return (int) ((medFrameSize*GetSampleRateFrequencyInt())/(1000.0*((layerIndex == 3) ? 12.0 : 144.0))); + } + + private int GetSampleRateFrequencyInt() + { + return mSampleFrequencyIntegerTable[getVersionIndex(), getFrequencyIndex()]; + } + + private SampleRateFrequency GetSampleRateFrequency() + { + return mSampleFrequencyTable[getVersionIndex(), getFrequencyIndex()]; + } + + private ChannelMode GetChannelMode() + { + switch (getModeIndex()) + { + default: + return ChannelMode.Stereo; + case 1: + return ChannelMode.JointStereo; + case 2: + return ChannelMode.DualChannel; + case 3: + return ChannelMode.SingleChannel; + } + } + + private int GetLengthInSeconds() + { + // "intKilBitFileSize" made by dividing by 1000 in order to match the "Kilobits/second" + int intKiloBitFileSize = ((8*(int) mFileSize)/1000); + return (intKiloBitFileSize/GetBitrate()); + } + + private int getNumberOfFrames() + { + // Again, the number of MPEG frames is dependant on whether it's a variable bitrate MP3 or not + if (!boolVBitRate) + { + double medFrameSize = (((getLayerIndex() == 3) ? 12 : 144)* + ((1000.0*GetBitrate())/GetSampleRateFrequencyInt())); + return (int) (mFileSize/medFrameSize); + } + else + return intVFrames; + } + + private static string TruncateStringAtNull(string sourceString) + { + var targetCharList = new ArrayList(sourceString.Length); + for (int i = 0; i < sourceString.Length && sourceString[i] != 0; i++) + { + // Debug.WriteLine("c : " + (int)sourceString[i] + " " + sourceString[i]); + + targetCharList.Add(sourceString[i] < 32 ? ' ' : sourceString[i]); + } + return new string((char[]) targetCharList.ToArray(typeof (char))); + // Regex.Replace(new string((char [])targetCharList.ToArray(typeof (char))), @"\W*", ""); + } + + private void ReadMP3Tag() + { + // Read the 128 byte ID3 tag into a byte array + FileStream oFileStream; + oFileStream = new FileStream(Mp3PathAndFilename, FileMode.Open); + var bBuffer = new byte[128]; + oFileStream.Seek(-128, SeekOrigin.End); + oFileStream.Read(bBuffer, 0, 128); + oFileStream.Close(); + + // Convert the Byte Array to a String + Encoding instEncoding = new ASCIIEncoding(); // NB: Encoding is an Abstract class + string id3Tag = instEncoding.GetString(bBuffer); + + // If there is an attched ID3 v1.x TAG then read it + if (id3Tag.Substring(0, 3) == "TAG") + { + mId3Title = TruncateStringAtNull(id3Tag.Substring(3, 30)); + mId3Artist = TruncateStringAtNull(id3Tag.Substring(33, 30)); + mId3Album = TruncateStringAtNull(id3Tag.Substring(63, 30)); + mId3Year = TruncateStringAtNull(id3Tag.Substring(93, 4)); + mId3Comment = TruncateStringAtNull(id3Tag.Substring(97, 28)); + + // Get the track number if TAG conforms to ID3 v1.1 + if (id3Tag[125] == 0) + mId3TrackNumber = bBuffer[126]; + else + mId3TrackNumber = 0; + mId3Genre = bBuffer[127]; + if (mGenres.Length > mId3Genre) + mId3GenreName = mGenres[mId3Genre]; + mId3TagExists = true; + // ********* IF USED IN ANGER: ENSURE to test for non-numeric year + } + else + { + // ID3 Tag not found so create an empty TAG in case the user saces later + mId3Title = ""; + mId3Artist = ""; + mId3Album = ""; + mId3Year = ""; + mId3Comment = ""; + mId3TrackNumber = 0; + mId3Genre = 0; + mId3GenreName = ""; + mId3TagExists = false; + } + } + + private static void UpdateMP3ID3Tag(ref Mp3FileProperties paramMP3) + { + // Trim any whitespace + paramMP3.mId3Title = paramMP3.mId3Title.Trim(); + paramMP3.mId3Artist = paramMP3.mId3Artist.Trim(); + paramMP3.mId3Album = paramMP3.mId3Album.Trim(); + paramMP3.mId3Year = paramMP3.mId3Year.Trim(); + paramMP3.mId3Comment = paramMP3.mId3Comment.Trim(); + + // Ensure all properties are correct size + if (paramMP3.mId3Title.Length > 30) paramMP3.mId3Title = paramMP3.mId3Title.Substring(0, 30); + if (paramMP3.mId3Artist.Length > 30) paramMP3.mId3Artist = paramMP3.mId3Artist.Substring(0, 30); + if (paramMP3.mId3Album.Length > 30) paramMP3.mId3Album = paramMP3.mId3Album.Substring(0, 30); + if (paramMP3.mId3Year.Length > 4) paramMP3.mId3Year = paramMP3.mId3Year.Substring(0, 4); + if (paramMP3.mId3Comment.Length > 28) paramMP3.mId3Comment = paramMP3.mId3Comment.Substring(0, 28); + + // Build a new ID3 Tag (128 Bytes) + var tagByteArray = new byte[128]; + for (int i = 0; i < tagByteArray.Length; i++) + tagByteArray[i] = 0; // Initialise array to nulls + + // Convert the Byte Array to a String + Encoding instEncoding = new ASCIIEncoding(); + // NB: Encoding is an Abstract class // ************ To DO: Make a shared instance of ASCIIEncoding so we don't keep creating/destroying it + // Copy "TAG" to Array + byte[] workingByteArray = instEncoding.GetBytes("TAG"); + Array.Copy(workingByteArray, 0, tagByteArray, 0, workingByteArray.Length); + // Copy Title to Array + workingByteArray = instEncoding.GetBytes(paramMP3.mId3Title); + Array.Copy(workingByteArray, 0, tagByteArray, 3, workingByteArray.Length); + // Copy Artist to Array + workingByteArray = instEncoding.GetBytes(paramMP3.mId3Artist); + Array.Copy(workingByteArray, 0, tagByteArray, 33, workingByteArray.Length); + // Copy Album to Array + workingByteArray = instEncoding.GetBytes(paramMP3.mId3Album); + Array.Copy(workingByteArray, 0, tagByteArray, 63, workingByteArray.Length); + // Copy Year to Array + workingByteArray = instEncoding.GetBytes(paramMP3.mId3Year); + Array.Copy(workingByteArray, 0, tagByteArray, 93, workingByteArray.Length); + // Copy Comment to Array + workingByteArray = instEncoding.GetBytes(paramMP3.mId3Comment); + Array.Copy(workingByteArray, 0, tagByteArray, 97, workingByteArray.Length); + // Copy Track and Genre to Array + tagByteArray[126] = paramMP3.mId3TrackNumber; + tagByteArray[127] = paramMP3.mId3Genre; + + // SAVE TO DISK: Replace the final 128 Bytes with our new ID3 tag + var oFileStream = new FileStream(paramMP3.Mp3PathAndFilename, FileMode.Open); + if (paramMP3.mId3TagExists) + oFileStream.Seek(-128, SeekOrigin.End); + else + oFileStream.Seek(0, SeekOrigin.End); + oFileStream.Write(tagByteArray, 0, 128); + oFileStream.Close(); + paramMP3.mId3TagExists = true; + } + + #endregion + + /// <summary> + /// Returns a <see cref="T:System.String"></see> that represents the current <see cref="T:System.Object"></see>. + /// </summary> + /// <returns> + /// A <see cref="T:System.String"></see> that represents the current <see cref="T:System.Object"></see>. + /// </returns> + public override string ToString() + { + return string.Format("{0}, BR:{1}, SR:{2}, CH:{3}", mMp3Filename, mBitRate, mSampleRateFrequency, + mChannelMode); + } + + public static Mp3FileProperties GetCommonProperties(List<Mp3FileProperties> properties) + { + var targetProperty = new Mp3FileProperties(); + + Type mp3FilePropertiesType = typeof (Mp3FileProperties); + foreach (PropertyInfo propertyInfo in mp3FilePropertiesType.GetProperties()) + { + FieldInfo field = mp3FilePropertiesType.GetField("m" + propertyInfo.Name, + BindingFlags.NonPublic | BindingFlags.Instance); + if (field == null) + { + // Debug.WriteLine("Couldn't find " + "m" + propertyInfo.Name); + continue; + } + if (propertyInfo.PropertyType == typeof (string)) + { + string commonString = GetCommonString(properties, propertyInfo); + if (commonString != null) + field.SetValue(targetProperty, commonString); + Debug.WriteLine(propertyInfo.Name + " " + commonString); + } + else if (propertyInfo.PropertyType == typeof (int)) + { + field.SetValue(targetProperty, GetCommonThing<int>(properties, propertyInfo)); + } + else if (propertyInfo.PropertyType == typeof (long)) + { + field.SetValue(targetProperty, GetCommonThing<long>(properties, propertyInfo)); + } + else if (propertyInfo.PropertyType == typeof (ChannelMode)) + { + field.SetValue(targetProperty, GetCommonThing<ChannelMode>(properties, propertyInfo)); + } + else if (propertyInfo.PropertyType == typeof (SampleRateFrequency)) + { + field.SetValue(targetProperty, GetCommonThing<SampleRateFrequency>(properties, propertyInfo)); + } + + else if (propertyInfo.PropertyType == typeof (byte)) + { + field.SetValue(targetProperty, GetCommonThing<byte>(properties, propertyInfo)); + } + else if (propertyInfo.PropertyType == typeof (bool)) + { + field.SetValue(targetProperty, GetCommonThing<bool>(properties, propertyInfo)); + } + else + throw new ApplicationException("Unknown type " + propertyInfo.PropertyType); + + + // Debug.WriteLine(propertyInfo); + } + return targetProperty; + } + + /// <summary> + /// Gets the common thing. + /// </summary> + /// <param name="properties">The properties.</param> + /// <param name="propertyInfo">The property info.</param> + /// <returns></returns> + public static T GetCommonThing<T>(List<Mp3FileProperties> properties, PropertyInfo propertyInfo) where T : new() + { + if (typeof (T) != propertyInfo.PropertyType) + throw new ArgumentException("Incorrect type. Expecting " + typeof (T) + ", got " + + propertyInfo.PropertyType); + var tCounts = new Dictionary<T, int>(); + + foreach (Mp3FileProperties property in properties) + { + var value = (T) propertyInfo.GetValue(property, null); + if (tCounts.ContainsKey(value)) + tCounts[value] = tCounts[value] + 1; + else + tCounts.Add(value, 1); + } + + var keys = new List<T>(tCounts.Keys); + keys.Sort(delegate(T x, T y) { return tCounts[x].CompareTo(tCounts[y]); } + ); + if (keys.Count > 0) + return keys[0]; + return new T(); + } + + + /// <summary> + /// Gets the common string. + /// </summary> + /// <param name="properties">The properties.</param> + /// <param name="propertyInfo">The property info.</param> + /// <returns></returns> + public static string GetCommonString(List<Mp3FileProperties> properties, PropertyInfo propertyInfo) + { + var stringCounts = new Dictionary<string, int>(); + var originalStrings = new Dictionary<string, string>(); + foreach (Mp3FileProperties property in properties) + { + var originalString = (string) propertyInfo.GetValue(property, null); + if (originalString == null) + continue; + + // remove whitespace + while (originalString.Contains(" ")) + originalString.Replace(" ", " "); + while (originalString.Contains("\t")) + originalString.Replace("\t", " "); + + string value = originalString.ToLower(); + + if (!originalStrings.ContainsKey(value)) + originalStrings.Add(value, originalString); + + if (stringCounts.ContainsKey(value)) + stringCounts[value] = stringCounts[value] + 1; + else + stringCounts[value] = 1; + } + if (stringCounts.Count == 0) + return null; + + var keys = new List<string>(originalStrings.Keys); + keys.Sort(delegate(string x, string y) { return stringCounts[x].CompareTo(stringCounts[y]); } + ); + + if (stringCounts.Count > 1 && stringCounts.Count > (properties.Count/2)) + { + // Dictionary<string, int> substringLengths = new Dictionary<string, int>(); + var substringCounts = new Dictionary<string, int>(); + + foreach (string keyLHS in keys) + foreach (string keyRHS in keys) + foreach (string substring in LongestCommonSubstrings.GetLongestSubstring(keyLHS, keyRHS)) + substringCounts[substring] = (substringCounts.ContainsKey(substring) + ? substringCounts[substring] + : 0) + 1; + + var allSubstringsSortedByCount = new List<string>(substringCounts.Keys); + var allSubstringsSortedByLength = new List<string>(substringCounts.Keys); + + allSubstringsSortedByCount.Sort( + delegate(string x, string y) { return substringCounts[x].CompareTo(substringCounts[y]); } + ); + + allSubstringsSortedByLength.Sort(delegate(string x, string y) { return x.Length.CompareTo(y.Length); } + ); + + var allSubstringsSortedRankCombination = new List<string>(allSubstringsSortedByLength); + + allSubstringsSortedRankCombination.Sort(delegate(string x, string y) + { + return + (allSubstringsSortedByCount.IndexOf(x) + + allSubstringsSortedByLength.IndexOf(x)).CompareTo( + allSubstringsSortedByCount.IndexOf(y) + + allSubstringsSortedByLength.IndexOf(y) + ); + } + ); + + int startIndex = keys[0].IndexOf(allSubstringsSortedRankCombination[0]); + return originalStrings[keys[0]].Substring(startIndex, allSubstringsSortedRankCombination[0].Length); + } + return originalStrings[keys[0]]; + } + } +} \ No newline at end of file diff --git a/Mp3ToAacBatch.Designer.cs b/Mp3ToAacBatch.Designer.cs new file mode 100644 index 0000000..a32c139 --- /dev/null +++ b/Mp3ToAacBatch.Designer.cs @@ -0,0 +1,306 @@ +namespace m3uTool +{ + partial class Mp3ToAacBatch + { + /// <summary> + /// Required designer variable. + /// </summary> + private System.ComponentModel.IContainer components = null; + + /// <summary> + /// Clean up any resources being used. + /// </summary> + /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param> + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// <summary> + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// </summary> + private void InitializeComponent() + { + System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(Mp3ToAacBatch)); + this.inputListBox = new System.Windows.Forms.ListBox(); + this.addFolderButton = new System.Windows.Forms.Button(); + this.addFileButton = new System.Windows.Forms.Button(); + this.moveUpButton = new System.Windows.Forms.Button(); + this.moveDownButton = new System.Windows.Forms.Button(); + this.sortButton = new System.Windows.Forms.Button(); + this.updownSplitContainer = new System.Windows.Forms.SplitContainer(); + this.inputLabel = new System.Windows.Forms.Label(); + this.inputPanel = new System.Windows.Forms.Panel(); + this.clearButton = new System.Windows.Forms.Button(); + this.optionsPanel = new System.Windows.Forms.Panel(); + this.singleFileCheckBox = new System.Windows.Forms.CheckBox(); + this.outputDirectoryTextBox = new System.Windows.Forms.TextBox(); + this.optionsLabel = new System.Windows.Forms.Label(); + this.savePanel = new System.Windows.Forms.Panel(); + this.saveLabel = new System.Windows.Forms.Label(); + this.updownSplitContainer.Panel1.SuspendLayout(); + this.updownSplitContainer.Panel2.SuspendLayout(); + this.updownSplitContainer.SuspendLayout(); + this.inputPanel.SuspendLayout(); + this.optionsPanel.SuspendLayout(); + this.savePanel.SuspendLayout(); + this.SuspendLayout(); + // + // inputListBox + // + this.inputListBox.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.inputListBox.FormattingEnabled = true; + this.inputListBox.Location = new System.Drawing.Point(6, 35); + this.inputListBox.Name = "inputListBox"; + this.inputListBox.Size = new System.Drawing.Size(551, 264); + this.inputListBox.TabIndex = 0; + // + // addFolderButton + // + this.addFolderButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.addFolderButton.BackgroundImageLayout = System.Windows.Forms.ImageLayout.Center; + this.addFolderButton.FlatStyle = System.Windows.Forms.FlatStyle.Flat; + this.addFolderButton.Image = ((System.Drawing.Image)(resources.GetObject("addFolderButton.Image"))); + this.addFolderButton.ImageAlign = System.Drawing.ContentAlignment.TopLeft; + this.addFolderButton.Location = new System.Drawing.Point(470, 3); + this.addFolderButton.Name = "addFolderButton"; + this.addFolderButton.Size = new System.Drawing.Size(87, 30); + this.addFolderButton.TabIndex = 2; + this.addFolderButton.Text = "Add Folder"; + this.addFolderButton.TextImageRelation = System.Windows.Forms.TextImageRelation.ImageBeforeText; + this.addFolderButton.UseVisualStyleBackColor = true; + // + // addFileButton + // + this.addFileButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.addFileButton.BackgroundImageLayout = System.Windows.Forms.ImageLayout.Center; + this.addFileButton.FlatStyle = System.Windows.Forms.FlatStyle.Flat; + this.addFileButton.Image = ((System.Drawing.Image)(resources.GetObject("addFileButton.Image"))); + this.addFileButton.ImageAlign = System.Drawing.ContentAlignment.TopLeft; + this.addFileButton.Location = new System.Drawing.Point(390, 3); + this.addFileButton.Name = "addFileButton"; + this.addFileButton.Size = new System.Drawing.Size(74, 30); + this.addFileButton.TabIndex = 3; + this.addFileButton.Text = "Add File"; + this.addFileButton.TextImageRelation = System.Windows.Forms.TextImageRelation.ImageBeforeText; + this.addFileButton.UseVisualStyleBackColor = true; + // + // moveUpButton + // + this.moveUpButton.BackgroundImageLayout = System.Windows.Forms.ImageLayout.Center; + this.moveUpButton.Dock = System.Windows.Forms.DockStyle.Fill; + this.moveUpButton.FlatStyle = System.Windows.Forms.FlatStyle.Flat; + this.moveUpButton.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.moveUpButton.ImageAlign = System.Drawing.ContentAlignment.TopLeft; + this.moveUpButton.Location = new System.Drawing.Point(0, 0); + this.moveUpButton.Name = "moveUpButton"; + this.moveUpButton.Size = new System.Drawing.Size(18, 107); + this.moveUpButton.TabIndex = 4; + this.moveUpButton.Text = "^|"; + this.moveUpButton.TextImageRelation = System.Windows.Forms.TextImageRelation.ImageBeforeText; + this.moveUpButton.UseVisualStyleBackColor = true; + // + // moveDownButton + // + this.moveDownButton.BackgroundImageLayout = System.Windows.Forms.ImageLayout.Center; + this.moveDownButton.Dock = System.Windows.Forms.DockStyle.Fill; + this.moveDownButton.FlatStyle = System.Windows.Forms.FlatStyle.Flat; + this.moveDownButton.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.moveDownButton.ImageAlign = System.Drawing.ContentAlignment.TopLeft; + this.moveDownButton.Location = new System.Drawing.Point(0, 0); + this.moveDownButton.Name = "moveDownButton"; + this.moveDownButton.Size = new System.Drawing.Size(18, 157); + this.moveDownButton.TabIndex = 5; + this.moveDownButton.Text = "|v"; + this.moveDownButton.TextImageRelation = System.Windows.Forms.TextImageRelation.ImageBeforeText; + this.moveDownButton.UseVisualStyleBackColor = true; + // + // sortButton + // + this.sortButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.sortButton.BackgroundImageLayout = System.Windows.Forms.ImageLayout.Center; + this.sortButton.FlatStyle = System.Windows.Forms.FlatStyle.Flat; + this.sortButton.Image = ((System.Drawing.Image)(resources.GetObject("sortButton.Image"))); + this.sortButton.ImageAlign = System.Drawing.ContentAlignment.TopLeft; + this.sortButton.Location = new System.Drawing.Point(0, 307); + this.sortButton.Name = "sortButton"; + this.sortButton.Size = new System.Drawing.Size(61, 30); + this.sortButton.TabIndex = 6; + this.sortButton.Text = "Sort"; + this.sortButton.TextImageRelation = System.Windows.Forms.TextImageRelation.ImageBeforeText; + this.sortButton.UseVisualStyleBackColor = true; + // + // updownSplitContainer + // + this.updownSplitContainer.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Right))); + this.updownSplitContainer.IsSplitterFixed = true; + this.updownSplitContainer.Location = new System.Drawing.Point(563, 35); + this.updownSplitContainer.Name = "updownSplitContainer"; + this.updownSplitContainer.Orientation = System.Windows.Forms.Orientation.Horizontal; + // + // updownSplitContainer.Panel1 + // + this.updownSplitContainer.Panel1.Controls.Add(this.moveUpButton); + // + // updownSplitContainer.Panel2 + // + this.updownSplitContainer.Panel2.Controls.Add(this.moveDownButton); + this.updownSplitContainer.Size = new System.Drawing.Size(18, 266); + this.updownSplitContainer.SplitterDistance = 107; + this.updownSplitContainer.SplitterWidth = 2; + this.updownSplitContainer.TabIndex = 8; + // + // inputLabel + // + this.inputLabel.AutoSize = true; + this.inputLabel.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.inputLabel.Location = new System.Drawing.Point(3, 3); + this.inputLabel.Name = "inputLabel"; + this.inputLabel.Size = new System.Drawing.Size(126, 13); + this.inputLabel.TabIndex = 9; + this.inputLabel.Text = "1) Select Input MP3s"; + // + // inputPanel + // + this.inputPanel.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.inputPanel.Controls.Add(this.clearButton); + this.inputPanel.Controls.Add(this.inputLabel); + this.inputPanel.Controls.Add(this.inputListBox); + this.inputPanel.Controls.Add(this.updownSplitContainer); + this.inputPanel.Controls.Add(this.addFolderButton); + this.inputPanel.Controls.Add(this.addFileButton); + this.inputPanel.Controls.Add(this.sortButton); + this.inputPanel.Location = new System.Drawing.Point(12, 12); + this.inputPanel.Name = "inputPanel"; + this.inputPanel.Size = new System.Drawing.Size(584, 344); + this.inputPanel.TabIndex = 10; + // + // clearButton + // + this.clearButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.clearButton.BackgroundImageLayout = System.Windows.Forms.ImageLayout.Center; + this.clearButton.FlatStyle = System.Windows.Forms.FlatStyle.Flat; + this.clearButton.Image = ((System.Drawing.Image)(resources.GetObject("clearButton.Image"))); + this.clearButton.ImageAlign = System.Drawing.ContentAlignment.TopLeft; + this.clearButton.Location = new System.Drawing.Point(491, 307); + this.clearButton.Name = "clearButton"; + this.clearButton.Size = new System.Drawing.Size(66, 30); + this.clearButton.TabIndex = 10; + this.clearButton.Text = "Clear"; + this.clearButton.TextImageRelation = System.Windows.Forms.TextImageRelation.ImageBeforeText; + this.clearButton.UseVisualStyleBackColor = true; + // + // optionsPanel + // + this.optionsPanel.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.optionsPanel.Controls.Add(this.singleFileCheckBox); + this.optionsPanel.Controls.Add(this.outputDirectoryTextBox); + this.optionsPanel.Controls.Add(this.optionsLabel); + this.optionsPanel.Location = new System.Drawing.Point(12, 362); + this.optionsPanel.Name = "optionsPanel"; + this.optionsPanel.Size = new System.Drawing.Size(584, 135); + this.optionsPanel.TabIndex = 11; + // + // singleFileCheckBox + // + this.singleFileCheckBox.AutoSize = true; + this.singleFileCheckBox.Location = new System.Drawing.Point(448, 12); + this.singleFileCheckBox.Name = "singleFileCheckBox"; + this.singleFileCheckBox.Size = new System.Drawing.Size(109, 17); + this.singleFileCheckBox.TabIndex = 12; + this.singleFileCheckBox.Text = "Output Single File"; + this.singleFileCheckBox.UseVisualStyleBackColor = true; + // + // outputDirectoryTextBox + // + this.outputDirectoryTextBox.Location = new System.Drawing.Point(8, 112); + this.outputDirectoryTextBox.Name = "outputDirectoryTextBox"; + this.outputDirectoryTextBox.Size = new System.Drawing.Size(100, 20); + this.outputDirectoryTextBox.TabIndex = 11; + // + // optionsLabel + // + this.optionsLabel.AutoSize = true; + this.optionsLabel.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.optionsLabel.Location = new System.Drawing.Point(3, 0); + this.optionsLabel.Name = "optionsLabel"; + this.optionsLabel.Size = new System.Drawing.Size(105, 13); + this.optionsLabel.TabIndex = 10; + this.optionsLabel.Text = "2) Select Options"; + // + // savePanel + // + this.savePanel.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.savePanel.Controls.Add(this.saveLabel); + this.savePanel.Location = new System.Drawing.Point(12, 503); + this.savePanel.Name = "savePanel"; + this.savePanel.Size = new System.Drawing.Size(584, 93); + this.savePanel.TabIndex = 12; + // + // saveLabel + // + this.saveLabel.AutoSize = true; + this.saveLabel.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.saveLabel.Location = new System.Drawing.Point(3, 0); + this.saveLabel.Name = "saveLabel"; + this.saveLabel.Size = new System.Drawing.Size(79, 13); + this.saveLabel.TabIndex = 10; + this.saveLabel.Text = "3) Save AAC"; + // + // Mp3ToAacBatch + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(608, 608); + this.Controls.Add(this.savePanel); + this.Controls.Add(this.optionsPanel); + this.Controls.Add(this.inputPanel); + this.Name = "Mp3ToAacBatch"; + this.Text = "Mp3 To AAC Batch"; + this.updownSplitContainer.Panel1.ResumeLayout(false); + this.updownSplitContainer.Panel2.ResumeLayout(false); + this.updownSplitContainer.ResumeLayout(false); + this.inputPanel.ResumeLayout(false); + this.inputPanel.PerformLayout(); + this.optionsPanel.ResumeLayout(false); + this.optionsPanel.PerformLayout(); + this.savePanel.ResumeLayout(false); + this.savePanel.PerformLayout(); + this.ResumeLayout(false); + + } + + #endregion + + private System.Windows.Forms.ListBox inputListBox; + private System.Windows.Forms.Button addFolderButton; + private System.Windows.Forms.Button addFileButton; + private System.Windows.Forms.Button moveUpButton; + private System.Windows.Forms.Button moveDownButton; + private System.Windows.Forms.Button sortButton; + private System.Windows.Forms.SplitContainer updownSplitContainer; + private System.Windows.Forms.Label inputLabel; + private System.Windows.Forms.Panel inputPanel; + private System.Windows.Forms.Panel optionsPanel; + private System.Windows.Forms.Label optionsLabel; + private System.Windows.Forms.Button clearButton; + private System.Windows.Forms.Panel savePanel; + private System.Windows.Forms.Label saveLabel; + private System.Windows.Forms.TextBox outputDirectoryTextBox; + private System.Windows.Forms.CheckBox singleFileCheckBox; + } +} \ No newline at end of file diff --git a/Mp3ToAacBatch.cs b/Mp3ToAacBatch.cs new file mode 100644 index 0000000..a33407b --- /dev/null +++ b/Mp3ToAacBatch.cs @@ -0,0 +1,12 @@ +using System.Windows.Forms; + +namespace m3uTool +{ + public partial class Mp3ToAacBatch : Form + { + public Mp3ToAacBatch() + { + InitializeComponent(); + } + } +} \ No newline at end of file diff --git a/Mp3ToAacBatch.resx b/Mp3ToAacBatch.resx new file mode 100644 index 0000000..079f5b8 --- /dev/null +++ b/Mp3ToAacBatch.resx @@ -0,0 +1,174 @@ +<?xml version="1.0" encoding="utf-8"?> +<root> + <!-- + Microsoft ResX Schema + + Version 2.0 + + The primary goals of this format is to allow a simple XML format + that is mostly human readable. The generation and parsing of the + various data types are done through the TypeConverter classes + associated with the data types. + + Example: + + ... ado.net/XML headers & schema ... + <resheader name="resmimetype">text/microsoft-resx</resheader> + <resheader name="version">2.0</resheader> + <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader> + <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader> + <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data> + <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data> + <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64"> + <value>[base64 mime encoded serialized .NET Framework object]</value> + </data> + <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64"> + <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value> + <comment>This is a comment</comment> + </data> + + There are any number of "resheader" rows that contain simple + name/value pairs. + + Each data row contains a name, and value. The row also contains a + type or mimetype. Type corresponds to a .NET class that support + text/value conversion through the TypeConverter architecture. + Classes that don't support this are serialized and stored with the + mimetype set. + + The mimetype is used for serialized objects, and tells the + ResXResourceReader how to depersist the object. This is currently not + extensible. For a given mimetype the value must be set accordingly: + + Note - application/x-microsoft.net.object.binary.base64 is the format + that the ResXResourceWriter will generate, however the reader can + read any of the formats listed below. + + mimetype: application/x-microsoft.net.object.binary.base64 + value : The object must be serialized with + : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter + : and then encoded with base64 encoding. + + mimetype: application/x-microsoft.net.object.soap.base64 + value : The object must be serialized with + : System.Runtime.Serialization.Formatters.Soap.SoapFormatter + : and then encoded with base64 encoding. + + mimetype: application/x-microsoft.net.object.bytearray.base64 + value : The object must be serialized into a byte array + : using a System.ComponentModel.TypeConverter + : and then encoded with base64 encoding. + --> + <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"> + <xsd:import namespace="http://www.w3.org/XML/1998/namespace" /> + <xsd:element name="root" msdata:IsDataSet="true"> + <xsd:complexType> + <xsd:choice maxOccurs="unbounded"> + <xsd:element name="metadata"> + <xsd:complexType> + <xsd:sequence> + <xsd:element name="value" type="xsd:string" minOccurs="0" /> + </xsd:sequence> + <xsd:attribute name="name" use="required" type="xsd:string" /> + <xsd:attribute name="type" type="xsd:string" /> + <xsd:attribute name="mimetype" type="xsd:string" /> + <xsd:attribute ref="xml:space" /> + </xsd:complexType> + </xsd:element> + <xsd:element name="assembly"> + <xsd:complexType> + <xsd:attribute name="alias" type="xsd:string" /> + <xsd:attribute name="name" type="xsd:string" /> + </xsd:complexType> + </xsd:element> + <xsd:element name="data"> + <xsd:complexType> + <xsd:sequence> + <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /> + <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" /> + </xsd:sequence> + <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" /> + <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" /> + <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" /> + <xsd:attribute ref="xml:space" /> + </xsd:complexType> + </xsd:element> + <xsd:element name="resheader"> + <xsd:complexType> + <xsd:sequence> + <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /> + </xsd:sequence> + <xsd:attribute name="name" type="xsd:string" use="required" /> + </xsd:complexType> + </xsd:element> + </xsd:choice> + </xsd:complexType> + </xsd:element> + </xsd:schema> + <resheader name="resmimetype"> + <value>text/microsoft-resx</value> + </resheader> + <resheader name="version"> + <value>2.0</value> + </resheader> + <resheader name="reader"> + <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> + </resheader> + <resheader name="writer"> + <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> + </resheader> + <assembly alias="System.Drawing" name="System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" /> + <data name="addFolderButton.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64"> + <value> + iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8 + YQUAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAO9JREFUOE9jYKAW + OLdI8T8yJslckEZ0cGyWxH+iDAEphIA5aPj/f5AcNoxiMNiAz0DNn3swMYa7IIbOrZJEuA5swOuW/48O + +uO0EZsrlhbxQAwBG3C9Baz51v5shJ2/v2OxHyj0vAUsjmLA36v5YAO+f+jEEhZoYXM86v/vn9NQDXi0 + 2en/yQUKIHP///8+BS9+vi/o/7k90agGnF+k+v/6DnuI7cDwwImBzn+/ywmhGRYGx2ZJ/v/wpBJiwONq + 3Ph+9f97m7AYcHyONETzdWAgEsD7Z2qgugDkigPTJP4Ti+dU4kihIAliMFHJm66KAORP5xRdT7ByAAAA + AElFTkSuQmCC +</value> + </data> + <data name="addFileButton.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64"> + <value> + iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8 + YQUAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAiJJREFUOE+Nk+1L + U1Ecx/WP6v1e9d53azYqIogixAcoR2+UjErQIFjSWgZbLJnkbPMur+7pzm3OPXhRvDp0TndxbKV7cLt7 + cGtf77ntgrVLdeBwD4f7+Xx/P845vT2dEYwdQl7/7buePusZ6rvW2/WPcymEP0e7DTRbP1FvtiA0WqBj + x5haiEjzn4K2SMfjSRgM8zjiczivNfHMuY/rln30vXB0S65WQGCS7PWxePJ4Enduj8Hl20OuUEYyVwLl + 4qDT6X6vQhbIcP2iBbd3Ax+MFEZHLRgZmcUqwyJTrIn7HDQaTbeAwBekZxGu1JvgEjzs9k3M26LQ3nyK + twYr+HwVzBoHtVrdLbgKl6pNFIQGlukd+EK70L+bg83px9GpAH9gR1lQ6yRLcKWBH+UGnMuboD1x7PGn + EnzwvQKff1tZQMomcL4DZ0s1LFIBfPq8glQHTmTL8DBbygIZTqTyoFcTMJkCeHB/AtPTFimZwFymDJeX + VRbIyeFIGsWiAMZ/gMGB5xgefAlKPFIuc47tkxJod1xZQHpmuSysVgbhcBJmswe3tMOYfGXCV3dMglm+ + iG+uqLKA9BzZ4qHtH8K9uzr03xjAo4fjmJlZhJc9lODocQHUyoay4KRQRfpMwNTrOYyPGWB8vwS9/gvM + Ni/Y9C84nCrAQYeVBaH1BNaCu2DEc/4464DRsAA7FZR6JmWTZAK/Ed9H10Uir4tsqlSq/5qy4BL8gXM2 + KgxAeQAAAABJRU5ErkJggg== +</value> + </data> + <data name="sortButton.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64"> + <value> + iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8 + YQUAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAOJJREFUOE+9UrkN + wzAM9Cbps4SnYO1RNIq7FFwirlOnzBDeQLkTSUOGAkhJEQOEbIP38Khp+seja863x56plZJm1le6umom + iYFxLoMEzzuaAVScRmAlIBhywiYCCxggWeQAk+R6kf4oRQkEMlvz9tpAYi5OOYRSHZRZNrsEMIt45/eJ + xAhiNlMoBeXkija7Z8AR6IrrUfws5xEUGj0ocfUgk9mB4T/5imJNEVQ4YFC1XfY388d+iyUPKmx3L8zn + oKoMepfGZkNVszZBdW2ggSQMjmM0QQ0RVHvlOpugRkh+6XkD9f86XlAO9kEAAAAASUVORK5CYII= +</value> + </data> + <data name="clearButton.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64"> + <value> + iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8 + YQUAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAh1JREFUOE+Vk1tP + E1EUhaeY8L+M/6WPPpgYYzTG6INEEo0aQBo00ShBjRhTi1UsUFqx1SLQG/RGL1AK0wu92jJnziz3Hm0k + meHBSU5mkplvrW+fyXEof687r7IYPp++O0ZGFH0wUM6Njip5313F5XI57L5TOIAvYQADAfQ0A62BgcYv + A7WOBL+/4lbhdDpti8wAIYE+wd0TgvsG6j0DVYIrLYkLs31znX9YsA/hgL4GdKi1ya1dA0dtAwcEl44l + IgmY67W7hxtjbmsIB7RZmVprXYnDtkS5SXBDYrcmkVYldg51xA90fA537QPqPQm1Y6BC8D61FutDWMc2 + wTGCN/d1+NY79gEqzcvKewTnCc5WdaSOJJIViVhZxwbBP4pkEDkjoNw0UCTlPClnqqRMcIJatwj+uSfw + neBveYGP4bbV4PbzBAoE5xhW/ylvUet6iWGBNYIDOQFPyCbg5swGcqTMm5Ws/JmXlSMlHeECwbsCq1mB + 5YyG919bVoPrEyFzl1k5airzvAIhgoME+7MallICizsa3gWa1oCr91cRp2ZWZng4b5BaVzICX9IaPm0L + LCQE3vhtAi6PLVqUA6ycJpiavUkNngTpxzTMLR9bDS7d8pi/aKjM8y5RKyt7qflDnODoCeY3Nbz0NawB + F6/N48nbFFxzSTyejWHqRRTTzyKYehrG5MwaJqaDeDTpx/iDBYzf89qfBz5p/7P4WP8G/jqMg6Vn2nUA + AAAASUVORK5CYII= +</value> + </data> +</root> \ No newline at end of file diff --git a/Mp4Encoder.cs b/Mp4Encoder.cs new file mode 100644 index 0000000..75cb81e --- /dev/null +++ b/Mp4Encoder.cs @@ -0,0 +1,21 @@ +using System.IO; + +namespace m3uTool +{ + public class Mp4Encoder : ProcessStreamWrapper + { + public Mp4Encoder(Mp4EncodingOptions encOpt) : base(encOpt) + { + } + + public Mp4Encoder(Mp4EncodingOptions encOpt, Stream outputStream) + : base(encOpt, outputStream) + { + } + + protected override string ProcessExecutableFilename + { + get { return "faac.exe"; } + } + } +} \ No newline at end of file diff --git a/Mp4EncodingOptions.cs b/Mp4EncodingOptions.cs new file mode 100644 index 0000000..3c56113 --- /dev/null +++ b/Mp4EncodingOptions.cs @@ -0,0 +1,515 @@ +using System.Text; + +namespace m3uTool +{ + /// <summary> + ///FAAC 1.24.1 (May 17 2005) UNSTABLE + /// + ///Usage: faac [options] infiles ... + ///Options: + /// -q <quality> Set quantizer quality. + /// -b <bitrate> Set average bitrate to x kbps. (ABR, lower quality mode) + /// -c <freq> Set the bandwidth in Hz. (default=automatic) + /// -o X Set output file to X (only for one input file) + /// -r Use RAW AAC output file. + /// -P Raw PCM input mode (default 44100Hz 16bit stereo). + /// -R Raw PCM input rate. + /// -B Raw PCM input sample size (8, 16 (default), 24 or 32bits). + /// -C Raw PCM input channels. + /// -X Raw PCM swap input bytes + /// -I <C,LF> Input channel config, default is 3,4 (Center third, LF fourth) + /// + ///MP4 specific options: + /// -w Wrap AAC data in MP4 container. (default for *.mp4 and *.m4a) + /// --artist X Set artist to X + /// --writer X Set writer to X + /// --title X Set title to X + /// --genre X Set genre to X + /// --album X Set album to X + /// --compilation Set compilation + /// --track X Set track to X (number/total) + /// --disc X Set disc to X (number/total) + /// --year X Set year to X + /// --cover-art X Read cover art from file X + /// --comment X Set comment to X + /// </summary> + public class Mp4EncodingOptions : ProcessArguments + { + #region Private variables + + /// <summary> + /// Set album to X --album X + /// </summary> + private string mAlbum; + + /// <summary> + /// Set artist to X --artist X + /// </summary> + private string mArtist; + + /// <summary> + /// The bandwidth in Hz -c [freq] + /// </summary> + private SampleRateFrequency mBandwidth = SampleRateFrequency.Hz_0; + + /// <summary> + /// BitRate -b [bitrate] + /// </summary> + private int mBitrate = int.MinValue; + + /// <summary> + /// Set comment to X --comment X + /// </summary> + private string mComment; + + /// <summary> + /// Set compilation --compilation + /// </summary> + private string mCompilation; + + /// <summary> + /// Load cover art from filename --cover-art X + /// </summary> + private string mCoverArtFilename; + + /// <summary> + /// Set disc to X (number/total) --disc X + /// </summary> + private string mDisc; + + /// <summary> + /// Set genre to X --genre X + /// </summary> + private string mGenre; + + /// <summary> + /// Files to use for input + /// </summary> + private string[] mInfiles; + + /// <summary> + /// The output filename (only for one input file) -o [filename] + /// </summary> + private string mOutputFilename; + + /// <summary> + /// Quantinizer Quality -q [quality] + /// </summary> + private int mQuantinizerQuality = int.MinValue; + + /// <summary> + /// Raw PCM input channels -C + /// </summary> + private ChannelMode mRawInputChannels; + + /// <summary> + /// Raw PCM input mode (default 44100Hz 16bit stereo) -P + /// </summary> + private bool mRawPCMInputMode; + + /// <summary> + /// Raw PCM input rate (default 16) -B + /// </summary> + private int mRawPCMInputSampleRate = 16; + + /// <summary> + /// Raw PCMInputSampleSize (default 44100Hz 16bit stereo) -R + /// </summary> + private SampleRateFrequency mRawPCMInputSampleSize = SampleRateFrequency.Hz_44100; + + /// <summary> + /// Raw PCM swap input bytes -X + /// </summary> + private bool mRawPCMSwapInputBytes; + + /// <summary> + /// Set title to X --title X + /// </summary> + private string mTitle; + + /// <summary> + /// Set track to X (number/total) --track X + /// </summary> + private string mTrack; + + /// <summary> + /// Wrap AAC data in MP4 container. (default for *.mp4 and *.m4a) -w + /// </summary> + private bool mWrapAACDataInMp4; + + /// <summary> + /// Set writer to X --writer X + /// </summary> + private string mWriter; + + /// <summary> + /// Set year to X --year X + /// </summary> + private int mYear = int.MinValue; + + #endregion + + /// <summary> + /// Gets or sets the input files. + /// </summary> + /// <value>The input files.</value> + public string[] InputFiles + { + get + { + if (mInfiles == null) + return new string[] {}; + return mInfiles; + } + set { mInfiles = value; } + } + + /// <summary> + /// Quantinizer Quality -q [quality] + /// </summary> + public int QuantinizerQuality + { + get { return mQuantinizerQuality; } + set { mQuantinizerQuality = value; } + } + + /// <summary> + /// BitRate -b [bitrate] + /// </summary> + public int Bitrate + { + get { return mBitrate; } + set { mBitrate = value; } + } + + /// <summary> + /// The bandwidth in Hz -c [freq] + /// </summary> + public SampleRateFrequency Bandwidth + { + get { return mBandwidth; } + set { mBandwidth = value; } + } + + /// <summary> + /// The output filename (only for one input file) -o [filename] + /// </summary> + public string OutputFilename + { + get { return mOutputFilename; } + set { mOutputFilename = value; } + } + + /// <summary> + /// Use RAW AAC output file -r + /// </summary> + public bool RawAACOutputFile { get; set; } + + /// <summary> + /// Raw PCM input mode (default 44100Hz 16bit stereo) -P + /// </summary> + public bool RawPCMInputMode + { + get { return mRawPCMInputMode; } + set { mRawPCMInputMode = value; } + } + + /// <summary> + /// Raw PCMInputSampleSize -R + /// </summary> + public SampleRateFrequency RawPCMInputSampleSize + { + get { return mRawPCMInputSampleSize; } + set + { + mRawPCMInputSampleSize = value; + RawPCMInputMode = true; + } + } + + /// <summary> + /// Raw PCM input rate (default 16) -B + /// </summary> + public int RawPCMInputSampleRate + { + get { return mRawPCMInputSampleRate; } + set + { + mRawPCMInputSampleRate = value; + RawPCMInputMode = true; + } + } + + /// <summary> + /// Raw PCM input channels -C + /// </summary> + public ChannelMode RawInputChannels + { + get { return mRawInputChannels; } + set + { + mRawInputChannels = value; + RawPCMInputMode = true; + } + } + + /// <summary> + /// Raw PCM swap input bytes -X + /// </summary> + public bool RawPCMSwapInputBytes + { + get { return mRawPCMSwapInputBytes; } + set + { + mRawPCMSwapInputBytes = value; + RawPCMInputMode = true; + } + } + + /// <summary> + /// Wrap AAC data in MP4 container. (default for *.mp4 and *.m4a) + /// </summary> + public bool WrapAACDataInMp4 + { + get { return mWrapAACDataInMp4; } + set { mWrapAACDataInMp4 = value; } + } + + /// <summary> + /// Set artist to X + /// </summary> + public string Artist + { + get { return mArtist; } + set + { + mArtist = value; + if (!string.IsNullOrEmpty(value)) + WrapAACDataInMp4 = true; + } + } + + /// <summary> + /// Set writer to X + /// </summary> + public string Writer + { + get { return mWriter; } + set + { + mWriter = value; + if (!string.IsNullOrEmpty(value)) + WrapAACDataInMp4 = true; + } + } + + /// <summary> + /// Set title to X + /// </summary> + public string Title + { + get { return mTitle; } + set + { + mTitle = value; + if (!string.IsNullOrEmpty(value)) + WrapAACDataInMp4 = true; + } + } + + /// <summary> + /// Set genre to X + /// </summary> + public string Genre + { + get { return mGenre; } + set + { + mGenre = value; + if (!string.IsNullOrEmpty(value)) + WrapAACDataInMp4 = true; + } + } + + /// <summary> + /// Set album to X + /// </summary> + public string Album + { + get { return mAlbum; } + set + { + mAlbum = value; + if (!string.IsNullOrEmpty(value)) + WrapAACDataInMp4 = true; + } + } + + /// <summary> + /// Set compilation + /// </summary> + public string Compilation + { + get { return mCompilation; } + set + { + mCompilation = value; + if (!string.IsNullOrEmpty(value)) + WrapAACDataInMp4 = true; + } + } + + /// <summary> + /// Set track to X (number/total) + /// </summary> + public string Track + { + get { return mTrack; } + set + { + mTrack = value; + if (!string.IsNullOrEmpty(value)) + WrapAACDataInMp4 = true; + } + } + + /// <summary> + /// Set disc to X (number/total) + /// </summary> + public string Disc + { + get { return mDisc; } + set + { + mDisc = value; + if (!string.IsNullOrEmpty(value)) + WrapAACDataInMp4 = true; + } + } + + /// <summary> + /// Set year to X + /// </summary> + public int Year + { + get { return mYear; } + set + { + mYear = value; + if (value != int.MinValue) + WrapAACDataInMp4 = true; + } + } + + /// <summary> + /// Load cover art from filename + /// </summary> + public string CoverArtFilename + { + get { return mCoverArtFilename; } + set + { + mCoverArtFilename = value; + if (!string.IsNullOrEmpty(value)) + WrapAACDataInMp4 = true; + } + } + + /// <summary> + /// Quantinizer Quality -q [quality] + /// </summary> + public string Comment + { + get { return mComment; } + set + { + mComment = value; + if (!string.IsNullOrEmpty(value)) + WrapAACDataInMp4 = true; + } + } + + /// <summary> + /// Gets a string representing the command line arguments specified by the properties in the object. + /// </summary> + /// <returns></returns> + public override string GetCommandLineArguments() + { + var stringBuilder = new StringBuilder(); + if (mQuantinizerQuality != int.MinValue) + stringBuilder.Append(" -q " + mQuantinizerQuality); + if (mBitrate != int.MinValue) + stringBuilder.Append(" -b " + mBitrate); + if (mBandwidth != SampleRateFrequency.Hz_0) + stringBuilder.Append(" -c " + SampleRateFrequencyUtility.GetSampleRateFrequencyInt(mBandwidth)); + + if (!string.IsNullOrEmpty(mOutputFilename)) + { + if (mOutputFilename.Trim() == "-") + stringBuilder.Append(" -o-"); + else + stringBuilder.Append(" -o " + GetQuotedCommandLineArgument(mOutputFilename)); + } + else + stringBuilder.Append(" -o-"); + + if (mRawPCMInputMode) + { + stringBuilder.Append(" -P "); + if (mRawPCMInputSampleSize != SampleRateFrequency.Hz_44100) + stringBuilder.Append(" -R " + + SampleRateFrequencyUtility.GetSampleRateFrequencyInt(mRawPCMInputSampleSize)); + if (mRawPCMInputSampleRate != 16) + stringBuilder.Append(" -B " + mRawPCMInputSampleRate); + int chanelMode = ChannelModeUtility.GetChannelModeInt(mRawInputChannels); + if (chanelMode != 2) + stringBuilder.Append(" -C " + chanelMode); + if (mRawPCMSwapInputBytes) + stringBuilder.Append(" -X "); + } + + if (mWrapAACDataInMp4) + { + stringBuilder.Append(" -w "); + if (!string.IsNullOrEmpty(mArtist)) + stringBuilder.Append(" --artist " + mArtist); + if (!string.IsNullOrEmpty(mWriter)) + stringBuilder.Append(" --writer " + mWriter); + if (!string.IsNullOrEmpty(mTitle)) + stringBuilder.Append(" --title " + mTitle); + if (!string.IsNullOrEmpty(mGenre)) + stringBuilder.Append(" --genre " + mGenre); + if (!string.IsNullOrEmpty(mAlbum)) + stringBuilder.Append(" --album " + mAlbum); + if (!string.IsNullOrEmpty(mCompilation)) + stringBuilder.Append(" --compilation " + mCompilation); + if (!string.IsNullOrEmpty(mTrack)) + stringBuilder.Append(" --track " + mTrack); + if (!string.IsNullOrEmpty(mDisc)) + stringBuilder.Append(" --disc " + mDisc); + if (mYear != int.MinValue) + stringBuilder.Append(" --year " + mYear); + if (!string.IsNullOrEmpty(mCoverArtFilename)) + stringBuilder.Append(" --cover-art " + mCoverArtFilename); + if (!string.IsNullOrEmpty(mComment)) + stringBuilder.Append(" --comment " + mComment); + } + if (mInfiles != null && mInfiles.Length > 0) + { + // use standard input + if (mInfiles[0].Trim() == "-") + stringBuilder.Append(" -"); + else + { + foreach (string infile in mInfiles) + stringBuilder.Append(" " + GetQuotedCommandLineArgument(infile)); + } + } + else + stringBuilder.Append(" -"); + + + return stringBuilder.ToString(); + } + } +} \ No newline at end of file diff --git a/ProcessArguments.cs b/ProcessArguments.cs new file mode 100644 index 0000000..6bbeb95 --- /dev/null +++ b/ProcessArguments.cs @@ -0,0 +1,23 @@ +namespace m3uTool +{ + public class ProcessArguments + { + /// <summary> + /// Gets a string representing the command line arguments specified by the properties in the object. + /// </summary> + /// <returns></returns> + public virtual string GetCommandLineArguments() + { + return ""; + } + + protected string GetQuotedCommandLineArgument(string argument) + { + if (argument.Contains(" ") || argument.Contains("\t")) + { + return "\"" + argument + "\""; + } + return argument; + } + } +} \ No newline at end of file diff --git a/ProcessStreamWrapper.cs b/ProcessStreamWrapper.cs new file mode 100644 index 0000000..d787e33 --- /dev/null +++ b/ProcessStreamWrapper.cs @@ -0,0 +1,361 @@ +using System; +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> + /// 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; + + /// <summary> + /// The process + /// </summary> + protected Process mProcess; + + /// <summary> + /// The arguments to use for the process + /// </summary> + protected ProcessArguments mProcessArguments; + + /// <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 += Process_Exited; + mProcess.Start(); + if (mOutputStreamWriter != null) + { + var thread = new Thread(GetStandardOutput); + thread.Start(); + } + + var standardErrorThread = new Thread(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) + { + var 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> + private 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; + + var sr = new StreamReader(inputStream, Encoding.Default); + //using (StreamReader sr = new StreamReader(inputStream, Encoding.Default)) + { + mProgressCallback.Begin(0, 100); + mProgressCallback.SetText(mProcessArguments.GetCommandLineArguments()); + + var 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> + private bool mInputDone; + + /// <summary> + /// Collects all the data from Process.StandardOutput and sends it to the outputstream writer + /// </summary> + private void GetStandardOutput() + { + int readTotal = 0; + var 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) 0x1A, '\n'})); + } + } + + mProgressCallback.SetText("Dispose() with exit code " + mProcess.ExitCode); + } + + #endregion + } +} \ No newline at end of file diff --git a/Properties/Resources.Designer.cs b/Properties/Resources.Designer.cs new file mode 100644 index 0000000..47eef55 --- /dev/null +++ b/Properties/Resources.Designer.cs @@ -0,0 +1,63 @@ +//------------------------------------------------------------------------------ +// <auto-generated> +// This code was generated by a tool. +// Runtime Version:4.0.30319.1 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// </auto-generated> +//------------------------------------------------------------------------------ + +namespace m3uTool.Properties { + using System; + + + /// <summary> + /// A strongly-typed resource class, for looking up localized strings, etc. + /// </summary> + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// <summary> + /// Returns the cached ResourceManager instance used by this class. + /// </summary> + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("m3uTool.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// <summary> + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// </summary> + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + } +} diff --git a/Properties/Resources.resx b/Properties/Resources.resx new file mode 100644 index 0000000..5ea0895 --- /dev/null +++ b/Properties/Resources.resx @@ -0,0 +1,120 @@ +<?xml version="1.0" encoding="utf-8"?> +<root> + <!-- + Microsoft ResX Schema + + Version 2.0 + + The primary goals of this format is to allow a simple XML format + that is mostly human readable. The generation and parsing of the + various data types are done through the TypeConverter classes + associated with the data types. + + Example: + + ... ado.net/XML headers & schema ... + <resheader name="resmimetype">text/microsoft-resx</resheader> + <resheader name="version">2.0</resheader> + <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader> + <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader> + <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data> + <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data> + <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64"> + <value>[base64 mime encoded serialized .NET Framework object]</value> + </data> + <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64"> + <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value> + <comment>This is a comment</comment> + </data> + + There are any number of "resheader" rows that contain simple + name/value pairs. + + Each data row contains a name, and value. The row also contains a + type or mimetype. Type corresponds to a .NET class that support + text/value conversion through the TypeConverter architecture. + Classes that don't support this are serialized and stored with the + mimetype set. + + The mimetype is used for serialized objects, and tells the + ResXResourceReader how to depersist the object. This is currently not + extensible. For a given mimetype the value must be set accordingly: + + Note - application/x-microsoft.net.object.binary.base64 is the format + that the ResXResourceWriter will generate, however the reader can + read any of the formats listed below. + + mimetype: application/x-microsoft.net.object.binary.base64 + value : The object must be serialized with + : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter + : and then encoded with base64 encoding. + + mimetype: application/x-microsoft.net.object.soap.base64 + value : The object must be serialized with + : System.Runtime.Serialization.Formatters.Soap.SoapFormatter + : and then encoded with base64 encoding. + + mimetype: application/x-microsoft.net.object.bytearray.base64 + value : The object must be serialized into a byte array + : using a System.ComponentModel.TypeConverter + : and then encoded with base64 encoding. + --> + <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"> + <xsd:import namespace="http://www.w3.org/XML/1998/namespace" /> + <xsd:element name="root" msdata:IsDataSet="true"> + <xsd:complexType> + <xsd:choice maxOccurs="unbounded"> + <xsd:element name="metadata"> + <xsd:complexType> + <xsd:sequence> + <xsd:element name="value" type="xsd:string" minOccurs="0" /> + </xsd:sequence> + <xsd:attribute name="name" use="required" type="xsd:string" /> + <xsd:attribute name="type" type="xsd:string" /> + <xsd:attribute name="mimetype" type="xsd:string" /> + <xsd:attribute ref="xml:space" /> + </xsd:complexType> + </xsd:element> + <xsd:element name="assembly"> + <xsd:complexType> + <xsd:attribute name="alias" type="xsd:string" /> + <xsd:attribute name="name" type="xsd:string" /> + </xsd:complexType> + </xsd:element> + <xsd:element name="data"> + <xsd:complexType> + <xsd:sequence> + <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /> + <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" /> + </xsd:sequence> + <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" /> + <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" /> + <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" /> + <xsd:attribute ref="xml:space" /> + </xsd:complexType> + </xsd:element> + <xsd:element name="resheader"> + <xsd:complexType> + <xsd:sequence> + <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /> + </xsd:sequence> + <xsd:attribute name="name" type="xsd:string" use="required" /> + </xsd:complexType> + </xsd:element> + </xsd:choice> + </xsd:complexType> + </xsd:element> + </xsd:schema> + <resheader name="resmimetype"> + <value>text/microsoft-resx</value> + </resheader> + <resheader name="version"> + <value>2.0</value> + </resheader> + <resheader name="reader"> + <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> + </resheader> + <resheader name="writer"> + <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> + </resheader> +</root> \ No newline at end of file diff --git a/Redirect.cs b/Redirect.cs new file mode 100644 index 0000000..19df9d0 --- /dev/null +++ b/Redirect.cs @@ -0,0 +1,25 @@ +using System; + +namespace m3uTool +{ + [Flags] + public enum Redirect + { + /// <summary> + /// Do not redirect anything + /// </summary> + None = 0, + /// <summary> + /// Redirect standard input + /// </summary> + StandardInput = 1, + /// <summary> + /// Redirect standard output + /// </summary> + StandardOutput = 2, + /// <summary> + /// Redirect standart error + /// </summary> + StandardError = 4 + } +} \ No newline at end of file diff --git a/References/PipeStream.dll b/References/PipeStream.dll new file mode 100644 index 0000000..f0b63e9 Binary files /dev/null and b/References/PipeStream.dll differ diff --git a/References/lame_enc.dll b/References/lame_enc.dll new file mode 100644 index 0000000..e505231 Binary files /dev/null and b/References/lame_enc.dll differ diff --git a/References/libfaac.dll b/References/libfaac.dll new file mode 100644 index 0000000..459da25 Binary files /dev/null and b/References/libfaac.dll differ diff --git a/References/nunit.framework.dll b/References/nunit.framework.dll new file mode 100644 index 0000000..639dbb0 Binary files /dev/null and b/References/nunit.framework.dll differ diff --git a/Resources/Collapsed.bmp b/Resources/Collapsed.bmp new file mode 100644 index 0000000..fe61680 Binary files /dev/null and b/Resources/Collapsed.bmp differ diff --git a/Tests/001-22.mp3 b/Tests/001-22.mp3 new file mode 100644 index 0000000..f4f6de3 Binary files /dev/null and b/Tests/001-22.mp3 differ diff --git a/Tests/001-44.mp3 b/Tests/001-44.mp3 new file mode 100644 index 0000000..9b22347 Binary files /dev/null and b/Tests/001-44.mp3 differ diff --git a/Tests/22_16_m.mp3 b/Tests/22_16_m.mp3 new file mode 100644 index 0000000..24e5753 Binary files /dev/null and b/Tests/22_16_m.mp3 differ diff --git a/Tests/22_16_s.mp3 b/Tests/22_16_s.mp3 new file mode 100644 index 0000000..70d479f Binary files /dev/null and b/Tests/22_16_s.mp3 differ diff --git a/Tests/22_8_m.mp3 b/Tests/22_8_m.mp3 new file mode 100644 index 0000000..3b0541d Binary files /dev/null and b/Tests/22_8_m.mp3 differ diff --git a/Tests/22_8_s.mp3 b/Tests/22_8_s.mp3 new file mode 100644 index 0000000..1f107f0 Binary files /dev/null and b/Tests/22_8_s.mp3 differ diff --git a/Tests/44_16_m.mp3 b/Tests/44_16_m.mp3 new file mode 100644 index 0000000..13aeefb Binary files /dev/null and b/Tests/44_16_m.mp3 differ diff --git a/Tests/44_16_s.mp3 b/Tests/44_16_s.mp3 new file mode 100644 index 0000000..6972ee4 Binary files /dev/null and b/Tests/44_16_s.mp3 differ diff --git a/Tests/44_16_s.wav b/Tests/44_16_s.wav new file mode 100644 index 0000000..0526235 Binary files /dev/null and b/Tests/44_16_s.wav differ diff --git a/Tests/44_8_m.mp3 b/Tests/44_8_m.mp3 new file mode 100644 index 0000000..ac2e9ce Binary files /dev/null and b/Tests/44_8_m.mp3 differ diff --git a/Tests/44_8_s.mp3 b/Tests/44_8_s.mp3 new file mode 100644 index 0000000..3c62693 Binary files /dev/null and b/Tests/44_8_s.mp3 differ diff --git a/Tests/48_16_m.mp3 b/Tests/48_16_m.mp3 new file mode 100644 index 0000000..6a563ce Binary files /dev/null and b/Tests/48_16_m.mp3 differ diff --git a/Tests/48_16_s.mp3 b/Tests/48_16_s.mp3 new file mode 100644 index 0000000..5ce98b8 Binary files /dev/null and b/Tests/48_16_s.mp3 differ diff --git a/Tests/48_8_m.mp3 b/Tests/48_8_m.mp3 new file mode 100644 index 0000000..599b9d8 Binary files /dev/null and b/Tests/48_8_m.mp3 differ diff --git a/Tests/48_8_s.mp3 b/Tests/48_8_s.mp3 new file mode 100644 index 0000000..d53d09d Binary files /dev/null and b/Tests/48_8_s.mp3 differ diff --git a/Tests/BridgeStreamTests.cs b/Tests/BridgeStreamTests.cs new file mode 100644 index 0000000..a35c1a2 --- /dev/null +++ b/Tests/BridgeStreamTests.cs @@ -0,0 +1,238 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Text; +using System.Threading; +using NUnit.Framework; +using Win32; + +namespace m3uTool.Tests +{ + /// <summary> + /// Tests for the BridgeStream class. + /// </summary> + [TestFixture] + public class BridgeStreamTests + { + #region Private Variables + /// <summary> + /// Bridge stream object to use for multi-threaded read/write testing. + /// </summary> + private BridgeStream mBridgeStream; + + /// <summary> + /// The string read from a threaded read process. + /// </summary> + private string mReadString = ""; + + /// <summary> + /// The total length of writes for a threaded writing process. + /// </summary> + private int mTotalWriteLength = -1; + + /// <summary> + /// Some non-random text data to test with. + /// </summary> + private const string mTestString = @"BEOWOLF: THE AUTHOR AND HIS TIMES Who wrote Beowulf--the oldest known epic poem written in English--is a question that has mystified readers for centuries. It's generally thought that the poem was performed orally by the poet before a live audience, and that in this way it eventually passed down to readers and listeners. Another theory is that the poem was recited by memory by a scop, a traveling entertainer who went from court to court, singing songs and telling stories, until it was finally written down at the request of a king who wanted to hear it again. Because there are three major battle scenes in the poem, some readers believe that Beowulf was composed by three different authors. Other readers claim that the sections that take place in Denmark and the sections that occur after Beowulf returns to Geatland were the work of different authors. The majority of critics agree that because of the unified structure of the poem, with its interweaving of historical information into the flow of the main narrative, the poem was most likely composed by one person. As you read the poem try to imagine yourself in the banquet hall of a large castle, eating and drinking with your friends. The court entertainer--much like a stand-up comedian in a nightclub--begins telling his story. Your presence in the hall means that you're probably a member of the aristocratic class, either a descendant of the founder of a particular tribe or one of your king's followers. (Anglo-Saxon society was divided into two main classes: the aristocracy and the proletariat. Beowulf, as you'll see, gives us very little information about the life of the average person in Anglo-Saxon society, but concerns itself exclusively with life in the court and on the battlefield.) Most of the stories were written and recited during this time to entertain and instruct the members of the aristocratic class. The scop assumed that his audience was familiar with the stories of ancient times. It was his job to make them as interesting and as vivid as possible."; + + #endregion + + #region Tests + + /// <summary> + /// Tests + /// </summary> + [Test] + public void ReadWriteSingleThreadTests() + { + mBridgeStream = new BridgeStream(); + WriteThread(); + ReadThread(); + Assert.AreEqual(mTestString, mReadString); + } + + /// <summary> + /// Tests the BridgeStream by reading and writing in separate threads. + /// </summary> + [Test] + public void ReadWriteMultiThreadTests() + { + mBridgeStream = new BridgeStream(); + + Thread readThread = new Thread(new ThreadStart(ReadThread)); + Thread writeThread = new Thread(new ThreadStart(WriteThread)); + readThread.Start(); + writeThread.Start(); + + writeThread.Join(); + readThread.Join(); + Assert.AreEqual(mTestString, mReadString); + } + + /// <summary> + /// Tests the BridgeStream by reading and writing in separate threads. + /// </summary> + [Test] + public void ReadWriteWithSeveralStreamsMultiThreadTests() + { + // Test with block on + mBridgeStream = new BridgeStream(); + mBridgeStream.MaxBufferLength = 1026; + mBridgeStream.BlockLastReadBuffer = true; + + Thread readThread = new Thread(new ThreadStart(ReadThread)); + Thread writeThread = new Thread(new ThreadStart(WriteWithSeveralStreamsThread)); + readThread.Start(); + writeThread.Start(); + + writeThread.Join(); + readThread.Join(); + Assert.AreEqual(mTotalWriteLength, mReadString.Length); + + // Test with block off + mBridgeStream = new BridgeStream(); + mBridgeStream.BlockLastReadBuffer = false; + + readThread = new Thread(new ThreadStart(ReadThread)); + writeThread = new Thread(new ThreadStart(WriteWithSeveralStreamsThread)); + readThread.Start(); + writeThread.Start(); + + writeThread.Join(); + readThread.Join(); + + // this next method is non-deterministic - it may end up reading the entire buffer, + // but I wouldn't bet on it. + Debug.WriteLine(String.Format("mTotalWriteLength={0}, mReadString.Length={1}", mTotalWriteLength, mReadString.Length)); + //Assert.AreNotEqual(mTotalWriteLength, mReadString.Length); + } + + #endregion + + #region Thread methods + /// <summary> + /// Read function for the multithread test + /// </summary> + private void ReadThread() + { + StreamReader sr = new StreamReader(mBridgeStream); + mReadString = ReadStringFromStreamReader(sr); + } + + /// <summary> + /// Write function for the multithread test + /// </summary> + private void WriteThread() + { + using (StreamWriter sw = new StreamWriter(mBridgeStream)) + { + WriteDataToStreamWriter(sw, mTestString); + } + } + + private void WriteWithSeveralStreamsThread() + { + mTotalWriteLength = 0; + + for (int i = 0; i < 10; i++) + { + using (StreamWriter sw = new StreamWriter(mBridgeStream)) + { + for (int j = 0; j < 20; j++ ) + mTotalWriteLength += WriteDataToStreamWriter(sw, mTestString); + Thread.Sleep(100); + } + } + mBridgeStream.BlockLastReadBuffer = false; + } + + #endregion + + #region Read and Write Methods + + /// <summary> + /// Reads the string from the passed in stream reader. + /// </summary> + /// <param name="sr">The stream reader.</param> + /// <returns></returns> + private string ReadStringFromStreamReader(StreamReader sr) + { + StringBuilder sb = new StringBuilder(); + char[] buffer = new char[80]; + while (!sr.EndOfStream) + { + int readLength = sr.Read(buffer, 0, buffer.Length); + sb.Append(buffer, 0, readLength); + //Debug.WriteLine("Reading..."); + } + return sb.ToString(); + } + + /// <summary> + /// Writes the string to stream writer. + /// </summary> + /// <param name="sw">The sw.</param> + /// <param name="str">The STR.</param> + /// <returns></returns> + private int WriteDataToStreamWriter(StreamWriter sw, string str) + { + int writeSize = 127; + for (int i = 0; i < str.Length; i += writeSize) + { + // select a substring of characters from the input string + string substring = str.Substring(i, (i + writeSize < str.Length) ? writeSize : str.Length - i); + sw.Write(substring.ToCharArray(), 0, substring.Length); + } + return str.Length; + } + + #endregion + + #region Time Trial Testing + + /* + /// <summary> + /// Compare the length of time to use BridgeStream vs MemoryStream. + /// </summary> + [Test] + public void TimeComparisonTest() + { + int iterations = 11024; + int arraySize = 11024; + byte[] bytes = new byte[arraySize]; + byte[] readBytes = new byte[arraySize]; + + HiPerfTimer h = new HiPerfTimer(); + + h.Start(); + BridgeStream bs = new BridgeStream(); + for (int i = 0; i < iterations; i++) + { + bs.Write(bytes, 0, bytes.Length); + } + for (int i = 0; i < iterations; i++) + { + bs.Read(readBytes, 0, bytes.Length); + } + h.Stop(); + Debug.WriteLine(h.Duration); + + h = new HiPerfTimer(); + h.Start(); + Stream s = new MemoryStream(); + for (int i = 0; i < iterations; i++) + { + s.Write(bytes, 0, bytes.Length); + } + for (int i = 0; i < iterations; i++) + { + s.Read(readBytes, 0, bytes.Length); + } + h.Stop(); + Debug.WriteLine(h.Duration); + } + */ + #endregion + } +} diff --git a/Tests/CreateM3uTests.cs b/Tests/CreateM3uTests.cs new file mode 100644 index 0000000..1ed54c7 --- /dev/null +++ b/Tests/CreateM3uTests.cs @@ -0,0 +1,39 @@ +using System.Diagnostics; +using System.IO; + +namespace m3uTool.Tests +{ + /// <summary> + /// Summary description for CreateM3uTest. + /// </summary> + public class CreateM3uTests + { + public void CreateM3uTest() + { + var c = new M3uCreate(new DirectoryInfo(@"C:\mp3\")); + // c.OutputFilename = @"c:\mym3u.m3u"; + Debug.WriteLine(c.OutputFilename); + c.Create(); + } + + public void CreateSubM3uTest() + { + var rootDir = new DirectoryInfo(@"C:\mp3\"); + + int count = 0; + foreach (DirectoryInfo subDir in rootDir.GetDirectories()) + { + var c = new M3uCreate(subDir); + + Debug.WriteLine(c.OutputFilename); + c.Create(); + + if (count++ > 10) + break; + } + + + // c.OutputFilename = @"c:\mym3u.m3u"; + } + } +} \ No newline at end of file diff --git a/Tests/Disc 01 - 04.mp3 b/Tests/Disc 01 - 04.mp3 new file mode 100644 index 0000000..be3d3be Binary files /dev/null and b/Tests/Disc 01 - 04.mp3 differ diff --git a/Tests/HiPerfTimer.cs b/Tests/HiPerfTimer.cs new file mode 100644 index 0000000..ad5aa62 --- /dev/null +++ b/Tests/HiPerfTimer.cs @@ -0,0 +1,55 @@ +using System.ComponentModel; +using System.Runtime.InteropServices; +using System.Threading; + +namespace Win32 +{ + internal class HiPerfTimer + { + private readonly long freq; + private long startTime, stopTime; + + // Constructor + public HiPerfTimer() + { + startTime = 0; + stopTime = 0; + + if (QueryPerformanceFrequency(out freq) == false) + { + // high-performance counter not supported + throw new Win32Exception(); + } + } + + public double Duration + { + get { return (stopTime - startTime)/(double) freq; } + } + + [DllImport("Kernel32.dll")] + private static extern bool QueryPerformanceCounter( + out long lpPerformanceCount); + + [DllImport("Kernel32.dll")] + private static extern bool QueryPerformanceFrequency( + out long lpFrequency); + + // Start the timer + public void Start() + { + // lets do the waiting threads there work + Thread.Sleep(0); + + QueryPerformanceCounter(out startTime); + } + + // Stop the timer + public void Stop() + { + QueryPerformanceCounter(out stopTime); + } + + // Returns the duration of the timer (in seconds) + } +} \ No newline at end of file diff --git a/Tests/LongestCommonSequenceTests.cs b/Tests/LongestCommonSequenceTests.cs new file mode 100644 index 0000000..2be5fdb --- /dev/null +++ b/Tests/LongestCommonSequenceTests.cs @@ -0,0 +1,18 @@ +using System.Collections.Generic; +using NUnit.Framework; + +namespace m3uTool.Tests +{ + [TestFixture] + public class LongestCommonSequenceTests + { + [Test] + public void FunctionalityTest() + { + List<string> substring = LongestCommonSubstrings.GetLongestSubstring("ABAB", "BABA"); + Assert.AreEqual(2, substring.Count); + Assert.IsTrue(substring.Contains("ABA")); + Assert.IsTrue(substring.Contains("BAB")); + } + } +} \ No newline at end of file diff --git a/Tests/MP3HeaderTests.cs b/Tests/MP3HeaderTests.cs new file mode 100644 index 0000000..f8970ba --- /dev/null +++ b/Tests/MP3HeaderTests.cs @@ -0,0 +1,42 @@ +using System; +using System.Diagnostics; +using System.IO; +using id3; + +namespace m3uTool.Tests +{ + /// <summary> + /// Summary description for MP3HeaderTests. + /// </summary> + public class MP3HeaderTests + { + private string mp3File1 = "001-22.mp3"; + private string mp3File2 = "001-44.mp3"; + + public void MP3HeaderTest() + { + string mp3Filename = mp3File1; + MP3Header mp3hdr = new MP3Header(); + bool boolIsMP3 = mp3hdr.ReadMP3Information(mp3Filename); + if(boolIsMP3) + { + Debug.WriteLine(mp3hdr.strFileName); + Debug.WriteLine(mp3hdr.lngFileSize.ToString()); + Debug.WriteLine(mp3hdr.intBitRate.ToString()); + Debug.WriteLine(mp3hdr.intFrequency.ToString()); + Debug.WriteLine(mp3hdr.strMode); + Debug.WriteLine(mp3hdr.strLengthFormatted); + Debug.WriteLine(mp3hdr.intLength.ToString()); + } + + FileInfo fFileInfo = new FileInfo(mp3Filename); // Creating this FileInfo so I don't have to change my generic class + MP3 mp3ID3 = new MP3(fFileInfo.DirectoryName, fFileInfo.Name); //fFile.DirectoryName, fFile.Name); + FileCommands.readMP3Tag(ref mp3ID3); + + Debug.WriteLine(mp3ID3.id3Title); + Debug.WriteLine(mp3ID3.id3Artist); + Debug.WriteLine(mp3ID3.id3Album); + Debug.WriteLine(mp3ID3.id3TrackNumber); + } + } +} diff --git a/Tests/Mp3EncoderTests.cs b/Tests/Mp3EncoderTests.cs new file mode 100644 index 0000000..cf2e988 --- /dev/null +++ b/Tests/Mp3EncoderTests.cs @@ -0,0 +1,185 @@ +using System; +using System.Diagnostics; +using System.IO; +using NUnit.Framework; + +namespace m3uTool.Tests +{ + [TestFixture] + public class Mp3EncoderTests : ProgressCallbackTestBase + { + private static void FilesAreEqual(FileInfo file1, FileInfo file2) + { + //Assert.AreEqual(file1.Length, file2.Length); + + int filePosition = 0; + using (FileStream outputOptionFs = file1.OpenRead()) + using (var outputOptionSr = new StreamReader(outputOptionFs)) + using (FileStream streamingFs = file2.OpenRead()) + using (var streamingSr = new StreamReader(streamingFs)) + { + var outputOptionBuffer = new char[4096]; + var streamingBuffer = new char[4096]; + + while (!outputOptionSr.EndOfStream) + { + int outputOptionReadLength = outputOptionSr.Read(outputOptionBuffer, 0, outputOptionBuffer.Length); + int streamingReadLength = streamingSr.Read(streamingBuffer, 0, streamingBuffer.Length); + + Assert.AreEqual(outputOptionReadLength, streamingReadLength); + Assert.AreEqual(outputOptionBuffer, streamingBuffer, "offset : " + filePosition); + filePosition += outputOptionBuffer.Length; + } + } + } + + /// <summary> + /// Call this method from the worker thread to increase the progress + /// counter by a specified value. + /// </summary> + public override void StepTo(int val) + { + if (val%10 == 0) + base.StepTo(val); + } + + /// <summary> + /// Tests this instance. + /// </summary> + [Test] + public void StreamingTest() + { + String inputWav = "44_16_s.wav"; + string encodedMp3 = "encodedMp3.mp3"; + String decodedWav = "decodedWav.wav"; + String reencodedMp3 = "reencodedMp3.mp3"; + + using (var fileStream = new FileStream(encodedMp3, FileMode.OpenOrCreate)) + { + var encOpts = new Mp3EncodingOptions(); + encOpts.Outfile = "-"; + encOpts.Infile = "-"; + + using (ProcessStreamWrapper enc = new Mp3Encoder(encOpts, fileStream)) + { + enc.ProgressCallback = this; + enc.ProcessInput(inputWav); + } + } + Debug.WriteLine("Finished writing " + encodedMp3); + + Debug.WriteLine("Begin writing " + decodedWav); + using (var fileStream = new FileStream(decodedWav, FileMode.OpenOrCreate)) + { + var wavOpts = new Mp3EncodingOptions(); + wavOpts.Infile = "-"; + wavOpts.Outfile = "-"; + // wavOpts.MonoDownmixing = true; + wavOpts.Mp3Input = true; + wavOpts.DecodeWav = true; + + using (ProcessStreamWrapper enc = new Mp3Encoder(wavOpts, fileStream)) + { + enc.ProgressCallback = this; + enc.ProcessInput(encodedMp3); + } + } + Debug.WriteLine("Finished writing " + decodedWav); + + using (var fileStream = new FileStream(reencodedMp3, FileMode.OpenOrCreate)) + { + var encOpts = new Mp3EncodingOptions(); + encOpts.Outfile = "-"; + encOpts.Infile = "-"; + + using (ProcessStreamWrapper enc = new Mp3Encoder(encOpts, fileStream)) + { + enc.ProgressCallback = this; + enc.ProcessInput(decodedWav); + } + } + + var reencodedProperties = new Mp3FileProperties(reencodedMp3); + var encodedProperties = new Mp3FileProperties(encodedMp3); + Assert.AreEqual(encodedProperties.FileLength, reencodedProperties.FileLength); + } + + /// <summary> + /// + /// </summary> + [Test] + public void StreamingVsOutputMp3DecodingTest() + { + String input = "44_16_s.mp3"; + String outputOptionDecoded = "outputOptionDecoded.wav"; + String streamingDecoded = "streamingDecode.wav"; + + var encOpts = new Mp3EncodingOptions(); + encOpts.Outfile = "-"; + encOpts.Infile = "-"; + encOpts.Mp3Input = true; + encOpts.DecodeWav = true; + + using (var fileStream = new FileStream(streamingDecoded, FileMode.OpenOrCreate)) + { + using (ProcessStreamWrapper enc = new Mp3Encoder(encOpts, fileStream)) + { + enc.ProgressCallback = this; + enc.ProcessInput(input); + } + } + + encOpts.Outfile = outputOptionDecoded; + + using (ProcessStreamWrapper enc = new Mp3Encoder(encOpts)) + { + enc.ProgressCallback = this; + enc.ProcessInput(input); + } + + var outputOptionFileInfo = new FileInfo(outputOptionDecoded); + var streamingFileInfo = new FileInfo(streamingDecoded); + + FilesAreEqual(outputOptionFileInfo, streamingFileInfo); + } + + /// <summary> + /// + /// </summary> + [Test] + public void StreamingVsOutputMp3EncodingTest() + { + //String inputWav = "44_16_s.wav"; + String inputWav = "test1.wav"; + String outputOptionEncodedMp3 = "outputOptionEncodedMp3.mp3"; + String streamingEncodedMp3 = "streamingEncodedMp3.mp3"; + + var encOpts = new Mp3EncodingOptions(); + encOpts.Outfile = "-"; + encOpts.Infile = "-"; + + using (var fs = new FileStream(streamingEncodedMp3, FileMode.OpenOrCreate)) + { + using (ProcessStreamWrapper enc = new Mp3Encoder(encOpts, fs)) + { + enc.ProgressCallback = this; + enc.ProcessInput(inputWav); + } + } + + encOpts.Outfile = outputOptionEncodedMp3; + + using (ProcessStreamWrapper enc = new Mp3Encoder(encOpts)) + { + enc.ProgressCallback = this; + enc.ProcessInput(inputWav); + } + + var outputOptionProperties = new Mp3FileProperties(outputOptionEncodedMp3); + var streamingProperties = new Mp3FileProperties(streamingEncodedMp3); + Assert.AreEqual(outputOptionProperties.LengthInSeconds, streamingProperties.LengthInSeconds); + + // FilesAreEqual(outputOptionFileInfo, streamingFileInfo); + } + } +} \ No newline at end of file diff --git a/Tests/Mp3EncodingOptionsTests.cs b/Tests/Mp3EncodingOptionsTests.cs new file mode 100644 index 0000000..96ee300 --- /dev/null +++ b/Tests/Mp3EncodingOptionsTests.cs @@ -0,0 +1,25 @@ +using System.Diagnostics; +using NUnit.Framework; + +namespace m3uTool.Tests +{ + /// <summary> + /// + /// </summary> + [TestFixture] + public class Mp3EncodingOptionsTests + { + /// <summary> + /// + /// </summary> + [Test] + public void Mp3EncodingOptionsTest() + { + var e = new Mp3EncodingOptions(); + e.ChannelMode = ChannelMode.SingleChannel; + e.Infile = "-"; + string arguments = e.GetCommandLineArguments(); + Debug.WriteLine(arguments); + } + } +} \ No newline at end of file diff --git a/Tests/Mp3FilePropertiesTests.cs b/Tests/Mp3FilePropertiesTests.cs new file mode 100644 index 0000000..65d1594 --- /dev/null +++ b/Tests/Mp3FilePropertiesTests.cs @@ -0,0 +1,54 @@ +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Text; + +namespace m3uTool.Tests +{ + /// <summary> + /// Summary description for MP3HeaderTests. + /// </summary> + public class Mp3FilePropertiesTests + { + public void Mp3FileDetailsTest() + { + string[] files = Directory.GetFiles(".", "*.mp3"); + var properties = new List<Mp3FileProperties>(); + foreach (string mp3Filename in files) + { + Debug.WriteLine(mp3Filename); + Debug.Indent(); + + // bool boolIsMP3 = mp3hdr.ReadMP3Information(mp3Filename); + var fileDetails = new Mp3FileProperties(mp3Filename); + properties.Add(fileDetails); + if (fileDetails != null) + { + Debug.WriteLine(GetMp3FilePropertyReport(fileDetails)); + } + Debug.Unindent(); + } + Mp3FileProperties commonProperties = Mp3FileProperties.GetCommonProperties(properties); + Debug.WriteLine(GetMp3FilePropertyReport(commonProperties)); + } + + private static string GetMp3FilePropertyReport(Mp3FileProperties fileDetails) + { + var report = new StringBuilder(); + report.AppendLine("Filename: " + fileDetails.Mp3PathAndFilename); + report.AppendLine("mFileSize: " + fileDetails.FileSize); + report.AppendLine("mBitRate: " + fileDetails.BitRate); + report.AppendLine("mSampleRateIntegerFrequency:" + fileDetails.SampleRateIntegerFrequency); + report.AppendLine("strMode: " + fileDetails.ChannelMode); + report.AppendLine("strLengthFo: " + fileDetails.LengthFormatted); + report.AppendLine("mLengthInSeconds: " + fileDetails.LengthInSeconds); + + report.AppendLine("title: " + fileDetails.Id3Title); + report.AppendLine("artist: " + fileDetails.Id3Artist); + report.AppendLine("album: " + fileDetails.Id3Album); + report.AppendLine("track#: " + fileDetails.Id3TrackNumber); + report.AppendLine("genre: " + fileDetails.Id3GenreName); + return report.ToString(); + } + } +} \ No newline at end of file diff --git a/Tests/Mp4EncoderTests.cs b/Tests/Mp4EncoderTests.cs new file mode 100644 index 0000000..7cdcf15 --- /dev/null +++ b/Tests/Mp4EncoderTests.cs @@ -0,0 +1,34 @@ +using NUnit.Framework; + +namespace m3uTool.Tests +{ + [TestFixture] + public class Mp4EncoderTests : ProgressCallbackTestBase + { + /// <summary> + /// Call this method from the worker thread to increase the progress + /// counter by a specified value. + /// </summary> + public override void StepTo(int val) + { + if (val%10 == 0) + base.StepTo(val); + } + + /// <summary> + /// Tests Encoding an Mp4 file. + /// </summary> + [Test] + public void Test() + { + var encOpts = new Mp4EncodingOptions(); + encOpts.OutputFilename = "44_16_s_opts.aac"; + + using (ProcessStreamWrapper enc = new Mp4Encoder(encOpts)) + { + enc.ProgressCallback = this; + enc.ProcessInput("44_16_s.wav"); + } + } + } +} \ No newline at end of file diff --git a/Tests/Mp4EncodingOptionsTests.cs b/Tests/Mp4EncodingOptionsTests.cs new file mode 100644 index 0000000..67d42b7 --- /dev/null +++ b/Tests/Mp4EncodingOptionsTests.cs @@ -0,0 +1,25 @@ +using System.Diagnostics; +using NUnit.Framework; + +namespace m3uTool.Tests +{ + [TestFixture] + public class Mp4EncodingOptionsTests + { + [Test] + public void Mp4EncodingOptionsTest() + { + var e = new Mp4EncodingOptions(); + e.RawInputChannels = ChannelMode.SingleChannel; + string arguments = e.GetCommandLineArguments(); + Debug.WriteLine(arguments); + + e = new Mp4EncodingOptions(); + e.OutputFilename = "har"; + Debug.WriteLine(e.GetCommandLineArguments()); + + e.InputFiles = new[] {"input.txt"}; + Debug.WriteLine(e.GetCommandLineArguments()); + } + } +} \ No newline at end of file diff --git a/Tests/ProcessStreamWrapperTests.cs b/Tests/ProcessStreamWrapperTests.cs new file mode 100644 index 0000000..a76f53a --- /dev/null +++ b/Tests/ProcessStreamWrapperTests.cs @@ -0,0 +1,67 @@ +using System.IO; +using System.Text; +using NUnit.Framework; + +namespace m3uTool.Tests +{ + [TestFixture] + public class ProcessStreamWrapperTests : ProgressCallbackTestBase + { + private class StdinToStdoutProcess : ProcessStreamWrapper + { + public StdinToStdoutProcess(ProcessArguments encOpt, Stream outputStream) + : base(encOpt, outputStream) + { + } + + /// <summary> + /// Gets the process executable filename, such as "LAME.EXE", "FAAC.EXE", etc. + /// </summary> + /// <value>The process executable filename.</value> + protected override string ProcessExecutableFilename + { + get { return "StdinToStdout.exe"; } + } + } + + [Test] + public void ProcessStreamWrapperFunctionalityTest() + { + string inputString = File.ReadAllText("testText.txt"); + string writtenString = ""; // string that was written to the process + + Stream inputStream = new MemoryStream(); + + var sw = new StreamWriter(inputStream, Encoding.Default); + { + int writeSize = 127; + for (int i = 0; i < inputString.Length; i += writeSize) + { + // select a substring of characters from the input string + string substring = inputString.Substring(i, + (i + writeSize < inputString.Length) + ? writeSize + : inputString.Length - i); + sw.Write(substring.ToCharArray(), 0, substring.Length); + writtenString += substring; + } + } + inputStream.Seek(0, SeekOrigin.Begin); + + Stream outputStream = new MemoryStream(); + using (var s = new StdinToStdoutProcess(new ProcessArguments(), outputStream)) + { + s.ProgressCallback = this; + s.ProcessInput(inputStream); + } + + outputStream.Seek(0, SeekOrigin.Begin); + string outputString; + using (var sr = new StreamReader(outputStream, Encoding.Default)) + { + outputString = sr.ReadToEnd(); + } + Assert.AreEqual(writtenString, outputString); + } + } +} \ No newline at end of file diff --git a/Tests/ProgressCallbackTestBase.cs b/Tests/ProgressCallbackTestBase.cs new file mode 100644 index 0000000..2ef27d2 --- /dev/null +++ b/Tests/ProgressCallbackTestBase.cs @@ -0,0 +1,100 @@ +using System; +using System.Diagnostics; + +namespace m3uTool.Tests +{ + /// <summary> + /// Implements the interface progress callback for testing output purposes + /// </summary> + public class ProgressCallbackTestBase : IProgressCallback + { + protected string mPreface = ""; + + #region IProgressCallback Members + + /// <summary> + /// Call this method from the worker thread to initialize + /// the progress callback. + /// </summary> + /// <param name="minimum">The minimum.</param> + /// <param name="maximum">The maximum.</param> + public virtual void Begin(int minimum, int maximum) + { + Debug.WriteLine(mPreface + String.Format("Begin min:{0} max:{1}", minimum, maximum)); + } + + /// <summary> + /// Call this method from the worker thread to initialize + /// the progress callback, without setting the range + /// </summary> + public virtual void Begin() + { + Debug.WriteLine(mPreface + String.Format("Begin")); + } + + /// <summary> + /// Call this method from the worker thread to reset the range in the + /// progress callback + /// </summary> + /// <param name="minimum">The minimum.</param> + /// <param name="maximum">The maximum.</param> + public virtual void SetRange(int minimum, int maximum) + { + Debug.WriteLine(mPreface + String.Format("SetRange min:{0} max:{1}", minimum, maximum)); + } + + /// <summary> + /// Call this method from the worker thread to update the progress text. + /// </summary> + /// <param name="text">The text.</param> + public virtual void SetText(string text) + { + Debug.WriteLine(mPreface + String.Format("SetText : {0}", text)); + } + + /// <summary> + /// Call this method from the worker thread to increase the progress + /// counter by a specified value. + /// </summary> + /// <param name="val">The val.</param> + public virtual void StepTo(int val) + { + Debug.WriteLine(mPreface + String.Format("StepTo : {0}", val)); + } + + /// <summary> + /// Call this method from the worker thread to step the progress meter to a + /// particular value. + /// </summary> + /// <param name="val">The val.</param> + public virtual void Increment(int val) + { + Debug.WriteLine(mPreface + String.Format("Increment : {0}", val)); + } + + /// <summary> + /// If this property is true, then you should abort work + /// </summary> + /// <value> + /// <c>true</c> if this instance is aborting; otherwise, <c>false</c>. + /// </value> + public virtual bool IsAborting + { + get + { + Debug.WriteLine(mPreface + "IsAborting"); + return false; + } + } + + /// <summary> + /// Call this method from the worker thread to finalize the progress meter + /// </summary> + public virtual void End() + { + Debug.WriteLine(mPreface + "End"); + } + + #endregion + } +} \ No newline at end of file diff --git a/Tests/TranscodeTest.cs b/Tests/TranscodeTest.cs new file mode 100644 index 0000000..523a8dc --- /dev/null +++ b/Tests/TranscodeTest.cs @@ -0,0 +1,178 @@ +using System.Diagnostics; +using System.IO; +using System.Text; +using System.Threading; +using NUnit.Framework; + +namespace m3uTool.Tests +{ + [TestFixture] + public class TranscodeTest : ProgressCallbackTestBase + { + private const string inputMp3 = "Disc 01 - 04.mp3"; + private readonly string outputAac = inputMp3.Replace(".mp3", ".aac"); + private readonly string outputWav = inputMp3.Replace(".mp3", ".wav"); + private readonly ReaderProgress mReaderProgress = new ReaderProgress(); + private readonly WriterProgress mWriterProgress = new WriterProgress(); + + private Stream _pipeStream; + + private void Mp3ReadThread() + { + //int skip = 3; + //int limit = 1; + //string directory = @"C:\tmp\venus bak\media\audiobooks\Bret Easton Ellis - Lunar Park"; + //// string directory = @"C:\tmp\venus bak\media\audiobooks\Clarke, Arthur C. - Space Odyssey 01 - 2001, A Space Odyssey"; + //// string directory = @"C:\tmp\venus bak\media\audiobooks\Guitar Gym\Part 1 - Alternate Picking (Mp3 And Tab)"; + //string[] files = Directory.GetFiles(directory); + var mp3Files = new[] {inputMp3}; + foreach (var mp3File in mp3Files) + { + //if (skip-- > 0) + // continue; + //if (limit-- == 0) + // break; + Debug.WriteLine(mp3File); + var fp = new Mp3FileProperties(mp3File); + Debug.WriteLine(fp.ToString()); + // Mp3 to wav + using (var fileStream = new FileStream(mp3File, FileMode.Open)) + { + var opts = new Mp3EncodingOptions + { + Infile = "-", + Outfile = "-", + Mp3Input = true, + DecodeWav = true + }; + // opts.BitRate = BitRate.KBPS_32; + // opts.Freeformat = true; + // opts.DisableWavHeader = true; + + Debug.WriteLine("MP3 : " + opts.GetCommandLineArguments()); + + // opts.Freeformat = true; + // read from the sr, and write to the memory stream + using (var encoder = new Mp3Encoder(opts, _pipeStream)) + { + encoder.ProgressCallback = mReaderProgress; + encoder.ProcessInput(fileStream); + } + } + } + } + + private void WavWriteThread() + { + using (var fs = new FileStream(outputWav, FileMode.OpenOrCreate)) + using (var sw = new StreamWriter(fs, Encoding.Default)) + using (var sr = new StreamReader(_pipeStream, Encoding.Default)) + { + var buffer = new char[1024*1024]; + + while (!sr.EndOfStream) + { + var readCount = sr.Read(buffer, 0, buffer.Length); + sw.Write(buffer, 0, readCount); + mWriterProgress.SetText("Wrote " + buffer.Length); + } + } + } + + private void AacWriteThread() + { + var mp4Opts = new Mp4EncodingOptions + { + OutputFilename = outputAac, + RawPCMSwapInputBytes = true, + RawPCMInputMode = true, + RawPCMInputSampleRate = 16, + RawPCMInputSampleSize = SampleRateFrequency.Hz_44100, + RawInputChannels = ChannelMode.Stereo + }; + Debug.WriteLine("AAC : " + mp4Opts.GetCommandLineArguments()); + + using (var encoder = new Mp4Encoder(mp4Opts)) + { + encoder.ProgressCallback = mWriterProgress; + encoder.ProcessInput(_pipeStream); + } + } + + + private class ReaderProgress : ProgressCallbackTestBase + { + public ReaderProgress() + { + mPreface = "Reader: "; + } + + /// <summary> + /// Call this method from the worker thread to increase the progress + /// counter by a specified value. + /// </summary> + public override void StepTo(int val) + { + if (val%10 == 0) + { + base.StepTo(val); + } + } + } + + private class WriterProgress : ProgressCallbackTestBase + { + public WriterProgress() + { + mPreface = "\t\t\t\tWriter: "; + } + + /// <summary> + /// Call this method from the worker thread to increase the progress + /// counter by a specified value. + /// </summary> + public override void StepTo(int val) + { + if (val%10 == 0) + { + base.StepTo(val); + } + } + } + + [Test] + public void TranscodeMultiThreadTest() + { + //_pipeStream = new BridgeStream(); + _pipeStream = new PipeStream.PipeStream(); + (_pipeStream as PipeStream.PipeStream).BlockLastReadBuffer = true; + (_pipeStream as PipeStream.PipeStream).MaxBufferLength = 1*PipeStream.PipeStream.MB; + + // _pipeStream.MaxBufferLength = 100; + + var writeThread = new Thread(AacWriteThread); + writeThread.Start(); + + var readThread = new Thread(Mp3ReadThread); + readThread.Start(); + + readThread.Join(); + (_pipeStream as PipeStream.PipeStream).BlockLastReadBuffer = false; + writeThread.Join(); + } + + [Test] + public void TranscodeSingleThreadTest() + { + var mp3Info = new Mp3FileProperties(inputMp3); + Debug.WriteLine(mp3Info); + + _pipeStream = new MemoryStream(); + + Mp3ReadThread(); + if (_pipeStream.CanSeek) + _pipeStream.Seek(0, SeekOrigin.Begin); + WavWriteThread(); + } + } +} \ No newline at end of file diff --git a/Tests/test.aac b/Tests/test.aac new file mode 100644 index 0000000..46f975d Binary files /dev/null and b/Tests/test.aac differ diff --git a/Tests/testText.txt b/Tests/testText.txt new file mode 100644 index 0000000..121b822 --- /dev/null +++ b/Tests/testText.txt @@ -0,0 +1,152 @@ +The Monkey's Paw +by W.W. Jacobs + + Without, the night was cold and wet, but in the small parlor of Lakesnam Villa the blinds were drawn and the fire burned brightly. Father and son were at chess, the former, who possessed ideas about the game involving radical changes, putting his king into such sharp and unnecessary perils that it even provoked comment from the white-haired old lady knitting placidly by the fire. + "Hark at the wind," said Mr. White, who having seen a fatal mistake after it was too late, was amiably desirous of preventing his son from seeing it. + "I'm listening," said the latter, grimly surveying the board as he stretched out his hand. "Check." + "I should hardly think that he'd come tonight," said his father, with his hand poised over the board. + "Mate," replied the son. + "That's the worst of living so far out," bawled Mr. White, with sudden and unlooked for violence; "of all the beastly slushy, out-of-the-way places to live in, this is the worst. Pathway's a bog, and the road's a torrent. I don't know what people are thinking about. I suppose because only two houses on the road are let, they think it doesn't matter." + "Never mind, dear," said his wife soothingly; "perhaps you'll win the next one." + Mr. White looked up sharply, just in time to intercept a knowing glance between mother and son. The words died away on his lips and he hid a guilty grin in his thin gray beard. + "There he is," said Herbert White, as the gate banged to loudly and heavy footsteps came toward the door. + The old man rose with hospitable haste, and, opening the door, was heard condoling with the new arrival. The new arrival also condoled with himself, so that Mrs. White said, "Tut, tut!" and coughed gently as her husband entered the room, followed by a tall burly man, beady of eye and rubicund of visage. + "Sergeant Major Morris," he said, introducing him. + The sergeant major shook hands, and taking the proffered seat by the fire, watched contentedly while his host got out whiskey and tumblers and stood a small copper kettle on the fire. + At the third glass his eyes got brighter, and he began to talk, the little family circle regarding with eager interest this visitor from distant parts, as he squared his broad shoulders in the chair and spoke of strange scenes and doughty deeds of wars and plagues and strange peoples. + "Twenty-one years of it," said Mr. White, nodding at his wife and son. "When he went away he was a slip of a youth in the warehouse. Now look at him." + "He don't look to have taken much harm," said Mrs. White politely. + "I'd like to go to India myself," said the old man, "just to look around a bit, you know." + "Better where you are," said the sergeant major, shaking his head. He put down the empty glass and, sighing softly, shook it again. + "I should like to see those old temples and fakirs and jugglers," said the old man. "What was that you started telling me the other day about the monkey's paw or something, Morris?" + "Nothing," said the soldier hastily. "Leastways, nothing worth hearing." + "Monkey's paw?" said Mrs. White curiously. + "Well it's just a bit of what you might call magic, perhaps." said the sergeant major offhandedly. + His three listeners leaned forward eagerly. The visitor absent-mindedly put his empty glass to his lips and then set it down again. His host filled it for him. + "To look at," said the sergeant major, fumbling in his pocket, "it's just an ordinary little paw, dried to a mummy." + He took something out of his pocket and proffered it. Mrs. White drew back with a grimace, but her son, taking it, examined it curiously. + "And what is there special about it?" inquired Mr. White as he took it from his son and, having examined it, placed it upon the table. + "It had a spell put on it by an old fakir," said the sergeant major, "a very holy man. He wanted to show that fate ruled people's lives, and that those who interfered with it did so to their sorrow. He put a spell on it so that three separate men could each have three wishes from it." + His manner was so impressive that his hearers were conscious that their light laughter jarred somewhat. + "Well, why don't you have three, sir?" said Herbert White cleverly. + The soldier regarded him in the way that middle age is wont to regard presumptuous youth. "I have," he said quietly, and his blotchy face whitened. + "And did you really have the three wishes granted?" asked Mrs. White. + "I did," said the sergeant major, and his glass tapped against his strong teeth. + "And has anybody else wished?" inquired the old lady. + "The first man had his three wishes, yes." was the reply. "I don't know what the first two were, but the third was for death. That's how I got the paw." + His tones were so grave that a hush fell upon the group. + "If you've had your three wishes, it's no good to you now, then, Morris," said the old man at last. "What do you keep it for?" + The soldier shook his head. "Fancy, I suppose," he said slowly. "I did have some idea of selling it, but I don't think I will. It has caused enough mischief already. Besides, people won't buy. They think it's a fairy tale, some of them, and those who do think anything of it want to try it first and pay me afterward." + "If you could have another three wishes," said the old man, eying him keenly, "would you have them?" + "I don't know," said the other. "I don't know." + He took the paw, and dangling it between his front finger and thumb, suddenly three it upon the fire. White, with a slight cry stooped down and snatched it off. + "Better let it burn," said the soldier solemnly + "If you don't want it, Morris," said the old man, "give it to me." + "I won't," said his friend doggedly. "I threw it on the fire. If you keep it, don't blame me for what happens. Pitch it on the fire again, lie a sensible man." + The other shook his head and examined his new possession closely. "How do you do it?" he inquired. + "Hold it up in your right hand and wish aloud," said the sergeant major, "but I warn you of the consequences." + "Sounds like the <i>Arabian Nights</i>," said Mrs. White, as she rose and began to set the supper. Don't you think you might wish for four pairs of hands for me?" + Her husband drew the talisman from hi pocket and then all three burst into laughter and the sergeant major, with a look of alarm on his face, caught him by the arm. "If you must wish," he said gruffly, "wish for something sensible." + Mr. White dropped it back into his pocket, and placing chairs, motioned his friend to the table. In the business of supper the talisman was partly forgotten, and afterward the three sat listening in an enthralled fashion to a second installment of the soldier's adventures in India. + "If the tale about the monkey paw is not more truthful than those he has been telling us," said Herbert, as the door closed behind their guest, just in time for him to catch the last train, "we shan't make much out of it." + "Did you give him anything for it, Father?" inquired Mrs. White, regarding her husband closely. + "A trifle," said he, coloring slightly. "He didn't want it, but I made him take it. And he pressed me again to throw it away." + "Likely," said Herbert, with pretended horror. "Why, we're going to be rich and famous and happy. Wish to be an emperor, Father, to begin with: then you can't be bossed around." + He darted around the table, pursued by the maligned Mrs. White armed with an antimacassar. + Mr. White took the paw from his pocket and eyed it dubiously. "I don't know what to wish for, and that's a fact," he said slowly. "It seems to me I've got all I want." + "If you only cleared the house, you'd be quite happy, wouldn't you?" said Herbert, with his hand on his shoulder. "Well, wish for two hundred pounds, then; that'll just do it." + His father, smiling shamefacedly at his own credulity, held up the talisman, as his son, with a solemn face somewhat marred by a wink at his mother, sat down at the piano and struck a few impressive chords. + "I wish for two hundred pounds," said the old man distinctly. + A fine crash from the piano greeted the words, interrupted by a shuddering cry from the old man. His wife and son ran toward him. + "It moved," he cried, with a glance of disgust at the object as it lay on the floor. "As I wished it twisted in my hands like a snake." + "Well, I don't see the money," said his son, as he picked it up and placed it on the table, "and I bet I never shall." + "It must have been your fancy, father," said his wife, regarding him anxiously. + He shook his head. "Never mind, though; there's no harm done, but it gave me a shock all the same." +They sat down by the fire again while the two men finished their pipes. Outside, the wind was higher than ever, and the old man started nervously at the sound of a door banging upstairs. A silence unusual and depressing settled upon all three, which lasted until the old couple rose to retire for the night. + "I expect you'll find the cash tied up in a big bag in the middle of your bed," said Herbert, as he bade them good night, "and something horrible squatting up on top of the wardrobe watching you as you pocket your ill-gotten gains." + + + In the brightness of the wintry sun next morning as it streamed over the breakfast table Herbert laughed at his fears. There was an air of prosaic wholesomeness about the room which it had lacked on the previous night, and the dirty, shriveled little paw was pitched on the sideboard with a carelessness which betokened no great belief in its virtues. + "I suppose all old soldiers are the same," said Mrs. White. "The idea of our listening to such nonsense! How could wishes be granted in these days? And if they could, how could two hundred pounds hurt you, Father?" + "Might drop on his head from the sky," said the frivolous Herbert. + "Morris said the things happened so naturally," said his farther, "that you might if you so wished attribute it to coincidence." + "Well, don't break into the money before I come back," said Herbert, as he rose from the table. "I'm afraid it'll turn you into a mean, avaricious man, and we shall have to disown you." + His mother laughed, and followed him to the door, watched him down the road, and, returning to the breakfast table, was very happy at the expense of her husband's credulity. All of which did not prevent her from scurrying to the door at the postman's knock, nor prevent her from referring somewhat shortly to retired sergeant majors of bibulous habits when she found that the post brought a tailor's bill. + "Herbert will have some more of his funny remarks, I expect, when he comes home," she said, as they sat at dinner. + "I dare say," said Mr. White, pouring himself out some beer; "but for all that, the thing moved in my hand; that I'll swear to." + "You thought it did," said the old lady soothingly. + "I say it did," replied the other. "There was no thought about it. I had just--What's the matter?" + His wife made no reply. She was watching the mysterious movements of a man outside, who, peering in an undecided fashion at the house, appeared to be trying to make up his mind to enter. In mental connection with the two hundred pounds, she noticed that the stranger was well dressed and wore a silk hat of glossy newness. Three times he paused at the gate, and then walked on again. The fourth time he stood with his hand upon it, and then with sudden resolution flung it open and walked up the path. Mrs. White at the same moment placed her hands behind her, and hurriedly unfastening the strings of her apron, put that useful article of apparel beneath the cushion of her chair. + She brought the stranger, who seemed ill at ease, into the room. He gazed furtively at Mrs. White, and listened in a preoccupied fashion as the old lady apologized for the appearance of the room, and her husband's coat, a garment which he usually reserved for the garden. She then waited patiently for him to broach his business, but he was at first strangely silent. + "I--was asked to call," he said at last, and stooped and picked a piece of cotton from his trousers. "I came from Maw and Meggins." + The old lady started. "Is anything the matter?" she asked breathlessly. "Has anything happened to Herbert? What is it? What is it?" + Her husband interposed. "There, there, Mother," He said hastily. "Sit down and don't jump to conclusions. You've not brought bad news, I'm sure, sir," and he eyed the other wistfully. + "I'm sorry--" began the visitor. + "Is he hurt?" demanded the mother. + The visitor bowed in assent. "Badly hurt." he said quietly, "but he is not in any pain." + "Oh, thank God!" said the old woman, clasping her hands. "Thank God for that! Thank--" + She broke off suddenly as the sinister meaning of the assurance dawned upon her and she saw the awful confirmation of her fears in the other's averted face. She caught her breath, and turning to her husband, laid her trembling old hand upon his. There was a long silence. + "He was caught in the machinery," said the visitor at length, in a low voice. + "Caught in the machinery," repeated Mr. White in a dazed fashion, "yes." + He sat staring blankly out at the window, and taking his wife's hand between his own, pressed it as he had been wont to do in their old courting days nearly forty years before. + "He was the only one left to us," he said, turning gently to the visitor. "It is hard." + The other coughed, and, rising, walked slowly to the window. "The firm wished me to convey their sincere sympathy with you in your great loss," he said, without looking around. "I beg that you will understand I am only their servant and merely obeying orders." + There was no reply; the old woman's face was white, her eyes staring, and he breath inaudible; on the husband's face was a look such as his friend the sergeant might have carried into his first action. + "I was to say that Maw and Meggins disclaim all responsibility," continued the other. "They admit no liability at all, but in consideration of your son's services they wish to present you with a certain sum as compensation." + Mr. White dropped his wife's hand, and rising to his feet, gazed with a look of horror at his visitor. His dry lips shaped the words, "How much?" + "Two hundred pounds," was the answer. + Unconscious of his wife's shriek, the old man smiled faintly, put out his hands like a sightless man, and dropped, a senseless heap, to the floor. + + + In the huge new cemetery, some two miles distant, the old people buried their dead, and came back to a house steeped in shadow and silence. It was all over so quickly that at first they could hardly realize it, and remained in a state of expectation as though of something else to happen--something else which was to lighten this load, too heavy for old hearts to bear. But the days passed, and expectations gave place to resignation--the hopeless resignation of the old, sometimes miscalled apathy. Sometimes they hardly exchanged a word, for now they had nothing to talk about, and their days were long to weariness. + It was about a week after that that the old man, waking suddenly in the night stretched out his hand and found himself alone. The room was in darkness, and the sound of subdued weeping came from the window. He raised himself in bed and listened. + "Come back," he said tenderly. "You will be cold." + "It is colder for my son," said the old woman, and wept afresh. + The sound of her sobs died away on his ears. The bed was warm, and his eyes heavy with sleep. He dozed fitfully, and then slept until a sudden wild cry from his wife awoke him with a start. + "The monkey's paw!" she cried wildly, "The monkey's paw!" + He started up in alarm. "Where? Where is it? What's the matter?" + She came stumbling across the room toward him. "I want it," she said quietly. "You've not destroyed it?" + "It's in the parlor, on the bracket," he replied, marveling. "Why?" + She cried and laughed together, and bending over, kissed his cheek. + "I only just thought of it," she said hysterically. "Why didn't I think of it before? Why didn't you think of it?" + "Think of what?" he questioned. + "The other two wishes," she replied rapidly. "We've only had one." + "Was not that enough?" he demanded fiercely. + "No," she cried triumphantly; "we'll have one more. Go down and get it quickly, and wish our boy alive again." + The man sat up in bed and flung the bedclothes from his quaking limbs. "You are mad!" he cried, aghast. + "Get it," she panted; "get it quickly, and wish--Oh, my boy, my boy!" + Her husband struck a match and lit the candle. "Get back to bed," he said unsteadily. "You don't know what you are saying." + "We had the first wish granted," said the old woman feverishly; "why not the second?" + "A coincidence," stammered the old man. + "Go and get it and wish," cried his wife, quivering with excitement. + The old man turned and regarded her, and his voice shook. "He has been dead ten days, and besides he--I would not tell you else, but--I could only recognize him by his clothing. If he was too terrible for you to see then, how now?" + "Bring him back," cried the old woman, and dragged him toward the door. "Do you think I fear the child I have nursed?" + He went down in the darkness, and felt his way to the parlor, and then to the mantelpiece. The talisman was in its place, and a horrible fear that the unspoken wish might bring his mutilated son before him ere he could escape from the room seized upon him, and he caught his breath as he found that he had lost the direction of the door. His brow cold with sweat, he felt his way round the table, and groped along the wall until he found himself in the small passage with the unwholesome thing in his hand. + Even his wife's face seemed changed as he entered the room. It was white and expectant, and to his fears seemed to have an unnatural look upon it. He was afraid of her. + "Wish!" she fried, in a strong voice. + "It is foolish and wicked," he faltered. + "Wish!" repeated his wife. + He raised his hand. "I wish my son alive again." + The talisman fell to the floor, and he regarded it shudderingly. Then he sank trembling into a chair as the old woman, with burning eyes, walked to the window and raised the blind. + He sat until he was chilled with the cold, glancing occasionally at the figure of the old woman peering through the window. The candle end, which had burned below the rim of the china candlestick, was throwing pulsating shadows on the ceiling and walls, until, with a flicker larger than the rest, it expired. The old man, with an unspeakable sense of relief at the failure of the talisman, crept back to his bed, and a minute or two afterward the old woman came silently and apathetically beside him. + Neither spoke, but both lay silently listening to the ticking of the clock. A stair creaked and a squeaky mouse scurried noisily through the wall. The darkness was oppressive, and after lying for some time screwing up his courage, the husband took the box of matches, and striking one, went downstairs for a candle. + At the foot of the stairs the match went out, and he paused to strike another, and at the same moment a knock, so quiet and stealthy as to be scarcely audible, sounded on the front door. + The matches fell from his hand. He stood motionless, his breath suspended until the knock was repeated. Then he turned and fled swiftly back to his room, and closed the door behind him. a third knock sounded through the house. + "What's that?" cried the old woman, starting up. + "A rat," said the old man, in shaking tones--"a rat. It passed me on the stairs." + His wife sat up in the bed listening. A loud knock resounded through the house. + "It's Herbert!" she screamed. "It's Herbert!" + She ran to the door, but her husband was before her, and catching her by the arm, held her tightly. + "What are you going to do?" he whispered hoarsely. + "It's my boy; it's Herbert!" she cried, struggling mechanically. "I forgot it was two miles away. What are you holding me for? Let's go. I must open the door." + "Don't let it in," cried the old man, trembling. + "You're afraid of your own son," she cried, struggling. "Let me go. I'm coming. Herbert; I'm coming." + There was another knock, and another. The old woman with a sudden wrench broke free and ran from the room. Her husband followed to the landing, and called after her appealingly as she hurried downstairs. He heard the chain rattle back and the bottom bolt drawn slowly and stiffly from the socket. Then the old woman's voice, strained and panting. + "The bolt," she cried loudly. "Come down. I can't reach it." + But her husband was on his hands and knees groping wildly on the floor in search of the paw. If he could only find it before the thing outside got in. A perfect fusillade of knocks reverberated through the house, and he heard the scraping of a chair as his wife put it down in the passage against the door. He heard the creaking of the bolt as it came slowly back and at the same moment he found the monkey's paw, and frantically breathed his third and last wish. + The knocking ceased suddenly, although the echoes of it were still in the house. He heard the chair drawn back and the door opened. A cold wind rushed up the staircase, and a long loud wail of disappointment and misery from his wife gave him courage to run down to her side, and then to the gate beyond. The street lamp flickering opposite shown on a quiet and deserted road. + + + +typed out by nepenthe +February 1, 2000 \ No newline at end of file diff --git a/UpgradeLog.XML b/UpgradeLog.XML new file mode 100644 index 0000000..d9eb96a --- /dev/null +++ b/UpgradeLog.XML @@ -0,0 +1,58 @@ +<?xml version="1.0" encoding="UTF-8"?><?xml-stylesheet type='text/xsl' href='_UpgradeReport_Files/UpgradeReport.xslt'?><UpgradeLog> +<Properties><Property Name="Solution" Value="m3uTool"> +</Property><Property Name="Solution File" Value="C:\Users\poprhythm\Documents\code\m3uTool\m3uTool.sln"> +</Property><Property Name="Date" Value="Monday, February 14, 2011"> +</Property><Property Name="Time" Value="8:08 AM"> +</Property></Properties><Event ErrorLevel="0" Project="" Source="m3uTool.sln" Description="File successfully backed up as C:\Users\poprhythm\Documents\code\m3uTool\Backup\m3uTool.sln"> +</Event><Event ErrorLevel="0" Project="m3uTool" Source="m3uTool.csproj" Description="Project file successfully backed up as C:\Users\poprhythm\Documents\code\m3uTool\Backup\m3uTool.csproj"> +</Event><Event ErrorLevel="0" Project="m3uTool" Source="DoNothingProgressCallback.cs" Description="File successfully backed up as C:\Users\poprhythm\Documents\code\m3uTool\Backup\DoNothingProgressCallback.cs"> +</Event><Event ErrorLevel="0" Project="m3uTool" Source="Redirect.cs" Description="File successfully backed up as C:\Users\poprhythm\Documents\code\m3uTool\Backup\Redirect.cs"> +</Event><Event ErrorLevel="0" Project="m3uTool" Source="Tests\HiPerfTimer.cs" Description="File successfully backed up as C:\Users\poprhythm\Documents\code\m3uTool\Backup\Tests\HiPerfTimer.cs"> +</Event><Event ErrorLevel="0" Project="m3uTool" Source="KeyedList.cs" Description="File successfully backed up as C:\Users\poprhythm\Documents\code\m3uTool\Backup\KeyedList.cs"> +</Event><Event ErrorLevel="0" Project="m3uTool" Source="LongestCommonSequence.cs" Description="File successfully backed up as C:\Users\poprhythm\Documents\code\m3uTool\Backup\LongestCommonSequence.cs"> +</Event><Event ErrorLevel="0" Project="m3uTool" Source="ProcessArguments.cs" Description="File successfully backed up as C:\Users\poprhythm\Documents\code\m3uTool\Backup\ProcessArguments.cs"> +</Event><Event ErrorLevel="0" Project="m3uTool" Source="IProgressCallback.cs" Description="File successfully backed up as C:\Users\poprhythm\Documents\code\m3uTool\Backup\IProgressCallback.cs"> +</Event><Event ErrorLevel="0" Project="m3uTool" Source="Mp3Encoder.cs" Description="File successfully backed up as C:\Users\poprhythm\Documents\code\m3uTool\Backup\Mp3Encoder.cs"> +</Event><Event ErrorLevel="0" Project="m3uTool" Source="Mp3EncodingOptions.cs" Description="File successfully backed up as C:\Users\poprhythm\Documents\code\m3uTool\Backup\Mp3EncodingOptions.cs"> +</Event><Event ErrorLevel="0" Project="m3uTool" Source="Mp3FileProperties.cs" Description="File successfully backed up as C:\Users\poprhythm\Documents\code\m3uTool\Backup\Mp3FileProperties.cs"> +</Event><Event ErrorLevel="0" Project="m3uTool" Source="ProcessStreamWrapper.cs" Description="File successfully backed up as C:\Users\poprhythm\Documents\code\m3uTool\Backup\ProcessStreamWrapper.cs"> +</Event><Event ErrorLevel="0" Project="m3uTool" Source="Mp4Encoder.cs" Description="File successfully backed up as C:\Users\poprhythm\Documents\code\m3uTool\Backup\Mp4Encoder.cs"> +</Event><Event ErrorLevel="0" Project="m3uTool" Source="Mp4EncodingOptions.cs" Description="File successfully backed up as C:\Users\poprhythm\Documents\code\m3uTool\Backup\Mp4EncodingOptions.cs"> +</Event><Event ErrorLevel="0" Project="m3uTool" Source="AssemblyInfo.cs" Description="File successfully backed up as C:\Users\poprhythm\Documents\code\m3uTool\Backup\AssemblyInfo.cs"> +</Event><Event ErrorLevel="0" Project="m3uTool" Source="M3uCreate.cs" Description="File successfully backed up as C:\Users\poprhythm\Documents\code\m3uTool\Backup\M3uCreate.cs"> +</Event><Event ErrorLevel="0" Project="m3uTool" Source="M3uMakeByDirectory.cs" Description="File successfully backed up as C:\Users\poprhythm\Documents\code\m3uTool\Backup\M3uMakeByDirectory.cs"> +</Event><Event ErrorLevel="0" Project="m3uTool" Source="M3uToAac.cs" Description="File successfully backed up as C:\Users\poprhythm\Documents\code\m3uTool\Backup\M3uToAac.cs"> +</Event><Event ErrorLevel="0" Project="m3uTool" Source="M3uToAac.Designer.cs" Description="File successfully backed up as C:\Users\poprhythm\Documents\code\m3uTool\Backup\M3uToAac.Designer.cs"> +</Event><Event ErrorLevel="0" Project="m3uTool" Source="Mp3ToAacBatch.cs" Description="File successfully backed up as C:\Users\poprhythm\Documents\code\m3uTool\Backup\Mp3ToAacBatch.cs"> +</Event><Event ErrorLevel="0" Project="m3uTool" Source="Mp3ToAacBatch.Designer.cs" Description="File successfully backed up as C:\Users\poprhythm\Documents\code\m3uTool\Backup\Mp3ToAacBatch.Designer.cs"> +</Event><Event ErrorLevel="0" Project="m3uTool" Source="Properties\Resources.Designer.cs" Description="File successfully backed up as C:\Users\poprhythm\Documents\code\m3uTool\Backup\Properties\Resources.Designer.cs"> +</Event><Event ErrorLevel="0" Project="m3uTool" Source="Tests\CreateM3uTests.cs" Description="File successfully backed up as C:\Users\poprhythm\Documents\code\m3uTool\Backup\Tests\CreateM3uTests.cs"> +</Event><Event ErrorLevel="0" Project="m3uTool" Source="Tests\LongestCommonSequenceTests.cs" Description="File successfully backed up as C:\Users\poprhythm\Documents\code\m3uTool\Backup\Tests\LongestCommonSequenceTests.cs"> +</Event><Event ErrorLevel="0" Project="m3uTool" Source="Tests\Mp3EncoderTests.cs" Description="File successfully backed up as C:\Users\poprhythm\Documents\code\m3uTool\Backup\Tests\Mp3EncoderTests.cs"> +</Event><Event ErrorLevel="0" Project="m3uTool" Source="Tests\Mp3EncodingOptionsTests.cs" Description="File successfully backed up as C:\Users\poprhythm\Documents\code\m3uTool\Backup\Tests\Mp3EncodingOptionsTests.cs"> +</Event><Event ErrorLevel="0" Project="m3uTool" Source="Tests\Mp3FilePropertiesTests.cs" Description="File successfully backed up as C:\Users\poprhythm\Documents\code\m3uTool\Backup\Tests\Mp3FilePropertiesTests.cs"> +</Event><Event ErrorLevel="0" Project="m3uTool" Source="Tests\Mp4EncoderTests.cs" Description="File successfully backed up as C:\Users\poprhythm\Documents\code\m3uTool\Backup\Tests\Mp4EncoderTests.cs"> +</Event><Event ErrorLevel="0" Project="m3uTool" Source="Tests\Mp4EncodingOptionsTests.cs" Description="File successfully backed up as C:\Users\poprhythm\Documents\code\m3uTool\Backup\Tests\Mp4EncodingOptionsTests.cs"> +</Event><Event ErrorLevel="0" Project="m3uTool" Source="Tests\ProgressCallbackTestBase.cs" Description="File successfully backed up as C:\Users\poprhythm\Documents\code\m3uTool\Backup\Tests\ProgressCallbackTestBase.cs"> +</Event><Event ErrorLevel="0" Project="m3uTool" Source="Tests\ProcessStreamWrapperTests.cs" Description="File successfully backed up as C:\Users\poprhythm\Documents\code\m3uTool\Backup\Tests\ProcessStreamWrapperTests.cs"> +</Event><Event ErrorLevel="0" Project="m3uTool" Source="Tests\TranscodeTest.cs" Description="File successfully backed up as C:\Users\poprhythm\Documents\code\m3uTool\Backup\Tests\TranscodeTest.cs"> +</Event><Event ErrorLevel="0" Project="m3uTool" Source="App.ico" Description="File successfully backed up as C:\Users\poprhythm\Documents\code\m3uTool\Backup\App.ico"> +</Event><Event ErrorLevel="0" Project="m3uTool" Source="M3uMakeByDirectory.resx" Description="File successfully backed up as C:\Users\poprhythm\Documents\code\m3uTool\Backup\M3uMakeByDirectory.resx"> +</Event><Event ErrorLevel="0" Project="m3uTool" Source="M3uToAac.resx" Description="File successfully backed up as C:\Users\poprhythm\Documents\code\m3uTool\Backup\M3uToAac.resx"> +</Event><Event ErrorLevel="0" Project="m3uTool" Source="Mp3ToAacBatch.resx" Description="File successfully backed up as C:\Users\poprhythm\Documents\code\m3uTool\Backup\Mp3ToAacBatch.resx"> +</Event><Event ErrorLevel="0" Project="m3uTool" Source="Properties\Resources.resx" Description="File successfully backed up as C:\Users\poprhythm\Documents\code\m3uTool\Backup\Properties\Resources.resx"> +</Event><Event ErrorLevel="1" Project="m3uTool" Source="m3uTool.csproj" Description="Your project is targeting .NET Framework 2.0 or 3.0. If your project uses assemblies requiring a newer .NET Framework, your project will fail to build. You can change the .NET Framework version by clicking Properties on the project menu and then selecting a new version in the '.NET Framework' dropdown box. (In Visual Basic, this is located on the Compile tab by clicking the 'Advanced Compiler Options...' button.)"> +</Event><Event ErrorLevel="0" Project="m3uTool" Source="m3uTool.csproj" Description="Project converted successfully"> +</Event><Event ErrorLevel="3" Project="m3uTool" Source="m3uTool.csproj" Description="Converted"> +</Event><Event ErrorLevel="1" Project="StdinToStdout" Source="..\StdinToStdout\StdinToStdout.csproj" Description="Warning: The project file is being backed up to a relative path that differs from the original solution relative path. The difference in folder hierarchy may create problems in opening or building the backed up solution and project."> +</Event><Event ErrorLevel="0" Project="StdinToStdout" Source="..\StdinToStdout\StdinToStdout.csproj" Description="Project file successfully backed up as C:\Users\poprhythm\Documents\code\m3uTool\Backup\StdinToStdout\StdinToStdout.csproj"> +</Event><Event ErrorLevel="0" Project="StdinToStdout" Source="..\StdinToStdout\Program.cs" Description="File successfully backed up as C:\Users\poprhythm\Documents\code\m3uTool\Backup\StdinToStdout\Program.cs"> +</Event><Event ErrorLevel="0" Project="StdinToStdout" Source="..\StdinToStdout\Properties\AssemblyInfo.cs" Description="File successfully backed up as C:\Users\poprhythm\Documents\code\m3uTool\Backup\StdinToStdout\Properties\AssemblyInfo.cs"> +</Event><Event ErrorLevel="1" Project="StdinToStdout" Source="..\StdinToStdout\StdinToStdout.csproj" Description="Your project is targeting .NET Framework 2.0 or 3.0. If your project uses assemblies requiring a newer .NET Framework, your project will fail to build. You can change the .NET Framework version by clicking Properties on the project menu and then selecting a new version in the '.NET Framework' dropdown box. (In Visual Basic, this is located on the Compile tab by clicking the 'Advanced Compiler Options...' button.)"> +</Event><Event ErrorLevel="0" Project="StdinToStdout" Source="..\StdinToStdout\StdinToStdout.csproj" Description="Project converted successfully"> +</Event><Event ErrorLevel="3" Project="StdinToStdout" Source="..\StdinToStdout\StdinToStdout.csproj" Description="Converted"> +</Event><Event ErrorLevel="0" Project="" Source="m3uTool.sln" Description="Solution converted successfully"> +</Event><Event ErrorLevel="3" Project="" Source="m3uTool.sln" Description="Converted"> +</Event><Event ErrorLevel="0" Project="m3uTool" Source="m3uTool.csproj" Description="Scan complete: Upgrade not required for project files."> +</Event><Event ErrorLevel="0" Project="StdinToStdout" Source="..\StdinToStdout\StdinToStdout.csproj" Description="Scan complete: Upgrade not required for project files."> +</Event></UpgradeLog> \ No newline at end of file diff --git a/id3.cs b/id3.cs new file mode 100644 index 0000000..1ceb86d --- /dev/null +++ b/id3.cs @@ -0,0 +1,185 @@ +namespace id3 { + + using System; + using System.IO; + using System.Windows.Forms; + using System.Drawing; + + public class ID3TagEditor : System.Windows.Forms.Form { + // Menu Components + private MainMenu mForm; + private MenuItem mtFile; + private MenuItem miLoad; + private MenuItem miSave; + private MenuItem miExit; + private MenuItem mtHelp; + private MenuItem miAbout; + private MenuItem miSeparator; + + // Label Components + private Label lblTitle; + private Label lblArtist; + private Label lblAlbum; + private Label lblYear; + private Label lblComment; + + // Text Box Components + private TextBox tbTitle; + private TextBox tbArtist; + private TextBox tbAlbum; + private TextBox tbYear; + private TextBox tbComment; + + // MP3 Struct + MP3 workingMP3; + + public ID3TagEditor() { + // Create the components + CreateComponents(); + // Setup the Form + this.Text = "MP3 Tag Editor (C# Example)"; + this.MinimizeBox = true; + this.MaximizeBox = false; + this.Menu = mForm; + this.Size = new Size(340,180); + } + + + // method to create a Label + private Label createLabel(string pText, int pRow) { + Label rLabel = new Label(); + rLabel.Text = pText; + rLabel.Location = new Point(5,8 + (pRow * 25)); + rLabel.Size = new System.Drawing.Size (70, 20); + rLabel.Font = new System.Drawing.Font ("Arial", 10, System.Drawing.FontStyle.Bold); + rLabel.BackColor = System.Drawing.SystemColors.Control; + rLabel.TextAlign = ContentAlignment.MiddleRight; + + return (rLabel); + } + + // method to create a Text Box + private TextBox createTextBox(string pText, int pRow, int pLength) { + TextBox rTextBox = new TextBox (); + rTextBox.Location = new Point(75,5 + (pRow * 25)); + rTextBox.ReadOnly = false; + rTextBox.Text = pText; + rTextBox.MaxLength = pLength; + rTextBox.Font = new System.Drawing.Font ("Arial", 10, System.Drawing.FontStyle.Bold); + rTextBox.Size = new System.Drawing.Size (250, 18); + rTextBox.BackColor = System.Drawing.SystemColors.Window; + rTextBox.TextAlign = System.Windows.Forms.HorizontalAlignment.Left; + return (rTextBox); + } + + public void CreateComponents() + { + // Create Labels + lblTitle = createLabel("Title", 0); + lblArtist = createLabel("Artist", 1); + lblAlbum = createLabel("Album", 2); + lblYear = createLabel("Year", 3); + lblComment = createLabel("Comment", 4); + + // Create TextBoxes + tbTitle = createTextBox ("", 0, 30); + tbArtist = createTextBox ("", 1, 30); + tbAlbum = createTextBox ("", 2, 30); + tbYear = createTextBox ("", 3, 4); + tbComment = createTextBox ("", 4, 28); + + // Add Labels + this.Controls.Add(lblTitle); + this.Controls.Add(lblArtist); + this.Controls.Add(lblAlbum); + this.Controls.Add(lblYear); + this.Controls.Add(lblComment); + + // Add Text Boxes + this.Controls.Add(tbTitle); + this.Controls.Add(tbArtist); + this.Controls.Add(tbAlbum); + this.Controls.Add(tbYear); + this.Controls.Add(tbComment); + + //Instantiating Main Menu + mForm = new MainMenu(); + + // Add top level menu items + mtFile = new MenuItem("&File"); + mtHelp = new MenuItem("&Help"); + miSeparator = new MenuItem("-"); + mForm.MenuItems.Add(mtFile); + mForm.MenuItems.Add(mtHelp); + + // Add the Load MP3 menu item + miLoad = new MenuItem("&Load MP3", new EventHandler(eventLoadMP3), Shortcut.CtrlO); + // Add the Save MP3 menu item + miSave = new MenuItem("&Save MP3", new EventHandler(eventSaveMP3), Shortcut.CtrlS); + // Add the exit menu + miExit = new MenuItem("&Exit", new EventHandler(eventCloseForm)); + // Add the about menu + miAbout = new MenuItem("&About", new EventHandler(eventAboutBox)); + + mtFile.MenuItems.Add(miLoad); + mtFile.MenuItems.Add(miSave); + mtFile.MenuItems.Add(miSeparator); + mtFile.MenuItems.Add(miExit); + mtHelp.MenuItems.Add(miAbout); + + } + + + // Event for Loading an MP3 + protected void eventLoadMP3(object pSender, EventArgs pArgs) { + OpenFileDialog fileDialog = new OpenFileDialog(); + fileDialog.Filter = "MP3 files files (*.mp3)|*.mp3"; + fileDialog.ShowDialog(); + + string fileName = fileDialog.FileName; + + // If a file was selected get its ID3 Tag + if (fileName.Length > 0) { + FileInfo fFileInfo = new FileInfo(fileName); // Creating this FileInfo so I don't have to change my generic class + workingMP3 = new MP3(fFileInfo.DirectoryName, fFileInfo.Name); //fFile.DirectoryName, fFile.Name); + FileCommands.readMP3Tag (ref workingMP3); + + tbTitle.Text = workingMP3.id3Title; + tbArtist.Text = workingMP3.id3Artist; + tbAlbum.Text = workingMP3.id3Album; + tbYear.Text = workingMP3.id3Year; + tbComment.Text = workingMP3.id3Comment; + } + } + + // Event for Saving an MP3 + protected void eventSaveMP3(object pSender, EventArgs pArgs) { + if (workingMP3.id3Title == null) return; + workingMP3.id3Title = tbTitle.Text; + workingMP3.id3Artist = tbArtist.Text; + workingMP3.id3Album = tbAlbum.Text; + workingMP3.id3Year = tbYear.Text; + workingMP3.id3Comment = tbComment.Text; + + FileCommands.updateMP3Tag (ref workingMP3); + + } + + // Event for About Box + protected void eventAboutBox(object pSender, EventArgs pArgs) { + MessageBox.Show("By Paul Lockwood (paul_lockwood@yahoo.com).", "C# Example Code", + MessageBoxButtons.OK, MessageBoxIcon.Information); + } + + // Event for Closing Form + protected void eventCloseForm(object pSender, EventArgs pArgs) { + Application.Exit(); + } + + // The Main method + public static void Main() { + Application.Run(new ID3TagEditor()); + } + } +} + diff --git a/id3Lib.cs b/id3Lib.cs new file mode 100644 index 0000000..f3f8f2e --- /dev/null +++ b/id3Lib.cs @@ -0,0 +1,135 @@ +namespace id3 { + + using System; + using System.IO; + using System.Text; + + class FileCommands { + public static void readMP3Tag (ref MP3 paramMP3) { + // Read the 128 byte ID3 tag into a byte array + FileStream oFileStream; + oFileStream = new FileStream(paramMP3.fileComplete , FileMode.Open); + byte[] bBuffer = new byte[128]; + oFileStream.Seek(-128, SeekOrigin.End); + oFileStream.Read(bBuffer,0, 128); + oFileStream.Close(); + + // Convert the Byte Array to a String + Encoding instEncoding = new ASCIIEncoding(); // NB: Encoding is an Abstract class + string id3Tag = instEncoding.GetString(bBuffer); + + // If there is an attched ID3 v1.x TAG then read it + if (id3Tag .Substring(0,3) == "TAG") { + paramMP3.id3Title = id3Tag.Substring( 3, 30).Trim(); + paramMP3.id3Artist = id3Tag.Substring( 33, 30).Trim(); + paramMP3.id3Album = id3Tag.Substring( 63, 30).Trim(); + paramMP3.id3Year = id3Tag.Substring( 93, 4).Trim(); + paramMP3.id3Comment = id3Tag.Substring( 97,28).Trim(); + + // Get the track number if TAG conforms to ID3 v1.1 + if (id3Tag[125]==0) + paramMP3.id3TrackNumber = bBuffer[126]; + else + paramMP3.id3TrackNumber = 0; + paramMP3.id3Genre = bBuffer[127]; + paramMP3.hasID3Tag = true; + // ********* IF USED IN ANGER: ENSURE to test for non-numeric year + } + else { + // ID3 Tag not found so create an empty TAG in case the user saces later + paramMP3.id3Title = ""; + paramMP3.id3Artist = ""; + paramMP3.id3Album = ""; + paramMP3.id3Year = ""; + paramMP3.id3Comment = ""; + paramMP3.id3TrackNumber = 0; + paramMP3.id3Genre = 0; + paramMP3.hasID3Tag = false; + } + } + + public static void updateMP3Tag (ref MP3 paramMP3) { + // Trim any whitespace + paramMP3.id3Title = paramMP3.id3Title.Trim(); + paramMP3.id3Artist = paramMP3.id3Artist.Trim(); + paramMP3.id3Album = paramMP3.id3Album.Trim(); + paramMP3.id3Year = paramMP3.id3Year.Trim(); + paramMP3.id3Comment = paramMP3.id3Comment.Trim(); + + // Ensure all properties are correct size + if (paramMP3.id3Title.Length > 30) paramMP3.id3Title = paramMP3.id3Title.Substring(0,30); + if (paramMP3.id3Artist.Length > 30) paramMP3.id3Artist = paramMP3.id3Artist.Substring(0,30); + if (paramMP3.id3Album.Length > 30) paramMP3.id3Album = paramMP3.id3Album.Substring(0,30); + if (paramMP3.id3Year.Length > 4) paramMP3.id3Year = paramMP3.id3Year.Substring(0,4); + if (paramMP3.id3Comment.Length > 28) paramMP3.id3Comment = paramMP3.id3Comment.Substring(0,28); + + // Build a new ID3 Tag (128 Bytes) + byte[] tagByteArray = new byte[128]; + for ( int i = 0; i < tagByteArray.Length; i++ ) tagByteArray[i] = 0; // Initialise array to nulls + + // Convert the Byte Array to a String + Encoding instEncoding = new ASCIIEncoding(); // NB: Encoding is an Abstract class // ************ To DO: Make a shared instance of ASCIIEncoding so we don't keep creating/destroying it + // Copy "TAG" to Array + byte[] workingByteArray = instEncoding.GetBytes("TAG"); + Array.Copy(workingByteArray, 0, tagByteArray, 0, workingByteArray.Length); + // Copy Title to Array + workingByteArray = instEncoding.GetBytes(paramMP3.id3Title); + Array.Copy(workingByteArray, 0, tagByteArray, 3, workingByteArray.Length); + // Copy Artist to Array + workingByteArray = instEncoding.GetBytes(paramMP3.id3Artist); + Array.Copy(workingByteArray, 0, tagByteArray, 33, workingByteArray.Length); + // Copy Album to Array + workingByteArray = instEncoding.GetBytes(paramMP3.id3Album); + Array.Copy(workingByteArray, 0, tagByteArray, 63, workingByteArray.Length); + // Copy Year to Array + workingByteArray = instEncoding.GetBytes(paramMP3.id3Year); + Array.Copy(workingByteArray, 0, tagByteArray, 93, workingByteArray.Length); + // Copy Comment to Array + workingByteArray = instEncoding.GetBytes(paramMP3.id3Comment); + Array.Copy(workingByteArray, 0, tagByteArray, 97, workingByteArray.Length); + // Copy Track and Genre to Array + tagByteArray[126] = paramMP3.id3TrackNumber; + tagByteArray[127] = paramMP3.id3Genre; + + // SAVE TO DISK: Replace the final 128 Bytes with our new ID3 tag + FileStream oFileStream = new FileStream(paramMP3.fileComplete , FileMode.Open); + if (paramMP3.hasID3Tag) + oFileStream.Seek(-128, SeekOrigin.End); + else + oFileStream.Seek(0, SeekOrigin.End); + oFileStream.Write(tagByteArray,0, 128); + oFileStream.Close(); + paramMP3.hasID3Tag = true; + } + + } + + struct MP3 { + public string filePath; + public string fileFileName; + public string fileComplete; + public bool hasID3Tag; + public string id3Title; + public string id3Artist; + public string id3Album; + public string id3Year; + public string id3Comment; + public byte id3TrackNumber; + public byte id3Genre; + + // Required struct constructor + public MP3(string path, string name) { + this.filePath = path; + this.fileFileName = name; + this.fileComplete = path + "\\" + name; + this.hasID3Tag = false; + this.id3Title = null; + this.id3Artist = null; + this.id3Album = null; + this.id3Year = null; + this.id3Comment = null; + this.id3TrackNumber = 0; + this.id3Genre = 0; + } + } +} \ No newline at end of file diff --git a/m3uTool.csproj b/m3uTool.csproj new file mode 100644 index 0000000..185e53a --- /dev/null +++ b/m3uTool.csproj @@ -0,0 +1,219 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="4.0"> + <PropertyGroup> + <ProjectType>Local</ProjectType> + <ProductVersion>8.0.50727</ProductVersion> + <SchemaVersion>2.0</SchemaVersion> + <ProjectGuid>{73C5A642-71EF-471C-A82D-70899904C495}</ProjectGuid> + <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> + <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> + <ApplicationIcon>App.ico</ApplicationIcon> + <AssemblyKeyContainerName> + </AssemblyKeyContainerName> + <AssemblyName>m3uTool</AssemblyName> + <AssemblyOriginatorKeyFile> + </AssemblyOriginatorKeyFile> + <DefaultClientScript>JScript</DefaultClientScript> + <DefaultHTMLPageLayout>Grid</DefaultHTMLPageLayout> + <DefaultTargetSchema>IE50</DefaultTargetSchema> + <DelaySign>false</DelaySign> + <OutputType>WinExe</OutputType> + <RootNamespace>m3uTool</RootNamespace> + <RunPostBuildEvent>OnBuildSuccess</RunPostBuildEvent> + <StartupObject> + </StartupObject> + <FileUpgradeFlags> + </FileUpgradeFlags> + <UpgradeBackupLocation> + </UpgradeBackupLocation> + <TargetFrameworkVersion>v2.0</TargetFrameworkVersion> + <OldToolsVersion>2.0</OldToolsVersion> + <PublishUrl>publish\</PublishUrl> + <Install>true</Install> + <InstallFrom>Disk</InstallFrom> + <UpdateEnabled>false</UpdateEnabled> + <UpdateMode>Foreground</UpdateMode> + <UpdateInterval>7</UpdateInterval> + <UpdateIntervalUnits>Days</UpdateIntervalUnits> + <UpdatePeriodically>false</UpdatePeriodically> + <UpdateRequired>false</UpdateRequired> + <MapFileExtensions>true</MapFileExtensions> + <ApplicationRevision>0</ApplicationRevision> + <ApplicationVersion>1.0.0.%2a</ApplicationVersion> + <IsWebBootstrapper>false</IsWebBootstrapper> + <UseApplicationTrust>false</UseApplicationTrust> + <BootstrapperEnabled>true</BootstrapperEnabled> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> + <OutputPath>bin\Debug\</OutputPath> + <AllowUnsafeBlocks>false</AllowUnsafeBlocks> + <BaseAddress>285212672</BaseAddress> + <CheckForOverflowUnderflow>false</CheckForOverflowUnderflow> + <ConfigurationOverrideFile> + </ConfigurationOverrideFile> + <DefineConstants>DEBUG;TRACE</DefineConstants> + <DocumentationFile> + </DocumentationFile> + <DebugSymbols>true</DebugSymbols> + <FileAlignment>4096</FileAlignment> + <NoStdLib>false</NoStdLib> + <NoWarn> + </NoWarn> + <Optimize>false</Optimize> + <RegisterForComInterop>false</RegisterForComInterop> + <RemoveIntegerChecks>false</RemoveIntegerChecks> + <TreatWarningsAsErrors>false</TreatWarningsAsErrors> + <WarningLevel>4</WarningLevel> + <DebugType>full</DebugType> + <ErrorReport>prompt</ErrorReport> + <UseVSHostingProcess>true</UseVSHostingProcess> + <CodeAnalysisRuleSet>AllRules.ruleset</CodeAnalysisRuleSet> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> + <OutputPath>bin\Release\</OutputPath> + <AllowUnsafeBlocks>false</AllowUnsafeBlocks> + <BaseAddress>285212672</BaseAddress> + <CheckForOverflowUnderflow>false</CheckForOverflowUnderflow> + <ConfigurationOverrideFile> + </ConfigurationOverrideFile> + <DefineConstants>TRACE</DefineConstants> + <DocumentationFile> + </DocumentationFile> + <DebugSymbols>false</DebugSymbols> + <FileAlignment>4096</FileAlignment> + <NoStdLib>false</NoStdLib> + <NoWarn> + </NoWarn> + <Optimize>true</Optimize> + <RegisterForComInterop>false</RegisterForComInterop> + <RemoveIntegerChecks>false</RemoveIntegerChecks> + <TreatWarningsAsErrors>false</TreatWarningsAsErrors> + <WarningLevel>4</WarningLevel> + <DebugType>none</DebugType> + <ErrorReport>prompt</ErrorReport> + <CodeAnalysisRuleSet>AllRules.ruleset</CodeAnalysisRuleSet> + </PropertyGroup> + <ItemGroup> + <Reference Include="nunit.framework"> + <HintPath>References\nunit.framework.dll</HintPath> + </Reference> + <Reference Include="PipeStream"> + <HintPath>References\PipeStream.dll</HintPath> + </Reference> + <Reference Include="System"> + <Name>System</Name> + </Reference> + <Reference Include="System.Data"> + <Name>System.Data</Name> + </Reference> + <Reference Include="System.Drawing"> + <Name>System.Drawing</Name> + </Reference> + <Reference Include="System.Windows.Forms"> + <Name>System.Windows.Forms</Name> + </Reference> + <Reference Include="System.Xml"> + <Name>System.XML</Name> + </Reference> + </ItemGroup> + <ItemGroup> + <Content Include="App.ico" /> + <Compile Include="DoNothingProgressCallback.cs" /> + <Compile Include="Redirect.cs" /> + <Compile Include="Tests\HiPerfTimer.cs" /> + <Compile Include="KeyedList.cs" /> + <Compile Include="LongestCommonSequence.cs" /> + <Compile Include="ProcessArguments.cs" /> + <Compile Include="IProgressCallback.cs" /> + <Compile Include="Mp3Encoder.cs" /> + <Compile Include="Mp3EncodingOptions.cs" /> + <Compile Include="Mp3FileProperties.cs" /> + <Compile Include="ProcessStreamWrapper.cs" /> + <Compile Include="Mp4Encoder.cs" /> + <Compile Include="Mp4EncodingOptions.cs" /> + <Compile Include="AssemblyInfo.cs"> + <SubType>Code</SubType> + </Compile> + <Compile Include="M3uCreate.cs"> + <SubType>Code</SubType> + </Compile> + <Compile Include="M3uMakeByDirectory.cs"> + <SubType>Form</SubType> + </Compile> + <Compile Include="M3uToAac.cs"> + <SubType>Form</SubType> + </Compile> + <Compile Include="M3uToAac.Designer.cs"> + <DependentUpon>M3uToAac.cs</DependentUpon> + </Compile> + <Compile Include="Mp3ToAacBatch.cs"> + <SubType>Form</SubType> + </Compile> + <Compile Include="Mp3ToAacBatch.Designer.cs"> + <DependentUpon>Mp3ToAacBatch.cs</DependentUpon> + </Compile> + <Compile Include="Properties\Resources.Designer.cs"> + <AutoGen>True</AutoGen> + <DesignTime>True</DesignTime> + <DependentUpon>Resources.resx</DependentUpon> + </Compile> + <Compile Include="Tests\CreateM3uTests.cs"> + <SubType>Code</SubType> + </Compile> + <Compile Include="Tests\LongestCommonSequenceTests.cs" /> + <Compile Include="Tests\Mp3EncoderTests.cs" /> + <Compile Include="Tests\Mp3EncodingOptionsTests.cs" /> + <Compile Include="Tests\Mp3FilePropertiesTests.cs" /> + <Compile Include="Tests\Mp4EncoderTests.cs" /> + <Compile Include="Tests\Mp4EncodingOptionsTests.cs" /> + <Compile Include="Tests\ProgressCallbackTestBase.cs" /> + <Compile Include="Tests\ProcessStreamWrapperTests.cs" /> + <Compile Include="Tests\TranscodeTest.cs" /> + <EmbeddedResource Include="M3uMakeByDirectory.resx"> + <DependentUpon>M3uMakeByDirectory.cs</DependentUpon> + <SubType>Designer</SubType> + </EmbeddedResource> + <EmbeddedResource Include="M3uToAac.resx"> + <SubType>Designer</SubType> + <DependentUpon>M3uToAac.cs</DependentUpon> + </EmbeddedResource> + <EmbeddedResource Include="Mp3ToAacBatch.resx"> + <SubType>Designer</SubType> + <DependentUpon>Mp3ToAacBatch.cs</DependentUpon> + </EmbeddedResource> + <EmbeddedResource Include="Properties\Resources.resx"> + <SubType>Designer</SubType> + <Generator>ResXFileCodeGenerator</Generator> + <LastGenOutput>Resources.Designer.cs</LastGenOutput> + </EmbeddedResource> + </ItemGroup> + <ItemGroup> + <Folder Include="Resources\" /> + </ItemGroup> + <ItemGroup> + <BootstrapperPackage Include="Microsoft.Net.Client.3.5"> + <Visible>False</Visible> + <ProductName>.NET Framework 3.5 SP1 Client Profile</ProductName> + <Install>false</Install> + </BootstrapperPackage> + <BootstrapperPackage Include="Microsoft.Net.Framework.3.5.SP1"> + <Visible>False</Visible> + <ProductName>.NET Framework 3.5 SP1</ProductName> + <Install>true</Install> + </BootstrapperPackage> + <BootstrapperPackage Include="Microsoft.Windows.Installer.3.1"> + <Visible>False</Visible> + <ProductName>Windows Installer 3.1</ProductName> + <Install>true</Install> + </BootstrapperPackage> + </ItemGroup> + <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" /> + <PropertyGroup> + <PreBuildEvent>copy /D /Y "$(ProjectDir)Tests\"*.mp3 "$(TargetDir)" +copy /D /Y "$(ProjectDir)Tests\"*.wav "$(TargetDir)" +copy /D /Y "$(ProjectDir)Tests\"*.exe "$(TargetDir)" +copy /D /Y "$(ProjectDir)Tests\"*.txt "$(TargetDir)"</PreBuildEvent> + <PostBuildEvent> + </PostBuildEvent> + </PropertyGroup> +</Project> \ No newline at end of file diff --git a/m3uTool.sln b/m3uTool.sln new file mode 100644 index 0000000..331e790 --- /dev/null +++ b/m3uTool.sln @@ -0,0 +1,25 @@ +Microsoft Visual Studio Solution File, Format Version 11.00 +# Visual Studio 2010 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "m3uTool", "m3uTool.csproj", "{73C5A642-71EF-471C-A82D-70899904C495}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StdinToStdout", "..\StdinToStdout\StdinToStdout.csproj", "{94C7033C-C8A8-4104-95FE-0D11122080E2}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {73C5A642-71EF-471C-A82D-70899904C495}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {73C5A642-71EF-471C-A82D-70899904C495}.Debug|Any CPU.Build.0 = Debug|Any CPU + {73C5A642-71EF-471C-A82D-70899904C495}.Release|Any CPU.ActiveCfg = Release|Any CPU + {73C5A642-71EF-471C-A82D-70899904C495}.Release|Any CPU.Build.0 = Release|Any CPU + {94C7033C-C8A8-4104-95FE-0D11122080E2}.Debug|Any CPU.ActiveCfg = Release|Any CPU + {94C7033C-C8A8-4104-95FE-0D11122080E2}.Debug|Any CPU.Build.0 = Release|Any CPU + {94C7033C-C8A8-4104-95FE-0D11122080E2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {94C7033C-C8A8-4104-95FE-0D11122080E2}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal