92

I found something for videos, which looks like this.

ffmpeg -i * -c:v libx264 -crf 22 -map 0 -segment_time 1 -g 1 -sc_threshold 0 -force_key_frames "expr:gte(t,n_forced*9)" -f segment output%03d.mp4

I tried using that for an audio file, but only the first audio file contained actual audio, the others were silent, other than that it was good, it made a new audio file for every second. Does anyone know what to modify to make this work with audio files, or another command that can do the same?

2
  • If you want other ways of doing this then please explain in more detail what are you trying to achieve. ffmpeg cmdlines are hard to remember.
    – user140866
    Commented May 3, 2016 at 11:35
  • 1
    @siblynx As I explained in the question, split every second of an audio file into new audio files. Commented May 3, 2016 at 11:58

9 Answers 9

132

This worked for me when I tried it on a mp3 file.

$ ffmpeg -i somefile.mp3 -f segment -segment_time 3 -c copy out%03d.mp3

Where -segment_time is the amount of time you want per each file (in seconds).

References

7
  • Worked on mp3s. Failed for me on m4bs saying, "Invalid audio stream. Exactly one MP3 audio stream is required."
    – vossad01
    Commented Aug 29, 2017 at 0:04
  • FYI, replace mp3 with m4a
    – Mikhail
    Commented Oct 14, 2017 at 23:27
  • 1
    What about a case where I have a 40min file that I'd like to break into, say, 4min, 6min, 12min, 8min, 10min instead breaking up into uniform sub-files? Commented Oct 23, 2017 at 20:44
  • 1
    It also worked for wav files. Commented Dec 20, 2017 at 13:44
  • 1
    Anyway to make the output files overlap by about a second? -- (I'm splitting them up into 3 minute chunks). Commented Dec 20, 2017 at 21:54
64

To split a big audio file into a set of tracks with varying lengths, you can use the following command:

# -to is the end time of the sub-file
ffmpeg -i BIG_FILE -acodec copy -ss START_TIME -to END_TIME LITTLE_FILE

For example, I broke up a single .opus file of the Inception Original Soundtrack into sub-files using this text file containing start, end, name:

00:00:00 00:01:11 01_half_remembered_dream
00:01:11 00:03:07 02_we_built_our_own_world
00:03:07 00:05:31 03_dream_is_collapsing
00:05:31 00:09:14 04_radical_notion
00:09:14 00:16:58 05_old_souls
00:16:58 00:19:22 06
00:19:22 00:24:16 07_mombasa
00:24:16 00:26:44 08_one_simple_idea
00:26:44 00:31:49 09_dream_within_a_dream
00:31:49 00:41:19 10_waiting_for_a_train
00:41:19 00:44:44 11_paradox
00:44:44 00:49:20 12_time

I wrote this short awk program to read the text file and create ffmpeg commands from each line:

{
    # make ffmpeg command string using sprintf
    cmd = sprintf("ffmpeg -i inception_ost.opus -acodec copy -ss %s -to %s %s.opus", $1, $2, $3)

    # execute ffmpeg command with awk's system function
    system(cmd)
}

Here is a more detailed python version of the program called split.py, where now both the original track file and the text file specifying sub-tracks are read from the command line:

import sys
import subprocess


def main():
    """split a music track into specified sub-tracks by calling ffmpeg from the shell"""

    # check command line for original file and track list file
    if len(sys.argv) != 3:
        print("usage: split <original_track> <track_list>")
        exit(1)

    # record command line args
    original_track = sys.argv[1]
    track_list = sys.argv[2]

    # create a template of the ffmpeg call in advance
    cmd_string = "ffmpeg -i {tr} -acodec copy -ss {st} -to {en} {nm}.opus"

    # read each line of the track list and split into start, end, name
    with open(track_list, "r") as f:
        for line in f:
            # skip comment and empty lines
            if line.startswith("#") or len(line) <= 1:
                continue

            # create command string for a given track
            start, end, name = line.strip().split()
            command = cmd_string.format(tr=original_track, st=start, en=end, nm=name)

            # use subprocess to execute the command in the shell
            subprocess.call(command, shell=True)

    return None


if __name__ == "__main__":
    main()

You can easily build up more and more complicated ffmpeg calls by modifying the command template and/or adding more fields to each line of the track_list file.

10
  • this is fantastic, ! please tell me How to run this from a python program, i.e i want to read audio, and the annotation file with start,end, label. and then split audio into pieces with sizes corresponding to each line in the file.
    – kRazzy R
    Commented Jan 31, 2018 at 20:47
  • 2
    @kRazzyR subprocess.call(command) is the python analog to system(command) in awk, both of which allow you to send ffmpeg commands the shell. I edited my post to include a flexible python implementation illustrating one way you could go about this. Commented Feb 1, 2018 at 3:14
  • 1
    In my experience the beginning time of track #2 should equal the end time of track #1, and so on. That is, your example times will skip a second between each track.
    – Tim Siegel
    Commented Jun 18, 2018 at 16:56
  • 2
    Here's a compact bash/zsh version. Edit the times as needed, and then remove the word echo to actually run the commands. source="source audio file.m4a"; i=0; t1=0:00; for end_time in 1:24 5:40 14:48 19:33 35:11 40:00 46:08 51:58 ''; do i=$((i+1)); t0=$t1 t1=$end_time; echo ffmpeg -i "$source" -acodec copy -ss $t0 ${t1:+-to} $t1 $(printf "track%02d.%s" $i ${source##*.}); done
    – Tim Siegel
    Commented Jun 18, 2018 at 17:10
  • @TimSmith thanks for catching that, I added an edit above. Depending on your player, there might still be a small hiccup between tracks but this definitely sounds better than chopping out 1s. Commented Jun 19, 2018 at 17:48
5

The following line will split an audio file into multiple files each with 30 sec duration.

ffmpeg -i file.wav -f segment -segment_time 30 -c copy parts/output%09d.wav

Change 30 (which is the number of seconds) to any number you want.

4

slm’s answer:

ffmpeg -i somefile.mp3  -f segment -segment_time 3 -c copy out%03d.mp3

wouldn't work for me with out the -map 0 added:

ffmpeg -i somefile.mp3 -map 0 -f segment -segment_time 3 -c copy out%03d.mp3
1
  • The mapping works great, but also add -vn to strip out any pictures embedded, or it will try to recreate the picture for each segment, (sloooowww).
    – Chris Reid
    Commented Jan 25, 2019 at 11:25
4

The given solution did not work for me. I believe this to be due to an older version of ffmpeg on my system.
In any case, I wanted to provide a solution that did work for me. You can also customise the timers so as to allow overlapping on audio if you'd like.

Output file #0 does not contain any stream

ffmpeg -i your_audio_file.mp3 -acodec copy -t 00:00:30 -ss 00:00:00 split_audio_file.mp3

split your audio files into 30 second increments

1
  • A new question would be in order, as this doesn't answer OP's question! Commented Jan 14, 2018 at 7:03
2

I liked @isosceleswheel answer but don't like python so I made a js version.

const readline = require('readline');
const fs = require('fs')
const args = process.argv.slice(2);

const timeStampRegex = /(\d{2}:\d{2}:\d{2}) (\d{2}:\d{2}:\d{2}) ([A-Za-z\d ']+)/;

const readInterface = readline.createInterface({
    input: fs.createReadStream(args[1])
});

readInterface.on('line', function(line) {
    const match = timeStampRegex.exec(line);
    
    console.log(`ffmpeg -i "${args[0]}" -ss ${match[1]} -to ${match[2]} "${match[3]}.mp3" &&`);
});

Run with:

node split.js "big.mp3" "timestamp.txt"
2

Great python script by @isosceleswheel, I've just used it, but I made some modification of my own, so you can name the tracks with spaces.

Line 18 -> cmd_string = 'ffmpeg -i {tr} -acodec copy -ss {st} -to {en} "{nm}".opus'

"{nm}", quotes, to take entire string as literal.

Line 28 -> start, end, name = line.strip().split(" ", 2)

It will consider only the first 2 spaces as string separation and take the rest as one entire string (the third).
i.e. 00:00:00(start), 00:01:11(end), 01 Half Remembered Dream(name)

With these modifications, you could name rename the music names as such:

00:00:00 00:01:11 01 Half Remembered Dream
00:01:11 00:03:07 02 We Built Our Own World
00:03:07 00:05:31 03 Dream Is Collapsing

1

Further automate isosceleswheel's answer into the below shell script:

#!/bin/sh


USAGE="$(cat <<EOF
Preview generated commands:
  cat <timestamp_file> | ffmpeg_split -i <to_be_split.mp4>

Execute generated commands:
  cat <timestamp_file> | ffmpeg_split -i <to_be_split.mp4> | sh


The timestamp_file must contain single-column rows with each row being start
timestamp of each chapter. Empty lines will be ignored.

Standard tools like cut, awk, etc. can be used to prepare a timestamp_file
EOF
)"
while getopts 'hi:' opt; do case "$opt" in
    i)    INPUT="$OPTARG" ;;
    h|*)  echo "$USAGE" >&2; exit 1 ;;
