91

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,688
  • 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

9 Answers9

127

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
  • 369,824
62

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.

  • 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,299
  • 1
  • 10
  • 5
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
  • 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
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

bkaiser
  • 103
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

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
  • 269
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(&quot;usage: split &lt;original_track&gt; &lt;track_list&gt;&quot;)
    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, &quot;r&quot;) as f:
    start=[]
    name=[]
    for line in f:
        # skip comment and empty lines
        if line.startswith(&quot;#&quot;) or len(line) &lt;= 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(&quot;9999:00:00&quot;)

# create a template of the ffmpeg call in advance
cmd_string = &quot;ffmpeg -hide_banner -i \'{tr}\' -vn -acodec copy -ss {st} -to {en} \'{nm}\'&quot; + 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(&quot;---------------------------------------------------------------------------------------&quot;)
    i = i+1
return None

if name == "main": main()

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