2626from . import __version__
2727
2828
29+ CONFIG_FIELD_IDS = [
30+ "name" ,
31+ "format" ,
32+ "sortBy" ,
33+ "codec" ,
34+ "gpu" ,
35+ "audioCodec" ,
36+ "crf" ,
37+ "preset" ,
38+ "fpsPolicy" ,
39+ "resolutionPolicy" ,
40+ "padColor" ,
41+ "ffmpegPath" ,
42+ "ffprobePath" ,
43+ "recursive" ,
44+ "overwrite" ,
45+ "dryRun" ,
46+ "keepTemp" ,
47+ "autoDownloadDeps" ,
48+ ]
49+
50+
2951HTML = r"""<!doctype html>
3052<html lang="en">
3153<head>
569591 files: [],
570592 running: false,
571593 statusTimer: null,
572- lang: localStorage.getItem("vmt_lang") || "en",
594+ lang: "en",
573595 deps: { status: "notChecked", message: "" }
574596 };
575597 const $ = (id) => document.getElementById(id);
627649 if (!response.ok) throw new Error(payload.error || payload.message || response.statusText);
628650 return payload;
629651 }
652+ function readConfig() {
653+ const values = { lang: state.lang, mode: state.mode };
654+ const ids = ["name", "format", "sortBy", "codec", "gpu", "audioCodec", "crf", "preset", "fpsPolicy", "resolutionPolicy", "padColor", "ffmpegPath", "ffprobePath", "recursive", "overwrite", "dryRun", "keepTemp", "autoDownloadDeps"];
655+ ids.forEach(id => {
656+ const node = $(id);
657+ values[id] = node.type === "checkbox" ? node.checked : node.value;
658+ });
659+ return values;
660+ }
661+ function applyConfig(config) {
662+ if (!config || typeof config !== "object") return;
663+ if (config.lang && messages[config.lang]) state.lang = config.lang;
664+ if (config.mode) state.mode = config.mode;
665+ Object.entries(config).forEach(([id, value]) => {
666+ const node = $(id);
667+ if (!node) return;
668+ if (node.type === "checkbox") node.checked = Boolean(value);
669+ else node.value = value;
670+ });
671+ }
672+ async function loadConfig() {
673+ try { applyConfig(await api("/config")); } catch (error) { log(`ERROR: ${error.message}`); }
674+ }
675+ let saveTimer = null;
676+ function scheduleSaveConfig() {
677+ if (saveTimer) clearTimeout(saveTimer);
678+ saveTimer = setTimeout(async () => {
679+ try { await api("/config", readConfig()); } catch (error) { log(`ERROR: ${error.message}`); }
680+ }, 250);
681+ }
630682 async function checkDeps() {
631683 state.deps = { status: "checking", message: "" };
632684 renderDepStatus();
791843 document.querySelectorAll(".mode-card").forEach(card => card.addEventListener("click", () => {
792844 state.mode = card.dataset.mode;
793845 renderModes();
846+ scheduleSaveConfig();
794847 }));
795848 $("selectSource").addEventListener("click", () => selectFolder("source"));
796849 $("selectOutput").addEventListener("click", () => selectFolder("output"));
799852 $("startMerge").addEventListener("click", merge);
800853 $("languageSelect").addEventListener("change", () => {
801854 state.lang = $("languageSelect").value;
802- localStorage.setItem("vmt_lang", state.lang);
803855 applyLanguage();
804856 if (state.files.length) renderFiles(state.files);
857+ scheduleSaveConfig();
858+ });
859+ ["name", "format", "sortBy", "codec", "gpu", "audioCodec", "crf", "preset", "fpsPolicy", "resolutionPolicy", "padColor", "ffmpegPath", "ffprobePath", "recursive", "overwrite", "dryRun", "keepTemp", "autoDownloadDeps"].forEach(id => {
860+ const node = $(id);
861+ node.addEventListener(node.type === "checkbox" ? "change" : "input", scheduleSaveConfig);
862+ node.addEventListener("change", scheduleSaveConfig);
805863 });
806864 $("ffmpegStatus").addEventListener("click", checkDeps);
807865 document.querySelectorAll(".info").forEach(icon => {
825883 $("tooltip").style.display = "none";
826884 });
827885 });
828- applyLanguage();
829- log(t("selectBegin"));
830- checkDeps();
886+ (async () => {
887+ await loadConfig();
888+ applyLanguage();
889+ log(t("selectBegin"));
890+ checkDeps();
891+ })();
831892 </script>
832893</body>
833894</html>
@@ -951,6 +1012,9 @@ def do_GET(self) -> None:
9511012 if parsed .path == "/status" :
9521013 self ._send_json (state .snapshot ())
9531014 return
1015+ if parsed .path == "/config" :
1016+ self ._send_json (_load_gui_config ())
1017+ return
9541018 self ._send_json ({"error" : "Not found" }, HTTPStatus .NOT_FOUND )
9551019
9561020 def do_POST (self ) -> None :
@@ -965,6 +1029,10 @@ def do_POST(self) -> None:
9651029 if parsed .path == "/cancel" :
9661030 self ._cancel ()
9671031 return
1032+ if parsed .path == "/config" :
1033+ _save_gui_config (payload )
1034+ self ._send_json ({"ok" : True })
1035+ return
9681036 self ._send_json ({"error" : "Not found" }, HTTPStatus .NOT_FOUND )
9691037
9701038 def _deps (self ) -> None :
@@ -1256,7 +1324,7 @@ def _pick_folder_windows(title: str) -> str:
12561324$dialog.CheckFileExists = $false
12571325$dialog.ValidateNames = $false
12581326$dialog.FileName = 'Select this folder'
1259- $dialog.Filter = 'Folders |*.folder '
1327+ $dialog.Filter = 'All files (*.*) |*.* '
12601328if ($dialog.ShowDialog() -eq [System.Windows.Forms.DialogResult]::OK) {{
12611329 $selected = $dialog.FileName
12621330 if (Test-Path -LiteralPath $selected -PathType Container) {{
@@ -1283,6 +1351,39 @@ def _pick_folder_windows(title: str) -> str:
12831351 return ""
12841352
12851353
1354+ def _config_dir () -> Path :
1355+ system = platform .system ()
1356+ if system == "Darwin" :
1357+ return Path .home () / "Library" / "Application Support" / "VideoMergingTool"
1358+ if system == "Windows" :
1359+ root = os .environ .get ("APPDATA" ) or os .environ .get ("LOCALAPPDATA" )
1360+ return Path (root ) / "VideoMergingTool" if root else Path .home () / "AppData" / "Roaming" / "VideoMergingTool"
1361+ return Path (os .environ .get ("XDG_CONFIG_HOME" ) or Path .home () / ".config" ) / "VideoMergingTool"
1362+
1363+
1364+ def _config_path () -> Path :
1365+ return _config_dir () / "config.json"
1366+
1367+
1368+ def _load_gui_config () -> dict [str , object ]:
1369+ path = _config_path ()
1370+ if not path .exists ():
1371+ return {}
1372+ try :
1373+ payload = json .loads (path .read_text (encoding = "utf-8" ))
1374+ except (OSError , json .JSONDecodeError ):
1375+ return {}
1376+ return payload if isinstance (payload , dict ) else {}
1377+
1378+
1379+ def _save_gui_config (payload : dict [str , object ]) -> None :
1380+ allowed = set (CONFIG_FIELD_IDS ) | {"lang" , "mode" }
1381+ clean = {key : value for key , value in payload .items () if key in allowed }
1382+ path = _config_path ()
1383+ path .parent .mkdir (parents = True , exist_ok = True )
1384+ path .write_text (json .dumps (clean , indent = 2 , ensure_ascii = False ), encoding = "utf-8" )
1385+
1386+
12861387def _pick_folder_tk (title : str ) -> str :
12871388 try :
12881389 import tkinter as tk
0 commit comments