Skip to content

Commit

Permalink
Selectable wall preview type (#510)
Browse files Browse the repository at this point in the history
* Add optional image preview generation
* Add setting for video preview encoding preset
  • Loading branch information
InfiniteTF authored May 26, 2020
1 parent 197918d commit 4ec6d62
Show file tree
Hide file tree
Showing 18 changed files with 493 additions and 341 deletions.
1 change: 1 addition & 0 deletions graphql/documents/data/config.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ fragment ConfigGeneralData on ConfigGeneralResult {
fragment ConfigInterfaceData on ConfigInterfaceResult {
soundOnPreview
wallShowTitle
wallPlayback
maximumLoopDuration
autostartVideo
showStudioAsText
Expand Down
4 changes: 4 additions & 0 deletions graphql/schema/types/config.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,8 @@ input ConfigInterfaceInput {
soundOnPreview: Boolean
"""Show title and tags in wall view"""
wallShowTitle: Boolean
"""Wall playback type"""
wallPlayback: String
"""Maximum duration (in seconds) in which a scene video will loop in the scene player"""
maximumLoopDuration: Int
"""If true, video will autostart on load in the scene player"""
Expand All @@ -103,6 +105,8 @@ type ConfigInterfaceResult {
soundOnPreview: Boolean
"""Show title and tags in wall view"""
wallShowTitle: Boolean
"""Wall playback type"""
wallPlayback: String
"""Maximum duration (in seconds) in which a scene video will loop in the scene player"""
maximumLoopDuration: Int
"""If true, video will autostart on load in the scene player"""
Expand Down
14 changes: 13 additions & 1 deletion graphql/schema/types/metadata.graphql
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
input GenerateMetadataInput {
sprites: Boolean!
previews: Boolean!
previewPreset: PreviewPreset
imagePreviews: Boolean!
markers: Boolean!
transcodes: Boolean!
"""gallery thumbnails for cache usage"""
Expand All @@ -22,6 +24,16 @@ input AutoTagMetadataInput {

type MetadataUpdateStatus {
progress: Float!
status: String!
status: String!
message: String!
}

enum PreviewPreset {
"X264_ULTRAFAST", ultrafast
"X264_VERYFAST", veryfast
"X264_FAST", fast
"X264_MEDIUM", medium
"X264_SLOW", slow
"X264_SLOWER", slower
"X264_VERYSLOW", veryslow
}
4 changes: 4 additions & 0 deletions pkg/api/resolver_mutation_configure.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,10 @@ func (r *mutationResolver) ConfigureInterface(ctx context.Context, input models.
config.Set(config.WallShowTitle, *input.WallShowTitle)
}

if input.WallPlayback != nil {
config.Set(config.WallPlayback, *input.WallPlayback)
}

if input.MaximumLoopDuration != nil {
config.Set(config.MaximumLoopDuration, *input.MaximumLoopDuration)
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/api/resolver_mutation_metadata.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ func (r *mutationResolver) MetadataExport(ctx context.Context) (string, error) {
}

func (r *mutationResolver) MetadataGenerate(ctx context.Context, input models.GenerateMetadataInput) (string, error) {
manager.GetInstance().Generate(input.Sprites, input.Previews, input.Markers, input.Transcodes, input.Thumbnails)
manager.GetInstance().Generate(input.Sprites, input.Previews, input.PreviewPreset, input.ImagePreviews, input.Markers, input.Transcodes, input.Thumbnails)
return "todo", nil
}

Expand Down
2 changes: 2 additions & 0 deletions pkg/api/resolver_query_configuration.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ func makeConfigGeneralResult() *models.ConfigGeneralResult {
func makeConfigInterfaceResult() *models.ConfigInterfaceResult {
soundOnPreview := config.GetSoundOnPreview()
wallShowTitle := config.GetWallShowTitle()
wallPlayback := config.GetWallPlayback()
maximumLoopDuration := config.GetMaximumLoopDuration()
autostartVideo := config.GetAutostartVideo()
showStudioAsText := config.GetShowStudioAsText()
Expand All @@ -75,6 +76,7 @@ func makeConfigInterfaceResult() *models.ConfigInterfaceResult {
return &models.ConfigInterfaceResult{
SoundOnPreview: &soundOnPreview,
WallShowTitle: &wallShowTitle,
WallPlayback: &wallPlayback,
MaximumLoopDuration: &maximumLoopDuration,
AutostartVideo: &autostartVideo,
ShowStudioAsText: &showStudioAsText,
Expand Down
5 changes: 3 additions & 2 deletions pkg/ffmpeg/encoder_scene_preview_chunk.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,10 @@ type ScenePreviewChunkOptions struct {
OutputPath string
}

func (e *Encoder) ScenePreviewVideoChunk(probeResult VideoFile, options ScenePreviewChunkOptions) {
func (e *Encoder) ScenePreviewVideoChunk(probeResult VideoFile, options ScenePreviewChunkOptions, preset string) {
args := []string{
"-v", "error",
"-xerror",
"-ss", strconv.Itoa(options.Time),
"-i", probeResult.Path,
"-t", "0.75",
Expand All @@ -25,7 +26,7 @@ func (e *Encoder) ScenePreviewVideoChunk(probeResult VideoFile, options ScenePre
"-pix_fmt", "yuv420p",
"-profile:v", "high",
"-level", "4.2",
"-preset", "slow",
"-preset", preset,
"-crf", "21",
"-threads", "4",
"-vf", fmt.Sprintf("scale=%v:-2", options.Width),
Expand Down
7 changes: 6 additions & 1 deletion pkg/ffmpeg/ffprobe.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"strings"
"time"

"github.com/stashapp/stash/pkg/logger"
"github.com/stashapp/stash/pkg/manager/config"
)

Expand Down Expand Up @@ -264,7 +265,11 @@ func parse(filePath string, probeJSON *FFProbeJSON) (*VideoFile, error) {
result.Container = probeJSON.Format.FormatName
duration, _ := strconv.ParseFloat(probeJSON.Format.Duration, 64)
result.Duration = math.Round(duration*100) / 100
fileStat, _ := os.Stat(filePath)
fileStat, err := os.Stat(filePath)
if err != nil {
logger.Errorf("Error statting file: %v", err)
return nil, err
}
result.Size = fileStat.Size()
result.StartTime, _ = strconv.ParseFloat(probeJSON.Format.StartTime, 64)
result.CreationTime = probeJSON.Format.Tags.CreationTime.Time
Expand Down
6 changes: 6 additions & 0 deletions pkg/manager/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ const MaximumLoopDuration = "maximum_loop_duration"
const AutostartVideo = "autostart_video"
const ShowStudioAsText = "show_studio_as_text"
const CSSEnabled = "cssEnabled"
const WallPlayback = "wall_playback"

// Playback force codec,container
const ForceMKV = "forceMKV"
Expand Down Expand Up @@ -241,6 +242,11 @@ func GetWallShowTitle() bool {
return viper.GetBool(WallShowTitle)
}

func GetWallPlayback() string {
viper.SetDefault(WallPlayback, "video")
return viper.GetString(WallPlayback)
}

func GetMaximumLoopDuration() int {
viper.SetDefault(MaximumLoopDuration, 0)
return viper.GetInt(MaximumLoopDuration)
Expand Down
25 changes: 19 additions & 6 deletions pkg/manager/generator_preview.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,14 @@ type PreviewGenerator struct {
VideoFilename string
ImageFilename string
OutputDirectory string

GenerateVideo bool
GenerateImage bool

PreviewPreset string
}

func NewPreviewGenerator(videoFile ffmpeg.VideoFile, videoFilename string, imageFilename string, outputDirectory string) (*PreviewGenerator, error) {
func NewPreviewGenerator(videoFile ffmpeg.VideoFile, videoFilename string, imageFilename string, outputDirectory string, generateVideo bool, generateImage bool, previewPreset string) (*PreviewGenerator, error) {
exists, err := utils.FileExists(videoFile.Path)
if !exists {
return nil, err
Expand All @@ -37,6 +42,9 @@ func NewPreviewGenerator(videoFile ffmpeg.VideoFile, videoFilename string, image
VideoFilename: videoFilename,
ImageFilename: imageFilename,
OutputDirectory: outputDirectory,
GenerateVideo: generateVideo,
GenerateImage: generateImage,
PreviewPreset: previewPreset,
}, nil
}

Expand All @@ -47,11 +55,16 @@ func (g *PreviewGenerator) Generate() error {
if err := g.generateConcatFile(); err != nil {
return err
}
if err := g.generateVideo(&encoder); err != nil {
return err

if g.GenerateVideo {
if err := g.generateVideo(&encoder); err != nil {
return err
}
}
if err := g.generateImage(&encoder); err != nil {
return err
if g.GenerateImage {
if err := g.generateImage(&encoder); err != nil {
return err
}
}
return nil
}
Expand Down Expand Up @@ -91,7 +104,7 @@ func (g *PreviewGenerator) generateVideo(encoder *ffmpeg.Encoder) error {
Width: 640,
OutputPath: chunkOutputPath,
}
encoder.ScenePreviewVideoChunk(g.Info.VideoFile, options)
encoder.ScenePreviewVideoChunk(g.Info.VideoFile, options, g.PreviewPreset)
}

videoOutputPath := filepath.Join(g.OutputDirectory, g.VideoFilename)
Expand Down
31 changes: 20 additions & 11 deletions pkg/manager/manager_tasks.go
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ func (s *singleton) Export() {
}()
}

func (s *singleton) Generate(sprites bool, previews bool, markers bool, transcodes bool, thumbnails bool) {
func (s *singleton) Generate(sprites bool, previews bool, previewPreset *models.PreviewPreset, imagePreviews bool, markers bool, transcodes bool, thumbnails bool) {
if s.Status.Status != Idle {
return
}
Expand All @@ -183,6 +183,11 @@ func (s *singleton) Generate(sprites bool, previews bool, markers bool, transcod
//this.job.total = await ObjectionUtils.getCount(Scene);
instance.Paths.Generated.EnsureTmpDir()

preset := string(models.PreviewPresetSlow)
if previewPreset != nil && previewPreset.IsValid() {
preset = string(*previewPreset)
}

go func() {
defer s.returnToIdleState()

Expand Down Expand Up @@ -212,12 +217,12 @@ func (s *singleton) Generate(sprites bool, previews bool, markers bool, transcod
logger.Info("Stopping due to user request")
return
}
totalsNeeded := s.neededGenerate(scenes, sprites, previews, markers, transcodes)
totalsNeeded := s.neededGenerate(scenes, sprites, previews, imagePreviews, markers, transcodes)
if totalsNeeded == nil {
logger.Infof("Taking too long to count content. Skipping...")
logger.Infof("Generating content")
} else {
logger.Infof("Generating %d sprites %d previews %d markers %d transcodes", totalsNeeded.sprites, totalsNeeded.previews, totalsNeeded.markers, totalsNeeded.transcodes)
logger.Infof("Generating %d sprites %d previews %d image previews %d markers %d transcodes", totalsNeeded.sprites, totalsNeeded.previews, totalsNeeded.imagePreviews, totalsNeeded.markers, totalsNeeded.transcodes)
}
for i, scene := range scenes {
s.Status.setProgress(i, total)
Expand All @@ -244,7 +249,7 @@ func (s *singleton) Generate(sprites bool, previews bool, markers bool, transcod
}

if previews {
task := GeneratePreviewTask{Scene: *scene}
task := GeneratePreviewTask{Scene: *scene, ImagePreview: imagePreviews, PreviewPreset: preset}
go task.Start(&wg)
}

Expand Down Expand Up @@ -602,13 +607,14 @@ func (s *singleton) neededScan(paths []string) int64 {
}

type totalsGenerate struct {
sprites int64
previews int64
markers int64
transcodes int64
sprites int64
previews int64
imagePreviews int64
markers int64
transcodes int64
}

func (s *singleton) neededGenerate(scenes []*models.Scene, sprites, previews, markers, transcodes bool) *totalsGenerate {
func (s *singleton) neededGenerate(scenes []*models.Scene, sprites, previews, imagePreviews, markers, transcodes bool) *totalsGenerate {

var totals totalsGenerate
const timeout = 90 * time.Second
Expand All @@ -633,10 +639,13 @@ func (s *singleton) neededGenerate(scenes []*models.Scene, sprites, previews, ma
}

if previews {
task := GeneratePreviewTask{Scene: *scene}
if !task.doesPreviewExist(task.Scene.Checksum) {
task := GeneratePreviewTask{Scene: *scene, ImagePreview: imagePreviews}
if !task.doesVideoPreviewExist(task.Scene.Checksum) {
totals.previews++
}
if imagePreviews && !task.doesImagePreviewExist(task.Scene.Checksum) {
totals.imagePreviews++
}
}

if markers {
Expand Down
17 changes: 12 additions & 5 deletions pkg/manager/task_generate_preview.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,18 @@ import (
)

type GeneratePreviewTask struct {
Scene models.Scene
Scene models.Scene
ImagePreview bool
PreviewPreset string
}

func (t *GeneratePreviewTask) Start(wg *sync.WaitGroup) {
defer wg.Done()

videoFilename := t.videoFilename()
imageFilename := t.imageFilename()
if t.doesPreviewExist(t.Scene.Checksum) {
videoExists := t.doesVideoPreviewExist(t.Scene.Checksum)
if (!t.ImagePreview || t.doesImagePreviewExist(t.Scene.Checksum)) && videoExists {
return
}

Expand All @@ -27,7 +30,7 @@ func (t *GeneratePreviewTask) Start(wg *sync.WaitGroup) {
return
}

generator, err := NewPreviewGenerator(*videoFile, videoFilename, imageFilename, instance.Paths.Generated.Screenshots)
generator, err := NewPreviewGenerator(*videoFile, videoFilename, imageFilename, instance.Paths.Generated.Screenshots, !videoExists, t.ImagePreview, t.PreviewPreset)
if err != nil {
logger.Errorf("error creating preview generator: %s", err.Error())
return
Expand All @@ -39,10 +42,14 @@ func (t *GeneratePreviewTask) Start(wg *sync.WaitGroup) {
}
}

func (t *GeneratePreviewTask) doesPreviewExist(sceneChecksum string) bool {
func (t *GeneratePreviewTask) doesVideoPreviewExist(sceneChecksum string) bool {
videoExists, _ := utils.FileExists(instance.Paths.Scene.GetStreamPreviewPath(sceneChecksum))
return videoExists
}

func (t *GeneratePreviewTask) doesImagePreviewExist(sceneChecksum string) bool {
imageExists, _ := utils.FileExists(instance.Paths.Scene.GetStreamPreviewImagePath(sceneChecksum))
return videoExists && imageExists
return imageExists
}

func (t *GeneratePreviewTask) videoFilename() string {
Expand Down
5 changes: 1 addition & 4 deletions pkg/utils/file.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,8 @@ func FileExists(path string) (bool, error) {
_, err := os.Stat(path)
if err == nil {
return true, nil
} else if os.IsNotExist(err) {
return false, err
} else {
panic(err)
}
return false, err
}

// DirExists returns true if the given path exists and is a directory
Expand Down
19 changes: 19 additions & 0 deletions ui/v2.5/src/components/Settings/SettingsInterfacePanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export const SettingsInterfacePanel: React.FC = () => {
const { data: config, error, loading } = useConfiguration();
const [soundOnPreview, setSoundOnPreview] = useState<boolean>(true);
const [wallShowTitle, setWallShowTitle] = useState<boolean>(true);
const [wallPlayback, setWallPlayback] = useState<string>("video");
const [maximumLoopDuration, setMaximumLoopDuration] = useState<number>(0);
const [autostartVideo, setAutostartVideo] = useState<boolean>(false);
const [showStudioAsText, setShowStudioAsText] = useState<boolean>(false);
Expand All @@ -19,6 +20,7 @@ export const SettingsInterfacePanel: React.FC = () => {
const [updateInterfaceConfig] = useConfigureInterface({
soundOnPreview,
wallShowTitle,
wallPlayback,
maximumLoopDuration,
autostartVideo,
showStudioAsText,
Expand All @@ -31,6 +33,7 @@ export const SettingsInterfacePanel: React.FC = () => {
const iCfg = config?.configuration?.interface;
setSoundOnPreview(iCfg?.soundOnPreview ?? true);
setWallShowTitle(iCfg?.wallShowTitle ?? true);
setWallPlayback(iCfg?.wallPlayback ?? "video");
setMaximumLoopDuration(iCfg?.maximumLoopDuration ?? 0);
setAutostartVideo(iCfg?.autostartVideo ?? false);
setShowStudioAsText(iCfg?.showStudioAsText ?? false);
Expand Down Expand Up @@ -85,6 +88,22 @@ export const SettingsInterfacePanel: React.FC = () => {
label="Enable sound"
onChange={() => setSoundOnPreview(!soundOnPreview)}
/>
<Form.Label htmlFor="wall-preview">
<h6>Preview Type</h6>
</Form.Label>
<Form.Control
as="select"
name="wall-preview"
className="col-4 input-control"
value={wallPlayback}
onChange={(e: React.ChangeEvent<HTMLSelectElement>) =>
setWallPlayback(e.currentTarget.value)
}
>
<option value="video">Video</option>
<option value="animation">Animated Image</option>
<option value="image">Static Image</option>
</Form.Control>
<Form.Text className="text-muted">
Configuration for wall items
</Form.Text>
Expand Down
Loading

0 comments on commit 4ec6d62

Please sign in to comment.