Last active
September 22, 2025 18:57
-
-
Save wjkoh/eb49409a2bce7ef117fec3ff4a3d24a0 to your computer and use it in GitHub Desktop.
Go: How to Cut or Chunk an Audio File using FFmpeg
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| package main | |
| import ( | |
| "context" | |
| "encoding/json" | |
| "fmt" | |
| "io" | |
| "log/slog" | |
| "os/exec" | |
| "strings" | |
| "time" | |
| ) | |
| type audioCutter struct{} | |
| func (s *audioCutter) Duration(ctx context.Context, filename string) (time.Duration, error) { | |
| f, err := s.format(ctx, filename) | |
| if err != nil { | |
| return time.Duration(0), err | |
| } | |
| return time.ParseDuration(f.Duration + "s") | |
| } | |
| func (s *audioCutter) Cut( | |
| ctx context.Context, w io.Writer, filename string, start time.Duration, end time.Duration, | |
| ) error { | |
| f, err := s.format(ctx, filename) | |
| if err != nil { | |
| return err | |
| } | |
| ctx, cancel := context.WithCancel(ctx) | |
| defer cancel() | |
| // -ss should be after -i. Check out https://trac.ffmpeg.org/wiki/Seeking#Cutting | |
| cmd := exec.CommandContext( | |
| ctx, | |
| "ffmpeg", | |
| "-f", f.FormatName, | |
| "-i", filename, | |
| "-ss", FormatHMS(start), | |
| "-to", FormatHMS(end), | |
| "-codec", "copy", | |
| "-f", f.FormatName, | |
| "-", | |
| ) | |
| cmd.Stdout = w | |
| var stderr strings.Builder | |
| cmd.Stderr = &stderr | |
| if err := cmd.Start(); err != nil { | |
| slog.Debug("ffmpeg", "err", err, slog.String("stderr", stderr.String())) | |
| return err | |
| } | |
| if err := cmd.Wait(); err != nil { | |
| slog.Debug("ffmpeg", "err", err, slog.String("stderr", stderr.String())) | |
| return err | |
| } | |
| return nil | |
| } | |
| type format struct { | |
| FormatName string `json:"format_name"` | |
| Duration string `json:"duration"` | |
| } | |
| func (s *audioCutter) format(ctx context.Context, filename string) (*format, error) { | |
| ctx, cancel := context.WithCancel(ctx) | |
| defer cancel() | |
| cmd := exec.CommandContext(ctx, "ffprobe", "-i", filename, "-show_format", "-output_format", "json") | |
| var stderr strings.Builder | |
| cmd.Stderr = &stderr | |
| stdout, err := cmd.StdoutPipe() | |
| if err != nil { | |
| return nil, err | |
| } | |
| if err := cmd.Start(); err != nil { | |
| slog.Debug("ffmpeg", "err", err, slog.String("stderr", stderr.String())) | |
| return nil, err | |
| } | |
| var output struct { | |
| Format format `json:"format"` | |
| } | |
| if err := json.NewDecoder(stdout).Decode(&output); err != nil { | |
| return nil, err | |
| } | |
| if err := cmd.Wait(); err != nil { | |
| slog.Debug("ffmpeg", "err", err, slog.String("stderr", stderr.String())) | |
| return nil, err | |
| } | |
| return &output.Format, nil | |
| } | |
| func FormatHMS(d time.Duration) string { | |
| m, s := (d%time.Hour)/time.Minute, d%time.Minute/time.Second | |
| if hours := d / time.Hour; hours > 0 { | |
| return fmt.Sprintf("%02d:%02d:%02d", hours, m, s) | |
| } | |
| return fmt.Sprintf("%02d:%02d", m, s) | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment