simple version

This commit is contained in:
Justyer
2021-05-01 15:25:22 +08:00
parent e021650142
commit e47ea55150
16 changed files with 357 additions and 134 deletions

3
Makefile Normal file
View File

@@ -0,0 +1,3 @@
.PHONY: test
test:
go test -v ./... -cover

View File

@@ -2,72 +2,63 @@ package ffmpeg
import (
"context"
"errors"
"fmt"
"log"
"os/exec"
"strings"
"fxkt.tech/ffmpeg/filter"
"fxkt.tech/ffmpeg/input"
"fxkt.tech/ffmpeg/output"
)
type FFmpeg struct {
cmd string
infiles []string
filters []*Filter
outputs []*Output
y bool // y is yes for cover output file.
inputs input.Inputs
filters filter.Filters
outputs output.Outputs
Sentence string
}
func NewFFmpeg() *FFmpeg {
func Default() *FFmpeg {
return &FFmpeg{
cmd: "ffmpeg",
y: true,
}
}
func (ff *FFmpeg) ChangeCmd(cmd string) {
ff.cmd = cmd
func (ff *FFmpeg) Yes(y bool) {
ff.y = y
}
func (ff *FFmpeg) AddInputs(inputs ...string) {
ff.infiles = append(ff.infiles, inputs...)
func (ff *FFmpeg) AddInput(inputs ...*input.Input) {
ff.inputs = append(ff.inputs, inputs...)
}
func (ff *FFmpeg) AddFilter(filters ...*Filter) {
func (ff *FFmpeg) AddFilter(filters ...*filter.Filter) {
ff.filters = append(ff.filters, filters...)
}
func (ff *FFmpeg) OutputGraph(output ...*Output) {
ff.outputs = output
func (ff *FFmpeg) AddOutput(outputs ...*output.Output) {
ff.outputs = append(ff.outputs, outputs...)
}
func (ff *FFmpeg) combination() []string {
var params []string
func (ff *FFmpeg) Params() (params []string) {
if ff.y {
params = append(params, "-y")
for _, infile := range ff.infiles {
params = append(params, "-i", infile)
}
params = append(params, ff.inputs.Params()...)
params = append(params, ff.filters.String())
params = append(params, ff.outputs.Params()...)
return
}
var filtersStr []string
for _, filter := range ff.filters {
filtersStr = append(filtersStr, filter.String())
func (ff *FFmpeg) Run(ctx context.Context) (err error) {
cc := exec.CommandContext(ctx, ff.cmd, ff.Params()...)
ff.Sentence = cc.String()
retbytes, err := cc.CombinedOutput()
if err != nil {
return
}
params = append(params, "-filter_complex", strings.Join(filtersStr, ";"))
for _, op := range ff.outputs {
params = append(params, op.params...)
}
return params
}
func (ff *FFmpeg) Run() error {
params := ff.combination()
cmd := exec.CommandContext(context.Background(), ff.cmd, params...)
ff.Sentence = cmd.String()
fmt.Println(ff.Sentence)
outBytes, err := cmd.CombinedOutput()
fmt.Println(string(outBytes))
if err != nil || cmd.ProcessState.ExitCode() != 0 {
sls := strings.Split(string(outBytes), "\n")
err = errors.New(strings.ReplaceAll(strings.Join(sls[len(sls)-4:len(sls)-1], "|"), "\n", "|"))
}
return err
log.Println(string(retbytes))
return
}

41
ffmpeg_test.go Normal file
View File

@@ -0,0 +1,41 @@
package ffmpeg
import (
"context"
"testing"
"fxkt.tech/ffmpeg/filter"
"fxkt.tech/ffmpeg/input"
"fxkt.tech/ffmpeg/output"
)
func TestFFmpeg(t *testing.T) {
ff := Default()
ff.AddInput(
input.New(
input.SetI("vieo.mp4"),
),
)
ff.AddFilter(
filter.New(
filter.SetInStream("0"),
filter.SetContent("scale=trunc(oh*a/2)*2:720"),
filter.SetOutStream("x720"),
),
filter.New(
filter.SetInStream("x720"),
filter.SetContent("delogo=0:0:100:100"),
filter.SetOutStream("xx720"),
),
)
ff.AddOutput(
output.New(
output.SetMap("xx720"),
output.SetMap("0:a"),
output.SetMetadata("comment=yilan888"),
output.SetFile("out720.mp4"),
),
)
ff.Run(context.Background())
t.Log(ff.Sentence)
}

View File

@@ -1,30 +0,0 @@
package ffmpeg
import "fmt"
type Filter struct {
alias string
content string
others []string
}
func NewFilter(alias string, content string, others ...string) *Filter {
return &Filter{
alias: alias,
content: content,
others: others,
}
}
//
func (f *Filter) String() string {
var steamsAlias string
for _, other := range f.others {
steamsAlias = fmt.Sprintf("%s[%s]", steamsAlias, other)
}
var alias string
if f.alias != "" {
alias = fmt.Sprintf("[%s]", f.alias)
}
return fmt.Sprintf("%s%s%s", steamsAlias, f.content, alias)
}

44
filter/filter.go Normal file
View File

@@ -0,0 +1,44 @@
package filter
import (
"fmt"
"strings"
)
type Filter struct {
// instreams []string
// content string
// outstreams []string
opt *option
}
func New(opts ...OptionFunc) *Filter {
o := &option{}
for _, opt := range opts {
opt(o)
}
return &Filter{
opt: o,
}
}
func (f *Filter) Params() (params []string) {
if len(f.opt.instreams) != 0 {
for _, stream := range f.opt.instreams {
params = append(params, fmt.Sprintf("[%s]", stream))
}
}
if f.opt.content != "" {
params = append(params, f.opt.content)
}
if len(f.opt.outstreams) != 0 {
for _, stream := range f.opt.outstreams {
params = append(params, fmt.Sprintf("[%s]", stream))
}
}
return
}
func (f *Filter) String() string {
return strings.Join(f.Params(), "")
}

27
filter/filter_option.go Normal file
View File

@@ -0,0 +1,27 @@
package filter
type OptionFunc func(*option)
type option struct {
instreams []string
content string
outstreams []string
}
func SetInStream(s string) OptionFunc {
return func(o *option) {
o.instreams = append(o.instreams, s)
}
}
func SetContent(c string) OptionFunc {
return func(o *option) {
o.content = c
}
}
func SetOutStream(s string) OptionFunc {
return func(o *option) {
o.outstreams = append(o.outstreams, s)
}
}

16
filter/filters.go Normal file
View File

@@ -0,0 +1,16 @@
package filter
import "strings"
type Filters []*Filter
func (filters Filters) Params() (params []string) {
for _, filter := range filters {
params = append(params, filter.String())
}
return
}
func (filters Filters) String() string {
return strings.Join(filters.Params(), ";")
}

View File

@@ -5,22 +5,33 @@ import (
"strings"
)
// Input is 输入.
// Input is common input info.
type Input struct {
i string // 输入的媒体文件
ss int64 // 媒体文件选择的起始时间点
t int64 //从起始时间开始的持续时间
ext []string // 额外字段
// i string // i is input file.
// ss int64 // ss is starttime.
// t int64 // t is duration.
// ext []string // extra params.
opt *option
}
func New(opts ...OptionFunc) *Input {
o := &option{}
for _, opt := range opts {
opt(o)
}
return &Input{
opt: o,
}
}
func (i *Input) Params() (params []string) {
if i.ss != 0 {
params = append(params, "-ss", strconv.FormatInt(i.ss, 10))
if i.opt.ss != 0 {
params = append(params, "-ss", strconv.FormatInt(i.opt.ss, 10))
}
if i.t != 0 {
params = append(params, "-t", strconv.FormatInt(i.t, 10))
if i.opt.t != 0 {
params = append(params, "-t", strconv.FormatInt(i.opt.t, 10))
}
params = append(params, "-i", i.i)
params = append(params, "-i", i.opt.i)
return
}

16
input/input_option.go Normal file
View File

@@ -0,0 +1,16 @@
package input
type OptionFunc func(*option)
type option struct {
i string // i is input file.
ss int64 // ss is starttime.
t int64 // t is duration.
ext []string // extra params.
}
func SetI(i string) OptionFunc {
return func(o *option) {
o.i = i
}
}

10
input/inputs.go Normal file
View File

@@ -0,0 +1,10 @@
package input
type Inputs []*Input
func (inputs Inputs) Params() (params []string) {
for _, input := range inputs {
params = append(params, input.Params()...)
}
return
}

View File

@@ -0,0 +1,31 @@
package strconv
import "strconv"
func ToInt32(s string) (int32, error) {
i, err := strconv.ParseInt(s, 10, 32)
if err != nil {
return 0, err
}
return int32(i), nil
}
func ToInt64(s string) (int64, error) {
return strconv.ParseInt(s, 10, 64)
}
func ToInt32ByFloatString(s string) (int32, error) {
f, err := strconv.ParseFloat(s, 64)
if err != nil {
return 0, err
}
return int32(f), nil
}
func ToInt64ByFloatString(s string) (int64, error) {
f, err := strconv.ParseFloat(s, 64)
if err != nil {
return 0, err
}
return int64(f), nil
}

View File

@@ -1,6 +1,70 @@
package output
import (
"fmt"
"strconv"
"strings"
)
// Output is common output info.
type Output struct {
maps []string
f string
// maps []string // mean is -map.
// cv, ca string // cv is c:v, ca is c:a.
// metadatas []string // mean is -metadata.
// threads int32 // thread counts, default 4.
// max_muxing_queue_size int32 // queue size when muxing, default 4086.
// movflags string // location of mp4 moov.
// f string // f is -f format.
// file string
opt *option
}
func New(opts ...OptionFunc) *Output {
o := &option{
threads: 4,
max_muxing_queue_size: 4086,
movflags: "faststart",
}
for _, opt := range opts {
opt(o)
}
return &Output{
opt: o,
}
}
func (o *Output) Params() (params []string) {
if len(o.opt.maps) != 0 {
for _, m := range o.opt.maps {
params = append(params, "-map", fmt.Sprintf("[%s]", m))
}
}
if o.opt.cv != "" {
params = append(params, "c:v", o.opt.cv)
}
if o.opt.ca != "" {
params = append(params, "c:a", o.opt.ca)
}
if len(o.opt.metadatas) != 0 {
for _, m := range o.opt.metadatas {
params = append(params, "-metadata", m)
}
}
if o.opt.max_muxing_queue_size != 0 {
params = append(params, "-max_muxing_queue_size", strconv.FormatInt(int64(o.opt.max_muxing_queue_size), 10))
}
if o.opt.movflags != "" {
params = append(params, "-movflags", o.opt.movflags)
}
if o.opt.f != "" {
params = append(params, "-f", o.opt.f)
}
if o.opt.file != "" {
params = append(params, o.opt.file)
}
return
}
func (o *Output) String() string {
return strings.Join(o.Params(), " ")
}

32
output/output_option.go Normal file
View File

@@ -0,0 +1,32 @@
package output
type OptionFunc func(*option)
type option struct {
maps []string // mean is -map.
cv, ca string // cv is c:v, ca is c:a.
metadatas []string // mean is -metadata.
threads int32 // thread counts, default 4.
max_muxing_queue_size int32 // queue size when muxing, default 4086.
movflags string // location of mp4 moov.
f string // f is -f format.
file string
}
func SetMap(m string) OptionFunc {
return func(o *option) {
o.maps = append(o.maps, m)
}
}
func SetMetadata(md string) OptionFunc {
return func(o *option) {
o.metadatas = append(o.metadatas, md)
}
}
func SetFile(f string) OptionFunc {
return func(o *option) {
o.file = f
}
}

10
output/outputs.go Normal file
View File

@@ -0,0 +1,10 @@
package output
type Outputs []*Output
func (Outputs Outputs) Params() (params []string) {
for _, output := range Outputs {
params = append(params, output.Params()...)
}
return
}

View File

@@ -27,7 +27,7 @@ ffmpeg
-y
-t 5
-i in.mp4 -i logo.png
-filter_complex '[0]delogo=1490:40:400:100[p1];[p1]split=2[p1_hd][p1_sd];[p1_hd]scale=-1:720[p2_hd];[p2_hd][1]overlay=W-w-10:10[p_hd];[p1_sd]scale=-1:540[p2_sd];[p2_sd][1]overlay=W-w-10:10[p_sd]'
-filter_complex '[0]delogo=1490:40:400:100[p1];[p1]split=2[p1_hd][p1_sd];[p1_hd]scale=trunc(oh*a/2)*2:720[p2_hd];[p2_hd][1]overlay=W-w-10:10[p_hd];[p1_sd]scale=-1:540[p2_sd];[p2_sd][1]overlay=W-w-10:10[p_sd]'
-map '[p_hd]'
-map 0:a
@@ -40,4 +40,8 @@ ffmpeg
out_hd.mp4
-map '[p_sd]' -map 0:a -metadata comment=fu789sg -c:v libwz264 -c:a copy -threads 4 -max_muxing_queue_size 4086 -movflags faststart out_sd.mp4
trunc(oh*a/2)*2:720
720:trunc(ow/a/2)*2
```

View File

@@ -1,47 +0,0 @@
package test
import (
"testing"
"fxkt.tech/ffmpeg"
)
func TestExample(t *testing.T) {
ff := ffmpeg.NewFFmpeg()
ff.AddInputs(
"video.mp4",
"logo.png",
)
ff.AddFilter(
ffmpeg.NewFilter("dl", `delogo=0:0:400:200`, "0"),
ffmpeg.NewFilter("d_480][d_360", `split=2`, "dl"),
ffmpeg.NewFilter("rlt_480", `scale=trunc(oh*a/2)*2:480`, "d_480"),
ffmpeg.NewFilter("rlt_360", `scale=trunc(oh*a/2)*2:360`, "d_360"),
)
ff.OutputGraph(
ffmpeg.NewOutput(
"-map", "[rlt_480]",
"-map", "0:a",
"-metadata", "comment=fu789sg",
"-c:v", "libx264", "-c:a", "copy",
"-threads", "4",
"-max_muxing_queue_size", "4086",
"-movflags", "faststart",
"out_480.mp4",
),
ffmpeg.NewOutput(
"-map", "[rlt_360]",
"-map", "0:a",
"-metadata", "comment=fu789sg",
"-c:v", "libx264", "-c:a", "copy",
"-threads", "4",
"-max_muxing_queue_size", "4086",
"-movflags", "faststart",
"out_480.mp4",
),
)
err := ff.Run()
if err != nil {
t.Fatal(err)
}
}