I'm using Spleeter to remove music from audios.
My goal is to build a script that automates the process of extracting audio from the video, execute Spleeter on the extracted audio & than merge the manipulated audio back to the video replacing the original one.
The main issue I had is that I don't have enough ram to process the whole extracted audio. I need to split it the into multiple pieces & execute Spleeter upon each piece.
Then concatenate the manipulated pieces together and merge the result to the video.
Here's what I tried:
#!/bin/bash
cd ~/Desktop/Video-convert
# create audio from video
ffmpeg -i *.mp4 output.mp3
# Split the audio into pieces
ffmpeg -i output.mp3 -f segment -segment_time 120 -c copy output_%03d.mp3
# Execute Spleeter upon each sample
FILES=~/Desktop/Video-convert/*.mp3
for f in $FILES
do
spleeter separate -i $f -o output_vocal
done
# delete unneeded audios
rm *.mp3
cd output_vocal
# ===========================================================
# the problem starts here
# ===========================================================
# concatenate manipulated audios together
find . -name 'vocals.wav' -exec echo {} >> mylist.txt \;
ffmpeg -f concat -safe 0 -i mylist.txt -c copy vocal.mp3
mv vocal.mp3 ../
cd ../
# merge the audio back to video
ffmpeg -i *.mp4 -i vocal.mp3 \
-c:v copy -c:a aac -strict experimental \
-map 0:v:0 -map 1:a:0 vocal-vid.mp4
Everything works well until having to concatenate the audios together. Spleeter outputs the result into vocal.wav & accompaniment.wav within a sub-folder that is named the same as the audio that was processed.
The File Tree looks like this:
output_vocal
- output_000
----- vocal.wav
----- accompaniment.wav
- output_001
----- vocal.wav
----- accompaniment.wav
- output_002
----- vocal.wav
----- accompaniment.wav
As you can see the problem comes with the naming. My objective is to concatenate all vocal.wav into one mp3 audio.
And then merge the final vocal.mp3 audio with the *.mp4 video.
Only issue is going around the way that Spleeter outputs the result audios.
the problem you are experiencing is that ffmpeg's concat demuxer requires an input file that contains directives, rather than a naive file-list.
Your find invocation creates a file like:
output_vocal/output_000/vocal.wav
output_vocal/output_001/vocal.wav
output_vocal/output_002/vocal.wav
whereas ffmpeg's concat demuxer really requires a file like:
file output_vocal/output_000/vocal.wav
file output_vocal/output_001/vocal.wav
file output_vocal/output_002/vocal.wav
Also note that find does not necessarily return the files in alphabetic order, whereas you will most likely want to concatenate the files in that order.
Finally, when concatenating the WAV-files, you cannot use the copy codec to generate an MP3 file (since the WAV/RIFF codec is not MP3). but you don't need an intermediate MP3-file anyhow
Here's an updated script, that
- uses a temporary directory for all intermediate files
- iterates over all mp4-files provided at the cmdline (rather than hardcoding the input directory)
- creates a "XXX_voc.mp4" file for each input file "XXX.mp4" (overwriting any existing files)
#!/bin/bash
for infile in "$#"
do
outfile=${infile%.mp4}_voc.mp4
# create a temp-directory to put our stuff to
TMPDIR=$(mktemp -d)
# create audio from video
ffmpeg -i "${infile}" "${TMPDIR}/output.mp3"
# Split the audio into pieces
ffmpeg -i "${TMPDIR}/output.mp3" -f segment -segment_time 120 -c copy "${TMPDIR}/output_%03d.mp3"
# Execute Spleeter upon each sample
find "${TMPDIR}" -maxdepth 1 -type f -name "output_*.mp3" \
-exec spleeter separate -i {} -o "${TMPDIR}/output_vocal" ";"
# find all 'vocal.wav' files generated by spleeter, sort them,
# prefix them with 'file ', and put them into output.txt
find "${TMPDIR}/output_vocal" -type f -name "vocal.wav" -print0 \
| sort -z \
| xargs -0 -I{} echo "file '{}'" \
> "${TMPDIR}/output.txt"
# concatenate the files and create an MP3 file
ffmpeg -f concat -safe 0 -i "${TMPDIR}/output.txt" -c copy "${TMPDIR}/vocal.wav"
# merge the audio back to video
ffmpeg -y -i "${infile}" -i "${TMPDIR}/vocal.wav" \
-c:v copy -c:a aac -strict experimental \
-map 0:v:0 -map 1:a:0 "${outfile}"
rm -rf "${TMPDIR}"
done
Related
there are what i done:
download a full mp4 file.
due to it's watermark(0s-10s), i split the full video into 2 parts from 10second. the first part with watermark.
use ffmpeg delogo the first part.
merge the two video into a full again.
wget -O download.mp4
ffmpeg -i download.mp4 -vcodec copy -acodec copy -t 00:00:10 tmp1.mp4
ffmpeg -i download.mp4 -vcodec copy -acodec copy -ss 00:00:10 tmp2.mp4
ffmpeg -i tmp1.mp4 -vf "delogo=x=432:y=44:w=1060:h=108" -c:a copy tmp3.mp4
echo file tmp3.mp4 > mergelist.txt && echo file tmp2.mp4 >> mergelist.txt
ffmpeg -f concat -i mergelist.txt -c copy output.mp4
problem i faced:
in the last merged video, only one tmp part is fine, the other's video and voice not sync and play time more faster than before.
why i divide it, delogo(although only the first 10 seconds shows) full video more than 1h, re-encode takes much time, 10s part fine to me.
Have you tried this?
ffmpeg -i download.mp4 -vf "delogo=enable='lte(t,10)':x=432:y=44:w=1060:h=108" -c:a copy output.mp4
I'm assuming delogo filter is timeline editing enabled.
Is it possible to mute a section of a video file (say 5 seconds) without having to re-encode the whole audio stream with ffmpeg? I know it's technically (though probably not easily) possible by reusing the majority of the existing audio stream and only re-encoding the changed section and possibly a short section before and after, but I'm not sure if ffmpeg supports this. If it doesn't, anyone know of any other library that does?
You can do the partial segmented encode, as you suggest, but if the source codec is DCT-based such as AAC/MP3, there will be glitches at the start and end of the re-encoded segment once you stitch it all back together.
You would use the segment muxer and concat demuxer to do this.
ffmpeg -i input -vn -c copy -f segment -segment_time 5 aud_%d.m4a
Re-encode the offending segment, say aud_2.m4a to noaud_2.m4a.
Now create a text file
file aud_0.mp4
file aud_1.mp4
file noaud_2.mp4
file aud_3.mp4
and run
ffmpeg -an -i input -f concat -safe 0 -i list.txt -c copy new.mp4
Download the small sample file.
Here is my plan visualized:
# original video
| video |
| audio |
# cut video into 3 parts. Mute the middle part.
| video | | video | | video |
| audio | | - | | audio |
# concatenate the 3 parts
| video | video | video |
| audio | - | audio |
# mux uncut original video with audio from concatenated video
| video |
| audio | - | audio |
Let's do this.
Store filename:
i="fridayafternext_http.mp4"
To mute the line "What the hell are you doing in my house!?", the silence should start at second 34 with a duration of 2 seconds.
Store all that for your convenience:
mute_starttime=34
mute_duration=2
bash supports simple math so we can automatically calculate the start time where the audio starts again, which is 36 of course:
rest_starttime=$(( $starttime + $duration))
Create all 3 parts. Notice that for the 2nd part we use -an to mute the audio:
ffmpeg -i "$i" -c copy -t $mute_starttime start.mp4 && \
ffmpeg -i "$i" -ss $mute_starttime -c copy -an -t ${mute_duration} muted.mp4 && \
ffmpeg -i "$i" -ss $rest_starttime -c copy rest.mp4
Create concat_videos.txt with the following text:
file 'start.mp4'
file 'muted.mp4'
file 'rest.mp4'
Concat videos with the Concat demuxer:
ffmpeg -f concat -safe 0 -i concat_videos.txt -c copy muted_audio.mp4
Mux original video with new audio
ffmpeg -i "$i" -i "muted_audio.mp4" -map 0:v -map 1:a -c copy "${i}_partly_muted.mp4"
Note:
I've learned from Gyan's answer that you do the last 2 steps in 1 take which is really cool.
ffmpeg -an -i "$i" -f concat -safe 0 -i concat_videos.txt -c copy "${i}_partly_muted.mp4"
I've looked everywhere to try to combine a bunch of FLAC files with differing sample rates into 1 file. What I've tried so far is:
ffmpeg concat with a wildcard:
ffmpeg -f concat -i <( for f in *.flac; do echo "file '$(pwd)/$f'"; done ) -safe 0 output.flac
I get for every filename, (even if I change pwd to './' for relative):
ffmpeg unsafe filename
Regardless of the file's filename.
I've tried sox:
sox *.flac output.flac
Which leads to:
sox FAIL sox: Input files must have the same sample-rate
I've even tried combining the two:
#!/usr/bin/env bash
set -eu
for i in *.flac *.ogg *.mp3
do
ffmpeg -i "$i" "$i.wav"
done
sox *.wav combined.wav
Same error as above.
Anyone have any tips? I'm sure that in some Windows program you can drag in 5 differing sound files and combine them with ease. Is there not a simple way to do this on linux cmdline?
safe 0 is a private option for the concat demuxer, so it has to appear before the input i.e. -f concat -safe 0 -i ...
ffmpeg has concat option for this but all streams start working really bad and breaking sound after a day of streaming.
I tried looking at loops but i couldnt figure out how to execute a loop with ffmpeg command so it transcodes all files in 1 directory
/lely/ffmpeg -y -re -i /home/ftp/kid1.mp4 -vcodec copy -acodec copy -dts_delta_threshold 1000 -ar 44100 -ab 32k -f flv rtmp://10.0.0.17:1935/live/kid
In folder /home/ftp/ there are files kid1, kid2, kid3 - all *.mp4 files
So basically i would like a loop to change the input to next file every time previous ends.
Maybe you could use find and xargs to help you feed the files for ffmpeg:
find /home/ftp -name "*.mp4" | xargs -I $ /lely/ffmpeg -y -re -i $ -vcodec copy -acodec copy -dts_delta_threshold 1000 -ar 44100 -ab 32k -f flv rtmp://10.0.0.17:1935/live/kid
Here you first ask find to look for all mp3 files in /home/ftp.
Then you feed the results to xargs. For xargs you tell it to replace input it receives with token $ in your ffmpeg string.
You can concatenate the video files to a "named pipe" and use the pipe as a source for ffmpeg.
For example:
mkfifo pipeFile # create a FIFO file (named pipe)
cat $(find /home/ftp -name "*.mp4") > pipeFile & # concatenate video files do the pipe (do not forget the "&" for running in background)
/lely/ffmpeg -y -re -i pipeFile -vcodec copy -acodec copy -dts_delta_threshold 1000 -ar 44100 -ab 32k -f flv rtmp://10.0.0.17:1935/live/kid # run ffmpeg with the pipe as the input
Notes:
The order of files in the input will be that the find generates. You can add a "sort" command after the find to produce files in a sorted manner.
I have not tested this, since a I do not have ffmpeg installed. However, it should work :-)
I would like to script this command
ffmpeg -i concat:file1.mp3\|file2.mp3 -acodec copy output.mp3
which merges file1.mp3 and file2.mp3 to become output.mp3.
The problem is that I have a lot more than 2 files that I would like to merge.
Example
ffmpeg -i concat:file1.mp3\|file2.mp3 -acodec copy output1.mp3
ffmpeg -i concat:output1.mp3\|file3.mp3 -acodec copy output2.mp3
ffmpeg -i concat:output2.mp3\|file4.mp3 -acodec copy output3.mp3
ffmpeg -i concat:output3.mp3\|file5.mp3 -acodec copy output4.mp3
output4.mp3 is the result I am looking for.
The files are not actually nicely called "file" adn then a number, but ls lists them in the order they should be merged in.
Question
How can this be scripted, so I can execute it in a directory with either an even or odd number of files?
if ffmpeg supports more then two files and no file contains |, and there are not too many, you can do:
ffmpeg -i concat:"$(ls|tr '\n' '|')" -acodec copy out.mp3
if not:
for cfile in *.mp3; do
ffmpeg -i concat:myout.mp3tmp1\|$cfile -acodec copy myout.mp3tmp2
mv myout.mp3tmp2 myout.mp3tmp1
done
mv myout.mp3tmp1 <your final file name>
If you can just concatenate all files in one wash, that'd be best. But a generic answer for your Bash question:
ffmpeg -i concat:file1.mp3\|file2.mp3 -acodec copy output1.mp3
for i in $(seq 1 10); do
ffmpeg -i concat:output${i}.mp3\|file$((i + 2)).mp3 -acodec copy output$((i + 1)).mp3
done
Here 10 is two less than your total number of input files.