stream-poc2/ffmpeg/ffmpeg.go
2025-10-09 22:21:38 +02:00

120 lines
3.3 KiB
Go

package ffmpeg
import (
"fmt"
"io"
"log"
"os"
"os/exec"
"path"
"strconv"
"stream-poc2/model"
)
// EncodeChunk doesn't have the nicest function signature...
func EncodeChunk(profileSettings model.VideoProfileSettings, nativeHeight int, preset string, origSegmentPath string, writer io.Writer) error {
ffmpegArgs := []string{
"-nostdin", "-hide_banner", "-loglevel", "error",
}
if profileSettings.Resolution.Height != nativeHeight {
// apply resize filter
ffmpegArgs = append(ffmpegArgs,
"-hwaccel", "cuda", "-hwaccel_output_format", "cuda",
"-i", origSegmentPath,
"-vf", fmt.Sprintf("scale_cuda=%d:%d", profileSettings.Resolution.Width, profileSettings.Resolution.Height),
)
} else {
// just load the segment
ffmpegArgs = append(ffmpegArgs,
"-i", origSegmentPath,
)
}
ffmpegArgs = append(ffmpegArgs,
"-copyts",
"-c:v", profileSettings.FFMpegCodec(),
)
if preset != "" {
ffmpegArgs = append(ffmpegArgs,
"-preset", preset,
)
}
ffmpegArgs = append(ffmpegArgs,
"-b:v", strconv.Itoa(profileSettings.AvgBitrate), "-maxrate", strconv.Itoa(profileSettings.MaxBitrate), "-bufsize", "1M",
"-an", // <- no audio
"-f", "mpegts", "-mpegts_copyts", "1",
"-",
)
log.Println("exec ffmpeg args: ", ffmpegArgs)
cmd := exec.Command("/usr/bin/ffmpeg", ffmpegArgs...)
cmd.Stdout = writer
cmd.Stderr = os.Stderr
err := cmd.Run()
if err != nil {
log.Println("Failed to run:", err)
return err
}
return nil
}
// MakeSegments has the ugliest function signature no the planet
func MakeSegments(filepath, outdir string, audioStreams []model.AudioStream) (string, []string, error) {
segmentsVideoCSV := path.Join(outdir, "segments_video.csv")
segmentsAudioCSV := make([]string, len(audioStreams))
// do slicing
ffmpegArgs := []string{
"-nostdin", "-hide_banner", "-loglevel", "error",
"-i", filepath,
// video stream
"-c:v", "copy", "-an",
"-f", "ssegment", "-segment_format", "mpegts", "-segment_list", segmentsVideoCSV,
"-segment_list_type", "csv", "-segment_time", strconv.Itoa(SlicerTargetDurationSec), path.Join(outdir, "video", "s%d.ts"),
}
for i, audioStream := range audioStreams {
csvName := path.Join(outdir, fmt.Sprintf("segments_audio_%d.csv", audioStream.Index))
segmentsAudioCSV[i] = csvName
ffmpegArgs = append(ffmpegArgs,
// audio streams (encode to mp3)
"-vn", "-map", fmt.Sprintf("0:a:%d", i),
)
if audioStream.Codec != "mp3" {
// mp3 always works
// TODO: This does not update the stream info in the metadata !!!
log.Println("Transcoding audio to mp3 for stream ", audioStream.Index)
ffmpegArgs = append(ffmpegArgs,
"-c:a", "libmp3lame", "-q:a", "0",
)
} else {
log.Println("Not transcoding audio as it is already mp3 for stream ", audioStream.Index)
ffmpegArgs = append(ffmpegArgs,
"-c:a", "copy",
)
}
ffmpegArgs = append(ffmpegArgs,
"-f", "ssegment", "-segment_format", "mpegts", "-segment_list", csvName,
"-segment_list_type", "csv", "-segment_time", strconv.Itoa(SlicerTargetDurationSec), path.Join(outdir, "audio", strconv.Itoa(audioStream.Index), "s%d.ts"),
)
}
cmd := exec.Command("/usr/bin/ffmpeg", ffmpegArgs...)
cmd.Stderr = os.Stderr
err := cmd.Run()
if err != nil {
log.Println("failed to segment:", err)
return "", nil, err
}
return segmentsVideoCSV, segmentsAudioCSV, nil
}