85

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?

DisplayName
  • 11,468
  • 20
  • 73
  • 115
  • 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. –  May 03 '16 at 11:35
  • 1
    @siblynx As I explained in the question, split every second of an audio file into new audio files. – DisplayName May 03 '16 at 11:58

8 Answers8

114

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

slm
  • 363,520
  • 117
  • 767
  • 871
59

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.

isosceleswheel
  • 731
  • 5
  • 6
  • 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 Jan 31 '18 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. – isosceleswheel Feb 01 '18 at 03: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 Jun 18 '18 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 Jun 18 '18 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. – isosceleswheel Jun 19 '18 at 17:48
  • @isosceleswheel Can it parse text file name and audio filename with blank space? – Porcupine Oct 13 '18 at 07:18
  • This method is not gapless, at least when using Opus. – Display Name Nov 10 '18 at 10:01
  • @TimSmith nice work! But how do I also specify track names along with track times? (I have no experience with bash). – Sevastyan Nov 10 '18 at 16:50
  • @Sevastyan `source="audio.mp3"; i=0; t1=0:00; for descr in 1:00,'first track' 2:00 3:00,'third track, ok' 4:00.5,fourth-track '','track #5!'; do i=$((i+1)); t0=$t1 t1=${descr%%,*} title=${descr#*,}; [[ -z "$title" || "$title" = "$descr" ]] && title=$(printf "track%02d" $i); echo ffmpeg -i "$source" -acodec copy -ss $t0 ${t1:+-to} $t1 $(printf "%s.%s" "$title" ${source##*.}); done` – Tim Siegel Feb 02 '19 at 22:34
  • +1, stumbled across the question while actually looking for something like you wrote. – Andreas H. May 09 '19 at 14:23
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.

Stryker
  • 1,209
  • 1
  • 10
  • 5
3

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
Rootless
  • 31
  • 2
  • 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 Jan 25 '19 at 11:25
2

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

bkaiser
  • 103
  • 3
tisaconundrum
  • 195
  • 1
  • 7
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"
Alex
  • 121
  • 2
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
KFL
  • 259
  • 1
  • 10