From 7ecc2207449522640813e268a5f997ca3a9bb064 Mon Sep 17 00:00:00 2001 From: hontheinternet Date: Tue, 23 May 2023 07:46:31 +0900 Subject: [PATCH 1/9] allow direct serve interactive CSVs to Handy --- graphql/documents/data/config.graphql | 1 + graphql/schema/types/config.graphql | 4 ++ internal/api/authentication.go | 2 +- internal/api/resolver_mutation_configure.go | 4 ++ internal/api/resolver_query_configuration.go | 6 ++- internal/api/routes_scene.go | 25 +++++++++ internal/manager/config/config.go | 10 +++- .../manager/config/config_concurrency_test.go | 1 + .../generator_interactive_heatmap_speed.go | 53 +++++++++++++++++++ .../SettingsInterfacePanel.tsx | 8 +++ ui/v2.5/src/hooks/Interactive/context.tsx | 5 +- ui/v2.5/src/hooks/Interactive/interactive.ts | 30 ++++++++--- ui/v2.5/src/locales/en-GB.json | 6 ++- 13 files changed, 142 insertions(+), 13 deletions(-) diff --git a/graphql/documents/data/config.graphql b/graphql/documents/data/config.graphql index 2a56e951252..330b7b02faf 100644 --- a/graphql/documents/data/config.graphql +++ b/graphql/documents/data/config.graphql @@ -93,6 +93,7 @@ fragment ConfigInterfaceData on ConfigInterfaceResult { } handyKey funscriptOffset + useStashHostedFunscript } fragment ConfigDLNAData on ConfigDLNAResult { diff --git a/graphql/schema/types/config.graphql b/graphql/schema/types/config.graphql index 6c99393858e..08fa4e21756 100644 --- a/graphql/schema/types/config.graphql +++ b/graphql/schema/types/config.graphql @@ -354,6 +354,8 @@ input ConfigInterfaceInput { handyKey: String """Funscript Time Offset""" funscriptOffset: Int + """Whether to use Stash Hosted Funscript""" + useStashHostedFunscript: Boolean """True if we should not auto-open a browser window on startup""" noBrowser: Boolean """True if we should send notifications to the desktop""" @@ -425,6 +427,8 @@ type ConfigInterfaceResult { handyKey: String """Funscript Time Offset""" funscriptOffset: Int + """Whether to use Stash Hosted Funscript""" + useStashHostedFunscript: Boolean } input ConfigDLNAInput { diff --git a/internal/api/authentication.go b/internal/api/authentication.go index 94b5328f5bf..fc9919dfad4 100644 --- a/internal/api/authentication.go +++ b/internal/api/authentication.go @@ -26,7 +26,7 @@ const ( func allowUnauthenticated(r *http.Request) bool { // #2715 - allow access to UI files - return strings.HasPrefix(r.URL.Path, loginEndpoint) || r.URL.Path == logoutEndpoint || r.URL.Path == "/css" || strings.HasPrefix(r.URL.Path, "/assets") + return strings.HasPrefix(r.URL.Path, loginEndpoint) || r.URL.Path == logoutEndpoint || r.URL.Path == "/css" || strings.HasPrefix(r.URL.Path, "/assets") || strings.HasSuffix(r.URL.Path, "/interactive_csv") } func authenticateHandler() func(http.Handler) http.Handler { diff --git a/internal/api/resolver_mutation_configure.go b/internal/api/resolver_mutation_configure.go index bdc93137f17..177b444277c 100644 --- a/internal/api/resolver_mutation_configure.go +++ b/internal/api/resolver_mutation_configure.go @@ -479,6 +479,10 @@ func (r *mutationResolver) ConfigureInterface(ctx context.Context, input ConfigI c.Set(config.FunscriptOffset, *input.FunscriptOffset) } + if input.UseStashHostedFunscript != nil { + c.Set(config.UseStashHostedFunscript, *input.UseStashHostedFunscript) + } + if err := c.Write(); err != nil { return makeConfigInterfaceResult(), err } diff --git a/internal/api/resolver_query_configuration.go b/internal/api/resolver_query_configuration.go index 4c9f00aea0d..7de9bda0da6 100644 --- a/internal/api/resolver_query_configuration.go +++ b/internal/api/resolver_query_configuration.go @@ -159,6 +159,7 @@ func makeConfigInterfaceResult() *ConfigInterfaceResult { language := config.GetLanguage() handyKey := config.GetHandyKey() scriptOffset := config.GetFunscriptOffset() + useStashHostedFunscript := config.GetUseStashHostedFunscript() imageLightboxOptions := config.GetImageLightboxOptions() // FIXME - misnamed output field means we have redundant fields disableDropdownCreate := config.GetDisableDropdownCreate() @@ -190,8 +191,9 @@ func makeConfigInterfaceResult() *ConfigInterfaceResult { DisabledDropdownCreate: disableDropdownCreate, DisableDropdownCreate: disableDropdownCreate, - HandyKey: &handyKey, - FunscriptOffset: &scriptOffset, + HandyKey: &handyKey, + FunscriptOffset: &scriptOffset, + UseStashHostedFunscript: &useStashHostedFunscript, } } diff --git a/internal/api/routes_scene.go b/internal/api/routes_scene.go index 9a5e8149657..7c12f4a2f3e 100644 --- a/internal/api/routes_scene.go +++ b/internal/api/routes_scene.go @@ -5,6 +5,7 @@ import ( "context" "errors" "net/http" + "path" "strconv" "strings" @@ -72,6 +73,7 @@ func (rs sceneRoutes) Routes() chi.Router { r.Get("/vtt/thumbs", rs.VttThumbs) r.Get("/vtt/sprite", rs.VttSprite) r.Get("/funscript", rs.Funscript) + r.Get("/interactive_csv", rs.InteractiveCSV) r.Get("/interactive_heatmap", rs.InteractiveHeatmap) r.Get("/caption", rs.CaptionLang) @@ -374,6 +376,29 @@ func (rs sceneRoutes) Funscript(w http.ResponseWriter, r *http.Request) { utils.ServeStaticFile(w, r, filepath) } +func (rs sceneRoutes) InteractiveCSV(w http.ResponseWriter, r *http.Request) { + s := r.Context().Value(sceneKey).(*models.Scene) + filepath := video.GetFunscriptPath(s.Path) + ext := path.Ext(filepath) + + // saves the csv file to the same folder as the funscript with .csv extension + csvfile := filepath[0:len(filepath)-len(ext)] + ".csv" + + // if the converted csv file already exists, we use it as it is + // maybe we need to check if the funscript file is updated to maybe force update the csv + exists, err := fsutil.FileExists(csvfile) + if err != nil || !exists { + // TheHandy directly only accepts interactive CSV files + err := manager.ConvertFunscriptToCSV(filepath, csvfile) + + if err != nil { + utils.ServeStaticFile(w, r, filepath) + } + } + + utils.ServeStaticFile(w, r, csvfile) +} + func (rs sceneRoutes) InteractiveHeatmap(w http.ResponseWriter, r *http.Request) { scene := r.Context().Value(sceneKey).(*models.Scene) sceneHash := scene.GetHash(config.GetInstance().GetVideoFileNamingAlgorithm()) diff --git a/internal/manager/config/config.go b/internal/manager/config/config.go index 44c64392515..2a242c114a3 100644 --- a/internal/manager/config/config.go +++ b/internal/manager/config/config.go @@ -192,8 +192,10 @@ const ( DisableDropdownCreateStudio = "disable_dropdown_create.studio" DisableDropdownCreateTag = "disable_dropdown_create.tag" - HandyKey = "handy_key" - FunscriptOffset = "funscript_offset" + HandyKey = "handy_key" + FunscriptOffset = "funscript_offset" + UseStashHostedFunscript = "use_stash_hosted_funscript" + useStashHostedFunscriptDefault = false DrawFunscriptHeatmapRange = "draw_funscript_heatmap_range" drawFunscriptHeatmapRangeDefault = true @@ -1260,6 +1262,10 @@ func (i *Instance) GetFunscriptOffset() int { return i.getInt(FunscriptOffset) } +func (i *Instance) GetUseStashHostedFunscript() bool { + return i.getBoolDefault(UseStashHostedFunscript, useStashHostedFunscriptDefault) +} + func (i *Instance) GetDeleteFileDefault() bool { return i.getBool(DeleteFileDefault) } diff --git a/internal/manager/config/config_concurrency_test.go b/internal/manager/config/config_concurrency_test.go index 81bb7e81687..0ede5f05518 100644 --- a/internal/manager/config/config_concurrency_test.go +++ b/internal/manager/config/config_concurrency_test.go @@ -93,6 +93,7 @@ func TestConcurrentConfigAccess(t *testing.T) { i.Set(CSSEnabled, i.GetCSSEnabled()) i.Set(CSSEnabled, i.GetCustomLocalesEnabled()) i.Set(HandyKey, i.GetHandyKey()) + i.Set(UseStashHostedFunscript, i.GetUseStashHostedFunscript()) i.Set(DLNAServerName, i.GetDLNAServerName()) i.Set(DLNADefaultEnabled, i.GetDLNADefaultEnabled()) i.Set(DLNADefaultIPWhitelist, i.GetDLNADefaultIPWhitelist()) diff --git a/internal/manager/generator_interactive_heatmap_speed.go b/internal/manager/generator_interactive_heatmap_speed.go index 3cae5f5621e..907cb62eea9 100644 --- a/internal/manager/generator_interactive_heatmap_speed.go +++ b/internal/manager/generator_interactive_heatmap_speed.go @@ -1,6 +1,7 @@ package manager import ( + "bytes" "encoding/json" "fmt" "image" @@ -11,6 +12,7 @@ import ( "sort" "github.com/lucasb-eyer/go-colorful" + "github.com/stashapp/stash/pkg/fsutil" "github.com/stashapp/stash/pkg/logger" ) @@ -365,3 +367,54 @@ func getSegmentColor(intensity float64) colorful.Color { return c } + +func LoadFunscriptData(path string) (Script, error) { + data, err := os.ReadFile(path) + if err != nil { + return Script{}, err + } + + var funscript Script + err = json.Unmarshal(data, &funscript) + if err != nil { + return Script{}, err + } + + if funscript.Actions == nil { + return Script{}, fmt.Errorf("actions list missing in %s", path) + } + + sort.SliceStable(funscript.Actions, func(i, j int) bool { return funscript.Actions[i].At < funscript.Actions[j].At }) + + return funscript, nil +} + +func convertRange(value int, fromLow int, fromHigh int, toLow int, toHigh int) int { + return ((value-fromLow)*(toHigh-toLow))/(fromHigh-fromLow) + toLow +} + +func ConvertFunscriptToCSV(funscriptPath string, csvPath string) error { + funscript, err := LoadFunscriptData(funscriptPath) + + if err != nil { + return err + } + + var buffer bytes.Buffer + for _, action := range funscript.Actions { + pos := action.Pos + + if funscript.Inverted { + pos = convertRange(pos, 0, 100, 100, 0) + } + + if funscript.Range > 0 { + pos = convertRange(pos, 0, funscript.Range, 0, 100) + } + + buffer.WriteString(fmt.Sprintf("%d,%d\r\n", action.At, pos)) + } + fsutil.WriteFile(csvPath, buffer.Bytes()) + + return nil +} diff --git a/ui/v2.5/src/components/Settings/SettingsInterfacePanel/SettingsInterfacePanel.tsx b/ui/v2.5/src/components/Settings/SettingsInterfacePanel/SettingsInterfacePanel.tsx index c44f3ab78b3..dd4dce07021 100644 --- a/ui/v2.5/src/components/Settings/SettingsInterfacePanel/SettingsInterfacePanel.tsx +++ b/ui/v2.5/src/components/Settings/SettingsInterfacePanel/SettingsInterfacePanel.tsx @@ -756,6 +756,14 @@ export const SettingsInterfacePanel: React.FC = () => { value={iface.funscriptOffset ?? undefined} onChange={(v) => saveInterface({ funscriptOffset: v })} /> + + saveInterface({ useStashHostedFunscript: v })} + /> ); diff --git a/ui/v2.5/src/hooks/Interactive/context.tsx b/ui/v2.5/src/hooks/Interactive/context.tsx index c3c5ccb86b5..fc9fda739a1 100644 --- a/ui/v2.5/src/hooks/Interactive/context.tsx +++ b/ui/v2.5/src/hooks/Interactive/context.tsx @@ -81,6 +81,7 @@ export const InteractiveProvider: React.FC = ({ children }) => { undefined ); const [scriptOffset, setScriptOffset] = useState(0); + const [useStashHostedFunscript, setUseStashHostedFunscript] = useState(false); const [interactive] = useState(new InteractiveAPI("", 0)); const [initialised, setInitialised] = useState(false); @@ -118,6 +119,7 @@ export const InteractiveProvider: React.FC = ({ children }) => { setHandyKey(stashConfig.interface.handyKey ?? undefined); setScriptOffset(stashConfig.interface.funscriptOffset ?? 0); + setUseStashHostedFunscript(stashConfig.interface.useStashHostedFunscript ?? false); }, [stashConfig]); useEffect(() => { @@ -129,11 +131,12 @@ export const InteractiveProvider: React.FC = ({ children }) => { interactive.handyKey = handyKey ?? ""; interactive.scriptOffset = scriptOffset; + interactive.useStashHostedFunscript = useStashHostedFunscript; if (oldKey !== interactive.handyKey && interactive.handyKey) { initialise(); } - }, [handyKey, scriptOffset, config, interactive, initialise]); + }, [handyKey, scriptOffset, useStashHostedFunscript, config, interactive, initialise]); const sync = useCallback(async () => { if ( diff --git a/ui/v2.5/src/hooks/Interactive/interactive.ts b/ui/v2.5/src/hooks/Interactive/interactive.ts index 1198ac59e6b..d29fa9472ee 100644 --- a/ui/v2.5/src/hooks/Interactive/interactive.ts +++ b/ui/v2.5/src/hooks/Interactive/interactive.ts @@ -127,27 +127,45 @@ export class Interactive { return this._handy.connectionKey; } + set useStashHostedFunscript(useStashHostedFunscript: boolean) { + this._handy.useStashHostedFunscript = useStashHostedFunscript; + } + + get useStashHostedFunscript(): boolean { + return this._handy.useStashHostedFunscript; + } + set scriptOffset(offset: number) { this._scriptOffset = offset; } async uploadScript(funscriptPath: string) { + console.log("FS Path: " + funscriptPath); if (!(this._handy.connectionKey && funscriptPath)) { return; } - const csv = await fetch(funscriptPath) + var funscriptUrl; + + if (this._handy.useStashHostedFunscript) { + funscriptUrl = funscriptPath.replace("/funscript", "/interactive_csv") + } + else { + const csv = await fetch(funscriptPath) .then((response) => response.json()) .then((json) => convertFunscriptToCSV(json)); - const fileName = `${Math.round(Math.random() * 100000000)}.csv`; - const csvFile = new File([csv], fileName); - - const tempURL = await uploadCsv(csvFile).then((response) => response.url); + const fileName = `${Math.round(Math.random() * 100000000)}.csv`; + const csvFile = new File([csv], fileName); + + funscriptUrl = await uploadCsv(csvFile).then((response) => response.url); + } + + console.log("funscriptUrl:" + funscriptUrl); await this._handy.setMode(HandyMode.hssp); this._connected = await this._handy - .setHsspSetup(tempURL) + .setHsspSetup(funscriptUrl) .then((result) => result === HsspSetupResult.downloaded); } diff --git a/ui/v2.5/src/locales/en-GB.json b/ui/v2.5/src/locales/en-GB.json index ca92b0fc14c..af37bb04d1a 100644 --- a/ui/v2.5/src/locales/en-GB.json +++ b/ui/v2.5/src/locales/en-GB.json @@ -702,7 +702,11 @@ } } }, - "title": "User Interface" + "title": "User Interface", + "use_stash_hosted_funscript": { + "description": "When enabled, given that the stash instance is accessible from the Handy (like on the same network), the funscript will be transferred directly from Stash server to Handy, without going through TheHandy servers.", + "heading": "Use the funscript hosted by stash funscript" + } } }, "configuration": "Configuration", From 57a4d21abcac41176a51c558e4d6178a9e557a30 Mon Sep 17 00:00:00 2001 From: hontheinternet Date: Tue, 23 May 2023 08:14:25 +0900 Subject: [PATCH 2/9] fix useStashHostedFunscript not found --- ui/v2.5/src/hooks/Interactive/interactive.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/ui/v2.5/src/hooks/Interactive/interactive.ts b/ui/v2.5/src/hooks/Interactive/interactive.ts index d29fa9472ee..5440630e85f 100644 --- a/ui/v2.5/src/hooks/Interactive/interactive.ts +++ b/ui/v2.5/src/hooks/Interactive/interactive.ts @@ -97,11 +97,13 @@ export class Interactive { _playing: boolean; _scriptOffset: number; _handy: Handy; + _useStashHostedFunscript: boolean; constructor(handyKey: string, scriptOffset: number) { this._handy = new Handy(); this._handy.connectionKey = handyKey; this._scriptOffset = scriptOffset; + this._useStashHostedFunscript = false; this._connected = false; this._playing = false; } @@ -128,11 +130,11 @@ export class Interactive { } set useStashHostedFunscript(useStashHostedFunscript: boolean) { - this._handy.useStashHostedFunscript = useStashHostedFunscript; + this._useStashHostedFunscript = useStashHostedFunscript; } get useStashHostedFunscript(): boolean { - return this._handy.useStashHostedFunscript; + return this._useStashHostedFunscript; } set scriptOffset(offset: number) { @@ -147,7 +149,7 @@ export class Interactive { var funscriptUrl; - if (this._handy.useStashHostedFunscript) { + if (this._useStashHostedFunscript) { funscriptUrl = funscriptPath.replace("/funscript", "/interactive_csv") } else { From e50373e0161cbc6f0d295ef02826f8c87432fa50 Mon Sep 17 00:00:00 2001 From: hontheinternet Date: Tue, 23 May 2023 09:18:51 +0900 Subject: [PATCH 3/9] Fix lint and formatting --- .../generator_interactive_heatmap_speed.go | 4 +--- ui/v2.5/src/hooks/Interactive/context.tsx | 16 +++++++++++++--- ui/v2.5/src/hooks/Interactive/interactive.ts | 13 ++++++------- 3 files changed, 20 insertions(+), 13 deletions(-) diff --git a/internal/manager/generator_interactive_heatmap_speed.go b/internal/manager/generator_interactive_heatmap_speed.go index 907cb62eea9..b41d408b1d8 100644 --- a/internal/manager/generator_interactive_heatmap_speed.go +++ b/internal/manager/generator_interactive_heatmap_speed.go @@ -414,7 +414,5 @@ func ConvertFunscriptToCSV(funscriptPath string, csvPath string) error { buffer.WriteString(fmt.Sprintf("%d,%d\r\n", action.At, pos)) } - fsutil.WriteFile(csvPath, buffer.Bytes()) - - return nil + return fsutil.WriteFile(csvPath, buffer.Bytes()) } diff --git a/ui/v2.5/src/hooks/Interactive/context.tsx b/ui/v2.5/src/hooks/Interactive/context.tsx index fc9fda739a1..f2bf001ca6f 100644 --- a/ui/v2.5/src/hooks/Interactive/context.tsx +++ b/ui/v2.5/src/hooks/Interactive/context.tsx @@ -81,7 +81,8 @@ export const InteractiveProvider: React.FC = ({ children }) => { undefined ); const [scriptOffset, setScriptOffset] = useState(0); - const [useStashHostedFunscript, setUseStashHostedFunscript] = useState(false); + const [useStashHostedFunscript, setUseStashHostedFunscript] = + useState(false); const [interactive] = useState(new InteractiveAPI("", 0)); const [initialised, setInitialised] = useState(false); @@ -119,7 +120,9 @@ export const InteractiveProvider: React.FC = ({ children }) => { setHandyKey(stashConfig.interface.handyKey ?? undefined); setScriptOffset(stashConfig.interface.funscriptOffset ?? 0); - setUseStashHostedFunscript(stashConfig.interface.useStashHostedFunscript ?? false); + setUseStashHostedFunscript( + stashConfig.interface.useStashHostedFunscript ?? false + ); }, [stashConfig]); useEffect(() => { @@ -136,7 +139,14 @@ export const InteractiveProvider: React.FC = ({ children }) => { if (oldKey !== interactive.handyKey && interactive.handyKey) { initialise(); } - }, [handyKey, scriptOffset, useStashHostedFunscript, config, interactive, initialise]); + }, [ + handyKey, + scriptOffset, + useStashHostedFunscript, + config, + interactive, + initialise, + ]); const sync = useCallback(async () => { if ( diff --git a/ui/v2.5/src/hooks/Interactive/interactive.ts b/ui/v2.5/src/hooks/Interactive/interactive.ts index 5440630e85f..3188fbaa90f 100644 --- a/ui/v2.5/src/hooks/Interactive/interactive.ts +++ b/ui/v2.5/src/hooks/Interactive/interactive.ts @@ -150,18 +150,17 @@ export class Interactive { var funscriptUrl; if (this._useStashHostedFunscript) { - funscriptUrl = funscriptPath.replace("/funscript", "/interactive_csv") - } - else { + funscriptUrl = funscriptPath.replace("/funscript", "/interactive_csv"); + } else { const csv = await fetch(funscriptPath) - .then((response) => response.json()) - .then((json) => convertFunscriptToCSV(json)); + .then((response) => response.json()) + .then((json) => convertFunscriptToCSV(json)); const fileName = `${Math.round(Math.random() * 100000000)}.csv`; const csvFile = new File([csv], fileName); - + funscriptUrl = await uploadCsv(csvFile).then((response) => response.url); } - + console.log("funscriptUrl:" + funscriptUrl); await this._handy.setMode(HandyMode.hssp); From 78c04ba2f147037139fa81380d1d1d9356b70dec Mon Sep 17 00:00:00 2001 From: hontheinternet <121332499+hontheinternet@users.noreply.github.com> Date: Thu, 25 May 2023 01:50:22 +0900 Subject: [PATCH 4/9] Apply suggestions from code review - Description Co-authored-by: kermieisinthehouse --- ui/v2.5/src/locales/en-GB.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ui/v2.5/src/locales/en-GB.json b/ui/v2.5/src/locales/en-GB.json index af37bb04d1a..6aab27265cf 100644 --- a/ui/v2.5/src/locales/en-GB.json +++ b/ui/v2.5/src/locales/en-GB.json @@ -704,8 +704,8 @@ }, "title": "User Interface", "use_stash_hosted_funscript": { - "description": "When enabled, given that the stash instance is accessible from the Handy (like on the same network), the funscript will be transferred directly from Stash server to Handy, without going through TheHandy servers.", - "heading": "Use the funscript hosted by stash funscript" + "description": "When enabled, funscripts will be served directly from Stash to your Handy device without using the third party Handy server. Requires that Stash be accessible from your Handy device.", + "heading": "Serve funscripts directly" } } }, From 3ea3ca3fb503d049def679931fe16926e798cdb8 Mon Sep 17 00:00:00 2001 From: hontheinternet Date: Thu, 25 May 2023 02:20:12 +0900 Subject: [PATCH 5/9] remove hole in authentication --- internal/api/authentication.go | 2 +- internal/api/urlbuilders/scene.go | 4 ++++ ui/v2.5/src/hooks/Interactive/context.tsx | 4 ++-- ui/v2.5/src/hooks/Interactive/interactive.ts | 7 ++++++- 4 files changed, 13 insertions(+), 4 deletions(-) diff --git a/internal/api/authentication.go b/internal/api/authentication.go index fc9919dfad4..94b5328f5bf 100644 --- a/internal/api/authentication.go +++ b/internal/api/authentication.go @@ -26,7 +26,7 @@ const ( func allowUnauthenticated(r *http.Request) bool { // #2715 - allow access to UI files - return strings.HasPrefix(r.URL.Path, loginEndpoint) || r.URL.Path == logoutEndpoint || r.URL.Path == "/css" || strings.HasPrefix(r.URL.Path, "/assets") || strings.HasSuffix(r.URL.Path, "/interactive_csv") + return strings.HasPrefix(r.URL.Path, loginEndpoint) || r.URL.Path == logoutEndpoint || r.URL.Path == "/css" || strings.HasPrefix(r.URL.Path, "/assets") } func authenticateHandler() func(http.Handler) http.Handler { diff --git a/internal/api/urlbuilders/scene.go b/internal/api/urlbuilders/scene.go index c70feafe753..f8c7d476272 100644 --- a/internal/api/urlbuilders/scene.go +++ b/internal/api/urlbuilders/scene.go @@ -72,3 +72,7 @@ func (b SceneURLBuilder) GetCaptionURL() string { func (b SceneURLBuilder) GetInteractiveHeatmapURL() string { return b.BaseURL + "/scene/" + b.SceneID + "/interactive_heatmap" } + +func (b SceneURLBuilder) GetInteractiveCSV() string { + return b.BaseURL + "/scene/" + b.SceneID + "/interactive_csv" +} diff --git a/ui/v2.5/src/hooks/Interactive/context.tsx b/ui/v2.5/src/hooks/Interactive/context.tsx index f2bf001ca6f..e758dc96a53 100644 --- a/ui/v2.5/src/hooks/Interactive/context.tsx +++ b/ui/v2.5/src/hooks/Interactive/context.tsx @@ -176,14 +176,14 @@ export const InteractiveProvider: React.FC = ({ children }) => { setState(ConnectionState.Uploading); try { - await interactive.uploadScript(funscriptPath); + await interactive.uploadScript(funscriptPath, stashConfig?.general?.apiKey); setCurrentScript(funscriptPath); setState(ConnectionState.Ready); } catch (e) { setState(ConnectionState.Error); } }, - [interactive, currentScript] + [interactive, currentScript, stashConfig] ); return ( diff --git a/ui/v2.5/src/hooks/Interactive/interactive.ts b/ui/v2.5/src/hooks/Interactive/interactive.ts index 3188fbaa90f..f5ff11efbb4 100644 --- a/ui/v2.5/src/hooks/Interactive/interactive.ts +++ b/ui/v2.5/src/hooks/Interactive/interactive.ts @@ -141,7 +141,7 @@ export class Interactive { this._scriptOffset = offset; } - async uploadScript(funscriptPath: string) { + async uploadScript(funscriptPath: string, apiKey?: string) { console.log("FS Path: " + funscriptPath); if (!(this._handy.connectionKey && funscriptPath)) { return; @@ -151,6 +151,11 @@ export class Interactive { if (this._useStashHostedFunscript) { funscriptUrl = funscriptPath.replace("/funscript", "/interactive_csv"); + if (typeof apiKey !== 'undefined') { + var url = new URL(funscriptUrl) + url.searchParams.append("apikey", apiKey); + funscriptUrl = url.toString() + } } else { const csv = await fetch(funscriptPath) .then((response) => response.json()) From 5b8aefa5db6a39fda04ec6d0264b71748daee1c4 Mon Sep 17 00:00:00 2001 From: hontheinternet Date: Thu, 1 Jun 2023 00:13:56 +0900 Subject: [PATCH 6/9] fix lint errors --- ui/v2.5/src/hooks/Interactive/context.tsx | 5 ++++- ui/v2.5/src/hooks/Interactive/interactive.ts | 6 +++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/ui/v2.5/src/hooks/Interactive/context.tsx b/ui/v2.5/src/hooks/Interactive/context.tsx index e758dc96a53..487fa8468f5 100644 --- a/ui/v2.5/src/hooks/Interactive/context.tsx +++ b/ui/v2.5/src/hooks/Interactive/context.tsx @@ -176,7 +176,10 @@ export const InteractiveProvider: React.FC = ({ children }) => { setState(ConnectionState.Uploading); try { - await interactive.uploadScript(funscriptPath, stashConfig?.general?.apiKey); + await interactive.uploadScript( + funscriptPath, + stashConfig?.general?.apiKey + ); setCurrentScript(funscriptPath); setState(ConnectionState.Ready); } catch (e) { diff --git a/ui/v2.5/src/hooks/Interactive/interactive.ts b/ui/v2.5/src/hooks/Interactive/interactive.ts index f5ff11efbb4..c1358cccf70 100644 --- a/ui/v2.5/src/hooks/Interactive/interactive.ts +++ b/ui/v2.5/src/hooks/Interactive/interactive.ts @@ -151,10 +151,10 @@ export class Interactive { if (this._useStashHostedFunscript) { funscriptUrl = funscriptPath.replace("/funscript", "/interactive_csv"); - if (typeof apiKey !== 'undefined') { - var url = new URL(funscriptUrl) + if (typeof apiKey !== "undefined" && apiKey !== "") { + var url = new URL(funscriptUrl); url.searchParams.append("apikey", apiKey); - funscriptUrl = url.toString() + funscriptUrl = url.toString(); } } else { const csv = await fetch(funscriptPath) From 664f359acefe73dc637c2e2c7e8dc1b512f3074e Mon Sep 17 00:00:00 2001 From: hontheinternet Date: Tue, 4 Jul 2023 19:59:03 +0900 Subject: [PATCH 7/9] funscript to csv on the fly --- internal/api/routes_scene.go | 20 ++++--------------- .../generator_interactive_heatmap_speed.go | 16 ++++++++++++--- 2 files changed, 17 insertions(+), 19 deletions(-) diff --git a/internal/api/routes_scene.go b/internal/api/routes_scene.go index 7c12f4a2f3e..629f886a34e 100644 --- a/internal/api/routes_scene.go +++ b/internal/api/routes_scene.go @@ -5,7 +5,6 @@ import ( "context" "errors" "net/http" - "path" "strconv" "strings" @@ -379,24 +378,13 @@ func (rs sceneRoutes) Funscript(w http.ResponseWriter, r *http.Request) { func (rs sceneRoutes) InteractiveCSV(w http.ResponseWriter, r *http.Request) { s := r.Context().Value(sceneKey).(*models.Scene) filepath := video.GetFunscriptPath(s.Path) - ext := path.Ext(filepath) - // saves the csv file to the same folder as the funscript with .csv extension - csvfile := filepath[0:len(filepath)-len(ext)] + ".csv" + // TheHandy directly only accepts interactive CSVs + csvBytes, err := manager.ConvertFunscriptToCSV(filepath) - // if the converted csv file already exists, we use it as it is - // maybe we need to check if the funscript file is updated to maybe force update the csv - exists, err := fsutil.FileExists(csvfile) - if err != nil || !exists { - // TheHandy directly only accepts interactive CSV files - err := manager.ConvertFunscriptToCSV(filepath, csvfile) - - if err != nil { - utils.ServeStaticFile(w, r, filepath) - } + if err != nil { + utils.ServeStaticContent(w, r, csvBytes) } - - utils.ServeStaticFile(w, r, csvfile) } func (rs sceneRoutes) InteractiveHeatmap(w http.ResponseWriter, r *http.Request) { diff --git a/internal/manager/generator_interactive_heatmap_speed.go b/internal/manager/generator_interactive_heatmap_speed.go index b41d408b1d8..17f8c2a8a02 100644 --- a/internal/manager/generator_interactive_heatmap_speed.go +++ b/internal/manager/generator_interactive_heatmap_speed.go @@ -393,11 +393,11 @@ func convertRange(value int, fromLow int, fromHigh int, toLow int, toHigh int) i return ((value-fromLow)*(toHigh-toLow))/(fromHigh-fromLow) + toLow } -func ConvertFunscriptToCSV(funscriptPath string, csvPath string) error { +func ConvertFunscriptToCSV(funscriptPath string) ([]byte, error) { funscript, err := LoadFunscriptData(funscriptPath) if err != nil { - return err + return nil, err } var buffer bytes.Buffer @@ -414,5 +414,15 @@ func ConvertFunscriptToCSV(funscriptPath string, csvPath string) error { buffer.WriteString(fmt.Sprintf("%d,%d\r\n", action.At, pos)) } - return fsutil.WriteFile(csvPath, buffer.Bytes()) + return buffer.Bytes(), nil +} + +func ConvertFunscriptToCSVFile(funscriptPath string, csvPath string) error { + csvBytes, err := ConvertFunscriptToCSV(funscriptPath) + + if err != nil { + return err + } + + return fsutil.WriteFile(csvPath, csvBytes) } From 751abca58cdc758b2727cc35eceba20a7013c047 Mon Sep 17 00:00:00 2001 From: hontheinternet Date: Tue, 4 Jul 2023 20:24:37 +0900 Subject: [PATCH 8/9] fix bug and add error handling --- internal/api/routes_scene.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/internal/api/routes_scene.go b/internal/api/routes_scene.go index 629f886a34e..43d37da36e0 100644 --- a/internal/api/routes_scene.go +++ b/internal/api/routes_scene.go @@ -383,8 +383,10 @@ func (rs sceneRoutes) InteractiveCSV(w http.ResponseWriter, r *http.Request) { csvBytes, err := manager.ConvertFunscriptToCSV(filepath) if err != nil { - utils.ServeStaticContent(w, r, csvBytes) + http.Error(w, err.Error(), http.StatusInternalServerError) + return } + utils.ServeStaticContent(w, r, csvBytes) } func (rs sceneRoutes) InteractiveHeatmap(w http.ResponseWriter, r *http.Request) { From 5b77100156c294e5cf1d6b669df8166dac2ff751 Mon Sep 17 00:00:00 2001 From: WithoutPants <53250216+WithoutPants@users.noreply.github.com> Date: Tue, 11 Jul 2023 13:23:44 +1000 Subject: [PATCH 9/9] Cleanup --- internal/api/urlbuilders/scene.go | 4 ---- ui/v2.5/src/hooks/Interactive/interactive.ts | 3 --- 2 files changed, 7 deletions(-) diff --git a/internal/api/urlbuilders/scene.go b/internal/api/urlbuilders/scene.go index f8c7d476272..c70feafe753 100644 --- a/internal/api/urlbuilders/scene.go +++ b/internal/api/urlbuilders/scene.go @@ -72,7 +72,3 @@ func (b SceneURLBuilder) GetCaptionURL() string { func (b SceneURLBuilder) GetInteractiveHeatmapURL() string { return b.BaseURL + "/scene/" + b.SceneID + "/interactive_heatmap" } - -func (b SceneURLBuilder) GetInteractiveCSV() string { - return b.BaseURL + "/scene/" + b.SceneID + "/interactive_csv" -} diff --git a/ui/v2.5/src/hooks/Interactive/interactive.ts b/ui/v2.5/src/hooks/Interactive/interactive.ts index c1358cccf70..ef34bd2ef6a 100644 --- a/ui/v2.5/src/hooks/Interactive/interactive.ts +++ b/ui/v2.5/src/hooks/Interactive/interactive.ts @@ -142,7 +142,6 @@ export class Interactive { } async uploadScript(funscriptPath: string, apiKey?: string) { - console.log("FS Path: " + funscriptPath); if (!(this._handy.connectionKey && funscriptPath)) { return; } @@ -166,8 +165,6 @@ export class Interactive { funscriptUrl = await uploadCsv(csvFile).then((response) => response.url); } - console.log("funscriptUrl:" + funscriptUrl); - await this._handy.setMode(HandyMode.hssp); this._connected = await this._handy