mirror of https://github.com/stashapp/stash.git
188 lines
4.7 KiB
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)
|
||
|
}
|
||
|
}
|