import React, { useEffect, useState } from "react"; import { Button, Form, InputGroup } from "react-bootstrap"; import * as GQL from "src/core/generated-graphql"; import { useConfiguration, useConfigureGeneral } from "src/core/StashService"; import { useToast } from "src/hooks"; import { Icon, LoadingIndicator } from "src/components/Shared"; import { FolderSelect } from "src/components/Shared/FolderSelect/FolderSelect"; export const SettingsConfigurationPanel: React.FC = () => { const Toast = useToast(); // Editing config state const [stashes, setStashes] = useState([]); const [databasePath, setDatabasePath] = useState( undefined ); const [generatedPath, setGeneratedPath] = useState( undefined ); const [cachePath, setCachePath] = useState(undefined); const [calculateMD5, setCalculateMD5] = useState(false); const [videoFileNamingAlgorithm, setVideoFileNamingAlgorithm] = useState< GQL.HashAlgorithm | undefined >(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 ); const [maxTranscodeSize, setMaxTranscodeSize] = useState< GQL.StreamingResolutionEnum | undefined >(undefined); const [maxStreamingTranscodeSize, setMaxStreamingTranscodeSize] = useState< GQL.StreamingResolutionEnum | undefined >(undefined); const [username, setUsername] = useState(undefined); const [password, setPassword] = useState(undefined); const [maxSessionAge, setMaxSessionAge] = useState(0); const [logFile, setLogFile] = useState(); const [logOut, setLogOut] = useState(true); const [logLevel, setLogLevel] = useState("Info"); const [logAccess, setLogAccess] = useState(true); const [excludes, setExcludes] = useState([]); const [scraperUserAgent, setScraperUserAgent] = useState( undefined ); const [scraperCDPPath, setScraperCDPPath] = useState( undefined ); const { data, error, loading } = useConfiguration(); const [updateGeneralConfig] = useConfigureGeneral({ stashes, databasePath, generatedPath, cachePath, calculateMD5, videoFileNamingAlgorithm: (videoFileNamingAlgorithm as GQL.HashAlgorithm) ?? undefined, previewSegments, previewSegmentDuration, previewExcludeStart, previewExcludeEnd, previewPreset: (previewPreset as GQL.PreviewPreset) ?? undefined, maxTranscodeSize, maxStreamingTranscodeSize, username, password, maxSessionAge, logFile, logOut, logLevel, logAccess, excludes, scraperUserAgent, scraperCDPPath, }); useEffect(() => { if (!data?.configuration || error) return; const conf = data.configuration; if (conf.general) { setStashes(conf.general.stashes ?? []); setDatabasePath(conf.general.databasePath); setGeneratedPath(conf.general.generatedPath); setCachePath(conf.general.cachePath); setVideoFileNamingAlgorithm(conf.general.videoFileNamingAlgorithm); setCalculateMD5(conf.general.calculateMD5); 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( conf.general.maxStreamingTranscodeSize ?? undefined ); setUsername(conf.general.username); setPassword(conf.general.password); setMaxSessionAge(conf.general.maxSessionAge); setLogFile(conf.general.logFile ?? undefined); setLogOut(conf.general.logOut); setLogLevel(conf.general.logLevel); setLogAccess(conf.general.logAccess); setExcludes(conf.general.excludes); setScraperUserAgent(conf.general.scraperUserAgent ?? undefined); setScraperCDPPath(conf.general.scraperCDPPath ?? undefined); } }, [data, error]); function onStashesChanged(directories: string[]) { setStashes(directories); } function excludeRegexChanged(idx: number, value: string) { const newExcludes = excludes.map((regex, i) => { const ret = idx !== i ? regex : value; return ret; }); setExcludes(newExcludes); } function excludeRemoveRegex(idx: number) { const newExcludes = excludes.filter((_regex, i) => i !== idx); setExcludes(newExcludes); } function excludeAddRegex() { const demo = "sample\\.mp4$"; const newExcludes = excludes.concat(demo); setExcludes(newExcludes); } async function onSave() { try { const result = await updateGeneralConfig(); // eslint-disable-next-line no-console console.log(result); Toast.success({ content: "Updated config" }); } catch (e) { Toast.error(e); } } const transcodeQualities = [ GQL.StreamingResolutionEnum.Low, GQL.StreamingResolutionEnum.Standard, GQL.StreamingResolutionEnum.StandardHd, GQL.StreamingResolutionEnum.FullHd, GQL.StreamingResolutionEnum.FourK, GQL.StreamingResolutionEnum.Original, ].map(resolutionToString); function resolutionToString(r: GQL.StreamingResolutionEnum | undefined) { switch (r) { case GQL.StreamingResolutionEnum.Low: return "240p"; case GQL.StreamingResolutionEnum.Standard: return "480p"; case GQL.StreamingResolutionEnum.StandardHd: return "720p"; case GQL.StreamingResolutionEnum.FullHd: return "1080p"; case GQL.StreamingResolutionEnum.FourK: return "4k"; case GQL.StreamingResolutionEnum.Original: return "Original"; } return "Original"; } function translateQuality(quality: string) { switch (quality) { case "240p": return GQL.StreamingResolutionEnum.Low; case "480p": return GQL.StreamingResolutionEnum.Standard; case "720p": return GQL.StreamingResolutionEnum.StandardHd; case "1080p": return GQL.StreamingResolutionEnum.FullHd; case "4k": return GQL.StreamingResolutionEnum.FourK; case "Original": return GQL.StreamingResolutionEnum.Original; } return GQL.StreamingResolutionEnum.Original; } const namingHashAlgorithms = [ GQL.HashAlgorithm.Md5, GQL.HashAlgorithm.Oshash, ].map(namingHashToString); function namingHashToString(value: GQL.HashAlgorithm | undefined) { switch (value) { case GQL.HashAlgorithm.Oshash: return "oshash"; case GQL.HashAlgorithm.Md5: return "MD5"; } return "MD5"; } function translateNamingHash(value: string) { switch (value) { case "oshash": return GQL.HashAlgorithm.Oshash; case "MD5": return GQL.HashAlgorithm.Md5; } return GQL.HashAlgorithm.Md5; } if (error) return

{error.message}

; if (!data?.configuration || loading) return ; return ( <>

Library

Stashes
Directory locations to your content
Database Path
) => setDatabasePath(e.currentTarget.value) } /> File location for the SQLite database (requires restart)
Generated Path
) => setGeneratedPath(e.currentTarget.value) } /> Directory location for the generated files (scene markers, scene previews, sprites, etc)
Cache Path
) => setCachePath(e.currentTarget.value) } /> Directory location of the cache
Excluded Patterns
{excludes && excludes.map((regexp, i) => ( ) => excludeRegexChanged(i, e.currentTarget.value) } /> ))} Regexps of files/paths to exclude from Scan and add to Clean

