Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Using FFmpeg to split multimedia file into parts based on audio volume level
#!/bin/bash
IN=$1
OUT=$2
true ${SD_PARAMS:="-55dB:d=0.3"};
true ${MIN_FRAGMENT_DURATION:="20"};
export MIN_FRAGMENT_DURATION
if [ -z "$OUT" ]; then
echo "Usage: split_by_silence.sh input_media.mp4 output_template_%03d.mkv"
echo "Depends on FFmpeg, Bash, Awk, Perl 5. Not tested on Mac or Windows."
echo ""
echo "Environment variables (with their current values):"
echo " SD_PARAMS=$SD_PARAMS Parameters for FFmpeg's silencedetect filter: noise tolerance and minimal silence duration"
echo " MIN_FRAGMENT_DURATION=$MIN_FRAGMENT_DURATION Minimal fragment duration"
exit 1
fi
echo "Determining split points..." >& 2
SPLITS=$(
ffmpeg -v warning -i "$IN" -af silencedetect="$SD_PARAMS",ametadata=mode=print:file=-:key=lavfi.silence_start -vn -sn -f s16le -y /dev/null \
| grep lavfi.silence_start= \
| cut -f 2-2 -d= \
| perl -ne '
our $prev;
INIT { $prev = 0.0; }
chomp;
if (($_ - $prev) >= $ENV{MIN_FRAGMENT_DURATION}) {
print "$_,";
$prev = $_;
}
' \
| sed 's!,$!!'
)
echo "Splitting points are $SPLITS"
ffmpeg -v warning -i "$IN" -c copy -map 0 -f segment -segment_times "$SPLITS" "$OUT"
@vi

This comment has been minimized.

Copy link
Owner Author

@vi vi commented Mar 14, 2019

Note: at least one user had problem with ametadata filter. You may want to use the first revision of this script.

@vi

This comment has been minimized.

Copy link
Owner Author

@vi vi commented Jun 6, 2019

Note: there is also a version of this script that takes video keyframes into account: https://gist.github.com/vi/2af29b9652a813ffe4b7e87c9a895f81

@328791

This comment has been minimized.

Copy link

@328791 328791 commented Sep 2, 2019

Hello,
could you tell me, how to change your script, that one can run it on multiple mp3-files in a directory at once and move the split files to another directory?
Many thanks in advance!

@vi

This comment has been minimized.

Copy link
Owner Author

@vi vi commented Sep 2, 2019

Something like for i in *.mp3; do ./split_by_silence.sh "$i" "your_directory/${i}_%03d.mkv"; done. Not checked this.

@328791

This comment has been minimized.

Copy link

@328791 328791 commented Sep 4, 2019

Thank you for your answer.

But this doesn't work yet. I have specified the variables IN and OUT to:

IN=$(find . -regex '.*\.mp3')
OUT=$"$IN"%02d.mp3

The find command finds the mp3-files. When I run the script, it splits the file, if there is only one file in the directory. But if there are two or more files in the directory, nothing will be split and I get an error:

./01aaa.mp3
./02aaa.mp3: No such file or directory

I suppose the find command has to be changed, so that it will work for multiple files and together with the command in your answer to my first question.
Could you help me please?

@vi

This comment has been minimized.

Copy link
Owner Author

@vi vi commented Sep 4, 2019

IN=$(find . -regex '.*\.mp3')

This places result of find (newline-separated filenames) in scalar variable IN.

OUT=$"$IN"%02d.mp3

This creates scalar variable OUT, considing of content of IN, but the last line of has %02d.mp3 appended. It can be something like this:

./01aaa.mp3
./02aaa.mp3%02d.mp3

Assuming you changed for i in *.mp3 to for i in $IN, it tries to read a single file with a newline inside the name, like $"./01aaa.mp3\n./02aaa.mp3".

You may want to use Bash arrays, find ... -exec or xargs -0, especially if filenames contain spaces.


What's wrong with glob version *.mp3? Do you need fancier filename matching?

@328791

This comment has been minimized.

Copy link

@328791 328791 commented Sep 5, 2019

No, I don't need fancier filename matching. I just didn't know, how to cut off the ".mp3" extension. Now I know, I could use:

IN=$(find . -name '*.mp3' -print0 | xargs -0 rename 's/([0-9]{2}[^0-9]+?[0-9]{2})(\.mp3)/$1/g')

But this doesn't work with your script.

May be, the command line you have posted after my first question, I don't need anymore. Excuse the mistake please. Since the output filenames have a different structur (added number), I can move them later to another directory.

The question is: how can I apply your script to multiple mp3-files with different filenames which are numbered and are all in the same folder? How do I have to format the variable "IN" or what else has to be changed in your script, that it works for multiple files?

