mirror of
https://github.com/fxkt-tech/liv
synced 2025-09-26 20:11:20 +08:00
simple version
This commit is contained in:
77
ffmpeg.go
77
ffmpeg.go
@@ -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
|
||||
|
||||
cmd string
|
||||
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
|
||||
params = append(params, "-y")
|
||||
for _, infile := range ff.infiles {
|
||||
params = append(params, "-i", infile)
|
||||
func (ff *FFmpeg) Params() (params []string) {
|
||||
if ff.y {
|
||||
params = append(params, "-y")
|
||||
}
|
||||
|
||||
var filtersStr []string
|
||||
for _, filter := range ff.filters {
|
||||
filtersStr = append(filtersStr, filter.String())
|
||||
}
|
||||
params = append(params, "-filter_complex", strings.Join(filtersStr, ";"))
|
||||
for _, op := range ff.outputs {
|
||||
params = append(params, op.params...)
|
||||
}
|
||||
return params
|
||||
params = append(params, ff.inputs.Params()...)
|
||||
params = append(params, ff.filters.String())
|
||||
params = append(params, ff.outputs.Params()...)
|
||||
return
|
||||
}
|
||||
|
||||
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", "|"))
|
||||
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
|
||||
}
|
||||
return err
|
||||
log.Println(string(retbytes))
|
||||
return
|
||||
}
|
||||
|
41
ffmpeg_test.go
Normal file
41
ffmpeg_test.go
Normal 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)
|
||||
}
|
30
filter.go
30
filter.go
@@ -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
44
filter/filter.go
Normal 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
27
filter/filter_option.go
Normal 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
16
filter/filters.go
Normal 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(), ";")
|
||||
}
|
@@ -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
16
input/input_option.go
Normal 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
10
input/inputs.go
Normal 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
|
||||
}
|
31
internal/pkg/strconv/strconv.go
Normal file
31
internal/pkg/strconv/strconv.go
Normal 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
|
||||
}
|
@@ -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
32
output/output_option.go
Normal 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
10
output/outputs.go
Normal 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
|
||||
}
|
@@ -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
|
||||
```
|
||||
|
||||
|
@@ -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)
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user