feat(ffmpeg): select frame by duration and float position (#40)
* seek duration private * feat(ffmpeg): select frame by duration and float position
|
|
@ -41,6 +41,7 @@ type AVContext struct {
|
||||||
codecContext *C.AVCodecContext
|
codecContext *C.AVCodecContext
|
||||||
thumbContext *C.ThumbContext
|
thumbContext *C.ThumbContext
|
||||||
selectedIndex C.int
|
selectedIndex C.int
|
||||||
|
selectedDuration time.Duration
|
||||||
frame *C.AVFrame
|
frame *C.AVFrame
|
||||||
durationInFormat bool
|
durationInFormat bool
|
||||||
orientation int
|
orientation int
|
||||||
|
|
@ -109,15 +110,7 @@ func (av *AVContext) SelectDuration(ts time.Duration) (err error) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return av.SelectFrame(1)
|
return av.ProcessFrames(-1)
|
||||||
}
|
|
||||||
|
|
||||||
func (av *AVContext) SeekPosition(f float64) (err error) {
|
|
||||||
return av.SeekDuration(av.positionToDuration(f))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (av *AVContext) SeekDuration(ts time.Duration) (err error) {
|
|
||||||
return seekDuration(av, ts)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (av *AVContext) Export(bands int) (buf []byte, err error) {
|
func (av *AVContext) Export(bands int) (buf []byte, err error) {
|
||||||
|
|
@ -234,6 +227,7 @@ func createDecoder(av *AVContext) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func seekDuration(av *AVContext, ts time.Duration) error {
|
func seekDuration(av *AVContext, ts time.Duration) error {
|
||||||
|
av.selectedDuration = ts
|
||||||
tts := C.int64_t(ts.Milliseconds()) * C.AV_TIME_BASE / 1000
|
tts := C.int64_t(ts.Milliseconds()) * C.AV_TIME_BASE / 1000
|
||||||
err := C.av_seek_frame(av.formatContext, C.int(-1), tts, C.AVSEEK_FLAG_BACKWARD)
|
err := C.av_seek_frame(av.formatContext, C.int(-1), tts, C.AVSEEK_FLAG_BACKWARD)
|
||||||
C.avcodec_flush_buffers(av.codecContext)
|
C.avcodec_flush_buffers(av.codecContext)
|
||||||
|
|
@ -249,6 +243,9 @@ func incrementDuration(av *AVContext, frame *C.AVFrame, i C.int) {
|
||||||
ptsToNano := C.int64_t(1000000000 * av.stream.time_base.num / av.stream.time_base.den)
|
ptsToNano := C.int64_t(1000000000 * av.stream.time_base.num / av.stream.time_base.den)
|
||||||
newDuration := time.Duration(frame.pts * ptsToNano)
|
newDuration := time.Duration(frame.pts * ptsToNano)
|
||||||
av.availableDuration = newDuration
|
av.availableDuration = newDuration
|
||||||
|
if av.selectedDuration > 0 && i > 0 && newDuration <= av.selectedDuration {
|
||||||
|
av.selectedIndex = i
|
||||||
|
}
|
||||||
if !av.durationInFormat && newDuration > av.duration {
|
if !av.durationInFormat && newDuration > av.duration {
|
||||||
av.duration = newDuration
|
av.duration = newDuration
|
||||||
}
|
}
|
||||||
|
|
@ -304,6 +301,9 @@ func createThumbContext(av *AVContext, maxFrames C.int) error {
|
||||||
if av.selectedIndex > -1 && n > av.selectedIndex+1 {
|
if av.selectedIndex > -1 && n > av.selectedIndex+1 {
|
||||||
n = av.selectedIndex + 1
|
n = av.selectedIndex + 1
|
||||||
}
|
}
|
||||||
|
if av.selectedDuration > 0 && av.selectedIndex < 0 {
|
||||||
|
av.selectedIndex = 0
|
||||||
|
}
|
||||||
frames := make(chan *C.AVFrame, n)
|
frames := make(chan *C.AVFrame, n)
|
||||||
done := populateFrames(av, frames)
|
done := populateFrames(av, frames)
|
||||||
frames <- frame
|
frames <- frame
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ import (
|
||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
var files = []string{
|
var files = []string{
|
||||||
|
|
@ -71,7 +72,7 @@ func TestAVContext(t *testing.T) {
|
||||||
if n == 10 {
|
if n == 10 {
|
||||||
require.NoError(t, av.ProcessFrames(n))
|
require.NoError(t, av.ProcessFrames(n))
|
||||||
} else if n == 99999 {
|
} else if n == 99999 {
|
||||||
require.NoError(t, av.SeekPosition(0.6))
|
require.NoError(t, av.SelectDuration(time.Millisecond*500))
|
||||||
} else if n == 9999 {
|
} else if n == 9999 {
|
||||||
require.NoError(t, av.SelectPosition(0.7))
|
require.NoError(t, av.SelectPosition(0.7))
|
||||||
} else if n > -1 {
|
} else if n > -1 {
|
||||||
|
|
|
||||||
10
processor.go
|
|
@ -125,16 +125,6 @@ func (p *Processor) Process(ctx context.Context, in *imagor.Blob, params imagorp
|
||||||
bands = 4
|
bands = 4
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case "seek":
|
|
||||||
if ts, e := time.ParseDuration(filter.Args); e == nil {
|
|
||||||
if err = av.SeekDuration(ts); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
} else if f, e := strconv.ParseFloat(filter.Args, 64); e == nil {
|
|
||||||
if err = av.SeekPosition(f); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case "frame":
|
case "frame":
|
||||||
if ts, e := time.ParseDuration(filter.Args); e == nil {
|
if ts, e := time.ParseDuration(filter.Args); e == nil {
|
||||||
if err = av.SelectDuration(ts); err != nil {
|
if err = av.SelectDuration(ts); err != nil {
|
||||||
|
|
|
||||||
|
|
@ -57,8 +57,6 @@ func TestProcessor(t *testing.T) {
|
||||||
{name: "alpha", path: "fit-in/filters:format(png)/alpha-webm.webm"},
|
{name: "alpha", path: "fit-in/filters:format(png)/alpha-webm.webm"},
|
||||||
{name: "alpha frame duration", path: "500x/filters:frame(5s):format(png)/alpha-webm.webm", sizeOnly: true},
|
{name: "alpha frame duration", path: "500x/filters:frame(5s):format(png)/alpha-webm.webm", sizeOnly: true},
|
||||||
{name: "alpha frame position", path: "500x/filters:frame(0.5):format(png)/alpha-webm.webm", sizeOnly: true},
|
{name: "alpha frame position", path: "500x/filters:frame(0.5):format(png)/alpha-webm.webm", sizeOnly: true},
|
||||||
{name: "alpha seek duration", path: "500x/filters:seek(5s):format(png)/alpha-webm.webm", sizeOnly: true},
|
|
||||||
{name: "alpha seek position", path: "500x/filters:seek(0.5):format(png)/alpha-webm.webm", sizeOnly: true},
|
|
||||||
{name: "corrupted", path: "fit-in/100x100/corrupt/everybody-betray-me.mkv", expectCode: 406},
|
{name: "corrupted", path: "fit-in/100x100/corrupt/everybody-betray-me.mkv", expectCode: 406},
|
||||||
{name: "no cover meta", path: "meta/no_cover.mp3"},
|
{name: "no cover meta", path: "meta/no_cover.mp3"},
|
||||||
{name: "no cover 406", path: "fit-in/100x100/no_cover.mp3", expectCode: 406},
|
{name: "no cover 406", path: "fit-in/100x100/no_cover.mp3", expectCode: 406},
|
||||||
|
|
|
||||||
BIN
testdata/golden/export/alpha-webm.webm-9999.jpg
vendored
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 16 KiB |
BIN
testdata/golden/export/alpha-webm.webm-99999.jpg
vendored
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 20 KiB |
BIN
testdata/golden/export/macabre.mp4-9999.jpg
vendored
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 14 KiB |
BIN
testdata/golden/export/macabre.mp4-99999.jpg
vendored
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
BIN
testdata/golden/export/schizo.flv-9999.jpg
vendored
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 28 KiB |
BIN
testdata/golden/export/schizo.flv-99999.jpg
vendored
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 28 KiB |
BIN
testdata/golden/export/schizo_0.mp4-9999.jpg
vendored
|
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 28 KiB |
BIN
testdata/golden/export/schizo_0.mp4-99999.jpg
vendored
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 28 KiB |
BIN
testdata/golden/export/schizo_180.mp4-9999.jpg
vendored
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 28 KiB |
BIN
testdata/golden/export/schizo_180.mp4-99999.jpg
vendored
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 28 KiB |
BIN
testdata/golden/export/schizo_270.mp4-9999.jpg
vendored
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 28 KiB |
BIN
testdata/golden/export/schizo_270.mp4-99999.jpg
vendored
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 28 KiB |
BIN
testdata/golden/export/schizo_90.mp4-9999.jpg
vendored
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 28 KiB |
BIN
testdata/golden/export/schizo_90.mp4-99999.jpg
vendored
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 28 KiB |
|
|
@ -1 +1 @@
|
||||||
{"orientation":1,"duration":12040,"width":720,"height":576,"has_video":true,"has_audio":false}
|
{"orientation":1,"duration":12040,"width":720,"height":576,"fps":10.9,"has_video":true,"has_audio":false}
|
||||||
|
|
@ -1 +1 @@
|
||||||
{"orientation":1,"duration":12040,"width":720,"height":576,"has_video":true,"has_audio":false}
|
{"orientation":1,"duration":12040,"width":720,"height":576,"fps":25,"has_video":true,"has_audio":false}
|
||||||
|
|
@ -1 +1 @@
|
||||||
{"orientation":1,"duration":7407,"width":640,"height":480,"has_video":true,"has_audio":true}
|
{"orientation":1,"duration":7407,"width":640,"height":480,"fps":9.3,"has_video":true,"has_audio":true}
|
||||||
|
|
@ -1 +1 @@
|
||||||
{"orientation":1,"duration":7407,"width":640,"height":480,"has_video":true,"has_audio":true}
|
{"orientation":1,"duration":7407,"width":640,"height":480,"fps":26.3,"has_video":true,"has_audio":true}
|
||||||
|
|
@ -1 +1 @@
|
||||||
{"orientation":1,"duration":3925,"width":492,"height":360,"has_video":true,"has_audio":true}
|
{"orientation":1,"duration":3925,"width":492,"height":360,"fps":30,"has_video":true,"has_audio":true}
|
||||||
|
|
@ -1 +1 @@
|
||||||
{"orientation":1,"duration":3925,"width":492,"height":360,"has_video":true,"has_audio":true}
|
{"orientation":1,"duration":3925,"width":492,"height":360,"fps":30,"has_video":true,"has_audio":true}
|
||||||
|
|
@ -1 +1 @@
|
||||||
{"orientation":1,"duration":2560,"width":480,"height":360,"has_video":true,"has_audio":true}
|
{"orientation":1,"duration":2560,"width":480,"height":360,"fps":29.7,"has_video":true,"has_audio":true}
|
||||||
|
|
@ -1 +1 @@
|
||||||
{"orientation":1,"duration":2560,"width":480,"height":360,"has_video":true,"has_audio":true}
|
{"orientation":1,"duration":2560,"width":480,"height":360,"fps":29.7,"has_video":true,"has_audio":true}
|
||||||
|
|
@ -1 +1 @@
|
||||||
{"orientation":1,"duration":2544,"width":480,"height":360,"has_video":true,"has_audio":true}
|
{"orientation":1,"duration":2544,"width":480,"height":360,"fps":30,"has_video":true,"has_audio":true}
|
||||||
|
|
@ -1 +1 @@
|
||||||
{"orientation":1,"duration":2544,"width":480,"height":360,"has_video":true,"has_audio":true}
|
{"orientation":1,"duration":2544,"width":480,"height":360,"fps":30,"has_video":true,"has_audio":true}
|
||||||
|
|
@ -1 +1 @@
|
||||||
{"orientation":3,"duration":2544,"width":480,"height":360,"has_video":true,"has_audio":true}
|
{"orientation":3,"duration":2544,"width":480,"height":360,"fps":30,"has_video":true,"has_audio":true}
|
||||||
|
|
@ -1 +1 @@
|
||||||
{"orientation":3,"duration":2544,"width":480,"height":360,"has_video":true,"has_audio":true}
|
{"orientation":3,"duration":2544,"width":480,"height":360,"fps":30,"has_video":true,"has_audio":true}
|
||||||
|
|
@ -1 +1 @@
|
||||||
{"orientation":6,"duration":2544,"width":360,"height":480,"has_video":true,"has_audio":true}
|
{"orientation":6,"duration":2544,"width":360,"height":480,"fps":30,"has_video":true,"has_audio":true}
|
||||||
|
|
@ -1 +1 @@
|
||||||
{"orientation":6,"duration":2544,"width":360,"height":480,"has_video":true,"has_audio":true}
|
{"orientation":6,"duration":2544,"width":360,"height":480,"fps":30,"has_video":true,"has_audio":true}
|
||||||
|
|
@ -1 +1 @@
|
||||||
{"orientation":8,"duration":2544,"width":360,"height":480,"has_video":true,"has_audio":true}
|
{"orientation":8,"duration":2544,"width":360,"height":480,"fps":30,"has_video":true,"has_audio":true}
|
||||||
|
|
@ -1 +1 @@
|
||||||
{"orientation":8,"duration":2544,"width":360,"height":480,"has_video":true,"has_audio":true}
|
{"orientation":8,"duration":2544,"width":360,"height":480,"fps":30,"has_video":true,"has_audio":true}
|
||||||
|
Before Width: | Height: | Size: 97 KiB |
|
Before Width: | Height: | Size: 195 KiB |