How to use MP3Sharp with IFormFile - c#

I want to convert an mp3 file to pcm using MP3Sharp (https://github.com/ZaneDubya/MP3Sharp) in a web app, where the mp3 file is passed in as IFormFile
It works if I save the file to disk first like this ...
using (Stream fileStream = new FileStream("file.mp3", FileMode.Create))
{
await file.CopyToAsync(fileStream);
}
MP3Stream stream = new MP3Stream("file.mp3");
... but when I try to do it as a stream without writing to file it doesn't work:
using (var fileStream = new MemoryStream())
{
await file.CopyToAsync(fileStream);
MP3Stream stream = new MP3Stream(fileStream);
}
The MP3Stream constructor throws this exception:
MP3Sharp.MP3SharpException: 'Unhandled channel count rep: -1 (allowed values are 1-mono and 2-stereo).'
... Any ideas on what I'm doing wrong?

After your code does file.CopyToAsync(fileStream), the MemoryStream's read/write position is pointing after the written MP3 data at the end of the MemoryStream.
The MP3Stream then trying to read from the MemoryStream will only notice that the end of the MemoryStream has been reached (because its read/write position is already at the end) and throws an exception.[1]
Thus, after copying the MP3 data into the MemoryStream, set the MemoryStream's read/write position back to where it was before you copied the MP3 data into it (in the case of your example code it's the beginning of the MemoryStream, position 0):
await file.CopyToAsync(fileStream);
fileStream.Position = 0;
Side note: Like FileStream and MemoryStream, MP3Stream is also a Stream and therefore an IDisposable, too. And like you already did with the FileStream and the MemoryStream, you should use the using statement for the MP3Stream as well.
[1] The exception you got from the MP3Sharp library is misleading, and as such is kind of a bug in the library. Because, when attempting to read a byte from a stream and the stream is at its end, the Stream.ReadByte method will return -1 to indicate end-of-stream. And as apparent by the exception message, it seems the MP3Sharp library does not properly treat the -1 value here as simply meaning "the end of the stream has been reached and no (further) data could be read" and misinterprets it as a channel count value.

Related

Read from a compressing GZipStream

I'm exploring how to implement an HTTP server in C#. (And before you ask, I know there is Kestrel (and nothing else that isn't obsolete), and I want a much, much smaller application.) So, the response could be a Stream that cannot be seeked and has an unknown length. For this situation, chunked encoding can be used instead of sending a Content-Length header.
The response can also be compressed with gzip or br as indicated by the client. This can be accomplished with e.g. the GZipStream class. I had almost said "easily", because that's not really the case. I always find the GZipStream API confusing each time I use it. I usually bump into every exception there is until I finally get it right.
It seems like I can only write (push) to a GZipStream and the compressed data will trickle out the other end into the specified "base" stream. But that's not desirable because I can't just let the compressed data flow to the client. It needs to be chunked. That is, each bit of compressed data needs to be prefixed with its chunk size. Of course the GZipStream cannot produce that format.
Instead, I'd like to read (pull) from the compressing GZipStream, but that doesn't seem to be possible. The documentation says it will throw an exception if I try that. But there has to be some instance that brings the compressed bytes into the chunked format.
So how would I get the expected result? Can it even be achieved with this API? Why can't I pull from the compressing stream, only push?
I'm not trying to make up (non-functional) sample code because that would only be confusing.
PS: Okay, maybe this:
Stream responseBody = ...;
if (canCompress)
{
responseBody = new GZipStream(responseBody, CompressionMode.Compress); // <-- probably wrong
}
// not shown: add appropriate headers
while (true)
{
int chunkLength = responseBody.Read(buffer); // <-- not possible
if (chunkLength == 0)
break;
response.Write($"{chunkLength:X}\r\n");
response.Write(buffer.AsMemory()[..chunkLength]);
response.Write("\r\n");
}
response.Write("0\r\n\r\n");
Your usage of GZipStream is incomplete. While your input responseBuffer is the correct target buffer, you have to actually write the bytes TO the GZipStream itself.
In addition, once you are done writing, you must close the GZipStream instance to write all compressed bytes to your target buffer. This is the critical step because there is no such thing as "partial compression" of an input stream in GZip. You would have to analyze the entire input in order to properly compress it. As such, this is the critical missing link that MUST happen before you can continue to write the response.
Finally, you need to reset the position of your output stream so that you can read it into an intermediary response buffer.
using MemoryStream responseBody = new MemoryStream();
GZipStream gzipStream = null; // make sure to dispose after use
if (canCompress)
{
using MemoryStream gzipStreamBuffer = new MemoryStream(bytes);
gzipStream = new GZipStream(responseBody, CompressionMode.Compress, true);
gzipStreamBuffer.CopyTo(gzipStream);
gzipStream.Close(); // close the stream so that all compressed bytes are written
responseBody.Seek(0, SeekOrigin.Begin); // reset the response so that we can read it to the buffer
}
var buffer = new byte[20];
while (true)
{
int chunkLength = responseBody.Read(buffer);
if (chunkLength == 0)
break;
// write response
}
In my test example, my bytes input was 241 bytes, whereas the compressed bytes written to the buffer totaled 82 bytes.

Do I need to 'rewind' a stream before reading what was written to it?

With this code:
using (var stream = new MemoryStream())
{
thumbnail.Save(stream); // you get the idea
stream.Position = 0; // <- is this needed?
WriteStreamToDisk(stream);
}
If I have a method writing to a memory stream, and then I want to write that stream to disk, do I need to set the position to 0?
Or, do streams have different read / write pointers?
A stream has only a single position which is used for both reading and writing. So, assuming that...
Thumbnail.Save(O); doesn't rewind the stream after it's done writing to the stream, and
WriteStreamToDisk(O); doesn't rewind the stream before it starts reading from the stream,
then yes, you will need to rewind the stream yourself.

