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:
4
.gitignore
vendored
4
.gitignore
vendored
@@ -15,4 +15,6 @@
|
|||||||
# vendor/
|
# vendor/
|
||||||
|
|
||||||
*.mp4
|
*.mp4
|
||||||
*.png
|
*.png
|
||||||
|
*.jpg
|
||||||
|
*.jpeg
|
@@ -3,5 +3,6 @@ package liv
|
|||||||
import "errors"
|
import "errors"
|
||||||
|
|
||||||
var (
|
var (
|
||||||
ErrParamsInvalid = errors.New("params is invalid")
|
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 {
|
func (cf *CommonFilter) Name(index int) string {
|
||||||
|
if cf.name == "" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
return fmt.Sprintf("[%s%d]", cf.name, index)
|
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
|
// filter slice
|
||||||
|
|
||||||
type Filters []Filter
|
type Filters []Filter
|
||||||
|
@@ -26,3 +26,7 @@ func (n *Naming) Gen() string {
|
|||||||
func (n *Naming) Gen64() string {
|
func (n *Naming) Gen64() string {
|
||||||
return fmt.Sprintf("%x", math.MaxInt64)
|
return fmt.Sprintf("%x", math.MaxInt64)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (n *Naming) Empty() string {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
@@ -4,8 +4,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/fxkt-tech/liv/ffmpeg/codec"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type OutputOption func(*Output)
|
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
|
// hls
|
||||||
|
|
||||||
func HLSSegmentType(value string) OutputOption {
|
func HLSSegmentType(value string) OutputOption {
|
||||||
@@ -121,16 +125,19 @@ func HLSPlaylistType(value string) OutputOption {
|
|||||||
o.hls_playlist_type = value
|
o.hls_playlist_type = value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func HLSTime(value int32) OutputOption {
|
func HLSTime(value int32) OutputOption {
|
||||||
return func(o *Output) {
|
return func(o *Output) {
|
||||||
o.hls_time = value
|
o.hls_time = value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func MasterPlName(value string) OutputOption {
|
func MasterPlName(value string) OutputOption {
|
||||||
return func(o *Output) {
|
return func(o *Output) {
|
||||||
o.master_pl_name = value
|
o.master_pl_name = value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func HLSSegmentFilename(value string) OutputOption {
|
func HLSSegmentFilename(value string) OutputOption {
|
||||||
return func(o *Output) {
|
return func(o *Output) {
|
||||||
o.hls_segment_filename = value
|
o.hls_segment_filename = value
|
||||||
@@ -166,6 +173,7 @@ type Output struct {
|
|||||||
f string // f is -f format.
|
f string // f is -f format.
|
||||||
file string
|
file string
|
||||||
var_stream_map string
|
var_stream_map string
|
||||||
|
vsync string
|
||||||
|
|
||||||
// hls configs
|
// hls configs
|
||||||
hls_segment_type string
|
hls_segment_type string
|
||||||
@@ -182,12 +190,12 @@ type Output struct {
|
|||||||
|
|
||||||
func New(opts ...OutputOption) *Output {
|
func New(opts ...OutputOption) *Output {
|
||||||
op := &Output{
|
op := &Output{
|
||||||
threads: 4,
|
// threads: 4,
|
||||||
max_muxing_queue_size: 4086,
|
// max_muxing_queue_size: 4086,
|
||||||
movflags: "faststart",
|
// movflags: "faststart",
|
||||||
cv: codec.X264,
|
// cv: codec.X264,
|
||||||
ca: codec.Copy,
|
// ca: codec.Copy,
|
||||||
hls_time: 2,
|
// hls_time: 2,
|
||||||
}
|
}
|
||||||
for _, o := range opts {
|
for _, o := range opts {
|
||||||
o(op)
|
o(op)
|
||||||
@@ -227,6 +235,9 @@ func (o *Output) Params() (params []string) {
|
|||||||
if o.var_stream_map != "" {
|
if o.var_stream_map != "" {
|
||||||
params = append(params, "-var_stream_map", 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.f == "hls" {
|
||||||
if o.hls_segment_type != "" {
|
if o.hls_segment_type != "" {
|
||||||
params = append(params, "-hls_segment_type", 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
|
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
|
||||||
|
}
|
32
transcode.go
32
transcode.go
@@ -11,33 +11,20 @@ import (
|
|||||||
"github.com/fxkt-tech/liv/ffmpeg/output"
|
"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 {
|
type Transcode struct {
|
||||||
ffmpegOpts []ffmpeg.FFmpegOption
|
*options
|
||||||
ffprobeOpts []ffmpeg.FFprobeOption
|
|
||||||
|
|
||||||
spec *TranscodeSpec
|
spec *TranscodeSpec
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewTranscode(opts ...TranscodeOption) *Transcode {
|
func NewTranscode(opts ...Option) *Transcode {
|
||||||
tc := &Transcode{
|
o := &options{}
|
||||||
spec: NewTranscodeSpec(),
|
|
||||||
}
|
|
||||||
for _, opt := range opts {
|
for _, opt := range opts {
|
||||||
opt(tc)
|
opt(o)
|
||||||
|
}
|
||||||
|
tc := &Transcode{
|
||||||
|
spec: NewTranscodeSpec(),
|
||||||
|
options: o,
|
||||||
}
|
}
|
||||||
return tc
|
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.Map(filter.SelectStream(0, filter.StreamAudio, false)),
|
||||||
output.VideoCodec(codec.X264),
|
output.VideoCodec(codec.X264),
|
||||||
output.AudioCodec(codec.AAC),
|
output.AudioCodec(codec.AAC),
|
||||||
|
output.MovFlags("faststart"),
|
||||||
|
output.Thread(4),
|
||||||
|
output.MaxMuxingQueueSize(4086),
|
||||||
output.File(sub.Outfile),
|
output.File(sub.Outfile),
|
||||||
}
|
}
|
||||||
// 处理在每一路输出流的裁剪
|
// 处理在每一路输出流的裁剪
|
||||||
|
Reference in New Issue
Block a user