diff --git a/graphql/documents/data/config.graphql b/graphql/documents/data/config.graphql index 653f2d3fd..71f7b9ef3 100644 --- a/graphql/documents/data/config.graphql +++ b/graphql/documents/data/config.graphql @@ -3,6 +3,10 @@ fragment ConfigGeneralData on ConfigGeneralResult { databasePath generatedPath cachePath + previewSegments + previewSegmentDuration + previewExcludeStart + previewExcludeEnd previewPreset maxTranscodeSize maxStreamingTranscodeSize diff --git a/graphql/schema/types/config.graphql b/graphql/schema/types/config.graphql index c1c684e63..ff7e3f845 100644 --- a/graphql/schema/types/config.graphql +++ b/graphql/schema/types/config.graphql @@ -26,6 +26,14 @@ input ConfigGeneralInput { generatedPath: String """Path to cache""" cachePath: String + """Number of segments in a preview file""" + previewSegments: Int + """Preview segment duration, in seconds""" + previewSegmentDuration: Float + """Duration of start of video to exclude when generating previews""" + previewExcludeStart: String + """Duration of end of video to exclude when generating previews""" + previewExcludeEnd: String """Preset when generating preview""" previewPreset: PreviewPreset """Max generated transcode size""" @@ -61,6 +69,14 @@ type ConfigGeneralResult { generatedPath: String! """Path to cache""" cachePath: String! + """Number of segments in a preview file""" + previewSegments: Int! + """Preview segment duration, in seconds""" + previewSegmentDuration: Float! + """Duration of start of video to exclude when generating previews""" + previewExcludeStart: String! + """Duration of end of video to exclude when generating previews""" + previewExcludeEnd: String! """Preset when generating preview""" previewPreset: PreviewPreset! """Max generated transcode size""" diff --git a/graphql/schema/types/metadata.graphql b/graphql/schema/types/metadata.graphql index 8bb868255..25a5b66f7 100644 --- a/graphql/schema/types/metadata.graphql +++ b/graphql/schema/types/metadata.graphql @@ -2,6 +2,7 @@ input GenerateMetadataInput { sprites: Boolean! previews: Boolean! imagePreviews: Boolean! + previewOptions: GeneratePreviewOptionsInput markers: Boolean! transcodes: Boolean! """gallery thumbnails for cache usage""" @@ -18,6 +19,19 @@ input GenerateMetadataInput { overwrite: Boolean } +input GeneratePreviewOptionsInput { + """Number of segments in a preview file""" + previewSegments: Int + """Preview segment duration, in seconds""" + previewSegmentDuration: Float + """Duration of start of video to exclude when generating previews""" + previewExcludeStart: String + """Duration of end of video to exclude when generating previews""" + previewExcludeEnd: String + """Preset when generating preview""" + previewPreset: PreviewPreset +} + input ScanMetadataInput { useFileMetadata: Boolean! } diff --git a/pkg/api/resolver_mutation_configure.go b/pkg/api/resolver_mutation_configure.go index 7b6f2cbfc..45f0826ef 100644 --- a/pkg/api/resolver_mutation_configure.go +++ b/pkg/api/resolver_mutation_configure.go @@ -45,6 +45,18 @@ func (r *mutationResolver) ConfigureGeneral(ctx context.Context, input models.Co config.Set(config.Cache, input.CachePath) } + if input.PreviewSegments != nil { + config.Set(config.PreviewSegments, *input.PreviewSegments) + } + if input.PreviewSegmentDuration != nil { + config.Set(config.PreviewSegmentDuration, *input.PreviewSegmentDuration) + } + if input.PreviewExcludeStart != nil { + config.Set(config.PreviewExcludeStart, *input.PreviewExcludeStart) + } + if input.PreviewExcludeEnd != nil { + config.Set(config.PreviewExcludeEnd, *input.PreviewExcludeEnd) + } if input.PreviewPreset != nil { config.Set(config.PreviewPreset, input.PreviewPreset.String()) } diff --git a/pkg/api/resolver_query_configuration.go b/pkg/api/resolver_query_configuration.go index e8a9603e5..759400eed 100644 --- a/pkg/api/resolver_query_configuration.go +++ b/pkg/api/resolver_query_configuration.go @@ -46,6 +46,10 @@ func makeConfigGeneralResult() *models.ConfigGeneralResult { DatabasePath: config.GetDatabasePath(), GeneratedPath: config.GetGeneratedPath(), CachePath: config.GetCachePath(), + PreviewSegments: config.GetPreviewSegments(), + PreviewSegmentDuration: config.GetPreviewSegmentDuration(), + PreviewExcludeStart: config.GetPreviewExcludeStart(), + PreviewExcludeEnd: config.GetPreviewExcludeEnd(), PreviewPreset: config.GetPreviewPreset(), MaxTranscodeSize: &maxTranscodeSize, MaxStreamingTranscodeSize: &maxStreamingTranscodeSize, diff --git a/pkg/api/routes_scene.go b/pkg/api/routes_scene.go index 1a2c775ce..cd9dc4027 100644 --- a/pkg/api/routes_scene.go +++ b/pkg/api/routes_scene.go @@ -187,7 +187,7 @@ func (rs sceneRoutes) Screenshot(w http.ResponseWriter, r *http.Request) { func (rs sceneRoutes) Preview(w http.ResponseWriter, r *http.Request) { scene := r.Context().Value(sceneKey).(*models.Scene) filepath := manager.GetInstance().Paths.Scene.GetStreamPreviewPath(scene.Checksum) - http.ServeFile(w, r, filepath) + utils.ServeFileNoCache(w, r, filepath) } func (rs sceneRoutes) Webp(w http.ResponseWriter, r *http.Request) { diff --git a/pkg/ffmpeg/encoder_scene_preview_chunk.go b/pkg/ffmpeg/encoder_scene_preview_chunk.go index 4f6a8a6fd..e68ff5872 100644 --- a/pkg/ffmpeg/encoder_scene_preview_chunk.go +++ b/pkg/ffmpeg/encoder_scene_preview_chunk.go @@ -8,7 +8,8 @@ import ( ) type ScenePreviewChunkOptions struct { - Time int + StartTime float64 + Duration float64 Width int OutputPath string } @@ -17,9 +18,9 @@ func (e *Encoder) ScenePreviewVideoChunk(probeResult VideoFile, options ScenePre args := []string{ "-v", "error", "-xerror", - "-ss", strconv.Itoa(options.Time), + "-ss", strconv.FormatFloat(options.StartTime, 'f', 2, 64), "-i", probeResult.Path, - "-t", "0.75", + "-t", strconv.FormatFloat(options.Duration, 'f', 2, 64), "-max_muxing_queue_size", "1024", // https://trac.ffmpeg.org/ticket/6375 "-y", "-c:v", "libx264", diff --git a/pkg/manager/config/config.go b/pkg/manager/config/config.go index b395ecbe4..a3ebb48e5 100644 --- a/pkg/manager/config/config.go +++ b/pkg/manager/config/config.go @@ -32,6 +32,18 @@ const PreviewPreset = "preview_preset" const MaxTranscodeSize = "max_transcode_size" const MaxStreamingTranscodeSize = "max_streaming_transcode_size" +const PreviewSegmentDuration = "preview_segment_duration" +const previewSegmentDurationDefault = 0.75 + +const PreviewSegments = "preview_segments" +const previewSegmentsDefault = 12 + +const PreviewExcludeStart = "preview_exclude_start" +const previewExcludeStartDefault = "0" + +const PreviewExcludeEnd = "preview_exclude_end" +const previewExcludeEndDefault = "0" + const Host = "host" const Port = "port" const ExternalHost = "external_host" @@ -158,6 +170,36 @@ func GetExternalHost() string { return viper.GetString(ExternalHost) } +// GetPreviewSegmentDuration returns the duration of a single segment in a +// scene preview file, in seconds. +func GetPreviewSegmentDuration() float64 { + return viper.GetFloat64(PreviewSegmentDuration) +} + +// GetPreviewSegments returns the amount of segments in a scene preview file. +func GetPreviewSegments() int { + return viper.GetInt(PreviewSegments) +} + +// GetPreviewExcludeStart returns the configuration setting string for +// excluding the start of scene videos for preview generation. This can +// be in two possible formats. A float value is interpreted as the amount +// of seconds to exclude from the start of the video before it is included +// in the preview. If the value is suffixed with a '%' character (for example +// '2%'), then it is interpreted as a proportion of the total video duration. +func GetPreviewExcludeStart() string { + return viper.GetString(PreviewExcludeStart) +} + +// GetPreviewExcludeEnd returns the configuration setting string for +// excluding the end of scene videos for preview generation. A float value +// is interpreted as the amount of seconds to exclude from the end of the video +// when generating previews. If the value is suffixed with a '%' character, +// then it is interpreted as a proportion of the total video duration. +func GetPreviewExcludeEnd() string { + return viper.GetString(PreviewExcludeEnd) +} + // GetPreviewPreset returns the preset when generating previews. Defaults to // Slow. func GetPreviewPreset() models.PreviewPreset { @@ -371,6 +413,13 @@ func IsValid() bool { return setPaths } +func setDefaultValues() { + viper.SetDefault(PreviewSegmentDuration, previewSegmentDurationDefault) + viper.SetDefault(PreviewSegments, previewSegmentsDefault) + viper.SetDefault(PreviewExcludeStart, previewExcludeStartDefault) + viper.SetDefault(PreviewExcludeEnd, previewExcludeEndDefault) +} + // SetInitialConfig fills in missing required config fields func SetInitialConfig() error { // generate some api keys @@ -386,5 +435,7 @@ func SetInitialConfig() error { Set(SessionStoreKey, sessionStoreKey) } + setDefaultValues() + return Write() } diff --git a/pkg/manager/generator.go b/pkg/manager/generator.go index 53d5a92f5..63c2dfb7f 100644 --- a/pkg/manager/generator.go +++ b/pkg/manager/generator.go @@ -7,6 +7,7 @@ import ( "os/exec" "runtime" "strconv" + "strings" "github.com/stashapp/stash/pkg/ffmpeg" "github.com/stashapp/stash/pkg/logger" @@ -17,7 +18,13 @@ type GeneratorInfo struct { ChunkCount int FrameRate float64 NumberOfFrames int - NthFrame int + + // NthFrame used for sprite generation + NthFrame int + + ChunkDuration float64 + ExcludeStart string + ExcludeEnd string VideoFile ffmpeg.VideoFile } @@ -33,12 +40,7 @@ func newGeneratorInfo(videoFile ffmpeg.VideoFile) (*GeneratorInfo, error) { return generator, nil } -func (g *GeneratorInfo) configure() error { - videoStream := g.VideoFile.VideoStream - if videoStream == nil { - return fmt.Errorf("missing video stream") - } - +func (g *GeneratorInfo) calculateFrameRate(videoStream *ffmpeg.FFProbeStream) error { var framerate float64 if g.VideoFile.FrameRate == 0 { framerate, _ = strconv.ParseFloat(videoStream.RFrameRate, 64) @@ -94,7 +96,54 @@ func (g *GeneratorInfo) configure() error { g.FrameRate = framerate g.NumberOfFrames = numberOfFrames + + return nil +} + +func (g *GeneratorInfo) configure() error { + videoStream := g.VideoFile.VideoStream + if videoStream == nil { + return fmt.Errorf("missing video stream") + } + + if err := g.calculateFrameRate(videoStream); err != nil { + return err + } + g.NthFrame = g.NumberOfFrames / g.ChunkCount return nil } + +func (g GeneratorInfo) getExcludeValue(v string) float64 { + if strings.HasSuffix(v, "%") && len(v) > 1 { + // proportion of video duration + v = v[0 : len(v)-1] + prop, _ := strconv.ParseFloat(v, 64) + return prop / 100.0 * g.VideoFile.Duration + } + + prop, _ := strconv.ParseFloat(v, 64) + return prop +} + +// getStepSizeAndOffset calculates the step size for preview generation and +// the starting offset. +// +// Step size is calculated based on the duration of the video file, minus the +// excluded duration. The offset is based on the ExcludeStart. If the total +// excluded duration exceeds the duration of the video, then offset is 0, and +// the video duration is used to calculate the step size. +func (g GeneratorInfo) getStepSizeAndOffset() (stepSize float64, offset float64) { + duration := g.VideoFile.Duration + excludeStart := g.getExcludeValue(g.ExcludeStart) + excludeEnd := g.getExcludeValue(g.ExcludeEnd) + + if duration > excludeStart+excludeEnd { + duration = duration - excludeStart - excludeEnd + offset = excludeStart + } + + stepSize = duration / float64(g.ChunkCount) + return +} diff --git a/pkg/manager/generator_preview.go b/pkg/manager/generator_preview.go index a1a6c173e..f167e7aa3 100644 --- a/pkg/manager/generator_preview.go +++ b/pkg/manager/generator_preview.go @@ -36,9 +36,6 @@ func NewPreviewGenerator(videoFile ffmpeg.VideoFile, videoFilename string, image return nil, err } generator.ChunkCount = 12 // 12 segments to the preview - if err := generator.configure(); err != nil { - return nil, err - } return &PreviewGenerator{ Info: generator, @@ -53,6 +50,11 @@ func NewPreviewGenerator(videoFile ffmpeg.VideoFile, videoFilename string, image func (g *PreviewGenerator) Generate() error { logger.Infof("[generator] generating scene preview for %s", g.Info.VideoFile.Path) + + if err := g.Info.configure(); err != nil { + return err + } + encoder := ffmpeg.NewEncoder(instance.FFMPEGPath) if err := g.generateConcatFile(); err != nil { @@ -95,15 +97,17 @@ func (g *PreviewGenerator) generateVideo(encoder *ffmpeg.Encoder) error { return nil } - stepSize := int(g.Info.VideoFile.Duration / float64(g.Info.ChunkCount)) + stepSize, offset := g.Info.getStepSizeAndOffset() + for i := 0; i < g.Info.ChunkCount; i++ { - time := i * stepSize + time := offset + (float64(i) * stepSize) num := fmt.Sprintf("%.3d", i) filename := "preview" + num + ".mp4" chunkOutputPath := instance.Paths.Generated.GetTmpPath(filename) options := ffmpeg.ScenePreviewChunkOptions{ - Time: time, + StartTime: time, + Duration: g.Info.ChunkDuration, Width: 640, OutputPath: chunkOutputPath, } diff --git a/pkg/manager/manager.go b/pkg/manager/manager.go index bcfcdceda..a98d32b27 100644 --- a/pkg/manager/manager.go +++ b/pkg/manager/manager.go @@ -81,6 +81,8 @@ func initConfig() { } logger.Infof("using config file: %s", viper.ConfigFileUsed()) + config.SetInitialConfig() + viper.SetDefault(config.Database, paths.GetDefaultDatabaseFilePath()) // Set generated to the metadata path for backwards compat diff --git a/pkg/manager/manager_tasks.go b/pkg/manager/manager_tasks.go index 335a3097e..05875c115 100644 --- a/pkg/manager/manager_tasks.go +++ b/pkg/manager/manager_tasks.go @@ -167,6 +167,33 @@ func (s *singleton) Export() { }() } +func setGeneratePreviewOptionsInput(optionsInput *models.GeneratePreviewOptionsInput) { + if optionsInput.PreviewSegments == nil { + val := config.GetPreviewSegments() + optionsInput.PreviewSegments = &val + } + + if optionsInput.PreviewSegmentDuration == nil { + val := config.GetPreviewSegmentDuration() + optionsInput.PreviewSegmentDuration = &val + } + + if optionsInput.PreviewExcludeStart == nil { + val := config.GetPreviewExcludeStart() + optionsInput.PreviewExcludeStart = &val + } + + if optionsInput.PreviewExcludeEnd == nil { + val := config.GetPreviewExcludeEnd() + optionsInput.PreviewExcludeEnd = &val + } + + if optionsInput.PreviewPreset == nil { + val := config.GetPreviewPreset() + optionsInput.PreviewPreset = &val + } +} + func (s *singleton) Generate(input models.GenerateMetadataInput) { if s.Status.Status != Idle { return @@ -181,8 +208,6 @@ func (s *singleton) Generate(input models.GenerateMetadataInput) { //this.job.total = await ObjectionUtils.getCount(Scene); instance.Paths.Generated.EnsureTmpDir() - preset := config.GetPreviewPreset().String() - galleryIDs := utils.StringSliceToIntSlice(input.GalleryIDs) sceneIDs := utils.StringSliceToIntSlice(input.SceneIDs) markerIDs := utils.StringSliceToIntSlice(input.MarkerIDs) @@ -251,6 +276,12 @@ func (s *singleton) Generate(input models.GenerateMetadataInput) { overwrite = *input.Overwrite } + generatePreviewOptions := input.PreviewOptions + if generatePreviewOptions == nil { + generatePreviewOptions = &models.GeneratePreviewOptionsInput{} + } + setGeneratePreviewOptionsInput(generatePreviewOptions) + for i, scene := range scenes { s.Status.setProgress(i, total) if s.Status.stopping { @@ -276,7 +307,12 @@ func (s *singleton) Generate(input models.GenerateMetadataInput) { } if input.Previews { - task := GeneratePreviewTask{Scene: *scene, ImagePreview: input.ImagePreviews, PreviewPreset: preset, Overwrite: overwrite} + task := GeneratePreviewTask{ + Scene: *scene, + ImagePreview: input.ImagePreviews, + Options: *generatePreviewOptions, + Overwrite: overwrite, + } go task.Start(&wg) } diff --git a/pkg/manager/task_generate_preview.go b/pkg/manager/task_generate_preview.go index 492d16839..51f054449 100644 --- a/pkg/manager/task_generate_preview.go +++ b/pkg/manager/task_generate_preview.go @@ -10,10 +10,12 @@ import ( ) type GeneratePreviewTask struct { - Scene models.Scene - ImagePreview bool - PreviewPreset string - Overwrite bool + Scene models.Scene + ImagePreview bool + + Options models.GeneratePreviewOptionsInput + + Overwrite bool } func (t *GeneratePreviewTask) Start(wg *sync.WaitGroup) { @@ -32,13 +34,19 @@ func (t *GeneratePreviewTask) Start(wg *sync.WaitGroup) { return } - generator, err := NewPreviewGenerator(*videoFile, videoFilename, imageFilename, instance.Paths.Generated.Screenshots, true, t.ImagePreview, t.PreviewPreset) + generator, err := NewPreviewGenerator(*videoFile, videoFilename, imageFilename, instance.Paths.Generated.Screenshots, true, t.ImagePreview, t.Options.PreviewPreset.String()) if err != nil { logger.Errorf("error creating preview generator: %s", err.Error()) return } generator.Overwrite = t.Overwrite + // set the preview generation configuration from the global config + generator.Info.ChunkCount = *t.Options.PreviewSegments + generator.Info.ChunkDuration = *t.Options.PreviewSegmentDuration + generator.Info.ExcludeStart = *t.Options.PreviewExcludeStart + generator.Info.ExcludeEnd = *t.Options.PreviewExcludeEnd + if err := generator.Generate(); err != nil { logger.Errorf("error generating preview: %s", err.Error()) return diff --git a/pkg/utils/file.go b/pkg/utils/file.go index b80e888c9..760263334 100644 --- a/pkg/utils/file.go +++ b/pkg/utils/file.go @@ -3,13 +3,15 @@ package utils import ( "archive/zip" "fmt" - "github.com/h2non/filetype" - "github.com/h2non/filetype/types" "io/ioutil" "math" + "net/http" "os" "os/user" "path/filepath" + + "github.com/h2non/filetype" + "github.com/h2non/filetype/types" ) // FileType uses the filetype package to determine the given file path's type @@ -219,3 +221,11 @@ func GetParent(path string) *string { return &parentPath } } + +// ServeFileNoCache serves the provided file, ensuring that the response +// contains headers to prevent caching. +func ServeFileNoCache(w http.ResponseWriter, r *http.Request, filepath string) { + w.Header().Add("Cache-Control", "no-cache") + + http.ServeFile(w, r, filepath) +} diff --git a/ui/v2.5/src/components/Changelog/versions/v030.tsx b/ui/v2.5/src/components/Changelog/versions/v030.tsx index 9f800343d..9f844167c 100644 --- a/ui/v2.5/src/components/Changelog/versions/v030.tsx +++ b/ui/v2.5/src/components/Changelog/versions/v030.tsx @@ -13,6 +13,7 @@ const markup = ` * Add support for parent/child studios. ### 🎨 Improvements +* Allow customisation of preview video generation. * Add support for live transcoding in Safari. * Add mapped and fixed post-processing scraping options. * Add random sorting for performers. diff --git a/ui/v2.5/src/components/Scenes/SceneGenerateDialog.tsx b/ui/v2.5/src/components/Scenes/SceneGenerateDialog.tsx index 486b2246b..5f3372742 100644 --- a/ui/v2.5/src/components/Scenes/SceneGenerateDialog.tsx +++ b/ui/v2.5/src/components/Scenes/SceneGenerateDialog.tsx @@ -1,8 +1,12 @@ -import React, { useState } from "react"; -import { Form } from "react-bootstrap"; -import { mutateMetadataGenerate } from "src/core/StashService"; -import { Modal } from "src/components/Shared"; +import React, { useState, useEffect } from "react"; +import { Form, Button, Collapse } from "react-bootstrap"; +import { + mutateMetadataGenerate, + useConfiguration, +} from "src/core/StashService"; +import { Modal, Icon } from "src/components/Shared"; import { useToast } from "src/hooks"; +import * as GQL from "src/core/generated-graphql"; interface ISceneGenerateDialogProps { selectedIds: string[]; @@ -12,6 +16,8 @@ interface ISceneGenerateDialogProps { export const SceneGenerateDialog: React.FC = ( props: ISceneGenerateDialogProps ) => { + const { data, error, loading } = useConfiguration(); + const [sprites, setSprites] = useState(true); const [previews, setPreviews] = useState(true); const [markers, setMarkers] = useState(true); @@ -19,8 +25,37 @@ export const SceneGenerateDialog: React.FC = ( const [overwrite, setOverwrite] = useState(true); const [imagePreviews, setImagePreviews] = useState(false); + const [previewSegments, setPreviewSegments] = useState(0); + const [previewSegmentDuration, setPreviewSegmentDuration] = useState( + 0 + ); + const [previewExcludeStart, setPreviewExcludeStart] = useState< + string | undefined + >(undefined); + const [previewExcludeEnd, setPreviewExcludeEnd] = useState< + string | undefined + >(undefined); + const [previewPreset, setPreviewPreset] = useState( + GQL.PreviewPreset.Slow + ); + + const [previewOptionsOpen, setPreviewOptionsOpen] = useState(false); + const Toast = useToast(); + useEffect(() => { + if (!data?.configuration) return; + + const conf = data.configuration; + if (conf.general) { + setPreviewSegments(conf.general.previewSegments); + setPreviewSegmentDuration(conf.general.previewSegmentDuration); + setPreviewExcludeStart(conf.general.previewExcludeStart); + setPreviewExcludeEnd(conf.general.previewExcludeEnd); + setPreviewPreset(conf.general.previewPreset); + } + }, [data]); + async function onGenerate() { try { await mutateMetadataGenerate({ @@ -32,6 +67,13 @@ export const SceneGenerateDialog: React.FC = ( thumbnails: false, overwrite, sceneIDs: props.selectedIds, + previewOptions: { + previewPreset: (previewPreset as GQL.PreviewPreset) ?? undefined, + previewSegments, + previewSegmentDuration, + previewExcludeStart, + previewExcludeEnd, + }, }); Toast.success({ content: "Started generating" }); } catch (e) { @@ -41,6 +83,15 @@ export const SceneGenerateDialog: React.FC = ( } } + if (error) { + Toast.error(error); + props.onClose(); + } + + if (loading) { + return <>; + } + return ( = ( className="ml-2 flex-grow" /> +
+ + +
+ +
Preview encoding preset
+ ) => + setPreviewPreset(e.currentTarget.value) + } + > + {Object.keys(GQL.PreviewPreset).map((p) => ( + + ))} + + + The preset regulates size, quality and encoding time of + preview generation. Presets beyond “slow” have diminishing + returns and are not recommended. + +
+ + +
Number of segments in preview
+ ) => + setPreviewSegments( + Number.parseInt(e.currentTarget.value, 10) + ) + } + /> + + Number of segments in preview files. + +
+ + +
Preview segment duration
+ ) => + setPreviewSegmentDuration( + Number.parseFloat(e.currentTarget.value) + ) + } + /> + + Duration of each preview segment, in seconds. + +
+ + +
Exclude start time
+ ) => + setPreviewExcludeStart(e.currentTarget.value) + } + /> + + Exclude the first x seconds from scene previews. This can be + a value in seconds, or a percentage (eg 2%) of the total + scene duration. + +
+ + +
Exclude end time
+ ) => + setPreviewExcludeEnd(e.currentTarget.value) + } + /> + + Exclude the last x seconds from scene previews. This can be + a value in seconds, or a percentage (eg 2%) of the total + scene duration. + +
+
+
+
{ undefined ); const [cachePath, setCachePath] = useState(undefined); + const [previewSegments, setPreviewSegments] = useState(0); + const [previewSegmentDuration, setPreviewSegmentDuration] = useState( + 0 + ); + const [previewExcludeStart, setPreviewExcludeStart] = useState< + string | undefined + >(undefined); + const [previewExcludeEnd, setPreviewExcludeEnd] = useState< + string | undefined + >(undefined); const [previewPreset, setPreviewPreset] = useState( GQL.PreviewPreset.Slow ); @@ -45,6 +55,10 @@ export const SettingsConfigurationPanel: React.FC = () => { databasePath, generatedPath, cachePath, + previewSegments, + previewSegmentDuration, + previewExcludeStart, + previewExcludeEnd, previewPreset: (previewPreset as GQL.PreviewPreset) ?? undefined, maxTranscodeSize, maxStreamingTranscodeSize, @@ -68,6 +82,10 @@ export const SettingsConfigurationPanel: React.FC = () => { setDatabasePath(conf.general.databasePath); setGeneratedPath(conf.general.generatedPath); setCachePath(conf.general.cachePath); + setPreviewSegments(conf.general.previewSegments); + setPreviewSegmentDuration(conf.general.previewSegmentDuration); + setPreviewExcludeStart(conf.general.previewExcludeStart); + setPreviewExcludeEnd(conf.general.previewExcludeEnd); setPreviewPreset(conf.general.previewPreset); setMaxTranscodeSize(conf.general.maxTranscodeSize ?? undefined); setMaxStreamingTranscodeSize( @@ -273,28 +291,6 @@ export const SettingsConfigurationPanel: React.FC = () => {

Video

- -
Preview encoding preset
- ) => - setPreviewPreset(e.currentTarget.value) - } - > - {Object.keys(GQL.PreviewPreset).map((p) => ( - - ))} - - - The preset regulates size, quality and encoding time of preview - generation. Presets beyond “slow” have diminishing returns and are - not recommended. - -
Maximum transcode size
{
+ +

Preview Generation

+ + +
Preview encoding preset
+ ) => + setPreviewPreset(e.currentTarget.value) + } + > + {Object.keys(GQL.PreviewPreset).map((p) => ( + + ))} + + + The preset regulates size, quality and encoding time of preview + generation. Presets beyond “slow” have diminishing returns and are + not recommended. + +
+ +
Number of segments in preview
+ ) => + setPreviewSegments(Number.parseInt(e.currentTarget.value, 10)) + } + /> + + Number of segments in preview files. + +
+ + +
Preview segment duration
+ ) => + setPreviewSegmentDuration( + Number.parseFloat(e.currentTarget.value) + ) + } + /> + + Duration of each preview segment, in seconds. + +
+ + +
Exclude start time
+ ) => + setPreviewExcludeStart(e.currentTarget.value) + } + /> + + Exclude the first x seconds from scene previews. This can be a value + in seconds, or a percentage (eg 2%) of the total scene duration. + +
+ + +
Exclude end time
+ ) => + setPreviewExcludeEnd(e.currentTarget.value) + } + /> + + Exclude the last x seconds from scene previews. This can be a value + in seconds, or a percentage (eg 2%) of the total scene duration. + +
+
+
Scraping