Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 18 additions & 2 deletions ffmpeg.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
type FilterComplexBuilder struct {
filterComplex *strings.Builder
style *StyleOptions
framerate int
termWidth int
termHeight int
prevStageName string
Expand Down Expand Up @@ -50,6 +51,7 @@ func NewVideoFilterBuilder(videoOpts *VideoOptions) *FilterComplexBuilder {
termHeight: termHeight,
termWidth: termWidth,
style: videoOpts.Style,
framerate: videoOpts.Framerate,
prevStageName: "padded",
}
}
Expand Down Expand Up @@ -104,17 +106,30 @@ func calcTermDimensions(style StyleOptions) (int, int) {
return width, height
}

// fps pins a secondary stream to the target framerate. The window bar and
// margin fill sources (lavfi color, looped images) all default to 25fps, and
// they are the *main* input of the overlays below, so without this the whole
// render is forced to 25fps regardless of `Set Framerate`.
// Returns an empty string for screenshots, which have no framerate.
func (fb *FilterComplexBuilder) fps() string {
if fb.framerate <= 0 {
return ""
}
return fmt.Sprintf(",fps=%d", fb.framerate)
}

// WithWindowBar adds window bar options to ffmepg filter_complex.
func (fb *FilterComplexBuilder) WithWindowBar(barStream int) *FilterComplexBuilder {
if fb.style.WindowBar != "" {
fb.filterComplex.WriteString(";")
_, _ = fmt.Fprintf(
fb.filterComplex,
`
[%d]loop=-1[loopbar];
[%d]loop=-1%s[loopbar];
[loopbar][%s]overlay=0:%d[withbar]
`,
barStream,
fb.fps(),
fb.prevStageName,
fb.style.WindowBarSize,
)
Expand Down Expand Up @@ -154,12 +169,13 @@ func (fb *FilterComplexBuilder) WithMarginFill(marginStream int) *FilterComplexB
_, _ = fmt.Fprintf(
fb.filterComplex,
`
[%d]scale=%d:%d[bg];
[%d]scale=%d:%d%s[bg];
[bg][%s]overlay=(W-w)/2:(H-h)/2:shortest=1[withbg]
`,
marginStream,
fb.style.Width,
fb.style.Height,
fb.fps(),
fb.prevStageName,
)
fb.prevStageName = "withbg"
Expand Down
43 changes: 43 additions & 0 deletions ffmpeg_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package main

import (
"strings"
"testing"
)

// The margin fill and window bar sources default to 25fps and are the *main*
// input of their overlay, so they must be pinned to the target framerate or
// they silently override `Set Framerate`.
func TestSecondaryStreamsUseFramerate(t *testing.T) {
opts := DefaultVideoOptions()
opts.Framerate = 60
opts.Style = DefaultStyleOptions()
opts.Style.WindowBar = "Colorful"
opts.Style.MarginFill = "#000000"

got := strings.Join(NewVideoFilterBuilder(&opts).
WithWindowBar(2).
WithMarginFill(3).
Build(), " ")

for _, want := range []string{"loop=-1,fps=60[loopbar]", ",fps=60[bg]"} {
if !strings.Contains(got, want) {
t.Errorf("filter_complex missing %q\ngot: %s", want, got)
}
}
}

// Screenshots have no framerate; an fps filter there would be `fps=0`.
func TestScreenshotOmitsFramerate(t *testing.T) {
style := DefaultStyleOptions()
style.WindowBar = "Colorful"

got := strings.Join(NewScreenshotFilterComplexBuilder(style).
WithWindowBar(2).
WithMarginFill(3).
Build(), " ")

if strings.Contains(got, "fps=") {
t.Errorf("screenshot filter should not set fps, got: %s", got)
}
}