mirror of
https://github.com/fxkt-tech/liv
synced 2025-09-26 20:11:20 +08:00
feat: concat service
This commit is contained in:
35
examples/service/transcode/concat/main.go
Normal file
35
examples/service/transcode/concat/main.go
Normal file
@@ -0,0 +1,35 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/fxkt-tech/liv"
|
||||
"github.com/fxkt-tech/liv/ffmpeg"
|
||||
)
|
||||
|
||||
func main() {
|
||||
var (
|
||||
ctx = context.Background()
|
||||
params = &liv.ConcatParams{
|
||||
Infiles: []string{
|
||||
"../../../testdata/in1.mp4",
|
||||
"../../../testdata/in2.mp4",
|
||||
},
|
||||
ConcatFile: "mylist.txt",
|
||||
Outfile: "out.mp4",
|
||||
}
|
||||
)
|
||||
|
||||
tc := liv.NewTranscode(
|
||||
liv.FFmpegOptions(
|
||||
ffmpeg.Binary("ffmpeg"),
|
||||
ffmpeg.Debug(true),
|
||||
// ffmpeg.Dry(true),
|
||||
),
|
||||
)
|
||||
err := tc.Concat(ctx, params)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
}
|
2
go.mod
2
go.mod
@@ -4,5 +4,5 @@ go 1.21
|
||||
|
||||
require (
|
||||
github.com/ajstarks/svgo v0.0.0-20211024235047-1546f124cd8b
|
||||
golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e
|
||||
golang.org/x/exp v0.0.0-20231006140011-7918f672742d
|
||||
)
|
||||
|
4
go.sum
4
go.sum
@@ -8,8 +8,8 @@ github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9dec
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e h1:+WEEuIdZHnUeJJmEUjyYC2gfUMj69yZXw17EnHg/otA=
|
||||
golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e/go.mod h1:Kr81I6Kryrl9sr8s2FK3vxD90NdsKWRuOIl2O4CvYbA=
|
||||
golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI=
|
||||
golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
|
46
internal/sugar/mapper.go
Normal file
46
internal/sugar/mapper.go
Normal file
@@ -0,0 +1,46 @@
|
||||
package sugar
|
||||
|
||||
type Single[I, O any] func(I) O
|
||||
|
||||
func Range[I, O any](inS []I, f Single[I, O]) []O {
|
||||
outs := make([]O, len(inS))
|
||||
for i, in := range inS {
|
||||
outs[i] = f(in)
|
||||
}
|
||||
return outs
|
||||
}
|
||||
|
||||
func In[T int | string](elems []T, dest T) bool {
|
||||
for _, elem := range elems {
|
||||
if elem == dest {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func If[F func()](cond bool, f F) {
|
||||
if cond {
|
||||
f()
|
||||
}
|
||||
}
|
||||
|
||||
func Filter[T any](slices []T, satisfied func(T) bool) []T {
|
||||
var results []T
|
||||
for _, s := range slices {
|
||||
if satisfied(s) {
|
||||
results = append(results, s)
|
||||
}
|
||||
}
|
||||
return results
|
||||
}
|
||||
|
||||
func MapTo[T1, T2 any](slices []T1, deal func(T1) (T2, error)) []T2 {
|
||||
var results []T2
|
||||
for _, s := range slices {
|
||||
if t, err := deal(s); err == nil {
|
||||
results = append(results, t)
|
||||
}
|
||||
}
|
||||
return results
|
||||
}
|
42
transcode.go
42
transcode.go
@@ -3,6 +3,8 @@ package liv
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/fxkt-tech/liv/ffmpeg"
|
||||
"github.com/fxkt-tech/liv/ffmpeg/codec"
|
||||
@@ -11,6 +13,7 @@ import (
|
||||
"github.com/fxkt-tech/liv/ffmpeg/naming"
|
||||
"github.com/fxkt-tech/liv/ffmpeg/output"
|
||||
"github.com/fxkt-tech/liv/ffmpeg/util"
|
||||
"github.com/fxkt-tech/liv/internal/sugar"
|
||||
)
|
||||
|
||||
type Transcode struct {
|
||||
@@ -284,6 +287,7 @@ func (tc *Transcode) ConvertContainer(ctx context.Context, params *ConvertContai
|
||||
Run(ctx)
|
||||
}
|
||||
|
||||
// 转hls
|
||||
func (tc *Transcode) SimpleHLS(ctx context.Context, params *TranscodeSimpleHLSParams) error {
|
||||
err := tc.spec.SimpleHLSSatified(params)
|
||||
if err != nil {
|
||||
@@ -385,6 +389,7 @@ func (tc *Transcode) SimpleHLS(ctx context.Context, params *TranscodeSimpleHLSPa
|
||||
Run(ctx)
|
||||
}
|
||||
|
||||
// 转ts
|
||||
func (tc *Transcode) SimpleTS(ctx context.Context, params *TranscodeSimpleTSParams) error {
|
||||
err := tc.spec.SimpleTSSatified(params)
|
||||
if err != nil {
|
||||
@@ -498,6 +503,43 @@ func (tc *Transcode) SimpleTS(ctx context.Context, params *TranscodeSimpleTSPara
|
||||
Run(ctx)
|
||||
}
|
||||
|
||||
func concatFile(files []string, localPath string) error {
|
||||
f, err := os.Create(localPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
files = sugar.Range(files, func(f string) string { return fmt.Sprintf("file %s", f) })
|
||||
fs := strings.Join(files, "\n")
|
||||
_, err = f.Write([]byte(fs))
|
||||
return err
|
||||
}
|
||||
|
||||
// 多个视频合并成一个
|
||||
func (tc *Transcode) Concat(ctx context.Context, params *ConcatParams) error {
|
||||
err := tc.spec.ConcatSatified(params)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = concatFile(params.Infiles, params.ConcatFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return ffmpeg.NewFFmpeg(
|
||||
ffmpeg.Debug(true),
|
||||
).AddInput(
|
||||
input.WithConcat(params.ConcatFile),
|
||||
).AddOutput(
|
||||
output.New(
|
||||
output.VideoCodec(codec.Copy),
|
||||
output.AudioCodec(codec.Copy),
|
||||
output.Duration(params.Duration),
|
||||
output.File(params.Outfile),
|
||||
),
|
||||
).Run(ctx)
|
||||
}
|
||||
|
||||
// ffmpeg -i in.mp4 -vn -c:a copy out.aac
|
||||
func (tc *Transcode) ExtractAudio(ctx context.Context, params *ExtractAudioParams) error {
|
||||
err := tc.spec.ExtractAudioSatified(params)
|
||||
|
@@ -30,6 +30,13 @@ type TranscodeSimpleHLSParams struct {
|
||||
Threads int32
|
||||
}
|
||||
|
||||
type ConcatParams struct {
|
||||
Infiles []string
|
||||
ConcatFile string // eg. mylist.txt
|
||||
Outfile string
|
||||
Duration float64
|
||||
}
|
||||
|
||||
type ExtractAudioParams struct {
|
||||
Infile string
|
||||
Outfile string
|
||||
|
@@ -52,6 +52,14 @@ func (*TranscodeSpec) SimpleTSSatified(params *TranscodeSimpleTSParams) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (*TranscodeSpec) ConcatSatified(params *ConcatParams) error {
|
||||
if params == nil || len(params.Infiles) == 0 || params.Outfile == "" {
|
||||
return ErrParamsInvalid
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (*TranscodeSpec) ExtractAudioSatified(params *ExtractAudioParams) error {
|
||||
if params == nil || params.Infile == "" || params.Outfile == "" {
|
||||
return ErrParamsInvalid
|
||||
|
Reference in New Issue
Block a user