3

In my application I want to modify various mp3 and then mix them together. I know I could do it with a single command line in FFmpeg but It can end up very messy since I need to use various filter on each sample and I have five of them. My idea is to edit each sample individually, save them into a variable and finally mix them. This is my code:

import subprocess    

def create_samp():
    sample= subprocess.run(["ffmpeg", "-y", "-i", "https://freesound.org/data/previews/186/186942_2594536-hq.mp3", \
                           "-filter_complex", "adelay=15000|15000", "-codec:v", "copy", "-f", "mp3","-"], stdout=subprocess.PIPE)         
    return(sample)    

def record(samp):
    subprocess.run(["ffmpeg", "-y", "-i", "https://cdns-preview-b.dzcdn.net/stream/c-b0b684fe962f93dc43f1f7ea493683a1-3.mp3", \
                    "-i", samp.stdout, "-f", "-mp3", "copy", "output.mp3"])

samp = create_samp()
record(samp)

My issue is that I have to encode the stdout. I've tried 'utf-8' but got this error:

UnicodeDecodeError: 'utf-8' codec can't decode byte 0xff in position 45: invalid start byte

With `'utf-16':

UnicodeDecodeError: 'utf-16-le' codec can't decode bytes in position 239454-239455: illegal encoding

Why is the way to fix this issue? Is my approach the right one?

Thanks to @Rotem I succeed to do what I wanted to. But now I am facing an other issue, since I want to mix up to 5 sounds, I tried to implement it the lazy/easy way:

import subprocess

def create_samp_2():
    sample= subprocess.run(["ffmpeg", "-i", "https://freesound.org/data/previews/186/186942_2594536-hq.mp3", \
                            "-af", "adelay=15000|15000", "-f", "mp3", "pipe:"], stdout=subprocess.PIPE).stdout
    return(sample)

def create_samp():

    sample= subprocess.run(["ffmpeg", "-i", "https://freesound.org/data/previews/370/370934_6399962-lq.ogg", \
                            "-af", "adelay=1000|1000", "-f", "mp3", "pipe:"], stdout=subprocess.PIPE).stdout
    return(sample)


def record(samp, samp_2):        
    process = subprocess.Popen(["ffmpeg", "-y", '-f', 'mp3', \
                                "-i", "https://cdns-preview-b.dzcdn.net/stream/c-b0b684fe962f93dc43f1f7ea493683a1-3.mp3", \
                                "-i", "pipe:", \
                                "-i", "pipe:", \
                                "-filter_complex", "amix=inputs=3:duration=longest", "output.mp3"], stdin=subprocess.PIPE)

    process.stdin.write(samp) 
    process.stdin.write(samp_2)        
    process.stdin.close()  
    process.wait()

samp = create_samp()
samp_2 = create_samp_2()
record(samp, samp_2)

Surprisingly it works, my two sounds start at the right time, but the second sound is messed up. So it's not the right way to do it.

Then I tried named pipes as suggested this way:

"pipe1:"

But I get this error:

pipe1:: Protocol not found
Did you mean file:pipe1:?

Reading named pipe wiki it is stated that I have to create them with mkfifo().

So I tried:

import os
pipe1 = "pipe1"

def create_pipe1():
    os.mkfifo(pipe1)

But now I have this error: pipe1:: Protocol not found
Did you mean file:pipe1:?
Toto Briac
  • 908
  • 9
  • 29

1 Answers1

2

Your approach the right, but fixes are needed.

Fixing create_samp():

  • You don't need "-codec:v", "copy" arguments because there is no video stream.

Fixing record(samp):

  • You can't use "-i" "samp.stdout", because samp.stdout is a bytes array (Python subprocess module uses it as a string).
  • Use "-i", "pipe:" for receiving the the second audio from stdin pipe.
  • Since you want to mix two audio streams you have to use "-filter_complex" argument, and amix audio filter or amerge audio filter, as described here.
  • Write samp to stdin pipe, and close stdin pipe.

Here is the code:

import subprocess

def create_samp():
    # Read audio stream from https://freesound.org/data/previews/186/186942_2594536-hq.mp3
    # Apply adelay audio filter.
    # Encode the audio in mp3 format.
    # FFmpeg output is passed to stdout pipe, and stored in sample bytes array.
    sample= subprocess.run(["ffmpeg", "-i", "https://freesound.org/data/previews/186/186942_2594536-hq.mp3", \
                            "-af", "adelay=15000|15000", "-f", "mp3", "pipe:"], stdout=subprocess.PIPE).stdout
    return(sample)


def record(samp):
    # Open FFmpeg as sub-process
    # Use two audio input streams:
    # 1. WEB address
    # 2. PIPE (the input is going to be written stdin pipe).
    # Merge the two audio streams using amix audio filter.
    # Store the result to output file: output.mp3
    process = subprocess.Popen(["ffmpeg", "-y", '-f', 'mp3', \
                                "-i", "https://cdns-preview-b.dzcdn.net/stream/c-b0b684fe962f93dc43f1f7ea493683a1-3.mp3", \
                                "-i", "pipe:", \
                                "-filter_complex", "amix=inputs=2:duration=longest", "output.mp3"], stdin=subprocess.PIPE)

    process.stdin.write(samp)  # Write samp (bytes array containing mp3 data).
    process.stdin.close()  # Close stdin pipe.
    process.wait()  # Wait for FFmpeg sub-process to finish

samp = create_samp()
record(samp)

Sounds great...


Update:

Named pipes implementation moved to the following post.

Rotem
  • 30,366
  • 4
  • 32
  • 65
  • Works as It should, but I am struggling mixing more than two sounds. How could this be achieved? – Toto Briac May 04 '21 at 08:23
  • For more than two input streams you have to use [named pipes](https://en.wikipedia.org/wiki/Named_pipe). I never used named pipes, because I am using Windows. Using named pipes in Windows requires some extra work compared to Linux. – Rotem May 04 '21 at 12:03
  • Trying to use named pipes. Question edited. – Toto Briac May 04 '21 at 13:31
  • Try `"pipe1"` instead of `"pipe1:"`. Do you expect me to test it in a virtual machine? Check [here](https://stackoverflow.com/questions/63793809/use-2-named-pipes-in-python). The named pipe is used like a file. Check [here](https://stackoverflow.com/questions/34046083/understanding-named-pipes-fifo-in-python). I have no experience using named pipes. In case you can't solve it, you may post a "followed-up" question. In your new question put a reference to this post. – Rotem May 04 '21 at 14:32
  • 1
    I figured out how to use named pipes (updated my post). I hope I had it right, and not just overcomplicating thinks. Interestingly, I couldn't find a good reference for using named pipes as input to FFmpeg with Python... – Rotem May 04 '21 at 21:25
  • Works great with multiple samples as well; I would never have figure it out it on my own. – Toto Briac May 05 '21 at 06:56
  • Opened another question. @Rotem could you please answer it with this accepted answer?https://stackoverflow.com/questions/67388548/multiple-named-pipes-in-ffmpeg/67396400?noredirect=1#comment119142997_67396400 – Toto Briac May 05 '21 at 21:00
  • 1
    [Done!](https://stackoverflow.com/questions/67388548/multiple-named-pipes-in-ffmpeg) – Rotem May 06 '21 at 16:28