Skip to content

Instantly share code, notes, and snippets.

@wjkoh
Last active August 10, 2025 06:19
Show Gist options
  • Select an option

  • Save wjkoh/5f8748a589507dea28875f1301b24ffb to your computer and use it in GitHub Desktop.

Select an option

Save wjkoh/5f8748a589507dea28875f1301b24ffb to your computer and use it in GitHub Desktop.
Go + FFmpeg: How to Segment (Chunk) Audio File
type File struct {
Name string
Start time.Duration
End time.Duration
ContentType string
}
func SegmentAudio(ctx context.Context, filename string, maxDuration time.Duration, dir string, pattern string) ([]*File, error) {
if dir == "" {
dir = os.TempDir()
}
if pattern == "" {
pattern = "%d"
}
if strings.Contains(fmt.Sprintf(pattern, 0), "%!") {
return nil, fmt.Errorf("invalid pattern: %v", pattern)
}
formatName, err := FormatName(ctx, filename)
if err != nil {
return nil, err
}
segmentTime := strconv.FormatFloat(maxDuration.Seconds(), 'f', -1, 64)
f, err := os.CreateTemp("", "")
if err != nil {
return nil, err
}
defer os.Remove(f.Name())
defer f.Close()
ctx, cancel := context.WithCancel(ctx)
defer cancel()
cmd := exec.CommandContext(
ctx,
"ffmpeg",
"-f", formatName,
"-i", filename,
"-codec", "copy",
"-map", "0",
"-f", "segment",
"-segment_list_type", "csv",
"-segment_list", f.Name(),
"-segment_time", segmentTime,
"-segment_format", formatName,
filepath.Join(dir, pattern),
)
stderr, err := cmd.StderrPipe()
if err != nil {
return nil, err
}
if err := cmd.Start(); err != nil {
return nil, err
}
slurp, _ := io.ReadAll(stderr)
slog.Debug("ffmpeg", slog.String("stderr", string(slurp)))
if err := cmd.Wait(); err != nil {
return nil, err
}
var files []*File
csvReader := csv.NewReader(f)
for {
record, err := csvReader.Read()
if err == io.EOF {
break
}
if err != nil {
return nil, err
}
if len(record) != 3 {
return nil, fmt.Errorf("invalid segment list: %v", record)
}
fname := filepath.Join(dir, record[0])
f, err := os.Open(fname)
if err != nil {
return nil, err
}
defer f.Close()
contentType, err := detectContentType(f)
if err != nil {
return nil, err
}
start, err := time.ParseDuration(record[1] + "s")
if err != nil {
return nil, err
}
end, err := time.ParseDuration(record[2] + "s")
if err != nil {
return nil, err
}
files = append(files, &File{Name: fname, Start: start, End: end, ContentType: contentType})
}
return files, nil
}
func FormatName(ctx context.Context, filename string) (string, error) {
ctx, cancel := context.WithCancel(ctx)
defer cancel()
cmd := exec.CommandContext(ctx, "ffprobe", "-i", filename, "-show_format", "-output_format", "json")
stdout, err := cmd.StdoutPipe()
if err != nil {
return "", nil
}
if err := cmd.Start(); err != nil {
return "", nil
}
var output struct {
Format struct {
FormatName string `json:"format_name"`
Duration string `json:"duration"`
} `json:"format"`
}
if err := json.NewDecoder(stdout).Decode(&output); err != nil {
return "", nil
}
if err := cmd.Wait(); err != nil {
return "", nil
}
return output.Format.FormatName, nil
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment