How to get the length of a MP3 in C# - c#

Yes this is an exact duplicate of this question, but the link given and accepted as answer is not working for me. It is returning incorrect values (a 2 minutes mp3 will be listed as 1'30, 3 minutes as 2'20) with no obvious pattern.
So here it is again: how can I get the length of a MP3 using C# ?
or
What am I doing wrong with the MP3Header class:
MP3Header mp3hdr = new MP3Header();
bool boolIsMP3 = mp3hdr.ReadMP3Information("1.mp3");
if(boolIsMP3)
Response.Write(mp3hdr.intLength);

Apparently this class computes the duration using fileSize / bitRate. This can only work for constant bitrate, and I assume your MP3 has variable bitRate...
EDIT : have a look at TagLib Sharp, it can give you the duration

How have you ascertained the lengths of the MP3s which are "wrong"? I've often found that the header information can be wrong: there was a particular version of LAME which had this problem, for example.
If you bring the file's properties up in Windows Explorer, what does that show?

I wrapped mp3 decoder library and made it available for .net developers. You can find it here:
http://sourceforge.net/projects/mpg123net/
Included are the samples to convert mp3 file to PCM, and read ID3 tags.
I guess that you can use it to read mp3 file duration. Worst case will be that you read all the frames and compute the duration - VBR file.
To accurately determine mp3 duration, you HAVE TO read all the frames and calculate duration from their summed duration. There are lots of cases when people put various 'metadata' inside mp3 files, so if you estimate from bitrate and file size, you'll guess wrong.

I would consider using an external application to do this. Consider trying Sox and just run the version of the program that's executed by using soxi (no exe) and try parsing that output. Given your options I think you're better off just trusting someone else who has spent the time to work out all the weirdness in mp3 files unless this functionality is core to what you're doing. Good luck!

The second post in the thread might help you: http://social.msdn.microsoft.com/Forums/en-US/csharpgeneral/thread/c72033c2-c392-4e0e-9993-1f8991acb2fd

Length of the VBR file CAN'T be estimated at all. Every mp3 frame inside of it could have different bitrate, so from reading any part of the file you can't know what density of the data is at any other part of the file. Only way of determining EXACT length of VBR mp3 is to DECODE it in whole, OR (if you know how) read all the headers of the frames one by one, and collect their decoded DURATION.
You will use later method only if the CPU that you use is a precious resource that you need to save. Otherwise, decode the whole file and you'll have the duration.
You can use my port of mpg123 to do the job: http://sourceforge.net/projects/mpg123net/
More: many mp3 files have "stuff" added to it, as a id3 tags, and if you don't go through all the file you could mistakenly use that tag in duration calculation.