Response.BinaryWrite creating a .partial file?

I am attempting to write an audio file as a .wav in a memorystream out to the response so the client can download it. It looks like on client side when trying to open the file it has a ".partial" extension. It is almost as if the file is not getting released to the client.
The below is my code... Attempting to write the bytes directly to the local machine works fine (you will see that code commented out).
// Initialize a new instance of the speech synthesizer.
using (SpeechSynthesizer synth = new SpeechSynthesizer())
using (MemoryStream stream = new MemoryStream())
{
// Create a SoundPlayer instance to play the output audio file.
MemoryStream streamAudio = new MemoryStream();
// Configure the synthesizer to output to an audio stream.
synth.SetOutputToWaveStream(streamAudio);
synth.Speak("This is sample text-to-speech output. How did I do?");
streamAudio.Position = 0;
// Set the synthesizer output to null to release the stream.
synth.SetOutputToNull();
// Insert code to persist or process the stream contents here.
// THIS IS NOT WORKING WHEN WRITING TO THE RESPONSE, .PARTIAL FILE CREATED
Response.Clear();
Response.ContentType = "audio/wav";
Response.AppendHeader("Content-Disposition", "attachment; filename=mergedoutput.wav");
Response.BinaryWrite(streamAudio.GetBuffer());
Response.Flush();
// THIS WORKS WRITING TO A FILE
//System.IO.File.WriteAllBytes("c:\\temp\\als1.wav", streamAudio.GetBuffer());
}
MemoryStream.GetBuffer is not the correct method to call:
Note that the buffer contains allocated bytes which might be unused.
For example, if the string "test" is written into the MemoryStream
object, the length of the buffer returned from GetBuffer is 256, not
4, with 252 bytes unused. To obtain only the data in the buffer, use
the ToArray method; however, ToArray creates a copy of the data in
memory.
so use MemoryStream.ToArray instead:
Response.BinaryWrite(streamAudio.ToArray());
Looks like the issue was the fact the speak method needs to be run on its own thread. The following provides the solution to get back the byte array properly and then be able to write that to the response.
C# SpeechSynthesizer makes service unresponsive

stream.CopyTo - file empty. asp.net

I'm saving an uploaded image using this code:
using (var fileStream = File.Create(savePath))
{
stream.CopyTo(fileStream);
}
When the image is saved to its destination folder, it's empty, 0 kb. What could possible be wrong here? I've checked the stream.Length before copying and its not empty.
There is nothing wrong with your code. The fact you say "I've checked the stream.Length before copying and its not empty" makes me wonder about the stream position before copying.
If you've already consumed the source stream once then although the stream isn't zero length, its position may be at the end of the stream - so there is nothing left to copy.
If the stream is seekable (which it will be for a MemoryStream or a FileStream and many others), try putting
stream.Position = 0
just before the copy. This resets the stream position to the beginning, meaning the whole stream will be copied by your code.
I would recommend to put the following before CopyTo()
fileStream.Position = 0
Make sure to use the Flush() after this, to avoid empty file after copy.
fileStream.Flush()
This problem started for me after migrating my project from to .NET Core 1 to 2.2.
I fixed this issue by setting the Position of my filestream to zero.
using (var fileStream = new FileStream(savePath, FileMode.Create))
{
fileStream.Position = 0;
await imageFile.CopyToAsync(fileStream);
}

GZipStream not compressing?

I'm trying to zip a memory stream into another memory stream so I can upload to a rest API. image is the initial memory stream containing a tif image.
WebRequest request = CreateWebRequest(...);
request.ContentType = "application/zip";
MemoryStream zip = new MemoryStream();
GZipStream zipper = new GZipStream(zip, CompressionMode.Compress);
image.CopyTo(zipper);
zipper.Flush();
request.ContentLength = zip.Length; // zip.Length is returning 0
Stream reqStream = request.GetRequestStream();
zip.CopyTo(reqStream);
request.GetResponse().Close();
zip.Close();
To my understand, anything I write to the GZipStream will be compressed and written to whatever stream was passed into it's constructor. When I copy the image stream into zipper, it appears nothing is actually copied (image is 200+ MB). This is my first experience with GZipStream so it's likely I'm missing something, any advice as to what would be greatly appreciated.
EDIT:
Something I should note that was a problem for me, in the above code, image's position was at the very end of the stream... Thus when I called image.CopyTo(zipper); nothing was copied due to the position.
[Edited: to remove incorrect info on GZipStream and it's constructor args, and updated with the real answer :) ]
After you've copied to the zipper, you need to shift the position of the MemoryStream back to zero, as the process of the zipper writing to the memory stream advances it's "cursor" as well as the stream being read:
WebRequest request = CreateWebRequest(...);
request.ContentType = "application/zip";
MemoryStream zip = new MemoryStream();
GZipStream zipper = new GZipStream(zip, CompressionMode.Compress);
image.CopyTo(zipper);
zipper.Flush();
zip.Position = 0; // reset the zip position as this will have advanced when written to.
...
One other thing to note is that the GZipStream is not seekable, so calling .Length will throw an exception.
I don't know anything about C# and its libraries, but I would try to use Close instead of (or after) Flush first.
(Java's GZipOutputStream has the same problem that it doesn't properly flush, until Java 7.)
See this example:
http://msdn.microsoft.com/en-us/library/system.io.compression.gzipstream.flush.aspx#Y300
You shouldn't be calling flush on the stream.

Categories