2

I am trying to convert the audio file in WebM format into MP3 file. I need to keep both files in memory due to server limitations. I tried to do it using PyAv, but I can't manage to receive Python file-like object after processing stream. My attempt:

    with av.open(webm_file, 'r') as inp:
        f = SpooledTemporaryFile(mode="w+b")
        with av.open(f, 'w', format="mpeg") as out:
            out_stream = out.add_stream("mp3")
            for frame in inp.decode(audio=0):
                frame.pts = None
                for packets in out_stream.encode(frame):
                    out.mux(packets)
            for packets in out_stream.encode(None):
                out.mux(packets)

webm_file is of SpooledTemporaryFile type, but I can`t get outfile as file-like, can anyone help?

Denys Ivanenko
  • 388
  • 7
  • 21

1 Answers1

2

Replace format="mpeg" with format="mp3".

mpeg applies MPEG-1 program stream, and we want to create mp3 container format.
mpeg container supports mp3 codec as audio stream, but it's not the container we are looking for...

I suppose replacing "mpeg" with "mp3" is going to solve the issue.


I don't know how to test f = SpooledTemporaryFile(mode="w+b"), so I used input and output files instead.

Start by creating WebM file with synthetic audio and video (using FFmpeg CLI) for testing:

ffmpeg -y -f lavfi -i testsrc=size=192x108:rate=1 -f lavfi -i sine=frequency=100 -f lavfi -i sine=frequency=500 -map_channel 1.0.0 -map_channel 2.0.0 -vcodec libvpx-vp9 -crf 32 -acodec libopus -b:a 96K -ar 48000 -ac 2 -t 10s input.webm


Transcoding the audio channel from opus codec to mp3 codec in output.mp3 file using PyAV:

import av
#from tempfile import SpooledTemporaryFile

# Build input file for testing:
# ffmpeg -y -f lavfi -i testsrc=size=192x108:rate=1 -f lavfi -i sine=frequency=500 -f lavfi -i sine=frequency=800 -map_channel 1.0.0 -map_channel 2.0.0 -vcodec libvpx-vp9 -crf 32 -acodec libopus -b:a 96K -ar 48000 -ac 2 -t 10s input.webm

webm_file = "input.webm"
mp3_file = "output.mp3"

with av.open(webm_file, 'r') as inp:
    #f = SpooledTemporaryFile(mode="w+b")
    f = mp3_file
    with av.open(f, 'w', format="mp3") as out:  # Open file, setting format to mp3
        out_stream = out.add_stream("mp3")
        for frame in inp.decode(audio=0):
            frame.pts = None
            for packets in out_stream.encode(frame):
                out.mux(packets)
        for packets in out_stream.encode(None):
            out.mux(packets)

Output of MediaInfo tool:

General
Complete name                            : C:\Tmp\output.mp3
Format                                   : MPEG Audio
File size                                : 157 KiB
Duration                                 : 10 s 32 ms
Overall bit rate mode                    : Variable
Overall bit rate                         : 128 kb/s
Writing library                          : LAME3.100

Audio
Format                                   : MPEG Audio
Format version                           : Version 1
Format profile                           : Layer 3
Format settings                          : Joint stereo / MS Stereo
Duration                                 : 10 s 32 ms
Bit rate mode                            : Variable
Bit rate                                 : 128 kb/s
Channel(s)                               : 2 channels
Sampling rate                            : 48.0 kHz
Frame rate                               : 41.667 FPS (1152 SPF)
Compression mode                         : Lossy
Stream size                              : 157 KiB (100%)
Writing library                          : LAME3.100
Rotem
  • 30,366
  • 4
  • 32
  • 65
  • Thank you, this is totally relevant, but I also look at how to avoid writing resulting file to disk. I rather would like to keep it in RAM. I there a way to accomplish this? – Denys Ivanenko Jun 20 '22 at 08:43
  • I thought `SpooledTemporaryFile` is the way for keeping it in RAM. You may also try writing to `BytesIO` object. – Rotem Jun 20 '22 at 09:00
  • This is my attempt, but after out stream is closed, this temporary file object is empty. – Denys Ivanenko Jun 20 '22 at 09:12
  • Look for a way for flush instead of close. Maybe closing is not the right option. – Rotem Jun 20 '22 at 09:23
  • According to [tempfile documentation](https://docs.python.org/3/library/tempfile.html): " **It will be destroyed as soon as it is closed** (including an implicit close when the object is garbage collected)". I can't see if you are closing the file because your posted code is partial. **1.** Move `f = SpooledTemporaryFile(mode="w+b")` to be above `with av.open` (so `f` stays within scope). **2.** At the very bottom use `f.flush()`. For seeking to the beginning of the file use `f.seek(0)`. In case you are still having issues, please post enough code - code that shows the problem. – Rotem Jun 20 '22 at 13:57