There is my solution for C# with sox sound processing library.
public static double GetAudioDuration(string soxPath, string audioPath)
{
double duration = 0;
var startInfo = new ProcessStartInfo(soxPath,
string.Format("\"{0}\" -n stat", audioPath));
startInfo.UseShellExecute = false;
startInfo.CreateNoWindow = true;
startInfo.RedirectStandardError = true;
startInfo.RedirectStandardOutput = true;
var process = Process.Start(startInfo);
process.WaitForExit();
string str;
using (var outputThread = process.StandardError)
str = outputThread.ReadToEnd();
if (string.IsNullOrEmpty(str))
using (var outputThread = process.StandardOutput)
str = outputThread.ReadToEnd();
try
{
string[] lines = str.Split(new string[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries);
string lengthLine = lines.First(line => line.Contains("Length (seconds)"));
duration = double.Parse(lengthLine.Split(':')[1]);
}
catch (Exception ex)
{
}
return duration;
}

Related

SharpCompress & LZMA2 7z archive - very slow extraction of specific file. Why? Alternatives?

I have a 7zip archive craeted with LZMA2 compression (compression level: ultra).
The archive contains 1,749 files, which in total originally had a size of 661mb.
The zipped file is 39mb in size.
Now I'm trying to use C# to extract a tiny (~200kb'ish) single file from this archive.
I'm getting the corresponding IArchiveEntry from the IArchive (which works relatively fast),
but then calling IArchiveEntry.WriteToFile(targetPath) takes around 33 seconds! And similarly long if I write to a memory stream instead. (edit: When I'm running this on a 7z LZMA2 archive with compression level = normal, it still takes 9 seconds)
When I'm opening the same archive in the actual 7zip application and extract the same file from there, it takes around 2-3 seconds only.
I suspected it's some sort of multicore (7zip) vs single core (SharpCompress probably?) thing, but I don't notice any CPU usage spike during decompression with 7zip.. maybe its too fast to be noticeable though..
Does anyone know what could be the issue for such slow speeds with SharpCompress? Am I maybe missing some setting or using a wrong factory (ArchiveFactory) ?
If not - is there any C# library out there that might be significantly faster at decompressing this?
For reference, here's a sketch of how I'm using SharpCompress to extract:
private void Extract()
{
using(var archive = GetArchive())
{
var entryPath = /* ... path to entry .. */
var entry = TryGetEntry(archive, entryPath);
entry.WriteToFile(some_target_path);
}
}
private IArchive GetArchive()
{
string path = /* .. path to my .7z file */;
return ArchiveFactory.Open(path);
}
private IArchiveEntry TryGetEntry(IArchive archive, string path)
{
path = path.Replace("\\", "/");
foreach (var entry in archive.Entries)
{
if (!entry.IsDirectory)
{
if (entry.Key == path)
return entry;
}
}
return null;
}
Update: For a temporary solution, I'm now including the 7zr.exe from the 7zip SDK in my application, and run this in a new process to extract a single file, reading the process' output into a binary stream.
This works in around ~3 seconds compared to the ~33seconds with SharpCompress. Works for now, but kind of ugly.. so still curious why SharpCompress seems to be so slow there
This line is the problem
foreach (var entry in archive.Entries)
The problem is described here (ie. If there are 100 files, it decompresses the 1st file 100 times, 2nd file 99 times, and so on)
You need to use reader (forward-only). See the API.
But the sample code there doesn't support 7z.
For 7z you can use archive.ExtractAllEntries(), eg.
var reader = archive.ExtractAllEntries();
while (reader.MoveToNextEntry())
{
if (!reader.Entry.IsDirectory)
reader.WriteEntryToDirectory(extractDir, new ExtractionOptions() { ExtractFullPath = false, Overwrite = true });
}
It will be much faster.
If you need all the files you could also do:
using var reader = archive.ExtractAllEntries();
reader.WriteAllToDirectory(targetPath, new ExtractionOptions() { ExtractFullPath = true, Overwrite = true });

What would cause files being added to a zip file to not be included in the zip?

I work with a program that takes large amounts of data, turns the data into xml files, then takes those xml files and zips them for use in another program. Occasionally, during the zipping process, one or two xml files gets left out. It is fairly rare, once or twice a month, but when it does happen it's a big mess. I am looking for help figuring out why the files don't get zipped and how to prevent it. This code is straightforward:
public string AddToZip(string outfile, string toCompress)
{
if (!File.Exists(toCompress)) throw new FileNotFoundException("Could not find the file to compress", toCompress);
string dir = Path.GetDirectoryName(outfile);
if(!Directory.Exists(dir))
{
Directory.CreateDirectory(dir);
}
// The program that gets this data can't handle files over
// 20 MB, so it splits it up into two or more files if it hits the
// limit.
if (File.Exists(outfile))
{
FileInfo tooBig = new FileInfo(outfile);
int converter = 1024;
float fileSize = tooBig.Length / converter; //bytes to KB
fileSize = fileSize / converter; //KB to MB
int limit = CommonTypes.Helpers.ConfigHelper.GetConfigEntryInt("zipLimit", "19");
if (fileSize >= limit)
{
outfile = MakeNewName(outfile);
}
}
using (ZipFile zf = new ZipFile(outfile))
{
zf.AddFile(toCompress,"");
zf.Save();
}
return outfile;
}
Ultimately, what I want to do is have a check that sees if any xml files weren't added to the zip after the zip file is created, but stopping the problem in its tracks are best overall. Thanks for the help.
Make sure you have that code inside a try... catch statement. Also make sure that if you have done that, you do something with the exception. It would not be the first case that has this type of exception handling:
try
{
//...
}
catch { }
Given the code above if you have any exception on your process, you will never notice.
It's hard to judge from this function alone, here's a list of things that can go wrong:
- The toCompress file can be gone by the time zf.AddFile is called (but after the Exists test). Test return value or add exception handling to detect this.
- The zip outFile can be just below the size limit, adding a new file can make it go over the limit.
- The AddToZip() may be called concurrently, that may cause adding to fail.
How is the toCompress file remove handled? I think adding locking to the AddoZip() on a function scope might also be a good idea.
This could be a timing issue. You are checking to see if outfile is too big before trying to add the toCompress file. What you should be doing is:
Add toCompress to outfile
Check to see if adding the file made outfile too big
If outfile is now too big, remove toCompress, create new outfile, add toCompress to new outfile.
I suspect that you occasionally have an outfile that is just under the limit, but adding toCompress puts it over. Then the receiving program does not process outfile because it is too big.
I could be completely off base, but it is something to check.

How to change .wav format audio file bitrate

In my application I have some .wav format audio files, here I check audio file bit rates by using Naudio dll, if bitrate is below 128kbps then I want to change it to above 128kpbs, so that I wrote below code for check bit rate, if it is less then 128kbps then it convert to above 128kbps.
int bitrate;
using (var reader = new WaveFileReader(textBox1.Text))
{
bitrate = reader.WaveFormat.AverageBytesPerSecond * 8;
reader.Dispose();
}
if (bitrate < 128000)
{
using (var reader = new WaveFileReader(textBox1.Text))
{
var newFormat = new WaveFormat(8000, 16, 1);
using (var conversionStream = new WaveFormatConversionStream(newFormat, reader))
{
WaveFileWriter.CreateWaveFile(#"C:\Docs\Files\", conversionStream);
}
}
}
For some files it is working fine, but for some files I am getting below error,
An unhandled exception of type 'NAudio.MmException' occurred in NAudio.dll
Additional information: AcmNotPossible calling acmStreamOpen
I am attaching error Snap here. Error Error Snap
Here, how can I slove this issue?
I suggest you take a look at FFmpeg. It is what I use for all audio/video conversion tasks.
It is a command-line tool that can convert from pretty much anything to anything, with lots of options. To do what you want, you will probably need to run something like:
$ ffmpeg -i input.wav -ab 128 output.wav
In the above line, we convert the file to a 128 bitrate.
Easiest way to use this in code is to include the FFmpeg executable in your project (or install globally as an environment variable) and invoke it directly with something like:
Process process = new Process();
process.StartInfo.RedirectStandardOutput = true;
process.StartInfo.RedirectStandardError = true;
process.StartInfo.FileName = "ffmpeg";
process.StartInfo.Arguments = $"-i \"{originalFile}\" -ab 128 \"{outputPath}\"";
process.StartInfo.UseShellExecute = false;
process.StartInfo.CreateNoWindow = false;
process.Start();
process.WaitForExit();
There are more elegant solutions - wrappers around FFmpeg - but this should do the trick.
WaveFormatConversionStream looks for an ACM codec that can perform the requested transformation. Not every transformation is possible. For example, you can't typically change sample rate, bit depth and channel count in one go. So the possible transformations depend on the exact WaveFormat of the incoming audio. You might need to downsample in one step, and go from stereo to mono in another. The MediaFoundationResampler is more flexible and might be able to do it in one step.

How to know the compression rate for 7-Zip?

I have the following code for creating split archives using 7zip.
Compression level: MX9
Split archive size: 1MB
static void Main(string[] args)
{
string zipFileName = #"D:\ZIP\zipfile.7z";
string temp = #"D:\ZIP\ZM.pdf";
ProcessStartInfo info = new ProcessStartInfo();
info.FileName = AppDomain.CurrentDomain.BaseDirectory + #"..\..\7za.exe";
/**
* Switch -mx0: Don't compress at all. This is called "copy mode."
* Switch -mx1: Low compression. This is called "fastest" mode.
* Switch -mx3: Fast compression mode. Will automatically set various parameters.
* Switch -mx5: Same as above, but "normal."
* Switch -mx7: This means "maximum" compression.
* Switch -mx9: This means "ultra" compression.You probably want to use this.
**/
info.Arguments = string.Format("a -t7z \"" + zipFileName + "\" \"" + temp + "\" -v{0}k " + CompressionLevel.mx9, 1024);
info.WindowStyle = ProcessWindowStyle.Hidden;
Process process = Process.Start(info);
process.WaitForExit();
Console.WriteLine("Done zipping");
Console.ReadLine();
}
Normally for a 10MB file I get nine .7z files with extensions .7z.001, .7z.002, .7z.003 and so on. So for a 1MB file, I get one .7z file with the extension .7z.001. What I want to achieve is to eliminate the .001 extension if only a single file is generated. Is there any way to know how many split archives will be generated by 7zip based on its compression rate? I'm dealing with PDF files.
EDIT:
Basically what I want to do is to decide whether to create split archives or not. So I have to guess whether the resulting file will be greater than 1MB.
It is impossible to know what size the resulting files will have, unless you are able to analyze the content of the size and check how well it can be compressed. (Which can, to my knowledge, only be done by actually compressing it.)
For example, a PDF file containing only text might be better compressible than a file made up of only compressed images. The best solutions would be to stop splitting the archives, or to check for the presence of .002(etc.) files after compressing the input.
An alternative solution would be to compress the file in-memory using the C# LZMA sdk and then split the files manually if appropriate.
You could try compressing various combinations of PDF files and averaging out the compression rate, and then you'd know the rough input size after which you'd end up with more than one archive.
That said, it won't be precise. A simpler thing would be to wait for 7-zip to finish, and then check how many files you have, and drop the .001 if you only have one file.

Execute a line in a text file

I have a program that reads text files filled with code designed to be executed line by line by the program, like a batch file. The problem is that I don't no how to do the line executing part. Here is my code, I thought using the \r would fool the console. But it just shows me a list of lines in the file.
if (tok[0] == "read" && length == 2)
{
try
{
StreamReader tr = new StreamReader(#"C:\Users\Public\"+tok[1]+".txt");
while (!tr.EndOfStream)
{
Console.WriteLine(tr.ReadLine());
}
}
catch
{
Console.WriteLine("No such text file.\n");
}
Prompt();
}
If I knew what to search for to fix my problem in Google, I would have. But I've got no idea.
Thanks
EDIT - My program is a crude synthesizer. It takes inputs in the form of 440 5, or 415 2. The first number is frequency, the second duration. What I'm wanting to do is read text files, which my code does, and execute the sound info line by line, which it doesn't, hence my question. It works perfectly fine from standard input.
Audio synthesis is not straightforward, there used to be
Console.Beep(frequency,duration);
but that's using the PC speaker most systems don't have anymore - here's an example though using DirectSound to achieve something close to what you want.
To read the frequency and duration from your text file you can use something like this (splitting on space):
StreamReader tr = new StreamReader(#"test.txt");
while(!tr.EndOfStream)
{
string[] parts = tr.ReadLine().Split(new[]{' '});
int frequency = Convert.ToInt32(parts[0]);
int duration = Convert.ToInt32(parts[1]);
}
You should load your code and compile it in the runtime.
Check out following examples:
http://www.csharpfriends.com/articles/getarticle.aspx?articleid=118
http://www.codeproject.com/KB/dotnet/evaluator.aspx
EDIT:
You should use Process.Start(cmd); to execute commands in the shell. Here I've found few nice examples: http://dotnetperls.com/process-start
If your program works fine using Standard Input just pass the text file to it like this:
yourprogram.exe < textFile.txt
Then the contents of the text file will be passed to your program on Standard Input.

Categories