Hashing

setCalculateMD5(!calculateMD5)} /> Calculate MD5 checksum in addition to oshash. Enabling will cause initial scans to be slower. File naming hash must be set to oshash to disable MD5 calculation.
Generated file naming hash
) => setVideoFileNamingAlgorithm( translateNamingHash(e.currentTarget.value) ) } > {namingHashAlgorithms.map((q) => ( ))} Use MD5 or oshash for generated file naming. Changing this requires that all scenes have the applicable MD5/oshash value populated. After changing this value, existing generated files will need to be migrated or regenerated. See Tasks page for migration.

Video

Maximum transcode size
) => setMaxTranscodeSize(translateQuality(event.currentTarget.value)) } value={resolutionToString(maxTranscodeSize)} > {transcodeQualities.map((q) => ( ))} Maximum size for generated transcodes
Maximum streaming transcode size
) => setMaxStreamingTranscodeSize( translateQuality(event.currentTarget.value) ) } value={resolutionToString(maxStreamingTranscodeSize)} > {transcodeQualities.map((q) => ( ))} Maximum size for transcoded streams

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 || "0", 10) ) } /> Number of segments in preview files.
Preview segment duration
) => setPreviewSegmentDuration( Number.parseFloat(e.currentTarget.value || "0") ) } /> 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

Scraper User Agent
) => setScraperUserAgent(e.currentTarget.value) } /> User-Agent string used during scrape http requests
Chrome CDP path
) => setScraperCDPPath(e.currentTarget.value) } /> File path to the Chrome executable, or a remote address (starting with http:// or https://, for example http://localhost:9222/json/version) to a Chrome instance.

Authentication

Username
) => setUsername(e.currentTarget.value) } /> Username to access Stash. Leave blank to disable user authentication
Password
) => setPassword(e.currentTarget.value) } /> Password to access Stash. Leave blank to disable user authentication
Maximum Session Age
) => setMaxSessionAge( Number.parseInt(e.currentTarget.value || "0", 10) ) } /> Maximum idle time before a login session is expired, in seconds.

Logging

Log file
) => setLogFile(e.currentTarget.value) } /> Path to the file to output logging to. Blank to disable file logging. Requires restart.
setLogOut(!logOut)} /> Logs to the terminal in addition to a file. Always true if file logging is disabled. Requires restart.
Log Level
) => setLogLevel(event.currentTarget.value) } value={logLevel} > {["Trace", "Debug", "Info", "Warning", "Error"].map((o) => ( ))}
setLogAccess(!logAccess)} /> Logs http access to the terminal. Requires restart.
); };