Skip to content

Commit

Permalink
feat: spireav version 1.5 (#12)
Browse files Browse the repository at this point in the history
* feat: standardized transforms / complex filters

* chore: remove memfs

* feat: api improvements to filter graphs

* chore: code cleanup and refactoring

* chore: cleanup method names on Graph

* chore: remove unnecessary extra files

* chore: remove unnecessary extra files

* chore: more cleanup and refactoring
  • Loading branch information
connerdouglass authored Mar 7, 2024
1 parent b053831 commit 78070e7
Show file tree
Hide file tree
Showing 53 changed files with 788 additions and 4,272 deletions.
1 change: 1 addition & 0 deletions ffmpeg_path.go → binary_path.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ package spireav
var (
FfmpegPath = "ffmpeg"
FfprobePath = "ffprobe"
Mxf2RawPath = ""
)
51 changes: 25 additions & 26 deletions examples/graph/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,43 +6,37 @@ import (
"time"

"github.com/spiretechnology/spireav"
"github.com/spiretechnology/spireav/graph"
"github.com/spiretechnology/spireav/graph/output"
"github.com/spiretechnology/spireav/graph/transform"
"github.com/spiretechnology/spireav/filter/drawtext"
"github.com/spiretechnology/spireav/filter/expr"
"github.com/spiretechnology/spireav/filter/scale"
"github.com/spiretechnology/spireav/output"
)

func main() {

// Create a new graph
g := graph.New()
inputNode := g.NewInput("reference-media/BigBuckBunny.mp4")
outputNode := g.NewOutput(
g := spireav.New()
inputNode := g.Input("reference-media/BigBuckBunny.mp4")
outputNode := g.Output(
"reference-outputs/BigBuckBunny-GRAPH.mp4",
output.WithFormatMP4(),
)

// Create a text overlay node
textOverlay := g.AddTransform(&transform.TextOverlay{
Text: "SpireAV Test",
})
textOverlay := g.Filter(drawtext.DrawText(
drawtext.WithText("SpireAV Test"),
))

// Create a text overlay node
scale := g.AddTransform(&transform.Scale{
Width: 200,
Height: 200,
})
scaleNode := g.Filter(scale.Scale(
scale.WithWidth(expr.Int(200)),
scale.WithHeight(expr.Int(200)),
))

// Link together the nodes to create a video processing pipeline
g.AddLink(inputNode, 0, scale, 0)
g.AddLink(scale, 0, textOverlay, 0)
g.AddLink(textOverlay, 0, outputNode, 0)
g.AddLink(inputNode, 1, outputNode, 1)

// Create the process
p := spireav.Process{
FfmpegArger: g,
EstimatedLengthFrames: 14315,
}
inputNode.Stream(0).Pipe(scaleNode, 0)
scaleNode.Stream(0).Pipe(textOverlay, 0)
textOverlay.Stream(0).Pipe(outputNode, 0)
inputNode.Stream(1).Pipe(outputNode, 1)

// Create a progress handler function
progressFunc := func(progress spireav.Progress) {
Expand All @@ -55,9 +49,14 @@ func main() {
ctx, cancel := context.WithTimeout(ctx, 60*time.Second)
defer cancel()

// Create the process
runner := spireav.NewRunner(g,
spireav.WithEstimatedLengthFrames(14315),
spireav.WithProgressCallback(progressFunc),
)

// Run the transcoding job
if err := p.RunWithProgress(ctx, progressFunc); err != nil {
if err := runner.Run(ctx); err != nil {
fmt.Println(err.Error())
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,21 @@ import (
"os/signal"
"syscall"

"github.com/spiretechnology/spireav/mxf2raw"
"github.com/spiretechnology/spireav"
"github.com/spiretechnology/spireav/metadata"
)

func main() {
// Setup the mxf2raw path
mxf2raw.Mxf2RawPath = "/Users/conner/Desktop/bmx/out/build/apps/mxf2raw/mxf2raw"
spireav.Mxf2RawPath = "/Users/conner/Desktop/bmx/out/build/apps/mxf2raw/mxf2raw"

// Create the context for the process
ctx := context.Background()
ctx, cancel := signal.NotifyContext(ctx, syscall.SIGINT, syscall.SIGTERM, syscall.SIGKILL)
defer cancel()

// Get the metadata for a file
metadata, err := mxf2raw.GetMetadataWithOptions(ctx, "/Users/conner/Downloads/Stage AAF/AA01B7EAC60C.mxf", nil)
metadata, err := metadata.GetMxfMetadata(ctx, "/Users/conner/Downloads/Stage AAF/AA01B7EAC60C.mxf")
if err != nil {
panic(err)
}
Expand Down
4 changes: 2 additions & 2 deletions examples/metadata/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@ import (
"context"
"fmt"

"github.com/spiretechnology/spireav"
"github.com/spiretechnology/spireav/metadata"
)

func main() {

// Get the metadata for a file
metadata, err := spireav.GetMetadata(
metadata, err := metadata.GetMetadata(
context.Background(),
"reference-media/BigBuckBunny.mp4",
)
Expand Down
13 changes: 0 additions & 13 deletions ffmpeg_arger.go

This file was deleted.

24 changes: 24 additions & 0 deletions ffmpeg_error.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package spireav

import (
"fmt"
"strings"
)

type FfmpegError struct {
ExitCode int
Logs []string
child error
}

func (e *FfmpegError) Error() string {
if len(e.Logs) > 0 {
return fmt.Sprintf("ffmpeg exit code %d: %s", e.ExitCode, strings.Join(e.Logs, "\n"))
} else {
return fmt.Sprintf("ffmpeg exit code %d", e.ExitCode)
}
}

func (e *FfmpegError) Unwrap() error {
return e.child
}
16 changes: 16 additions & 0 deletions filter/amerge/amerge.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package amerge

import (
"github.com/spiretechnology/spireav/filter"
"github.com/spiretechnology/spireav/filter/expr"
)

// https://ffmpeg.org/ffmpeg-filters.html#amerge

func AudioMerge(opts ...filter.Option) filter.Filter {
return filter.New("amerge", 1, opts...)
}

func WithInputs(inputs int) filter.Option {
return filter.WithOption("inputs", expr.Int(inputs))
}
36 changes: 36 additions & 0 deletions filter/crop/crop.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package crop

import (
"github.com/spiretechnology/spireav/filter"
"github.com/spiretechnology/spireav/filter/expr"
)

// https://ffmpeg.org/ffmpeg-filters.html#toc-crop

func Crop(opts ...filter.Option) filter.Filter {
return filter.New("crop", 1, opts...)
}

func WithWidth(width expr.Expr) filter.Option {
return filter.WithOption("w", width)
}

func WithHeight(height expr.Expr) filter.Option {
return filter.WithOption("h", height)
}

func WithX(x expr.Expr) filter.Option {
return filter.WithOption("x", x)
}

func WithY(y expr.Expr) filter.Option {
return filter.WithOption("y", y)
}

func WithKeepAspect(keepAspect bool) filter.Option {
return filter.WithOption("keep_aspect", expr.Bool(keepAspect))
}

func WithExact(exact bool) filter.Option {
return filter.WithOption("exact", expr.Bool(exact))
}
51 changes: 51 additions & 0 deletions filter/drawtext/drawtext.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package drawtext

import (
"github.com/spiretechnology/spireav/filter"
"github.com/spiretechnology/spireav/filter/expr"
)

func DrawText(opts ...filter.Option) filter.Filter {
return filter.New("drawtext", 1, opts...)
}

func WithText(text string) filter.Option {
return filter.WithOption("text", expr.String(text))
}

func WithX(x expr.Expr) filter.Option {
return filter.WithOption("x", x)
}

func WithY(y expr.Expr) filter.Option {
return filter.WithOption("y", y)
}

func WithFontColor(color string) filter.Option {
return filter.WithOption("fontcolor", expr.String(color))
}

func WithFontFile(file string) filter.Option {
return filter.WithOption("fontfile", expr.String(file))
}

func WithFontSize(size expr.Expr) filter.Option {
return filter.WithOption("fontsize", size)
}

func WithTimecode(tc, framerate string) filter.Option {
if len(framerate) == 0 {
framerate = "23.976"
}
return filter.WithMulti(
filter.WithOption("timecode", expr.String(tc)),
filter.WithOption("r", expr.String(framerate)),
)
}

func WithBox(color string) filter.Option {
return filter.WithMulti(
filter.WithOption("box", expr.String("1")),
filter.WithOption("boxcolor", expr.String(color)),
)
}
9 changes: 9 additions & 0 deletions graph/transform/expr/expr.go → filter/expr/expr.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,15 @@ func String(str string) Expr {
return Expr(str)
}

// Bool creates an expression for a boolean value.
func Bool(val bool) Expr {
if val {
return Expr("1")
} else {
return Expr("0")
}
}

// Var creates an expression for a variable.
func Var(name string) Expr {
return Expr(name)
Expand Down
102 changes: 102 additions & 0 deletions filter/filter.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
package filter

import (
"fmt"
"strings"

"github.com/spiretechnology/spireav/filter/expr"
)

// Filter is a node that applies a filter to its inputs, producing one or more outputs.
type Filter interface {
String() string
Outputs() int
}

// Option defines an output option middleware that populates some option string
// into the slice of options for the output.
type Option func() []OptionValue

// New creates a new transform with the given name and some options.
func New(name string, outputs int, opts ...Option) Filter {
return &filterWithOpts{
name: name,
outputs: outputs,
opts: WithMulti(opts...)(),
}
}

// WithOption creates a new option middleware with a key and value.
func WithOption(key string, value expr.Expr) Option {
return func() []OptionValue {
return []OptionValue{
{
Key: key,
Val: value,
},
}
}
}

// WithMulti creates a new option middleware with multiple options.
func WithMulti(opts ...Option) Option {
return func() []OptionValue {
var options []OptionValue
for _, opt := range opts {
options = append(options, opt()...)
}
return options
}
}

// OptionValue defines a single option for a transform node. This includes only a single
// key-value pair.
type OptionValue struct {
Key string
Val expr.Expr
}

type filterWithOpts struct {
name string
outputs int
opts []OptionValue
}

func (t *filterWithOpts) String() string {
opts := make(map[string]string)
for _, optValue := range t.opts {
opts[optValue.Key] = optValue.Val.String()
}
return formatTransform(t.name, opts)
}

func (t *filterWithOpts) Outputs() int {
return t.outputs
}

// formatTransform formats a filter transform so that it conforms to the FFMpeg format
func formatTransform(filterName string, opts map[string]string) string {
optsStr := formatTransformOptions(opts)
if len(optsStr) == 0 {
return filterName
}
return fmt.Sprintf("%s=%s", filterName, optsStr)
}

// formatTransformOptions converts a map of keys and values into a string of options
func formatTransformOptions(opts map[string]string) string {
var keyvals []string
for k, v := range opts {
pair := fmt.Sprintf("%s=%s", k, formatTransformOptionValue(v))
keyvals = append(keyvals, pair)
}
return strings.Join(keyvals, ":")
}

// formatTransformOptionValue formats a single value to ensure it's properly escaped and quote-wrapped
func formatTransformOptionValue(value string) string {
if strings.Contains(value, ":") && !strings.HasPrefix(value, "'") {
return fmt.Sprintf("'%s'", value)
}
return value
}
Loading

0 comments on commit 78070e7

Please sign in to comment.