If there is only a single file in a folder, the script works fine. If there are two, it doesn't work.

@vi

This comment has been minimized.

Copy link
Owner Author

@vi vi commented Sep 5, 2019

IN=$(... rename ...)

rename is not expected to produce useful output. IN would likely be just empty.

Please write a script that iterates the files and just copies them to output directory (as if there were only one non-silent fragment). I may help turn it into a script that does the splitting.

@DraperMMC

This comment has been minimized.

Copy link

@DraperMMC DraperMMC commented Mar 5, 2020

hi, is this possible on windows? i would like to split audio into separate parts :/

@vi

This comment has been minimized.

Copy link
Owner Author

@vi vi commented Mar 5, 2020

@DraperMMC, Probably, if you install MinGW + FFmpeg or use WSL.

Similar functionality can be also be present in some interactive audio editor or its plugin.

@denimboiz

This comment has been minimized.

Copy link

@denimboiz denimboiz commented Apr 23, 2020

Hi all - I'm clueless when it comes to this, i've got this script as a .sh file saved on my desktop (alongside the video i'm looking to split by silent points) and FFmpeg installed...
I've learnt how to execute the simpliest of of simple scripts in terminal, but for this script i am confused as to where i declare the input and output and how I declare them? Are they declared within .sh file or within terminal and then execute .sh file?

Do I just put the file path for the video I am looking to edit in for the '$1' at the top of the script? e.g. IN=fileexample.mov

Also, what do i call the output - do you need to setup a naming convention for it as more than one file will be created?

If any of you have time to help this simple guy out i'll be hugely appreciative! If I've described anything badly, let me know - i'm learning!

@vi

This comment has been minimized.

Copy link
Owner Author

@vi vi commented Apr 23, 2020

@denimboiz Do you know how to use command line in general? Are you starting scripts by double-clicking at their icon?

This script accepts requires command-line parameters. This means just simply starting it won't work.

Your options are:

  • Create a Windows shortcut for this script (if you are using Windows) and edit this shortcut to include command line parameters. There should be two of them and second one should include someting like %03d.
  • Modify the script, replacing $1 with a file path to you input video and $2 with path template for your output videos. Template is like file path, but with a %03d inside it, meaning the place for putting sequence numbers.
  • Learn how to you command line and start the script interactively in command prompt. This way you get error messages in case something goes wrong (e.g. not all required components are installed). The command should look like ./split_by_silence.sh my_video.mp4 my_outputs_videos_%03d.mp4.
@denimboiz

This comment has been minimized.

Copy link

@denimboiz denimboiz commented Apr 23, 2020

@vi - firstly big thanks for helping!

UPDATE: I have managed to split the .mp4 or .mov files using this scripts however... when i split it only the first of the split videos actually plays, the others when i click on them are just a blank screen for different intervals. E.g. 000.mov works but 001,002,etc do not.
I have tried both .mp4 & .mov but neither working - any thoughts?

@vi

This comment has been minimized.

Copy link
Owner Author

@vi vi commented Apr 23, 2020

@denimboiz

This comment has been minimized.

Copy link

@denimboiz denimboiz commented Apr 24, 2020

Yes, I've tried both version on that page and still getting the same issue - every output after the first is a black screen with no audio....

@vi

This comment has been minimized.

Copy link
Owner Author

@vi vi commented Apr 24, 2020

@denimbioz, Are those black screen files small? You can publish such file somewhere for analysis.

@denimboiz

This comment has been minimized.

Copy link

@denimboiz denimboiz commented Apr 24, 2020

Appreciate your patience on this! Unfortunately i can only publish GIF, JPEG, JPG or PNG here. Not mp4 or mov - I have emailed you some examples of the outputs though. Let me know your thoughts.

Thank you!

@sadez

This comment has been minimized.

Copy link

@sadez sadez commented Nov 17, 2020

With some type of videos I get this message :
Could not write header for output file #0 (incorrect codec parameters ?): Invalid argument
Do you know what can be the cause ?

@vi

This comment has been minimized.

Copy link
Owner Author

@vi vi commented Nov 17, 2020

@sadez, Does simply remuxing the file with FFMpeg work?:

ffmpeg -i input_media.mp4 -c copy -y output.mkv
@sadez

This comment has been minimized.

Copy link

@sadez sadez commented Nov 17, 2020

Yeah it's work,
I find the problem, I used a video with no silence on it and it's generate no split points...
I added a condition to manage it :

if [ $SPLITS ]; then
    ffmpeg -v warning -i "$IN" -c copy -map 0 -reset_timestamps 1 -f segment -segment_times "$SPLITS" "$OUT"
else
    # do something else
fi
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment