feat(ffmpeg): seek and select frame by duration and float position (#39)
* feat(ffmpeg): seek by duration * remove selected_frame * test: update golden files * remove selected_frame * feat(ffmpeg): seek by duration * feat(ffmpeg): seek by duration * feat(ffmpeg): seek by duration * feat(ffmpeg): seek by duration * feat(ffmpeg): seek by duration * test: update golden files * feat(ffmpeg): seek by duration * test: update golden files
30
README.md
|
|
@ -7,7 +7,7 @@
|
||||||
|
|
||||||
imagorvideo is a new initiative that brings video thumbnail capability through ffmpeg, built on the foundations of [imagor](https://github.com/cshum/imagor) - a fast, Docker-ready image processing server written in Go with libvips.
|
imagorvideo is a new initiative that brings video thumbnail capability through ffmpeg, built on the foundations of [imagor](https://github.com/cshum/imagor) - a fast, Docker-ready image processing server written in Go with libvips.
|
||||||
|
|
||||||
imagorvideo uses ffmpeg C bindings that extracts image thumbnail from video by attempting to select the best frame, then forwards to libvips to perform all the image cropping, resizing and filters supported by imagor.
|
imagorvideo uses ffmpeg C bindings that extracts image thumbnail from video, by attempting to select the best frame. It then forwards to libvips to perform all the image cropping, resizing and filters supported by imagor.
|
||||||
|
|
||||||
imagorvideo uses read stream for mkv and webm video types. For other video types that requires seeking from a non seek-able source such as HTTP or S3, it simulates seek using memory or temp file as buffer.
|
imagorvideo uses read stream for mkv and webm video types. For other video types that requires seeking from a non seek-able source such as HTTP or S3, it simulates seek using memory or temp file as buffer.
|
||||||
|
|
||||||
|
|
@ -31,9 +31,9 @@ http://localhost:8000/unsafe/300x0/7x7/filters:label(imagorvideo,-10,-7,20,yello
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
imagorvideo works by streaming out a limited number of frames data from start of the video, looping through and populating the histogram data of each frame. It then choose the best frame for imaging, based on root-mean-square error (RMSE). This allow skipping the black frames that usually occur at the beginning of videos.
|
imagorvideo works by streaming out a limited number of frame data, looping through and calculating the histogram of each frame. It then choose the best frame for imaging, based on root-mean-square error (RMSE). This allow skipping the black frames that usually occur at the beginning of videos.
|
||||||
|
|
||||||
imagorvideo then converts the selected frame to RGB image data, forwards to the imagor libvips module, which has always been best at image processing with tons of features. Check out [imagor documentations](https://github.com/cshum/imagor#image-endpoint) for all the image options supported.
|
imagorvideo then converts the selected frame to RGB image data, forwards to the imagor libvips processor, which has always been best at image processing with tons of features. Check out [imagor documentations](https://github.com/cshum/imagor#image-endpoint) for all the image options supported.
|
||||||
|
|
||||||
### Filters
|
### Filters
|
||||||
|
|
||||||
|
|
@ -51,33 +51,17 @@ http://localhost:8000/unsafe/300x0/7x7/filters:max_frames(70):fill(yellow)/http:
|
||||||
|
|
||||||
imagorvideo provides metadata endpoint that extracts video metadata, dimension and duration data. By default, it only process the header without extracting the frame data for better processing speed.
|
imagorvideo provides metadata endpoint that extracts video metadata, dimension and duration data. By default, it only process the header without extracting the frame data for better processing speed.
|
||||||
|
|
||||||
To use the metadata endpoint, add `/meta` right after the URL signature hash before the image operations. Example:
|
To use the metadata endpoint, add `/meta` right after the URL signature hash before the image operations:
|
||||||
|
|
||||||
```
|
```
|
||||||
http://localhost:8000/unsafe/meta/https://test-videos.co.uk/vids/bigbuckbunny/mp4/h264/1080/Big_Buck_Bunny_1080_10s_30MB.mp4
|
http://localhost:8000/unsafe/meta/https://test-videos.co.uk/vids/bigbuckbunny/mp4/h264/1080/Big_Buck_Bunny_1080_10s_30MB.mp4
|
||||||
```
|
```
|
||||||
|
Appending the `max_frames()` or `frame(n)` filter however, would activate frame processing. This results more processing time but would also allows retrieving frame related info such as frames per second `fps`:
|
||||||
```json
|
|
||||||
{
|
|
||||||
"format": "mp4",
|
|
||||||
"content_type": "video/mp4",
|
|
||||||
"orientation": 1,
|
|
||||||
"duration": 10000,
|
|
||||||
"width": 1920,
|
|
||||||
"height": 1080,
|
|
||||||
"title": "Big Buck Bunny, Sunflower version",
|
|
||||||
"artist": "Blender Foundation 2008, Janus Bager Kristensen 2013",
|
|
||||||
"has_video": true,
|
|
||||||
"has_audio": false
|
|
||||||
}
|
|
||||||
```
|
|
||||||
Appending the `max_frames()` or `frame(n)` filter however, would activate frame processing. This results more processing time but would also allows retrieving frame related info such as frames per second `fps` and selected frame index. Example:
|
|
||||||
|
|
||||||
```
|
```
|
||||||
http://localhost:8000/unsafe/meta/filters:max_frames()/https://test-videos.co.uk/vids/bigbuckbunny/mp4/h264/1080/Big_Buck_Bunny_1080_10s_30MB.mp4
|
http://localhost:8000/unsafe/meta/filters:max_frames()/https://test-videos.co.uk/vids/bigbuckbunny/mp4/h264/1080/Big_Buck_Bunny_1080_10s_30MB.mp4
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"format": "mp4",
|
"format": "mp4",
|
||||||
|
|
@ -88,13 +72,13 @@ http://localhost:8000/unsafe/meta/filters:max_frames()/https://test-videos.co.uk
|
||||||
"height": 1080,
|
"height": 1080,
|
||||||
"title": "Big Buck Bunny, Sunflower version",
|
"title": "Big Buck Bunny, Sunflower version",
|
||||||
"artist": "Blender Foundation 2008, Janus Bager Kristensen 2013",
|
"artist": "Blender Foundation 2008, Janus Bager Kristensen 2013",
|
||||||
"fps": 30,
|
"fps": 30, // available only if frame processing activated
|
||||||
"selected_frame": 21,
|
|
||||||
"has_video": true,
|
"has_video": true,
|
||||||
"has_audio": false
|
"has_audio": false
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
### Configuration
|
### Configuration
|
||||||
|
|
||||||
Config options specific to imagorvideo. Please refer to [imagor configuration](https://github.com/cshum/imagor#configuration) for all existing options supported.
|
Config options specific to imagorvideo. Please refer to [imagor configuration](https://github.com/cshum/imagor#configuration) for all existing options supported.
|
||||||
|
|
|
||||||
|
|
@ -21,16 +21,15 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
type Metadata struct {
|
type Metadata struct {
|
||||||
Orientation int `json:"orientation"`
|
Orientation int `json:"orientation"`
|
||||||
Duration int `json:"duration,omitempty"`
|
Duration int `json:"duration,omitempty"`
|
||||||
Width int `json:"width,omitempty"`
|
Width int `json:"width,omitempty"`
|
||||||
Height int `json:"height,omitempty"`
|
Height int `json:"height,omitempty"`
|
||||||
Title string `json:"title,omitempty"`
|
Title string `json:"title,omitempty"`
|
||||||
Artist string `json:"artist,omitempty"`
|
Artist string `json:"artist,omitempty"`
|
||||||
FPS float64 `json:"fps,omitempty"`
|
FPS float64 `json:"fps,omitempty"`
|
||||||
SelectedFrame int `json:"selected_frame,omitempty"`
|
HasVideo bool `json:"has_video"`
|
||||||
HasVideo bool `json:"has_video"`
|
HasAudio bool `json:"has_audio"`
|
||||||
HasAudio bool `json:"has_audio"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type AVContext struct {
|
type AVContext struct {
|
||||||
|
|
@ -93,10 +92,32 @@ func (av *AVContext) SelectFrame(n int) (err error) {
|
||||||
nn = av.availableIndex
|
nn = av.availableIndex
|
||||||
}
|
}
|
||||||
av.selectedIndex = nn
|
av.selectedIndex = nn
|
||||||
if err = av.ProcessFrames(-1); err != nil {
|
return av.ProcessFrames(-1)
|
||||||
return
|
}
|
||||||
|
|
||||||
|
func (av *AVContext) positionToDuration(f float64) time.Duration {
|
||||||
|
return time.Duration(float64(av.duration) * math.Max(math.Min(f, 1), 0))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (av *AVContext) SelectPosition(f float64) (err error) {
|
||||||
|
return av.SelectDuration(av.positionToDuration(f))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (av *AVContext) SelectDuration(ts time.Duration) (err error) {
|
||||||
|
if ts > 0 {
|
||||||
|
if err = seekDuration(av, ts); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return av.SelectFrame(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) {
|
||||||
|
|
@ -121,21 +142,16 @@ func (av *AVContext) Metadata() *Metadata {
|
||||||
if av.availableDuration > 0 {
|
if av.availableDuration > 0 {
|
||||||
fps = float64(av.availableIndex) * float64(time.Second) / float64(av.availableDuration)
|
fps = float64(av.availableIndex) * float64(time.Second) / float64(av.availableDuration)
|
||||||
}
|
}
|
||||||
var selectedFrame int
|
|
||||||
if av.availableIndex > 0 && av.selectedIndex > -1 {
|
|
||||||
selectedFrame = int(av.selectedIndex) + 1
|
|
||||||
}
|
|
||||||
return &Metadata{
|
return &Metadata{
|
||||||
Orientation: av.orientation,
|
Orientation: av.orientation,
|
||||||
Duration: int(av.duration / time.Millisecond),
|
Duration: int(av.duration / time.Millisecond),
|
||||||
Width: av.width,
|
Width: av.width,
|
||||||
Height: av.height,
|
Height: av.height,
|
||||||
Title: av.title,
|
Title: av.title,
|
||||||
Artist: av.artist,
|
Artist: av.artist,
|
||||||
FPS: math.Round(fps*10) / 10,
|
FPS: math.Round(fps*10) / 10,
|
||||||
SelectedFrame: selectedFrame,
|
HasVideo: av.hasVideo,
|
||||||
HasVideo: av.hasVideo,
|
HasAudio: av.hasAudio,
|
||||||
HasAudio: av.hasAudio,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -217,6 +233,16 @@ func createDecoder(av *AVContext) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func seekDuration(av *AVContext, ts time.Duration) error {
|
||||||
|
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)
|
||||||
|
C.avcodec_flush_buffers(av.codecContext)
|
||||||
|
if err < 0 {
|
||||||
|
return avError(err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func incrementDuration(av *AVContext, frame *C.AVFrame, i C.int) {
|
func incrementDuration(av *AVContext, frame *C.AVFrame, i C.int) {
|
||||||
av.availableIndex = i
|
av.availableIndex = i
|
||||||
if frame.pts != C.AV_NOPTS_VALUE {
|
if frame.pts != C.AV_NOPTS_VALUE {
|
||||||
|
|
|
||||||
|
|
@ -70,13 +70,12 @@ func TestAVContext(t *testing.T) {
|
||||||
defer av.Close()
|
defer av.Close()
|
||||||
if n == 10 {
|
if n == 10 {
|
||||||
require.NoError(t, av.ProcessFrames(n))
|
require.NoError(t, av.ProcessFrames(n))
|
||||||
} else {
|
} else if n == 99999 {
|
||||||
if n == 9999 {
|
require.NoError(t, av.SeekPosition(0.6))
|
||||||
require.NoError(t, av.ProcessFrames(-1))
|
} else if n == 9999 {
|
||||||
}
|
require.NoError(t, av.SelectPosition(0.7))
|
||||||
if n > -1 {
|
} else if n > -1 {
|
||||||
require.NoError(t, av.SelectFrame(n))
|
require.NoError(t, av.SelectFrame(n))
|
||||||
}
|
|
||||||
}
|
}
|
||||||
meta := av.Metadata()
|
meta := av.Metadata()
|
||||||
metaBuf, err := json.Marshal(meta)
|
metaBuf, err := json.Marshal(meta)
|
||||||
|
|
|
||||||
28
processor.go
|
|
@ -10,6 +10,7 @@ import (
|
||||||
"io"
|
"io"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Processor struct {
|
type Processor struct {
|
||||||
|
|
@ -124,10 +125,31 @@ 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":
|
||||||
n, _ := strconv.Atoi(filter.Args)
|
if ts, e := time.ParseDuration(filter.Args); e == nil {
|
||||||
if err = av.SelectFrame(n); err != nil {
|
if err = av.SelectDuration(ts); err != nil {
|
||||||
return
|
return
|
||||||
|
}
|
||||||
|
} else if f, e := strconv.ParseFloat(filter.Args, 64); e == nil {
|
||||||
|
if strings.Contains(filter.Args, ".") {
|
||||||
|
if err = av.SelectPosition(f); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else if n := int(f); n >= 1 {
|
||||||
|
if err = av.SelectFrame(n); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
case "max_frames":
|
case "max_frames":
|
||||||
n, _ := strconv.Atoi(filter.Args)
|
n, _ := strconv.Atoi(filter.Args)
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,7 @@ type test struct {
|
||||||
name string
|
name string
|
||||||
path string
|
path string
|
||||||
expectCode int
|
expectCode int
|
||||||
|
sizeOnly bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestProcessor(t *testing.T) {
|
func TestProcessor(t *testing.T) {
|
||||||
|
|
@ -54,6 +55,10 @@ func TestProcessor(t *testing.T) {
|
||||||
{name: "mp4 orient 270", path: "200x100/schizo_270.mp4"},
|
{name: "mp4 orient 270", path: "200x100/schizo_270.mp4"},
|
||||||
{name: "image", path: "fit-in/100x100/demo.png"},
|
{name: "image", path: "fit-in/100x100/demo.png"},
|
||||||
{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 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},
|
||||||
|
|
@ -105,6 +110,9 @@ func doGoldenTests(t *testing.T, resultDir string, tests []test, opts ...Option)
|
||||||
assert.NoError(t, app.Shutdown(context.Background()))
|
assert.NoError(t, app.Shutdown(context.Background()))
|
||||||
})
|
})
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
|
if i == 1 && tt.sizeOnly {
|
||||||
|
continue
|
||||||
|
}
|
||||||
t.Run(fmt.Sprintf("%s-%d", tt.name, i+1), func(t *testing.T) {
|
t.Run(fmt.Sprintf("%s-%d", tt.name, i+1), func(t *testing.T) {
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
|
|
||||||
BIN
testdata/golden/export/alpha-webm.webm-9999.jpg
vendored
|
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 25 KiB |
BIN
testdata/golden/export/alpha-webm.webm-99999.jpg
vendored
|
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
BIN
testdata/golden/export/macabre.mp4-9999.jpg
vendored
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 13 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: 28 KiB After Width: | Height: | Size: 27 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,"fps":25,"selected_frame":5,"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":12040,"width":720,"height":576,"fps":25,"selected_frame":5,"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":12040,"width":720,"height":576,"fps":25,"selected_frame":100,"has_video":true,"has_audio":false}
|
{"orientation":1,"duration":12040,"width":720,"height":576,"has_video":true,"has_audio":false}
|
||||||
|
|
@ -1 +1 @@
|
||||||
{"orientation":1,"duration":12040,"width":720,"height":576,"fps":25,"selected_frame":100,"has_video":true,"has_audio":false}
|
{"orientation":1,"duration":12040,"width":720,"height":576,"has_video":true,"has_audio":false}
|
||||||
|
|
@ -1 +1 @@
|
||||||
{"orientation":1,"duration":7407,"width":640,"height":480,"fps":30,"selected_frame":6,"has_video":true,"has_audio":true}
|
{"orientation":1,"duration":7407,"width":640,"height":480,"fps":30,"has_video":true,"has_audio":true}
|
||||||
|
|
@ -1 +1 @@
|
||||||
{"orientation":1,"duration":7407,"width":640,"height":480,"fps":30.1,"selected_frame":5,"has_video":true,"has_audio":true}
|
{"orientation":1,"duration":7407,"width":640,"height":480,"fps":30.1,"has_video":true,"has_audio":true}
|
||||||
|
|
@ -1 +1 @@
|
||||||
{"orientation":1,"duration":7407,"width":640,"height":480,"fps":30,"selected_frame":100,"has_video":true,"has_audio":true}
|
{"orientation":1,"duration":7407,"width":640,"height":480,"has_video":true,"has_audio":true}
|
||||||
|
|
@ -1 +1 @@
|
||||||
{"orientation":1,"duration":7407,"width":640,"height":480,"fps":30,"selected_frame":100,"has_video":true,"has_audio":true}
|
{"orientation":1,"duration":7407,"width":640,"height":480,"has_video":true,"has_audio":true}
|
||||||
|
|
@ -1 +1 @@
|
||||||
{"orientation":1,"duration":3925,"width":492,"height":360,"fps":30,"selected_frame":5,"has_video":true,"has_audio":true}
|
{"orientation":1,"duration":3925,"width":492,"height":360,"fps":30,"has_video":true,"has_audio":true}
|
||||||
2
testdata/golden/meta/macabre.mp4-5.meta.json
vendored
|
|
@ -1 +1 @@
|
||||||
{"orientation":1,"duration":3925,"width":492,"height":360,"fps":30,"selected_frame":5,"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,"fps":30,"selected_frame":30,"has_video":true,"has_audio":true}
|
{"orientation":1,"duration":3925,"width":492,"height":360,"has_video":true,"has_audio":true}
|
||||||
|
|
@ -1 +1 @@
|
||||||
{"orientation":1,"duration":3925,"width":492,"height":360,"fps":30,"selected_frame":30,"has_video":true,"has_audio":true}
|
{"orientation":1,"duration":3925,"width":492,"height":360,"has_video":true,"has_audio":true}
|
||||||
2
testdata/golden/meta/schizo.flv-10.meta.json
vendored
|
|
@ -1 +1 @@
|
||||||
{"orientation":1,"duration":2560,"width":480,"height":360,"fps":27.9,"selected_frame":6,"has_video":true,"has_audio":true}
|
{"orientation":1,"duration":2560,"width":480,"height":360,"fps":27.9,"has_video":true,"has_audio":true}
|
||||||
2
testdata/golden/meta/schizo.flv-5.meta.json
vendored
|
|
@ -1 +1 @@
|
||||||
{"orientation":1,"duration":2560,"width":480,"height":360,"fps":25.6,"selected_frame":5,"has_video":true,"has_audio":true}
|
{"orientation":1,"duration":2560,"width":480,"height":360,"fps":25.6,"has_video":true,"has_audio":true}
|
||||||
|
|
@ -1 +1 @@
|
||||||
{"orientation":1,"duration":2560,"width":480,"height":360,"fps":29.7,"selected_frame":76,"has_video":true,"has_audio":true}
|
{"orientation":1,"duration":2560,"width":480,"height":360,"has_video":true,"has_audio":true}
|
||||||
|
|
@ -1 +1 @@
|
||||||
{"orientation":1,"duration":2560,"width":480,"height":360,"fps":29.7,"selected_frame":76,"has_video":true,"has_audio":true}
|
{"orientation":1,"duration":2560,"width":480,"height":360,"has_video":true,"has_audio":true}
|
||||||
|
|
@ -1 +1 @@
|
||||||
{"orientation":1,"duration":2544,"width":480,"height":360,"fps":30,"selected_frame":6,"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,"fps":30,"selected_frame":5,"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,"fps":30,"selected_frame":20,"has_video":true,"has_audio":true}
|
{"orientation":1,"duration":2544,"width":480,"height":360,"has_video":true,"has_audio":true}
|
||||||
|
|
@ -1 +1 @@
|
||||||
{"orientation":1,"duration":2544,"width":480,"height":360,"fps":30,"selected_frame":20,"has_video":true,"has_audio":true}
|
{"orientation":1,"duration":2544,"width":480,"height":360,"has_video":true,"has_audio":true}
|
||||||
|
|
@ -1 +1 @@
|
||||||
{"orientation":3,"duration":2544,"width":480,"height":360,"fps":30,"selected_frame":5,"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,"fps":30,"selected_frame":5,"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,"fps":30,"selected_frame":20,"has_video":true,"has_audio":true}
|
{"orientation":3,"duration":2544,"width":480,"height":360,"has_video":true,"has_audio":true}
|
||||||
|
|
@ -1 +1 @@
|
||||||
{"orientation":3,"duration":2544,"width":480,"height":360,"fps":30,"selected_frame":20,"has_video":true,"has_audio":true}
|
{"orientation":3,"duration":2544,"width":480,"height":360,"has_video":true,"has_audio":true}
|
||||||
|
|
@ -1 +1 @@
|
||||||
{"orientation":6,"duration":2544,"width":360,"height":480,"fps":30,"selected_frame":7,"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,"fps":30,"selected_frame":5,"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,"fps":30,"selected_frame":20,"has_video":true,"has_audio":true}
|
{"orientation":6,"duration":2544,"width":360,"height":480,"has_video":true,"has_audio":true}
|
||||||
|
|
@ -1 +1 @@
|
||||||
{"orientation":6,"duration":2544,"width":360,"height":480,"fps":30,"selected_frame":20,"has_video":true,"has_audio":true}
|
{"orientation":6,"duration":2544,"width":360,"height":480,"has_video":true,"has_audio":true}
|
||||||
|
|
@ -1 +1 @@
|
||||||
{"orientation":8,"duration":2544,"width":360,"height":480,"fps":30,"selected_frame":8,"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,"fps":30,"selected_frame":5,"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,"fps":30,"selected_frame":20,"has_video":true,"has_audio":true}
|
{"orientation":8,"duration":2544,"width":360,"height":480,"has_video":true,"has_audio":true}
|
||||||
|
|
@ -1 +1 @@
|
||||||
{"orientation":8,"duration":2544,"width":360,"height":480,"fps":30,"selected_frame":20,"has_video":true,"has_audio":true}
|
{"orientation":8,"duration":2544,"width":360,"height":480,"has_video":true,"has_audio":true}
|
||||||
BIN
testdata/golden/result/500x/filters%3Aframe%280.5%29%3Aformat%28png%29/alpha-webm.webm
vendored
Normal file
|
After Width: | Height: | Size: 76 KiB |
BIN
testdata/golden/result/500x/filters%3Aframe%285s%29%3Aformat%28png%29/alpha-webm.webm
vendored
Normal file
|
After Width: | Height: | Size: 159 KiB |
BIN
testdata/golden/result/500x/filters%3Aseek%280.5%29%3Aformat%28png%29/alpha-webm.webm
vendored
Normal file
|
After Width: | Height: | Size: 97 KiB |
BIN
testdata/golden/result/500x/filters%3Aseek%285s%29%3Aformat%28png%29/alpha-webm.webm
vendored
Normal file
|
After Width: | Height: | Size: 195 KiB |
|
|
@ -1 +1 @@
|
||||||
{"format":"mkv","content_type":"video/x-matroska","orientation":1,"duration":7407,"width":640,"height":480,"fps":30,"selected_frame":44,"has_video":true,"has_audio":true}
|
{"format":"mkv","content_type":"video/x-matroska","orientation":1,"duration":7407,"width":640,"height":480,"fps":30,"has_video":true,"has_audio":true}
|
||||||
|
|
@ -1 +1 @@
|
||||||
{"format":"mkv","content_type":"video/x-matroska","orientation":1,"duration":7407,"width":640,"height":480,"fps":29.9,"selected_frame":4,"has_video":true,"has_audio":true}
|
{"format":"mkv","content_type":"video/x-matroska","orientation":1,"duration":7407,"width":640,"height":480,"fps":29.9,"has_video":true,"has_audio":true}
|
||||||