esac done
shift $((OPTIND-1))

: "${INPUT:?}"


DURATION="$(ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 "$INPUT")" ||
    { 2>&1 echo "Cannot get duration"; exit 1; }

grep . |
awk '
    {
        starts[NR] = $0
    }
    END {
        starts[NR+1] = "'"$DURATION"'"
        for (i=1; i<=NR; i++) {
            printf("%3d %s %s\n", i, starts[i], starts[i+1])
        }
    }
' | 
while read -r index start end; do
    base="$(basename "$INPUT")"
    dir="$(dirname "$INPUT")"
    base_root="${base%.*}"
    base_ext="${base##*.}"
    cat <<CMDS
ffmpeg -nostdin -hide_banner -i "$INPUT" -acodec copy -ss "$start" -to "$end" "${dir}/${base_root}.part_${index}.${base_ext}"
CMDS
done

Example:

Have a chapters file look like:

>>> cat chapters 
0:00
0:00:21
0:03:22
0:15:47
0:36:58
0:39:29
0:47:46
1:06:44
1:13:17
>>> cat chapters | sh ./ffmpeg_split -i foo/bar.mp3 
ffmpeg -i "foo/bar.mp3" -acodec copy -ss "0:00" -to "0:00:21" "foo/bar.part_1.mp3"
ffmpeg -i "foo/bar.mp3" -acodec copy -ss "0:00:21" -to "0:03:22" "foo/bar.part_2.mp3"
ffmpeg -i "foo/bar.mp3" -acodec copy -ss "0:03:22" -to "0:15:47" "foo/bar.part_3.mp3"
ffmpeg -i "foo/bar.mp3" -acodec copy -ss "0:15:47" -to "0:36:58" "foo/bar.part_4.mp3"
ffmpeg -i "foo/bar.mp3" -acodec copy -ss "0:36:58" -to "0:39:29" "foo/bar.part_5.mp3"
ffmpeg -i "foo/bar.mp3" -acodec copy -ss "0:39:29" -to "0:47:46" "foo/bar.part_6.mp3"
ffmpeg -i "foo/bar.mp3" -acodec copy -ss "0:47:46" -to "1:06:44" "foo/bar.part_7.mp3"
ffmpeg -i "foo/bar.mp3" -acodec copy -ss "1:06:44" -to "1:13:17" "foo/bar.part_8.mp3"
ffmpeg -i "foo/bar.mp3" -acodec copy -ss "1:13:17" -to "13045.560000" "foo/bar.part_9.mp3"

