1212 lines
33 KiB
C#
1212 lines
33 KiB
C#
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]];
|
|
}
|
|
}
|
|
}
|