Skip to content

Commit fd9de69

Browse files
committed
initial commit
0 parents  commit fd9de69

File tree

17 files changed

+841
-0
lines changed

17 files changed

+841
-0
lines changed

LICENSE.txt

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
Copyright (c) 2021 Modfy, Inc.
2+
3+
Permission is hereby granted, free of charge, to any person obtaining a copy
4+
of this software and associated documentation files (the "Software"), to deal
5+
in the Software without restriction, including without limitation the rights
6+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7+
copies of the Software, and to permit persons to whom the Software is
8+
furnished to do so, subject to the following conditions:
9+
10+
The above copyright notice and this permission notice shall be included in
11+
all copies or substantial portions of the Software.
12+
13+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19+
THE SOFTWARE.

README.md

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
# go-fluent-ffmpeg
2+
3+
A Go version of [node-fluent-ffmpeg](https://github.com/fluent-ffmpeg/node-fluent-ffmpeg).
4+
5+
## Installation
6+
`go get -u github.com/modfy/fluent-ffmpeg`
7+
8+
### Requirements
9+
You will need FFmpeg installed on your machine, or you can specify a path to a binary:
10+
11+
```go
12+
// Provide an empty string to use default FFmpeg path
13+
cmd := fluentffmpeg.NewCommand("")
14+
15+
// Specify a path
16+
cmd = fluentffmpeg.NewCommand("/path/to/ffmpeg/binary")
17+
```
18+
19+
## Quick Start
20+
21+
Create and run commands using an API similar to node-fluent-ffmpeg:
22+
23+
```go
24+
err := fluentffmpeg.NewCommand("").
25+
InputPath("/path/to/video.avi").
26+
OutputFormat("mp4").
27+
OutputPath("/path/to/video.mp4").
28+
Run()
29+
```
30+
31+
If you want to view the errors/logs returned from FFmpeg, provide an io.Writer to receive the data.
32+
```go
33+
buf := &bytes.Buffer{}
34+
err := fluentffmpeg.NewCommand("").
35+
InputPath("./video.avi").
36+
OutputFormat("mp4").
37+
OutputPath("./video.mp4").
38+
Overwrite(true).
39+
OutputLogs(buf). // provide a io.Writer
40+
Run()
41+
42+
out, _ := ioutil.ReadAll(buf) // read logs
43+
fmt.Println(string(out))
44+
```
45+
46+
You can also get the command in the form of an [exec.Cmd](https://golang.org/pkg/os/exec/#Cmd) struct, with which you can have better control over the running process. For example, you can conditionally kill the FFmpeg command:
47+
48+
```go
49+
done := make(chan error, 1)
50+
cmd := fluentffmpeg.NewCommand("").
51+
InputPath("./video.avi").
52+
OutputFormat("mp4").
53+
OutputPath("./video.mp4").
54+
Overwrite(true).
55+
Build()
56+
cmd.Start()
57+
58+
go func() {
59+
done <- cmd.Wait()
60+
}()
61+
62+
select {
63+
case <-time.After(time.Second * 5):
64+
fmt.Println("Timed out")
65+
cmd.Process.Kill()
66+
case <-done:
67+
}
68+
```

args.go

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package fluentffmpeg
2+
3+
// Args contains the input and output args set for FFmpeg
4+
type Args struct {
5+
input inputArgs
6+
output outputArgs
7+
}
8+
9+
type inputArgs struct {
10+
inputPath string
11+
pipeInput bool
12+
fromFormat string
13+
nativeFramerateInput bool `getter:"none"`
14+
}
15+
16+
type outputArgs struct {
17+
outputPath string
18+
format string
19+
pipeOutput bool
20+
overwrite bool
21+
resolution string `getter:"none"`
22+
aspectRatio string
23+
pixelFormat string
24+
quality int
25+
preset string
26+
bufferSize int
27+
audioBitrate int
28+
audioChannels int
29+
keyframeInterval int
30+
audioCodec string
31+
videoBitRate int
32+
videoBitRateTolerance int
33+
videoMaxBitrate int
34+
videoMinBitrate int
35+
videoCodec string
36+
vFrames int
37+
frameRate int
38+
audioRate int
39+
}

command.go

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
package fluentffmpeg
2+
3+
import (
4+
"io"
5+
"os/exec"
6+
"reflect"
7+
"strings"
8+
9+
"github.com/fatih/structs"
10+
)
11+
12+
// Command is a struct that holds arguments and their values to run FFmpeg
13+
type Command struct {
14+
FFmpegPath string
15+
Args *Args
16+
input io.Reader
17+
output io.Writer
18+
logs io.Writer
19+
}
20+
21+
// NewCommand returns a new Command
22+
func NewCommand(ffmpegPath string) *Command {
23+
if ffmpegPath == "" {
24+
ffmpegPath = "ffmpeg"
25+
}
26+
return &Command{
27+
FFmpegPath: ffmpegPath,
28+
Args: &Args{},
29+
}
30+
}
31+
32+
// Run runs the FFmpeg command. It returns an error if the command fails with exit status code 1. This error message only signifies that
33+
// the command returned a non-zero status code, read from stderr to see more comprehensive FFmpeg errors.
34+
func (c *Command) Run() error {
35+
return c.Build().Run()
36+
}
37+
38+
// Build returns an exec.Cmd struct ready to run the FFmpeg command with its arguments
39+
func (c *Command) Build() *exec.Cmd {
40+
cmd := exec.Command(c.FFmpegPath, c.GetArgs()...)
41+
42+
if c.input != nil {
43+
cmd.Stdin = c.input
44+
}
45+
46+
if c.output != nil {
47+
cmd.Stdout = c.output
48+
}
49+
50+
if c.logs != nil {
51+
cmd.Stderr = c.logs
52+
}
53+
54+
return cmd
55+
}
56+
57+
// GetArgs returns the arguments for the FFmpeg command.
58+
func (c *Command) GetArgs() []string {
59+
var inputs []string
60+
var outputs []string
61+
62+
inputs = c.getArgs(c.Args.input, "pipeInput", "inputPath")
63+
outputs = c.getArgs(c.Args.output, "pipeOutput", "outputPath")
64+
65+
return append(inputs, outputs...)
66+
}
67+
68+
func (c *Command) getArgs(argType interface{}, targetNames ...string) []string {
69+
var options []string
70+
var target []string
71+
72+
fields := structs.Names(argType)
73+
74+
// Iterates through the fields,
75+
// and calls its corresponding getter function.
76+
for _, v := range fields {
77+
option := true
78+
if containsString(targetNames, v) {
79+
option = false
80+
}
81+
value := reflect.ValueOf(c.Args).MethodByName("Get" + strings.Title(v))
82+
if (value != reflect.Value{}) {
83+
result := value.Call([]reflect.Value{})
84+
if v, ok := result[0].Interface().([]string); ok {
85+
if option {
86+
options = append(options, v...)
87+
} else {
88+
target = append(target, v...)
89+
}
90+
}
91+
}
92+
}
93+
94+
return append(options, target...)
95+
}
96+
97+
// OutputLogs sets the destination to write the FFmpeg log output to
98+
func (c *Command) OutputLogs(writer io.Writer) *Command {
99+
c.logs = writer
100+
return c
101+
}

example/file/file.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package main
2+
3+
import (
4+
"bytes"
5+
"fmt"
6+
"io/ioutil"
7+
"log"
8+
9+
fluentffmpeg "github.com/modfy/fluent-ffmpeg"
10+
)
11+
12+
func main() {
13+
buf := &bytes.Buffer{}
14+
err := fluentffmpeg.NewCommand("").
15+
InputPath("./video.avi").
16+
OutputFormat("mp4").
17+
OutputPath("./video.mp4").
18+
Overwrite(true).
19+
OutputLogs(buf).
20+
Run()
21+
22+
if err != nil {
23+
log.Fatal(err)
24+
}
25+
26+
out, _ := ioutil.ReadAll(buf)
27+
fmt.Println(string(out))
28+
}

example/pipe/pipe.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package main
2+
3+
import (
4+
"net/http"
5+
6+
fluentffmpeg "github.com/modfy/fluent-ffmpeg"
7+
)
8+
9+
func main() {
10+
http.HandleFunc("/ffmpeg", handle)
11+
12+
http.ListenAndServe(":5000", nil)
13+
}
14+
15+
func handle(w http.ResponseWriter, r *http.Request) {
16+
r.ParseMultipartForm(32 << 20)
17+
file, _, _ := r.FormFile("video")
18+
19+
fluentffmpeg.
20+
NewCommand("").
21+
PipeInput(file).
22+
OutputFormat("flv").
23+
PipeOutput(w).
24+
Run()
25+
}

example/timeout/timeout.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
"time"
6+
7+
fluentffmpeg "github.com/modfy/fluent-ffmpeg"
8+
)
9+
10+
func main() {
11+
done := make(chan error, 1)
12+
cmd := fluentffmpeg.NewCommand("").
13+
InputPath("./video.avi").
14+
OutputFormat("mp4").
15+
OutputPath("./video.mp4").
16+
Overwrite(true).
17+
Build()
18+
cmd.Start()
19+
20+
go func() {
21+
done <- cmd.Wait()
22+
}()
23+
24+
select {
25+
case <-time.After(time.Second * 5):
26+
fmt.Println("Timed out")
27+
cmd.Process.Kill()
28+
case <-done:
29+
}
30+
}

fluentffmpeg_test/args_test.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package fluentffmpeg_test
2+
3+
import (
4+
"bytes"
5+
"reflect"
6+
"strings"
7+
"testing"
8+
9+
fluentffmpeg "github.com/modfy/fluent-ffmpeg"
10+
)
11+
12+
func TestArgsOrder(t *testing.T) {
13+
buff := &bytes.Buffer{}
14+
15+
desired := []string{"-f", "avi", "-i", "pipe:0", "-f", "mp4", "pipe:1"}
16+
args := fluentffmpeg.NewCommand("").
17+
PipeInput(buff).
18+
FromFormat("avi").
19+
OutputFormat("mp4").
20+
PipeOutput(buff).GetArgs()
21+
22+
if !reflect.DeepEqual(args, desired) {
23+
t.Errorf("Got wrong arguments. Expected: \"%s\", but got: \"%s\"", strings.Join(desired, " "), strings.Join(args, " "))
24+
}
25+
}

getters_internal_test.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package fluentffmpeg
2+
3+
import (
4+
"fmt"
5+
"reflect"
6+
"strings"
7+
"testing"
8+
9+
"github.com/fatih/structs"
10+
)
11+
12+
func TestFieldsHaveGetters(t *testing.T) {
13+
cmd := NewCommand("")
14+
15+
if err := testFieldsHaveGetters(cmd.Args.input, cmd); err != nil {
16+
t.Error(err.Error())
17+
return
18+
}
19+
20+
if err := testFieldsHaveGetters(cmd.Args.output, cmd); err != nil {
21+
t.Error(err.Error())
22+
}
23+
}
24+
25+
func testFieldsHaveGetters(argType interface{}, cmd *Command) error {
26+
27+
for _, field := range structs.Names(argType) {
28+
sf, _ := reflect.TypeOf(argType).FieldByName(field)
29+
if sf.Tag.Get("getter") != "none" {
30+
method := reflect.ValueOf(cmd.Args).MethodByName("Get" + strings.Title(field))
31+
if (method == reflect.Value{}) {
32+
return fmt.Errorf("field: (%s) has no getter", field)
33+
}
34+
}
35+
}
36+
37+
return nil
38+
}

go.mod

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
module github.com/modfy/fluent-ffmpeg
2+
3+
go 1.15
4+
5+
require (
6+
github.com/fatih/structs v1.1.0
7+
github.com/pkg/errors v0.9.1
8+
)

0 commit comments

Comments
 (0)