Then run by piping to sh:

>>> cat chapters | sh ./ffmpeg_split -i foo/bar.mp3 | sh
1

I improved @isosceleswheel's python script.
The script automatically calculates final timestamps.

#!/usr/bin/python3

import sys
import subprocess

import unicodedata
import re

import os

def slugify(value, allow_unicode=False):
    """
    Taken from https://github.com/django/django/blob/master/django/utils/text.py
    Convert to ASCII if 'allow_unicode' is False. Convert spaces or repeated
    dashes to single dashes. Remove characters that aren't alphanumerics,
    underscores, or hyphens. Convert to lowercase. Also strip leading and
    trailing whitespace, dashes, and underscores.
    """
    value = str(value)
    if allow_unicode:
        value = unicodedata.normalize('NFKC', value)
    else:
        value = unicodedata.normalize('NFKD', value).encode('ascii', 'ignore').decode('ascii')
    value = re.sub(r'[^\w\s-]', '', value.lower())
    return re.sub(r'[-\s]+', '-', value).strip('-_')


def main():
    """split a music track into specified sub-tracks by calling ffmpeg from the shell"""

    # check command line for original file and track list file
    if len(sys.argv) != 3:
        print("usage: split <original_track> <track_list>")
        exit(1)

    # record command line args
    original_track = sys.argv[1]
    original_track_ext = os.path.splitext(original_track)[1]
    track_list = sys.argv[2]

    # read each line of the track list and split into start, name
    with open(track_list, "r") as f:
        start=[]
        name=[]
        for line in f:
            # skip comment and empty lines
            if line.startswith("#") or len(line) <= 1:
                continue

            # create command string for a given track
            s,  n = line.strip().split(' ',1)
            start.append(s)
            name.append(slugify(n))

    # create end array        
    end = start.copy()
    end.pop(0)
    end.append("9999:00:00")

    # create a template of the ffmpeg call in advance
    cmd_string = "ffmpeg -hide_banner -i \'{tr}\' -vn -acodec copy -ss {st} -to {en} \'{nm}\'" + original_track_ext

    i = 1
    for st,en,nm in zip(start, end, name):
        command = cmd_string.format(tr=original_track, st=st, en=en, nm='{:02d}'.format(i) + ' - ' + nm)
        print(command)
        subprocess.call(command, shell=True)    
        print("---------------------------------------------------------------------------------------")
        i = i+1
    return None

if __name__ == "__main__":
    main()

https://github.com/caos-linux/split-audio-by-tracklist

1
  • finally a simple way to split large files of audio thank you
    – Sérgio
    Commented Nov 19, 2023 at 1:24

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .