refactor(stream): pos instead select

This commit is contained in:
Justyer
2024-04-06 00:50:14 +08:00
parent 82839fcccc
commit c9d38559f5
10 changed files with 193 additions and 138 deletions

View File

@@ -9,7 +9,6 @@ import (
"github.com/fxkt-tech/liv/ffmpeg/filter"
"github.com/fxkt-tech/liv/ffmpeg/input"
"github.com/fxkt-tech/liv/ffmpeg/output"
"github.com/fxkt-tech/liv/ffmpeg/stream"
)
func main() {
@@ -18,23 +17,27 @@ func main() {
// inputs
iMain = input.WithSimple("in.mp4")
// iSub = input.WithSimple("xx.mp4")
// filters
fSplit = filter.Split(2).Use(stream.V(0))
fOverlay = filter.Logo(50, 100, filter.LogoTopLeft).Use(fSplit.Get(0), fSplit.Get(1))
// fSplit = filter.Split(2).Use(iMain.V(), iSub.V())
// fOverlay = filter.Overlay(fsugar.LogoPos(50, 100, fsugar.LogoPosTopLeft)).Use(fSplit.S(0), fSplit.S(1))
fDelogo = filter.Delogo(0, 0, 400, 400).Use(iMain.V())
)
err := ffmpeg.New(
ffmpeg.WithDebug(true),
ffmpeg.WithDry(true),
).AddInput(
// iMain, iSub,
iMain,
).AddFilter(
fSplit, fOverlay,
// fSplit, fOverlay,
fDelogo,
).AddOutput(
output.New(
output.Map(fOverlay),
output.Map(stream.Select(0, stream.MayAudio)),
output.Map(fDelogo),
output.Map(iMain.MayA()),
output.Metadata("comment", "xx"),
output.VideoCodec(codec.X264),
output.AudioCodec(codec.Copy),

View File

@@ -61,7 +61,7 @@ func (fm *FFmpeg) Params() []string {
params = append(params, "-y")
}
params = append(params, fm.inputs.Params()...)
params = append(params, fm.inputs.Tidy().Params()...)
params = append(params, fm.filters.Params()...)
params = append(params, fm.outputs.Params()...)
return params

View File

@@ -8,45 +8,45 @@ import (
)
// 音频流复制成多份
func ASplit(n int) Filter {
return &multiple{
func ASplit(n int) *MultiFilter {
return &MultiFilter{
name: naming.Default.Gen(),
content: fmt.Sprintf("asplit=%d", n),
counts: n,
}
}
func ATempo[T constraints.Integer | constraints.Float | string](expr T) Filter {
return &single{
func ATempo[T constraints.Integer | constraints.Float | string](expr T) *SingleFilter {
return &SingleFilter{
name: naming.Default.Gen(),
content: fmt.Sprintf("atempo=%v", expr),
}
}
// 音频帧显示时间戳
func ASetPTS(expr string) Filter {
return &single{
func ASetPTS(expr string) *SingleFilter {
return &SingleFilter{
name: naming.Default.Gen(),
content: fmt.Sprintf("asetpts=%s", expr),
}
}
func AMix(inputs int32) Filter {
return &single{
func AMix(inputs int32) *SingleFilter {
return &SingleFilter{
name: naming.Default.Gen(),
content: fmt.Sprintf("amix=inputs=%d", inputs),
}
}
func Loudnorm(i, tp int32) Filter {
return &single{
func Loudnorm(i, tp int32) *SingleFilter {
return &SingleFilter{
name: naming.Default.Gen(),
content: fmt.Sprintf("loudnorm=I=%d:TP=%d", i, tp),
}
}
func ADelay(delays int32) Filter {
return &single{
func ADelay(delays int32) *SingleFilter {
return &SingleFilter{
name: naming.Default.Gen(),
content: fmt.Sprintf("adelay=delays=%ds:all=1", delays),
}

View File

@@ -9,85 +9,69 @@ import (
type Filter interface {
stream.Streamer
Get(int) stream.Streamer
Copy(int) Filter
String() string
Use(...stream.Streamer) Filter
// Get(int) stream.Streamer
// Copy(int) Filter
// Use(...stream.Streamer) Filter
}
// 单输出滤镜
type single struct {
type SingleFilter struct {
name string
content string
uses []stream.Streamer
}
func (s *single) Name() string {
func (s *SingleFilter) Name(_ stream.PosFrom) string {
if s.name == "" {
return ""
}
return fmt.Sprintf("[%s]", s.name)
}
func (s *single) Get(i int) stream.Streamer { return s }
func (s *SingleFilter) S() stream.Streamer { return s }
func (s *single) Copy(index int) Filter {
return &single{
name: s.name,
content: s.content,
uses: s.uses,
}
}
func (s *single) String() string {
func (s *SingleFilter) String() string {
fls := make([]string, len(s.uses))
for i, fl := range s.uses {
if fl != nil {
fls[i] = fl.Name()
fls[i] = fl.Name(stream.PosFromFilter)
}
}
return fmt.Sprintf("%s%s%s", strings.Join(fls, ""), s.content, s.Name())
return fmt.Sprintf("%s%s%s", strings.Join(fls, ""), s.content, s.Name(stream.PosFromFilter))
}
func (s *single) Use(streams ...stream.Streamer) Filter {
func (s *SingleFilter) Use(streams ...stream.Streamer) *SingleFilter {
s.uses = append(s.uses, streams...)
return s
}
// 多输出滤镜
type multiple struct {
type MultiFilter struct {
name string
counts int
content string
uses []stream.Streamer
}
func (s *multiple) Name() string {
func (s *MultiFilter) Name(_ stream.PosFrom) string {
if s.name == "" {
return ""
}
return fmt.Sprintf("[%s]", s.name)
}
func (s *multiple) Get(i int) stream.Streamer {
// 选择一个
func (s *MultiFilter) S(i int) stream.Streamer {
name := fmt.Sprintf("[%s_%d]", s.name, i)
return stream.StreamImpl(name)
}
func (s *multiple) Copy(index int) Filter {
return &multiple{
name: s.name,
counts: s.counts,
content: s.content,
uses: s.uses,
}
}
func (s *multiple) String() string {
func (s *MultiFilter) String() string {
fls := make([]string, len(s.uses))
for i, fl := range s.uses {
if fl != nil {
fls[i] = fl.Name()
fls[i] = fl.Name(stream.PosFromFilter)
}
}
var names []string
@@ -97,7 +81,7 @@ func (s *multiple) String() string {
return fmt.Sprintf("%s%s%s", strings.Join(fls, ""), s.content, strings.Join(names, ""))
}
func (s *multiple) Use(streams ...stream.Streamer) Filter {
func (s *MultiFilter) Use(streams ...stream.Streamer) *MultiFilter {
s.uses = append(s.uses, streams...)
return s
}
@@ -106,11 +90,6 @@ func (s *multiple) Use(streams ...stream.Streamer) Filter {
type Filters []Filter
// func (filters Filters) RefInput() Filters {
// return filters
// }
func (filters Filters) Params() (params []string) {
txt := filters.String()
if txt == "" {

View File

@@ -0,0 +1,29 @@
package fsugar
import (
"fmt"
"github.com/fxkt-tech/liv/ffmpeg/filter"
)
const (
LogoPosTopLeft = "TopLeft"
LogoPosTopRight = "TopRight"
LogoPosBottomRight = "BottomRight"
LogoPosBottomLeft = "BottomLeft"
)
// logo位置
func LogoPos[T filter.Expr](dx, dy T, pos string) (string, string) {
switch pos {
case LogoPosTopLeft:
return fmt.Sprintf("%v", dx), fmt.Sprintf("%v", dy)
case LogoPosTopRight:
return fmt.Sprintf("W-w-%v", dx), fmt.Sprintf("%v", dy)
case LogoPosBottomRight:
return fmt.Sprintf("W-w-%v", dx), fmt.Sprintf("H-h-%v", dy)
case LogoPosBottomLeft:
return fmt.Sprintf("%v", dx), fmt.Sprintf("H-h-%v", dy)
}
return "", ""
}

View File

@@ -9,52 +9,32 @@ import (
"golang.org/x/exp/constraints"
)
type LogoPos string
type Expr interface {
Numb | ~string
}
const (
LogoTopLeft LogoPos = "TopLeft"
LogoTopRight LogoPos = "TopRight"
LogoBottomRight LogoPos = "BottomRight"
LogoBottomLeft LogoPos = "BottomLeft"
)
// 贴logo
func Logo(dx, dy int64, pos LogoPos) Filter {
var content string
switch pos {
case LogoTopLeft:
content = fmt.Sprintf("overlay=%d:%d", dx, dy)
case LogoTopRight:
content = fmt.Sprintf("overlay=W-w-%d:%d", dx, dy)
case LogoBottomRight:
content = fmt.Sprintf("overlay=W-w-%d:H-h-%d", dx, dy)
case LogoBottomLeft:
content = fmt.Sprintf("overlay=%d:H-h-%d", dx, dy)
}
return &single{
name: naming.Default.Gen(),
content: content,
}
type Numb interface {
~int32 | ~int
}
// 一个图像覆盖另一个图像
func Overlay[T constraints.Signed | string](dx, dy T) Filter {
return &single{
func Overlay[T Expr](dx, dy T) *SingleFilter {
return &SingleFilter{
name: naming.Default.Gen(),
content: fmt.Sprintf("overlay=%v:%v", dx, dy),
}
}
// 一个图像覆盖另一个图像(可激活某一时间段)
func OverlayWithEnable[T constraints.Signed | string](dx, dy T, enable string) Filter {
return &single{
// Deprecated: 一个图像覆盖另一个图像(可激活某一时间段)
func OverlayWithEnable[T Expr](dx, dy T, enable string) *SingleFilter {
return &SingleFilter{
name: naming.Default.Gen(),
content: fmt.Sprintf("overlay=%v:%v:enable='%s'", dx, dy, enable),
}
}
// 缩放
func Scale[T int32 | int | string](w, h T) Filter {
func Scale[T Expr](w, h T) *SingleFilter {
var ww, hh any = w, h
switch ww.(type) {
case int32:
@@ -64,22 +44,22 @@ func Scale[T int32 | int | string](w, h T) Filter {
case string:
ww, hh = w, h
}
return &single{
return &SingleFilter{
name: naming.Default.Gen(),
content: fmt.Sprintf("scale=%v:%v", ww, hh),
}
}
// func UnsopportedSimple(fstr string) Filter {
// return &single{
// func UnsopportedSimple(fstr string) *SingleFilter {
// return &SingleFilter{
// name: naming.Default.Gen(),
// content: fstr,
// }
// }
// 绿幕抠像
func Chromakey(color string, similarity, blend float32) Filter {
return &single{
func Chromakey(color string, similarity, blend float32) *SingleFilter {
return &SingleFilter{
name: naming.Default.Gen(),
content: fmt.Sprintf(
"chromakey=%s:%.2f:%.2f",
@@ -89,8 +69,8 @@ func Chromakey(color string, similarity, blend float32) Filter {
}
// 创建一个底版
func Color(c string, w, h int32, d float32) Filter {
return &single{
func Color(c string, w, h int32, d float32) *SingleFilter {
return &SingleFilter{
name: naming.Default.Gen(),
content: fmt.Sprintf(
"color=c=%s:s=%d*%d:d=%.2f",
@@ -100,35 +80,26 @@ func Color(c string, w, h int32, d float32) Filter {
}
// 裁切
func Crop(x, y, w, h int32) Filter {
return &single{
func Crop[T Expr](x, y, w, h T) *SingleFilter {
return &SingleFilter{
name: naming.Default.Gen(),
content: fmt.Sprintf(
"crop=%d:%d:%d:%d",
"crop=%v:%v:%v:%v",
x, y, w, h,
),
}
}
// 视频帧显示时间戳
func SetPTS(expr string) Filter {
return &single{
func SetPTS(expr string) *SingleFilter {
return &SingleFilter{
name: naming.Default.Gen(),
content: fmt.Sprintf("setpts=%s", expr),
}
}
// 视频流复制成多份
func Split(n int) Filter {
return &multiple{
name: naming.Default.Gen(),
content: fmt.Sprintf("split=%d", n),
counts: n,
}
}
// 截取某一时间段
func Trim(s, e float64) Filter {
func Trim(s, e float64) *SingleFilter {
var ps []string
if s != 0 {
ps = append(ps, fmt.Sprintf("start=%f", s))
@@ -141,15 +112,15 @@ func Trim(s, e float64) Filter {
if psstr != "" {
eqs = "="
}
return &single{
return &SingleFilter{
name: naming.Default.Gen(),
content: fmt.Sprintf("trim%s%s", eqs, psstr),
}
}
// 擦除logo
func Delogo(x, y, w, h int64) Filter {
return &single{
// Deprecated: 擦除logo
func Delogo_Old(x, y, w, h int32) *SingleFilter {
return &SingleFilter{
name: naming.Default.Gen(),
content: fmt.Sprintf("delogo=%d:%d:%d:%d",
x+1, y+1, w-2, h-2,
@@ -157,29 +128,50 @@ func Delogo(x, y, w, h int64) Filter {
}
}
func Select(expr string) Filter {
return &single{
// 遮标
func Delogo[T Numb](x, y, w, h T) *SingleFilter {
return &SingleFilter{
name: naming.Default.Gen(),
content: fmt.Sprintf("delogo=x=%v:y=%v:w=%v:h=%v",
x+1, y+1, w-2, h-2,
),
}
}
func Select(expr string) *SingleFilter {
return &SingleFilter{
name: naming.Default.Gen(),
content: fmt.Sprintf("select=%s", expr),
}
}
func FPS[N, D constraints.Integer | constraints.Float](fps *math.Rational[N, D]) Filter {
func FPS[N, D constraints.Integer | constraints.Float](fps *math.Rational[N, D]) *SingleFilter {
var s string
if fps.Den == 0 {
s = "source_fps"
} else {
s = fmt.Sprintf("%v/%v", fps.Num, fps.Den)
}
return &single{
return &SingleFilter{
name: naming.Default.Gen(),
content: fmt.Sprintf("fps=fps=%s", s),
}
}
func Tile(xlen, ylen int32) Filter {
return &single{
func Tile(xlen, ylen int32) *SingleFilter {
return &SingleFilter{
name: naming.Default.Gen(),
content: fmt.Sprintf("tile=%d*%d", xlen, ylen),
}
}
// multi
// 视频流复制成多份
func Split(n int) *MultiFilter {
return &MultiFilter{
name: naming.Default.Gen(),
content: fmt.Sprintf("split=%d", n),
counts: n,
}
}

View File

@@ -3,11 +3,13 @@ package input
import (
"fmt"
"strings"
"github.com/fxkt-tech/liv/ffmpeg/stream"
)
// Input is common input info.
type Input struct {
index int
idx int
cv string
r string
@@ -48,6 +50,22 @@ func WithTime(ss, t float64, i string) *Input {
}
}
func (i *Input) V() stream.Streamer {
return &InputStream{input: i, s: stream.Video}
}
func (i *Input) A() stream.Streamer {
return &InputStream{input: i, s: stream.Audio}
}
func (i *Input) MayV() stream.Streamer {
return &InputStream{input: i, s: stream.MayVideo}
}
func (i *Input) MayA() stream.Streamer {
return &InputStream{input: i, s: stream.MayAudio}
}
func (i *Input) Params() (params []string) {
if i.r != "" {
params = append(params, "-r", i.r)
@@ -92,3 +110,27 @@ func (inputs Inputs) Params() (params []string) {
func (inputs Inputs) String() string {
return strings.Join(inputs.Params(), " ")
}
func (inputs Inputs) Tidy() Inputs {
for i, input := range inputs {
input.idx = i
}
return inputs
}
// stream
// input型stream
type InputStream struct {
input *Input
s stream.Stream
}
func (s *InputStream) Name(pf stream.PosFrom) string {
switch pf {
case stream.PosFromOutput:
return fmt.Sprintf("%d:%s", s.input.idx, s.s)
default:
return fmt.Sprintf("[%d:%s]", s.input.idx, s.s)
}
}

View File

@@ -62,7 +62,7 @@ func New(opts ...Option) *Output {
func (o *Output) Params() (params []string) {
if len(o.maps) != 0 {
for _, m := range o.maps {
params = append(params, "-map", m.Name())
params = append(params, "-map", m.Name(stream.PosFromOutput))
}
}
if o.cv != "" {

View File

@@ -14,13 +14,22 @@ const (
MayVideo Stream = "v?"
)
type PosFrom string
const (
PosFromInput PosFrom = "input"
PosFromFilter PosFrom = "filter"
PosFromOutput PosFrom = "output"
)
type Streamer interface {
Name() string
Name(PosFrom) string
}
// 常量型stream
type StreamImpl string
func (s StreamImpl) Name() string {
func (s StreamImpl) Name(pf PosFrom) string {
return string(s)
}
@@ -30,10 +39,10 @@ func Select(idx int, s Stream) Streamer {
// 从input中选择视频流仅用于filter中
func V(i int) Streamer {
return StreamImpl(fmt.Sprintf("[%d:v]", i))
return StreamImpl(fmt.Sprintf("[%d:%s]", i, Video))
}
// 从input中选择音频流仅用于filter中
func A(i int) Streamer {
return StreamImpl(fmt.Sprintf("[%d:a]", i))
return StreamImpl(fmt.Sprintf("[%d:%s]", i, Audio))
}

View File

@@ -9,6 +9,7 @@ import (
"github.com/fxkt-tech/liv/ffmpeg"
"github.com/fxkt-tech/liv/ffmpeg/codec"
"github.com/fxkt-tech/liv/ffmpeg/filter"
"github.com/fxkt-tech/liv/ffmpeg/filter/fsugar"
"github.com/fxkt-tech/liv/ffmpeg/input"
"github.com/fxkt-tech/liv/ffmpeg/output"
"github.com/fxkt-tech/liv/ffmpeg/stream"
@@ -58,14 +59,14 @@ func (tc *Transcode) SimpleMP4(ctx context.Context, params *TranscodeParams) err
filters = append(filters, fsplit)
for i, sub := range params.Subs {
// 处理filter
lastFilter := fsplit.Copy(i)
lastFilter := fsplit.S(i)
// 处理遮标
if delogos := sub.Filters.Delogo; len(delogos) > 0 {
for _, delogo := range delogos {
fdelogo := filter.Delogo(
int64(delogo.Rect.X), int64(delogo.Rect.Y),
int64(delogo.Rect.W), int64(delogo.Rect.H),
int32(delogo.Rect.X), int32(delogo.Rect.Y),
int32(delogo.Rect.W), int32(delogo.Rect.H),
).Use(lastFilter)
filters = append(filters, fdelogo)
lastFilter = fdelogo
@@ -97,7 +98,7 @@ func (tc *Transcode) SimpleMP4(ctx context.Context, params *TranscodeParams) err
} else {
finalLogoStream = logoStream
}
flogo := filter.Logo(int64(logo.Dx), int64(logo.Dy), filter.LogoPos(logo.Pos)).
flogo := filter.Overlay(fsugar.LogoPos(int32(logo.Dx), int32(logo.Dy), logo.Pos)).
Use(lastFilter, finalLogoStream)
filters = append(filters, flogo)
inputs = append(inputs, input.WithSimple(logo.File))
@@ -167,7 +168,7 @@ func (tc *Transcode) SimpleMP3(ctx context.Context, params *TranscodeParams) err
filters = append(filters, fsplit)
for i, sub := range params.Subs {
// 处理filter
lastFilter := fsplit.Copy(i)
lastFilter := fsplit.S(i)
// 处理output
outputOpts := []output.Option{
@@ -212,12 +213,12 @@ func (tc *Transcode) SimpleJPEG(ctx context.Context, params *TranscodeParams) er
filters = append(filters, fsplit)
for i, sub := range params.Subs {
// 处理filter
lastFilter := fsplit.Copy(i)
lastFilter := fsplit.S(i)
// 处理遮标
if delogos := sub.Filters.Delogo; len(delogos) > 0 {
for _, delogo := range delogos {
fdelogo := filter.Delogo(int64(delogo.Rect.X), int64(delogo.Rect.Y), int64(delogo.Rect.W), int64(delogo.Rect.H)).Use(lastFilter)
fdelogo := filter.Delogo(int32(delogo.Rect.X), int32(delogo.Rect.Y), int32(delogo.Rect.W), int32(delogo.Rect.H)).Use(lastFilter)
filters = append(filters, fdelogo)
lastFilter = fdelogo
}
@@ -233,7 +234,7 @@ func (tc *Transcode) SimpleJPEG(ctx context.Context, params *TranscodeParams) er
// 添加水印
if logos := sub.Filters.Logo; len(logos) > 0 {
for _, logo := range logos {
flogo := filter.Logo(int64(logo.Dx), int64(logo.Dy), filter.LogoPos(logo.Pos)).Use(lastFilter)
flogo := filter.Overlay(fsugar.LogoPos(int32(logo.Dx), int32(logo.Dy), logo.Pos)).Use(lastFilter)
filters = append(filters, flogo)
inputs = append(inputs, input.WithSimple(logo.File))
lastFilter = flogo
@@ -322,7 +323,7 @@ func (tc *Transcode) SimpleHLS(ctx context.Context, params *TranscodeSimpleHLSPa
if delogos := params.Filters.Delogo; len(delogos) > 0 {
for _, delogo := range delogos {
fdelogo := filter.Delogo(
int64(delogo.Rect.X), int64(delogo.Rect.Y), int64(delogo.Rect.W), int64(delogo.Rect.H)).
int32(delogo.Rect.X), int32(delogo.Rect.Y), int32(delogo.Rect.W), int32(delogo.Rect.H)).
Use(lastFilter)
filters = append(filters, fdelogo)
lastFilter = fdelogo
@@ -351,7 +352,7 @@ func (tc *Transcode) SimpleHLS(ctx context.Context, params *TranscodeSimpleHLSPa
} else {
finalLogoStream = logoStream
}
flogo := filter.Logo(int64(logo.Dx), int64(logo.Dy), filter.LogoPos(logo.Pos)).
flogo := filter.Overlay(fsugar.LogoPos(int32(logo.Dx), int32(logo.Dy), logo.Pos)).
Use(lastFilter, finalLogoStream)
filters = append(filters, flogo)
inputs = append(inputs, input.WithSimple(logo.File))
@@ -424,7 +425,7 @@ func (tc *Transcode) SimpleTS(ctx context.Context, params *TranscodeSimpleTSPara
if delogos := params.Filters.Delogo; len(delogos) > 0 {
for _, delogo := range delogos {
fdelogo := filter.Delogo(
int64(delogo.Rect.X), int64(delogo.Rect.Y), int64(delogo.Rect.W), int64(delogo.Rect.H)).
int32(delogo.Rect.X), int32(delogo.Rect.Y), int32(delogo.Rect.W), int32(delogo.Rect.H)).
Use(lastFilter)
filters = append(filters, fdelogo)
lastFilter = fdelogo
@@ -453,7 +454,7 @@ func (tc *Transcode) SimpleTS(ctx context.Context, params *TranscodeSimpleTSPara
} else {
finalLogoStream = logoStream
}
flogo := filter.Logo(int64(logo.Dx), int64(logo.Dy), filter.LogoPos(logo.Pos)).
flogo := filter.Overlay(fsugar.LogoPos(int32(logo.Dx), int32(logo.Dy), logo.Pos)).
Use(lastFilter, finalLogoStream)
filters = append(filters, flogo)
inputs = append(inputs, input.WithSimple(logo.File))