From 3d5dfd6d4858ec67212bd7958f7329d2121ce48a Mon Sep 17 00:00:00 2001 From: oop7 <110548351+oop7@users.noreply.github.com> Date: Sat, 7 Feb 2026 19:13:47 +0200 Subject: [PATCH] Add Inno Setup installers and build step Add Inno Setup compilation to the Windows CI: the workflow now installs Inno Setup via choco, adjusts PATH, and runs iscc to compile two installers (standard and -ffmpeg) using /DMyAppVersion,/DMyAppExeName and /DSourceDir, placing outputs into repo/artifacts and failing the job on non-zero exit. Also add two new Inno Setup scripts in setup-scripts/: Setup-windows.iss and Setup-windows-ffmpeg.iss. The scripts include metadata, multi-language support, file/icon/registry/run entries, and Pascal code to create or update the user's ytsage_config.json language setting; the -ffmpeg script adds an optional task to append the app folder to the user's PATH. --- .github/workflows/build-windows.yml | 33 ++++++ setup-scripts/Setup-windows-ffmpeg.iss | 151 +++++++++++++++++++++++++ setup-scripts/Setup-windows.iss | 129 +++++++++++++++++++++ 3 files changed, 313 insertions(+) create mode 100644 setup-scripts/Setup-windows-ffmpeg.iss create mode 100644 setup-scripts/Setup-windows.iss diff --git a/.github/workflows/build-windows.yml b/.github/workflows/build-windows.yml index 42a554e..7b205b2 100644 --- a/.github/workflows/build-windows.yml +++ b/.github/workflows/build-windows.yml @@ -449,6 +449,39 @@ jobs: Write-Host " Artifacts directory not found!" } + - name: Create Installer (Inno Setup) + shell: powershell + run: | + $version = "${{ steps.get_version.outputs.VERSION }}" + + # Install Inno Setup + choco install innosetup --no-progress + + # Add matches generally C:\Program Files (x86)\Inno Setup 6 + $env:Path = "C:\Program Files (x86)\Inno Setup 6;$env:Path" + + # Compile + $exeName = "YTSage-v$version.exe" + Write-Host "Compiling installer for $exeName..." + + # Note: Setup-windows.iss is in setup-scripts/ + # OutputDir is ..\artifacts (relative to script) -> repo/artifacts + # SourceDir is ..\dist\YTSage (relative to script) -> repo/dist/YTSage + + iscc /DMyAppVersion="$version" /DMyAppExeName="$exeName" /DSourceDir="..\dist\YTSage" "setup-scripts\Setup-windows.iss" + + # Compile FFmpeg Installer + $exeNameFFmpeg = "YTSage-v$version-ffmpeg.exe" + Write-Host "Compiling FFmpeg installer for $exeNameFFmpeg..." + iscc /DMyAppVersion="$version" /DMyAppExeName="$exeNameFFmpeg" /DSourceDir="..\dist\YTSage-FFmpeg" "setup-scripts\Setup-windows-ffmpeg.iss" + + if ($LASTEXITCODE -eq 0) { + Write-Host "Installer compilation successful." + } else { + Write-Host "Installer compilation failed." + exit 1 + } + - name: Create draft release uses: softprops/action-gh-release@v2 with: diff --git a/setup-scripts/Setup-windows-ffmpeg.iss b/setup-scripts/Setup-windows-ffmpeg.iss new file mode 100644 index 0000000..9ad8a10 --- /dev/null +++ b/setup-scripts/Setup-windows-ffmpeg.iss @@ -0,0 +1,151 @@ +; Script generated by the Inno Setup Script Wizard. + +#define MyAppName "YTSage" +#ifndef MyAppVersion + #define MyAppVersion "0.0.0" +#endif +#define MyAppPublisher "oop7" +#define MyAppURL "https://github.com/oop7/YTSage/" +#ifndef MyAppExeName + #define MyAppExeName "YTSage-ffmpeg.exe" +#endif +#ifndef SourceDir + #define SourceDir "..\dist\YTSage-FFmpeg" +#endif + +[Setup] +AppId={{56997322-2A3A-4338-AEF1-C3C8BB28AC4F} +AppName={#MyAppName} +AppVersion={#MyAppVersion} +AppPublisher={#MyAppPublisher} +AppPublisherURL={#MyAppURL} +AppSupportURL={#MyAppURL} +AppUpdatesURL={#MyAppURL} +DefaultDirName={autopf}\{#MyAppName} +UninstallDisplayIcon={app}\{#MyAppExeName} +ArchitecturesAllowed=x64compatible +ArchitecturesInstallIn64BitMode=x64compatible +DisableProgramGroupPage=yes +LicenseFile=..\LICENSE +PrivilegesRequired=lowest +PrivilegesRequiredOverridesAllowed=dialog +OutputBaseFilename=YTSage-v{#MyAppVersion}-ffmpeg-Setup +SolidCompression=yes +WizardStyle=modern +OutputDir=..\artifacts + +[Languages] +Name: "english"; MessagesFile: "compiler:Default.isl" +Name: "arabic"; MessagesFile: "compiler:Languages\Arabic.isl" +Name: "brazilianportuguese"; MessagesFile: "compiler:Languages\BrazilianPortuguese.isl" +Name: "french"; MessagesFile: "compiler:Languages\French.isl" +Name: "german"; MessagesFile: "compiler:Languages\German.isl" +Name: "italian"; MessagesFile: "compiler:Languages\Italian.isl" +Name: "japanese"; MessagesFile: "compiler:Languages\Japanese.isl" +Name: "polish"; MessagesFile: "compiler:Languages\Polish.isl" +Name: "portuguese"; MessagesFile: "compiler:Languages\Portuguese.isl" +Name: "russian"; MessagesFile: "compiler:Languages\Russian.isl" +Name: "spanish"; MessagesFile: "compiler:Languages\Spanish.isl" +Name: "turkish"; MessagesFile: "compiler:Languages\Turkish.isl" + +[Tasks] +Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; +Name: "addtopath"; Description: "Add ffmpeg to user PATH environment variable"; GroupDescription: "Additional options:"; Flags: unchecked + +[Files] +Source: "{#SourceDir}\{#MyAppExeName}"; DestDir: "{app}"; Flags: ignoreversion +Source: "{#SourceDir}\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs + +[Icons] +Name: "{autoprograms}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}" +Name: "{autodesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: desktopicon + +[Registry] +Root: HKCU; Subkey: "Environment"; ValueType: expandsz; ValueName: "Path"; ValueData: "{olddata};{app}"; Tasks: addtopath; Check: NeedsAddPath('{app}') + +[Run] +Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; Flags: nowait postinstall skipifsilent + +[Code] +function NeedsAddPath(Param: string): boolean; +var + OrigPath: string; + ParamExpanded: string; +begin + // Expand the setup constants like {app} from Param + ParamExpanded := ExpandConstant(Param); + if not RegQueryStringValue(HKEY_CURRENT_USER, 'Environment', 'Path', OrigPath) then + begin + Result := True; + exit; + end; + // Look for the path with leading and trailing semicolon + Result := Pos(';' + UpperCase(ParamExpanded) + ';', ';' + UpperCase(OrigPath) + ';') = 0; + if Result = True then + Result := Pos(';' + UpperCase(ParamExpanded) + '\;', ';' + UpperCase(OrigPath) + ';') = 0; +end; + +procedure UpdateLanguageConfig; +var + LanguageCode: string; + ConfigPath: string; + FileContent: AnsiString; + JsonContent: string; + LangPattern: string; + P_Start, P_End: Integer; +begin + if ActiveLanguage = 'english' then LanguageCode := 'en' + else if ActiveLanguage = 'arabic' then LanguageCode := 'ar' + else if ActiveLanguage = 'brazilianportuguese' then LanguageCode := 'pt-br' + else if ActiveLanguage = 'french' then LanguageCode := 'fr' + else if ActiveLanguage = 'german' then LanguageCode := 'de' + else if ActiveLanguage = 'italian' then LanguageCode := 'it' + else if ActiveLanguage = 'japanese' then LanguageCode := 'ja' + else if ActiveLanguage = 'polish' then LanguageCode := 'pl' + else if ActiveLanguage = 'portuguese' then LanguageCode := 'pt' + else if ActiveLanguage = 'russian' then LanguageCode := 'ru' + else if ActiveLanguage = 'spanish' then LanguageCode := 'es' + else if ActiveLanguage = 'turkish' then LanguageCode := 'tr' + else LanguageCode := 'en'; + + ConfigPath := ExpandConstant('{localappdata}\YTSage\data\ytsage_config.json'); + + if not DirExists(ExtractFilePath(ConfigPath)) then + ForceDirectories(ExtractFilePath(ConfigPath)); + + if FileExists(ConfigPath) then + begin + if LoadStringFromFile(ConfigPath, FileContent) then + begin + JsonContent := String(FileContent); + LangPattern := '"language": "'; + P_Start := Pos(LangPattern, JsonContent); + if P_Start > 0 then + begin + P_Start := P_Start + Length(LangPattern); + P_End := Pos('"', Copy(JsonContent, P_Start, Length(JsonContent))); + if P_End > 0 then + begin + Delete(JsonContent, P_Start, P_End - 1); + Insert(LanguageCode, JsonContent, P_Start); + SaveStringToFile(ConfigPath, AnsiString(JsonContent), False); + end; + end; + end; + end + else + begin + JsonContent := '{' + #13#10 + + ' "language": "' + LanguageCode + '"' + #13#10 + + '}'; + SaveStringToFile(ConfigPath, AnsiString(JsonContent), False); + end; +end; + +procedure CurStepChanged(CurStep: TSetupStep); +begin + if CurStep = ssPostInstall then + begin + UpdateLanguageConfig(); + end; +end; diff --git a/setup-scripts/Setup-windows.iss b/setup-scripts/Setup-windows.iss new file mode 100644 index 0000000..1f1228c --- /dev/null +++ b/setup-scripts/Setup-windows.iss @@ -0,0 +1,129 @@ +; Script generated by the Inno Setup Script Wizard. + +#define MyAppName "YTSage" +#ifndef MyAppVersion + #define MyAppVersion "0.0.0" +#endif +#define MyAppPublisher "oop7" +#define MyAppURL "https://github.com/oop7/YTSage/" +#ifndef MyAppExeName + #define MyAppExeName "YTSage.exe" +#endif +#ifndef SourceDir + #define SourceDir "..\dist\YTSage" +#endif + +[Setup] +AppId={{AE618DBF-DD56-462D-9C09-2C2B7A41B201} +AppName={#MyAppName} +AppVersion={#MyAppVersion} +AppPublisher={#MyAppPublisher} +AppPublisherURL={#MyAppURL} +AppSupportURL={#MyAppURL} +AppUpdatesURL={#MyAppURL} +DefaultDirName={autopf}\{#MyAppName} +UninstallDisplayIcon={app}\{#MyAppExeName} +ArchitecturesAllowed=x64compatible +ArchitecturesInstallIn64BitMode=x64compatible +DisableProgramGroupPage=yes +LicenseFile=..\LICENSE +PrivilegesRequired=lowest +PrivilegesRequiredOverridesAllowed=dialog +OutputBaseFilename=YTSage-v{#MyAppVersion}-Setup +SolidCompression=yes +WizardStyle=modern +OutputDir=..\artifacts + +[Languages] +Name: "english"; MessagesFile: "compiler:Default.isl" +Name: "arabic"; MessagesFile: "compiler:Languages\Arabic.isl" +Name: "brazilianportuguese"; MessagesFile: "compiler:Languages\BrazilianPortuguese.isl" +Name: "french"; MessagesFile: "compiler:Languages\French.isl" +Name: "german"; MessagesFile: "compiler:Languages\German.isl" +Name: "italian"; MessagesFile: "compiler:Languages\Italian.isl" +Name: "japanese"; MessagesFile: "compiler:Languages\Japanese.isl" +Name: "polish"; MessagesFile: "compiler:Languages\Polish.isl" +Name: "portuguese"; MessagesFile: "compiler:Languages\Portuguese.isl" +Name: "russian"; MessagesFile: "compiler:Languages\Russian.isl" +Name: "spanish"; MessagesFile: "compiler:Languages\Spanish.isl" +Name: "turkish"; MessagesFile: "compiler:Languages\Turkish.isl" + +[Tasks] +Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; + +[Files] +Source: "{#SourceDir}\{#MyAppExeName}"; DestDir: "{app}"; Flags: ignoreversion +Source: "{#SourceDir}\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs + +[Icons] +Name: "{autoprograms}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}" +Name: "{autodesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: desktopicon + +[Run] +Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; Flags: nowait postinstall skipifsilent + +[Code] +procedure UpdateLanguageConfig; +var + LanguageCode: string; + ConfigPath: string; + FileContent: AnsiString; + JsonContent: string; + LangPattern: string; + P_Start, P_End: Integer; +begin + if ActiveLanguage = 'english' then LanguageCode := 'en' + else if ActiveLanguage = 'arabic' then LanguageCode := 'ar' + else if ActiveLanguage = 'brazilianportuguese' then LanguageCode := 'pt-br' + else if ActiveLanguage = 'french' then LanguageCode := 'fr' + else if ActiveLanguage = 'german' then LanguageCode := 'de' + else if ActiveLanguage = 'italian' then LanguageCode := 'it' + else if ActiveLanguage = 'japanese' then LanguageCode := 'ja' + else if ActiveLanguage = 'polish' then LanguageCode := 'pl' + else if ActiveLanguage = 'portuguese' then LanguageCode := 'pt' + else if ActiveLanguage = 'russian' then LanguageCode := 'ru' + else if ActiveLanguage = 'spanish' then LanguageCode := 'es' + else if ActiveLanguage = 'turkish' then LanguageCode := 'tr' + else LanguageCode := 'en'; + + ConfigPath := ExpandConstant('{localappdata}\YTSage\data\ytsage_config.json'); + + if not DirExists(ExtractFilePath(ConfigPath)) then + ForceDirectories(ExtractFilePath(ConfigPath)); + + if FileExists(ConfigPath) then + begin + if LoadStringFromFile(ConfigPath, FileContent) then + begin + JsonContent := String(FileContent); + LangPattern := '"language": "'; + P_Start := Pos(LangPattern, JsonContent); + if P_Start > 0 then + begin + P_Start := P_Start + Length(LangPattern); + P_End := Pos('"', Copy(JsonContent, P_Start, Length(JsonContent))); + if P_End > 0 then + begin + Delete(JsonContent, P_Start, P_End - 1); + Insert(LanguageCode, JsonContent, P_Start); + SaveStringToFile(ConfigPath, AnsiString(JsonContent), False); + end; + end; + end; + end + else + begin + JsonContent := '{' + #13#10 + + ' "language": "' + LanguageCode + '"' + #13#10 + + '}'; + SaveStringToFile(ConfigPath, AnsiString(JsonContent), False); + end; +end; + +procedure CurStepChanged(CurStep: TSetupStep); +begin + if CurStep = ssPostInstall then + begin + UpdateLanguageConfig(); + end; +end;