Add ffmpeg unit tests.
This commit is contained in:
parent
a1263feac4
commit
37c4b45f2f
|
@ -1,101 +1,13 @@
|
||||||
package executable_worker
|
package executable_worker
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"os/exec"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/photoview/photoview/api/utils"
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
"gopkg.in/vansante/go-ffprobe.v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
func InitializeExecutableWorkers() {
|
func InitializeExecutableWorkers() {
|
||||||
Magick = newMagickCli()
|
Magick = newMagickCli()
|
||||||
FfmpegCli = newFfmpegWorker()
|
Ffmpeg = newFfmpegCli()
|
||||||
}
|
}
|
||||||
|
|
||||||
var Magick *MagickCli = nil
|
var Magick *MagickCli = nil
|
||||||
var FfmpegCli *FfmpegWorker = nil
|
var Ffmpeg *FfmpegCli = nil
|
||||||
|
|
||||||
type ExecutableWorker interface {
|
type ExecutableWorker interface {
|
||||||
Path() string
|
Path() string
|
||||||
}
|
}
|
||||||
|
|
||||||
type FfmpegWorker struct {
|
|
||||||
path string
|
|
||||||
}
|
|
||||||
|
|
||||||
func newFfmpegWorker() *FfmpegWorker {
|
|
||||||
if utils.EnvDisableVideoEncoding.GetBool() {
|
|
||||||
log.Printf("Executable worker disabled (%s=1): ffmpeg\n", utils.EnvDisableVideoEncoding.GetName())
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
path, err := exec.LookPath("ffmpeg")
|
|
||||||
if err != nil {
|
|
||||||
log.Println("Executable worker not found: ffmpeg")
|
|
||||||
} else {
|
|
||||||
version, err := exec.Command(path, "-version").Output()
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("Error getting version of ffmpeg: %s\n", err)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Printf("Found executable worker: ffmpeg (%s)\n", strings.Split(string(version), "\n")[0])
|
|
||||||
|
|
||||||
return &FfmpegWorker{
|
|
||||||
path: path,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (worker *FfmpegWorker) IsInstalled() bool {
|
|
||||||
return worker != nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (worker *FfmpegWorker) EncodeMp4(inputPath string, outputPath string) error {
|
|
||||||
args := []string{
|
|
||||||
"-i",
|
|
||||||
inputPath,
|
|
||||||
"-vcodec", "h264",
|
|
||||||
"-acodec", "aac",
|
|
||||||
"-vf", "scale='min(1080,iw)':'min(1080,ih)':force_original_aspect_ratio=decrease:force_divisible_by=2",
|
|
||||||
"-movflags", "+faststart+use_metadata_tags",
|
|
||||||
outputPath,
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd := exec.Command(worker.path, args...)
|
|
||||||
|
|
||||||
if err := cmd.Run(); err != nil {
|
|
||||||
return errors.Wrapf(err, "encoding video using: %s", worker.path)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (worker *FfmpegWorker) EncodeVideoThumbnail(inputPath string, outputPath string, probeData *ffprobe.ProbeData) error {
|
|
||||||
|
|
||||||
thumbnailOffsetSeconds := fmt.Sprintf("%d", int(probeData.Format.DurationSeconds*0.25))
|
|
||||||
|
|
||||||
args := []string{
|
|
||||||
"-ss", thumbnailOffsetSeconds, // grab frame at time offset
|
|
||||||
"-i",
|
|
||||||
inputPath,
|
|
||||||
"-vframes", "1", // output one frame
|
|
||||||
"-an", // disable audio
|
|
||||||
"-vf", "scale='min(1024,iw)':'min(1024,ih)':force_original_aspect_ratio=decrease:force_divisible_by=2",
|
|
||||||
outputPath,
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd := exec.Command(worker.path, args...)
|
|
||||||
|
|
||||||
if err := cmd.Run(); err != nil {
|
|
||||||
return errors.Wrapf(err, "encoding video using: %s", worker.path)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
|
@ -0,0 +1,98 @@
|
||||||
|
package executable_worker
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"os/exec"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/photoview/photoview/api/utils"
|
||||||
|
"gopkg.in/vansante/go-ffprobe.v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
type FfmpegCli struct {
|
||||||
|
path string
|
||||||
|
}
|
||||||
|
|
||||||
|
func newFfmpegCli() *FfmpegCli {
|
||||||
|
if path, err := exec.LookPath("ffprobe"); err == nil {
|
||||||
|
if version, err := exec.Command(path, "-version").Output(); err == nil {
|
||||||
|
log.Println("Found ffprobe:", path, "version:", strings.Split(string(version), "\n")[0])
|
||||||
|
ffprobe.SetFFProbeBinPath(path)
|
||||||
|
} else {
|
||||||
|
log.Println("Executable ffprobe not executable:", path)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.Println("Executable ffprobe not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
if utils.EnvDisableVideoEncoding.GetBool() {
|
||||||
|
log.Printf("Executable worker disabled (%s=%q): ffmpeg\n", utils.EnvDisableVideoEncoding.GetName(), utils.EnvDisableVideoEncoding.GetValue())
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
path, err := exec.LookPath("ffmpeg")
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Executable worker not found: ffmpeg")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
version, err := exec.Command(path, "-version").Output()
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Error getting version of ffmpeg: %s\n", err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("Found executable worker: ffmpeg (%s)\n", strings.Split(string(version), "\n")[0])
|
||||||
|
|
||||||
|
return &FfmpegCli{
|
||||||
|
path: path,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (worker *FfmpegCli) IsInstalled() bool {
|
||||||
|
return worker != nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (worker *FfmpegCli) EncodeMp4(inputPath string, outputPath string) error {
|
||||||
|
args := []string{
|
||||||
|
"-i",
|
||||||
|
inputPath,
|
||||||
|
"-vcodec", "h264",
|
||||||
|
"-acodec", "aac",
|
||||||
|
"-vf", "scale='min(1080,iw)':'min(1080,ih)':force_original_aspect_ratio=decrease:force_divisible_by=2",
|
||||||
|
"-movflags", "+faststart+use_metadata_tags",
|
||||||
|
outputPath,
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd := exec.Command(worker.path, args...)
|
||||||
|
|
||||||
|
if err := cmd.Run(); err != nil {
|
||||||
|
return fmt.Errorf("encoding video with %q %v error: %w", worker.path, args, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (worker *FfmpegCli) EncodeVideoThumbnail(inputPath string, outputPath string, probeData *ffprobe.ProbeData) error {
|
||||||
|
|
||||||
|
thumbnailOffsetSeconds := fmt.Sprintf("%d", int(probeData.Format.DurationSeconds*0.25))
|
||||||
|
|
||||||
|
args := []string{
|
||||||
|
"-ss", thumbnailOffsetSeconds, // grab frame at time offset
|
||||||
|
"-i",
|
||||||
|
inputPath,
|
||||||
|
"-vframes", "1", // output one frame
|
||||||
|
"-an", // disable audio
|
||||||
|
"-vf", "scale='min(1024,iw)':'min(1024,ih)':force_original_aspect_ratio=decrease:force_divisible_by=2",
|
||||||
|
outputPath,
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd := exec.Command(worker.path, args...)
|
||||||
|
|
||||||
|
if err := cmd.Run(); err != nil {
|
||||||
|
return fmt.Errorf("encoding video thumbnail with %q %v error: %w", worker.path, args, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,90 @@
|
||||||
|
package executable_worker_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"regexp"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/photoview/photoview/api/scanner/media_encoding/executable_worker"
|
||||||
|
"gopkg.in/vansante/go-ffprobe.v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestFfmpegWorkerNotExist(t *testing.T) {
|
||||||
|
done := setPathWithCurrent()
|
||||||
|
defer done()
|
||||||
|
|
||||||
|
executable_worker.InitializeExecutableWorkers()
|
||||||
|
|
||||||
|
if executable_worker.Ffmpeg.IsInstalled() {
|
||||||
|
t.Error("Ffmpeg should not be installed, but is found:", executable_worker.Ffmpeg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFfmpegWorkerIgnore(t *testing.T) {
|
||||||
|
donePath := setPathWithCurrent("./testdata/bin")
|
||||||
|
defer donePath()
|
||||||
|
|
||||||
|
doneEnv := setEnv("PHOTOVIEW_DISABLE_VIDEO_ENCODING", "true")
|
||||||
|
defer doneEnv()
|
||||||
|
|
||||||
|
executable_worker.InitializeExecutableWorkers()
|
||||||
|
|
||||||
|
if executable_worker.Ffmpeg.IsInstalled() {
|
||||||
|
t.Error("Ffmpeg should not be installed, but is found:", executable_worker.Ffmpeg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFfmpegWorker(t *testing.T) {
|
||||||
|
done := setPathWithCurrent("./testdata/bin")
|
||||||
|
defer done()
|
||||||
|
|
||||||
|
executable_worker.InitializeExecutableWorkers()
|
||||||
|
|
||||||
|
if !executable_worker.Ffmpeg.IsInstalled() {
|
||||||
|
t.Error("Ffmpeg should be installed")
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("EncodeMp4Failed", func(t *testing.T) {
|
||||||
|
doneEnv := setEnv("FAIL_WITH", "expect failure")
|
||||||
|
defer doneEnv()
|
||||||
|
|
||||||
|
err := executable_worker.Ffmpeg.EncodeMp4("input_fail", "output")
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("Ffmpeg.EncodeMp4(...) = nil, should be an error.")
|
||||||
|
}
|
||||||
|
if got, want := err.Error(), `^encoding video with ".*/testdata/bin/ffmpeg" .* error: .*$`; !regexp.MustCompile(want).MatchString(got) {
|
||||||
|
t.Errorf("Ffmpeg.EncodeMp4(...) = %q, should be as reg pattern %q", got, want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("EncodeMp4Succeeded", func(t *testing.T) {
|
||||||
|
err := executable_worker.Ffmpeg.EncodeMp4("input", "output")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Ffmpeg.EncodeMp4(...) = %v, should be nil.", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
probeData := &ffprobe.ProbeData{
|
||||||
|
Format: &ffprobe.Format{
|
||||||
|
DurationSeconds: 10,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
t.Run("EncodeVideoThumbnailMp4Failed", func(t *testing.T) {
|
||||||
|
doneEnv := setEnv("FAIL_WITH", "expect failure")
|
||||||
|
defer doneEnv()
|
||||||
|
|
||||||
|
err := executable_worker.Ffmpeg.EncodeVideoThumbnail("input_fail", "output", probeData)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("Ffmpeg.EncodeVideoThumbnail(...) = nil, should be an error.")
|
||||||
|
}
|
||||||
|
if got, want := err.Error(), `^encoding video thumbnail with ".*/testdata/bin/ffmpeg" .* error: .*$`; !regexp.MustCompile(want).MatchString(got) {
|
||||||
|
t.Errorf("Ffmpeg.EncodeVideoThumbnail(...) = %q, should be as reg pattern %q", got, want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("EncodeVideoThumbnailSucceeded", func(t *testing.T) {
|
||||||
|
err := executable_worker.Ffmpeg.EncodeVideoThumbnail("input", "output", probeData)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Ffmpeg.EncodeVideoThumbnail(...) = %v, should be nil.", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
#!/bin/sh
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
: ${FAIL_WITH=""}
|
||||||
|
|
||||||
|
case "$1" in
|
||||||
|
"--version")
|
||||||
|
echo ffmpeg: version fake
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
if [ "${FAIL_WITH}" != "" ]
|
||||||
|
then
|
||||||
|
echo ${FAIL_WITH}
|
||||||
|
exit -1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo $@
|
|
@ -0,0 +1,18 @@
|
||||||
|
#!/bin/sh
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
: ${FAIL_WITH=""}
|
||||||
|
|
||||||
|
case "$1" in
|
||||||
|
"--version")
|
||||||
|
echo ffprobe: version fake
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
if [ "${FAIL_WITH}" != "" ]
|
||||||
|
then
|
||||||
|
echo ${FAIL_WITH}
|
||||||
|
exit -1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo $@
|
|
@ -264,7 +264,7 @@ func (imgType *MediaType) IsSupported() bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
if executable_worker.FfmpegCli.IsInstalled() && imgType.IsVideo() {
|
if executable_worker.Ffmpeg.IsInstalled() && imgType.IsVideo() {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -94,7 +94,7 @@ func (t ProcessVideoTask) ProcessMedia(ctx scanner_task.TaskContext, mediaData *
|
||||||
|
|
||||||
webVideoPath := path.Join(mediaCachePath, web_video_name)
|
webVideoPath := path.Join(mediaCachePath, web_video_name)
|
||||||
|
|
||||||
err = executable_worker.FfmpegCli.EncodeMp4(video.Path, webVideoPath)
|
err = executable_worker.Ffmpeg.EncodeMp4(video.Path, webVideoPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return []*models.MediaURL{}, errors.Wrapf(err, "could not encode mp4 video (%s)", video.Path)
|
return []*models.MediaURL{}, errors.Wrapf(err, "could not encode mp4 video (%s)", video.Path)
|
||||||
}
|
}
|
||||||
|
@ -139,7 +139,7 @@ func (t ProcessVideoTask) ProcessMedia(ctx scanner_task.TaskContext, mediaData *
|
||||||
|
|
||||||
thumbImagePath := path.Join(mediaCachePath, video_thumb_name)
|
thumbImagePath := path.Join(mediaCachePath, video_thumb_name)
|
||||||
|
|
||||||
err = executable_worker.FfmpegCli.EncodeVideoThumbnail(video.Path, thumbImagePath, probeData)
|
err = executable_worker.Ffmpeg.EncodeVideoThumbnail(video.Path, thumbImagePath, probeData)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return []*models.MediaURL{}, errors.Wrapf(err, "failed to generate thumbnail for video (%s)", video.Title)
|
return []*models.MediaURL{}, errors.Wrapf(err, "failed to generate thumbnail for video (%s)", video.Title)
|
||||||
}
|
}
|
||||||
|
@ -177,7 +177,7 @@ func (t ProcessVideoTask) ProcessMedia(ctx scanner_task.TaskContext, mediaData *
|
||||||
fmt.Printf("Video thumbnail found in database but not in cache, re-encoding photo to cache: %s\n", videoThumbnailURL.MediaName)
|
fmt.Printf("Video thumbnail found in database but not in cache, re-encoding photo to cache: %s\n", videoThumbnailURL.MediaName)
|
||||||
updatedURLs = append(updatedURLs, videoThumbnailURL)
|
updatedURLs = append(updatedURLs, videoThumbnailURL)
|
||||||
|
|
||||||
err = executable_worker.FfmpegCli.EncodeVideoThumbnail(video.Path, thumbImagePath, probeData)
|
err = executable_worker.Ffmpeg.EncodeVideoThumbnail(video.Path, thumbImagePath, probeData)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return []*models.MediaURL{}, errors.Wrapf(err, "failed to generate thumbnail for video (%s)", video.Title)
|
return []*models.MediaURL{}, errors.Wrapf(err, "failed to generate thumbnail for video (%s)", video.Title)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue