stash/pkg/scene/generate/marker_preview.go

188 lines
4.7 KiB
Go

package generate
import (
"context"
"github.com/stashapp/stash/pkg/ffmpeg"
"github.com/stashapp/stash/pkg/ffmpeg/transcoder"
"github.com/stashapp/stash/pkg/fsutil"
"github.com/stashapp/stash/pkg/logger"
)
const (
markerPreviewWidth = 640
markerPreviewDuration = 20
markerPreviewAudioBitrate = "64k"
markerImageDuration = 5
markerWebpFPS = 12
markerScreenshotQuality = 2
)
func (g Generator) MarkerPreviewVideo(ctx context.Context, input string, hash string, seconds int, includeAudio bool) error {
lockCtx := g.LockManager.ReadLock(ctx, input)
defer lockCtx.Cancel()
output := g.MarkerPaths.GetVideoPreviewPath(hash, seconds)
if !g.Overwrite {
if exists, _ := fsutil.FileExists(output); exists {
return nil
}
}
if err := g.generateFile(lockCtx, g.MarkerPaths, mp4Pattern, output, g.markerPreviewVideo(input, sceneMarkerOptions{
Seconds: seconds,
Audio: includeAudio,
})); err != nil {
return err
}
logger.Debug("created marker video: ", output)
return nil
}
type sceneMarkerOptions struct {
Seconds int
Audio bool
}
func (g Generator) markerPreviewVideo(input string, options sceneMarkerOptions) generateFn {
return func(lockCtx *fsutil.LockContext, tmpFn string) error {
var videoFilter ffmpeg.VideoFilter
videoFilter = videoFilter.ScaleWidth(markerPreviewWidth)
var videoArgs ffmpeg.Args
videoArgs = videoArgs.VideoFilter(videoFilter)
videoArgs = append(videoArgs,
"-pix_fmt", "yuv420p",
"-profile:v", "high",
"-level", "4.2",
"-preset", "veryslow",
"-crf", "24",
"-movflags", "+faststart",
"-threads", "4",
"-sws_flags", "lanczos",
"-strict", "-2",
)
trimOptions := transcoder.TranscodeOptions{
Duration: markerPreviewDuration,
StartTime: float64(options.Seconds),
OutputPath: tmpFn,
VideoCodec: ffmpeg.VideoCodecLibX264,
VideoArgs: videoArgs,
}
if options.Audio {
var audioArgs ffmpeg.Args
audioArgs = audioArgs.AudioBitrate(markerPreviewAudioBitrate)
trimOptions.AudioCodec = ffmpeg.AudioCodecAAC
trimOptions.AudioArgs = audioArgs
}
args := transcoder.Transcode(input, trimOptions)
return g.generate(lockCtx, args)
}
}
func (g Generator) SceneMarkerWebp(ctx context.Context, input string, hash string, seconds int) error {
lockCtx := g.LockManager.ReadLock(ctx, input)
defer lockCtx.Cancel()
output := g.MarkerPaths.GetWebpPreviewPath(hash, seconds)
if !g.Overwrite {
if exists, _ := fsutil.FileExists(output); exists {
return nil
}
}
if err := g.generateFile(lockCtx, g.MarkerPaths, webpPattern, output, g.sceneMarkerWebp(input, sceneMarkerOptions{
Seconds: seconds,
})); err != nil {
return err
}
logger.Debug("created marker image: ", output)
return nil
}
func (g Generator) sceneMarkerWebp(input string, options sceneMarkerOptions) generateFn {
return func(lockCtx *fsutil.LockContext, tmpFn string) error {
var videoFilter ffmpeg.VideoFilter
videoFilter = videoFilter.ScaleWidth(markerPreviewWidth)
videoFilter = videoFilter.Fps(markerWebpFPS)
var videoArgs ffmpeg.Args
videoArgs = videoArgs.VideoFilter(videoFilter)
videoArgs = append(videoArgs,
"-lossless", "1",
"-q:v", "70",
"-compression_level", "6",
"-preset", "default",
"-loop", "0",
"-threads", "4",
)
trimOptions := transcoder.TranscodeOptions{
Duration: markerImageDuration,
StartTime: float64(options.Seconds),
OutputPath: tmpFn,
VideoCodec: ffmpeg.VideoCodecLibWebP,
VideoArgs: videoArgs,
}
args := transcoder.Transcode(input, trimOptions)
return g.generate(lockCtx, args)
}
}
func (g Generator) SceneMarkerScreenshot(ctx context.Context, input string, hash string, seconds int, width int) error {
lockCtx := g.LockManager.ReadLock(ctx, input)
defer lockCtx.Cancel()
output := g.MarkerPaths.GetScreenshotPath(hash, seconds)
if !g.Overwrite {
if exists, _ := fsutil.FileExists(output); exists {
return nil
}
}
if err := g.generateFile(lockCtx, g.MarkerPaths, jpgPattern, output, g.sceneMarkerScreenshot(input, SceneMarkerScreenshotOptions{
Seconds: seconds,
Width: width,
})); err != nil {
return err
}
logger.Debug("created marker screenshot: ", output)
return nil
}
type SceneMarkerScreenshotOptions struct {
Seconds int
Width int
}
func (g Generator) sceneMarkerScreenshot(input string, options SceneMarkerScreenshotOptions) generateFn {
return func(lockCtx *fsutil.LockContext, tmpFn string) error {
ssOptions := transcoder.ScreenshotOptions{
OutputPath: tmpFn,
OutputType: transcoder.ScreenshotOutputTypeImage2,
Quality: markerScreenshotQuality,
Width: options.Width,
}
args := transcoder.ScreenshotTime(input, float64(options.Seconds), ssOptions)
return g.generate(lockCtx, args)
}
}