mirror of
https://github.com/fxkt-tech/liv
synced 2025-09-26 20:11:20 +08:00
feat: simple snapshot
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -16,3 +16,5 @@
|
||||
|
||||
*.mp4
|
||||
*.png
|
||||
*.jpg
|
||||
*.jpeg
|
@@ -4,4 +4,5 @@ import "errors"
|
||||
|
||||
var (
|
||||
ErrParamsInvalid = errors.New("params is invalid")
|
||||
ErrParamsInvalid2 = errors.New("interval required when frame_type is 1(normal frame)")
|
||||
)
|
||||
|
34
examples/service/snapshot/main.go
Normal file
34
examples/service/snapshot/main.go
Normal file
@@ -0,0 +1,34 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/fxkt-tech/liv"
|
||||
"github.com/fxkt-tech/liv/ffmpeg"
|
||||
)
|
||||
|
||||
func main() {
|
||||
var (
|
||||
ctx = context.Background()
|
||||
params = &liv.SnapshotParams{
|
||||
Infile: "in.mp4",
|
||||
Outfile: "ss/%05d.jpg",
|
||||
StartTime: 3,
|
||||
FrameType: 0,
|
||||
Num: 1,
|
||||
Interval: 1,
|
||||
}
|
||||
)
|
||||
|
||||
tc := liv.NewSnapshot(
|
||||
liv.FFmpegOptions(
|
||||
ffmpeg.Binary("ffmpeg"),
|
||||
// ffmpeg.Dry(true),
|
||||
),
|
||||
)
|
||||
err := tc.Simple(ctx, params)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
}
|
@@ -24,6 +24,9 @@ type CommonFilter struct {
|
||||
}
|
||||
|
||||
func (cf *CommonFilter) Name(index int) string {
|
||||
if cf.name == "" {
|
||||
return ""
|
||||
}
|
||||
return fmt.Sprintf("[%s%d]", cf.name, index)
|
||||
}
|
||||
|
||||
@@ -153,6 +156,22 @@ func Delogo(name string, x, y, w, h int64) Filter {
|
||||
}
|
||||
}
|
||||
|
||||
func Select(name, expr string) Filter {
|
||||
return &CommonFilter{
|
||||
name: name,
|
||||
content: fmt.Sprintf("select=%s", expr),
|
||||
counts: 1,
|
||||
}
|
||||
}
|
||||
|
||||
func FPS(name string, fps *math.Rational[int32]) Filter {
|
||||
return &CommonFilter{
|
||||
name: name,
|
||||
content: fmt.Sprintf("fps=fps=%d/%d", fps.Num, fps.Den),
|
||||
counts: 1,
|
||||
}
|
||||
}
|
||||
|
||||
// filter slice
|
||||
|
||||
type Filters []Filter
|
||||
|
@@ -26,3 +26,7 @@ func (n *Naming) Gen() string {
|
||||
func (n *Naming) Gen64() string {
|
||||
return fmt.Sprintf("%x", math.MaxInt64)
|
||||
}
|
||||
|
||||
func (n *Naming) Empty() string {
|
||||
return ""
|
||||
}
|
||||
|
@@ -4,8 +4,6 @@ import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/fxkt-tech/liv/ffmpeg/codec"
|
||||
)
|
||||
|
||||
type OutputOption func(*Output)
|
||||
@@ -102,6 +100,12 @@ func VarStreamMap(s string) OutputOption {
|
||||
}
|
||||
}
|
||||
|
||||
func VSync(vsync string) OutputOption {
|
||||
return func(o *Output) {
|
||||
o.vsync = vsync
|
||||
}
|
||||
}
|
||||
|
||||
// hls
|
||||
|
||||
func HLSSegmentType(value string) OutputOption {
|
||||
@@ -121,16 +125,19 @@ func HLSPlaylistType(value string) OutputOption {
|
||||
o.hls_playlist_type = value
|
||||
}
|
||||
}
|
||||
|
||||
func HLSTime(value int32) OutputOption {
|
||||
return func(o *Output) {
|
||||
o.hls_time = value
|
||||
}
|
||||
}
|
||||
|
||||
func MasterPlName(value string) OutputOption {
|
||||
return func(o *Output) {
|
||||
o.master_pl_name = value
|
||||
}
|
||||
}
|
||||
|
||||
func HLSSegmentFilename(value string) OutputOption {
|
||||
return func(o *Output) {
|
||||
o.hls_segment_filename = value
|
||||
@@ -166,6 +173,7 @@ type Output struct {
|
||||
f string // f is -f format.
|
||||
file string
|
||||
var_stream_map string
|
||||
vsync string
|
||||
|
||||
// hls configs
|
||||
hls_segment_type string
|
||||
@@ -182,12 +190,12 @@ type Output struct {
|
||||
|
||||
func New(opts ...OutputOption) *Output {
|
||||
op := &Output{
|
||||
threads: 4,
|
||||
max_muxing_queue_size: 4086,
|
||||
movflags: "faststart",
|
||||
cv: codec.X264,
|
||||
ca: codec.Copy,
|
||||
hls_time: 2,
|
||||
// threads: 4,
|
||||
// max_muxing_queue_size: 4086,
|
||||
// movflags: "faststart",
|
||||
// cv: codec.X264,
|
||||
// ca: codec.Copy,
|
||||
// hls_time: 2,
|
||||
}
|
||||
for _, o := range opts {
|
||||
o(op)
|
||||
@@ -227,6 +235,9 @@ func (o *Output) Params() (params []string) {
|
||||
if o.var_stream_map != "" {
|
||||
params = append(params, "-var_stream_map", o.var_stream_map)
|
||||
}
|
||||
if o.vsync != "" {
|
||||
params = append(params, "-vsync", o.vsync)
|
||||
}
|
||||
if o.f == "hls" {
|
||||
if o.hls_segment_type != "" {
|
||||
params = append(params, "-hls_segment_type", o.hls_segment_type)
|
||||
|
@@ -17,3 +17,14 @@ func CeilOddInt32(n int32) int32 {
|
||||
}
|
||||
return n - 1
|
||||
}
|
||||
|
||||
type Rational[T constraints.Integer] struct {
|
||||
Num, Den T
|
||||
}
|
||||
|
||||
func Fraction[T constraints.Integer](num, den T) *Rational[T] {
|
||||
return &Rational[T]{
|
||||
Num: num,
|
||||
Den: den,
|
||||
}
|
||||
}
|
||||
|
22
options.go
Normal file
22
options.go
Normal file
@@ -0,0 +1,22 @@
|
||||
package liv
|
||||
|
||||
import "github.com/fxkt-tech/liv/ffmpeg"
|
||||
|
||||
type Option func(*options)
|
||||
|
||||
type options struct {
|
||||
ffmpegOpts []ffmpeg.FFmpegOption
|
||||
ffprobeOpts []ffmpeg.FFprobeOption
|
||||
}
|
||||
|
||||
func FFmpegOptions(ffmpegOpts ...ffmpeg.FFmpegOption) Option {
|
||||
return func(o *options) {
|
||||
o.ffmpegOpts = ffmpegOpts
|
||||
}
|
||||
}
|
||||
|
||||
func FFprobeOptions(ffprobeOpts ...ffmpeg.FFprobeOption) Option {
|
||||
return func(o *options) {
|
||||
o.ffprobeOpts = ffprobeOpts
|
||||
}
|
||||
}
|
68
snapshot.go
Normal file
68
snapshot.go
Normal file
@@ -0,0 +1,68 @@
|
||||
package liv
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/fxkt-tech/liv/ffmpeg"
|
||||
"github.com/fxkt-tech/liv/ffmpeg/filter"
|
||||
"github.com/fxkt-tech/liv/ffmpeg/input"
|
||||
"github.com/fxkt-tech/liv/ffmpeg/naming"
|
||||
"github.com/fxkt-tech/liv/ffmpeg/output"
|
||||
"github.com/fxkt-tech/liv/internal/math"
|
||||
)
|
||||
|
||||
type Snapshot struct {
|
||||
*options
|
||||
|
||||
spec *SnapshotSpec
|
||||
}
|
||||
|
||||
func NewSnapshot(opts ...Option) *Snapshot {
|
||||
o := &options{}
|
||||
for _, opt := range opts {
|
||||
opt(o)
|
||||
}
|
||||
ss := &Snapshot{
|
||||
spec: NewSnapshotSpec(),
|
||||
options: o,
|
||||
}
|
||||
return ss
|
||||
}
|
||||
|
||||
func (ss *Snapshot) Simple(ctx context.Context, params *SnapshotParams) error {
|
||||
err := ss.spec.CheckSatified(params)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var (
|
||||
nm = naming.New()
|
||||
inputs input.Inputs
|
||||
filters filter.Filters
|
||||
outputOptions []output.OutputOption
|
||||
)
|
||||
|
||||
inputs = append(inputs, input.WithTime(params.StartTime, 0, params.Infile))
|
||||
|
||||
// 使用普通帧截图时,必须要传截图间隔,除非只截一张
|
||||
switch params.FrameType {
|
||||
case 0: // 关键帧
|
||||
filters = append(filters, filter.Select(nm.Empty(), "'eq(pict_type,I)'"))
|
||||
outputOptions = append(outputOptions, output.VSync("vfr"))
|
||||
case 1:
|
||||
if params.Num != 1 {
|
||||
filters = append(filters, filter.FPS(nm.Empty(), math.Fraction(1, params.Interval)))
|
||||
}
|
||||
}
|
||||
|
||||
outputOptions = append(outputOptions,
|
||||
output.Vframes(params.Num),
|
||||
output.File(params.Outfile),
|
||||
)
|
||||
|
||||
return ffmpeg.NewFFmpeg(ss.ffmpegOpts...).
|
||||
AddInput(inputs...).
|
||||
AddFilter(filters...).
|
||||
AddOutput(output.New(outputOptions...)).
|
||||
Run(ctx)
|
||||
}
|
12
snapshot_params.go
Normal file
12
snapshot_params.go
Normal file
@@ -0,0 +1,12 @@
|
||||
package liv
|
||||
|
||||
type SnapshotParams struct {
|
||||
Infile string
|
||||
Outfile string
|
||||
StartTime float64
|
||||
Interval int32
|
||||
Num int32
|
||||
FrameType int32
|
||||
NotBlack bool
|
||||
NotWhite bool
|
||||
}
|
17
snapshot_spec.go
Normal file
17
snapshot_spec.go
Normal file
@@ -0,0 +1,17 @@
|
||||
package liv
|
||||
|
||||
type SnapshotSpec struct{}
|
||||
|
||||
func NewSnapshotSpec() *SnapshotSpec {
|
||||
return &SnapshotSpec{}
|
||||
}
|
||||
|
||||
func (*SnapshotSpec) CheckSatified(params *SnapshotParams) error {
|
||||
if params == nil {
|
||||
return ErrParamsInvalid
|
||||
}
|
||||
if params.FrameType == 1 && params.Num > 1 && params.Interval <= 0 {
|
||||
return ErrParamsInvalid2
|
||||
}
|
||||
return nil
|
||||
}
|
30
transcode.go
30
transcode.go
@@ -11,33 +11,20 @@ import (
|
||||
"github.com/fxkt-tech/liv/ffmpeg/output"
|
||||
)
|
||||
|
||||
type TranscodeOption func(*Transcode)
|
||||
|
||||
func FFmpegOptions(ffmpegOpts ...ffmpeg.FFmpegOption) TranscodeOption {
|
||||
return func(t *Transcode) {
|
||||
t.ffmpegOpts = ffmpegOpts
|
||||
}
|
||||
}
|
||||
|
||||
func FFprobeOptions(ffprobeOpts ...ffmpeg.FFprobeOption) TranscodeOption {
|
||||
return func(t *Transcode) {
|
||||
t.ffprobeOpts = ffprobeOpts
|
||||
}
|
||||
}
|
||||
|
||||
type Transcode struct {
|
||||
ffmpegOpts []ffmpeg.FFmpegOption
|
||||
ffprobeOpts []ffmpeg.FFprobeOption
|
||||
*options
|
||||
|
||||
spec *TranscodeSpec
|
||||
}
|
||||
|
||||
func NewTranscode(opts ...TranscodeOption) *Transcode {
|
||||
func NewTranscode(opts ...Option) *Transcode {
|
||||
o := &options{}
|
||||
for _, opt := range opts {
|
||||
opt(o)
|
||||
}
|
||||
tc := &Transcode{
|
||||
spec: NewTranscodeSpec(),
|
||||
}
|
||||
for _, opt := range opts {
|
||||
opt(tc)
|
||||
options: o,
|
||||
}
|
||||
return tc
|
||||
}
|
||||
@@ -100,6 +87,9 @@ func (tc *Transcode) SimpleMP4(ctx context.Context, params *TranscodeParams) err
|
||||
output.Map(filter.SelectStream(0, filter.StreamAudio, false)),
|
||||
output.VideoCodec(codec.X264),
|
||||
output.AudioCodec(codec.AAC),
|
||||
output.MovFlags("faststart"),
|
||||
output.Thread(4),
|
||||
output.MaxMuxingQueueSize(4086),
|
||||
output.File(sub.Outfile),
|
||||
}
|
||||
// 处理在每一路输出流的裁剪
|
||||
|
Reference in New Issue
Block a user