Skip to content

Commit 9e3c33c

Browse files
CopilotTyrrrzCopilot
authored
Replace Publish-MacOSBundle.ps1 and Download-FFmpeg.ps1 with .NET 10 single-file C# scripts using CliFx (#799)
Co-authored-by: Tyrrrz <1935960+Tyrrrz@users.noreply.github.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
1 parent 43fde91 commit 9e3c33c

6 files changed

Lines changed: 231 additions & 155 deletions

File tree

‎Directory.Packages.props‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
<PackageVersion Include="Avalonia.Controls.DataGrid" Version="11.3.0" />
1010
<PackageVersion Include="Avalonia.Desktop" Version="11.3.0" />
1111
<PackageVersion Include="Avalonia.Diagnostics" Version="11.3.0" />
12+
<PackageVersion Include="CliFx" Version="2.3.6" />
1213
<PackageVersion Include="Cogwheel" Version="2.1.0" />
1314
<PackageVersion Include="CommunityToolkit.Mvvm" Version="8.4.0" />
1415
<PackageVersion Include="CSharpier.MsBuild" Version="1.2.6" />

‎YoutubeDownloader/Download-FFmpeg.ps1‎

Lines changed: 0 additions & 65 deletions
This file was deleted.
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
#!/usr/bin/dotnet --
2+
#:package CliFx
3+
4+
using System.IO.Compression;
5+
using System.Runtime.InteropServices;
6+
using CliFx;
7+
using CliFx.Attributes;
8+
using CliFx.Exceptions;
9+
using CliFx.Infrastructure;
10+
11+
return await new CliApplicationBuilder().AddCommand<DownloadFFmpegCommand>().Build().RunAsync(args);
12+
13+
[Command(Description = "Downloads FFmpeg.")]
14+
public class DownloadFFmpegCommand : ICommand
15+
{
16+
[CommandOption("output", Description = "Output path for the downloaded FFmpeg binary.")]
17+
public string OutputPath { get; init; } = Directory.GetCurrentDirectory();
18+
19+
[CommandOption("platform", Description = "Target platform identifier (e.g. 'windows-x64').")]
20+
public string? Platform { get; init; }
21+
22+
[CommandOption("ffmpeg-version", Description = "FFmpeg version to download.")]
23+
public string FFmpegVersion { get; init; } = "8.0.1";
24+
25+
public async ValueTask ExecuteAsync(IConsole console)
26+
{
27+
var platform = Platform;
28+
29+
// If the platform is not specified, use the current OS/arch
30+
if (string.IsNullOrWhiteSpace(platform))
31+
{
32+
var arch = RuntimeInformation.OSArchitecture.ToString().ToLower();
33+
34+
if (OperatingSystem.IsWindows())
35+
platform = $"windows-{arch}";
36+
else if (OperatingSystem.IsLinux())
37+
platform = $"linux-{arch}";
38+
else if (OperatingSystem.IsMacOS())
39+
platform = $"osx-{arch}";
40+
else
41+
throw new CommandException("Unsupported platform.");
42+
}
43+
44+
// Normalize platform identifier
45+
platform = platform.ToLower().Replace("win-", "windows-");
46+
47+
// Identify the FFmpeg filename based on the platform
48+
var fileName = platform.Contains("windows-") ? "ffmpeg.exe" : "ffmpeg";
49+
50+
var outputPath = OutputPath;
51+
52+
// If the output path is an existing directory, append the default file name for the platform
53+
if (Directory.Exists(outputPath))
54+
outputPath = Path.Combine(outputPath, fileName);
55+
56+
// Delete the existing file if it exists
57+
if (File.Exists(outputPath))
58+
File.Delete(outputPath);
59+
60+
// Download the archive
61+
console.Output.WriteLine($"Downloading FFmpeg for {platform}...");
62+
var archiveFilePath = outputPath + ".zip";
63+
var cancellationToken = console.RegisterCancellationHandler();
64+
try
65+
{
66+
using var http = new HttpClient();
67+
using var responseStream = await http.GetStreamAsync(
68+
$"https://github.com/Tyrrrz/FFmpegBin/releases/download/{FFmpegVersion}/ffmpeg-{platform}.zip",
69+
cancellationToken
70+
);
71+
72+
await using (var archiveFile = File.Create(archiveFilePath))
73+
{
74+
await responseStream.CopyToAsync(archiveFile, cancellationToken);
75+
}
76+
77+
// Extract FFmpeg
78+
using var zip = ZipFile.OpenRead(archiveFilePath);
79+
80+
var entry =
81+
zip.GetEntry(fileName)
82+
?? throw new CommandException(
83+
$"Entry '{fileName}' not found in the downloaded archive."
84+
);
85+
86+
entry.ExtractToFile(outputPath, true);
87+
88+
console.Output.WriteLine("Done downloading FFmpeg.");
89+
}
90+
finally
91+
{
92+
// Clean up
93+
File.Delete(archiveFilePath);
94+
}
95+
}
96+
}

‎YoutubeDownloader/Publish-MacOSBundle.ps1‎

Lines changed: 0 additions & 87 deletions
This file was deleted.
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
#!/usr/bin/dotnet --
2+
#:package CliFx
3+
4+
using CliFx;
5+
using CliFx.Attributes;
6+
using CliFx.Infrastructure;
7+
8+
return await new CliApplicationBuilder()
9+
.AddCommand<PublishMacOSBundleCommand>()
10+
.Build()
11+
.RunAsync(args);
12+
13+
[Command(Description = "Publishes the GUI app as a macOS .app bundle.")]
14+
public class PublishMacOSBundleCommand : ICommand
15+
{
16+
private const string BundleName = "YoutubeDownloader.app";
17+
private const string AppName = "YoutubeDownloader";
18+
private const string AppCopyright = "© Oleksii Holub";
19+
private const string AppIdentifier = "me.Tyrrrz.YoutubeDownloader";
20+
private const string AppSpokenName = "YoutubeDownloader";
21+
private const string AppIconName = "AppIcon";
22+
23+
[CommandOption("publish-dir", Description = "Path to the publish output directory.")]
24+
public required string PublishDirPath { get; init; }
25+
26+
[CommandOption("icons-file", Description = "Path to the .icns icons file.")]
27+
public required string IconsFilePath { get; init; }
28+
29+
[CommandOption("full-version", Description = "Full version string (e.g. '1.2.3.4').")]
30+
public required string FullVersion { get; init; }
31+
32+
[CommandOption("short-version", Description = "Short version string (e.g. '1.2.3').")]
33+
public required string ShortVersion { get; init; }
34+
35+
public async ValueTask ExecuteAsync(IConsole console)
36+
{
37+
// Set up paths
38+
var publishDirPath = Path.GetFullPath(PublishDirPath);
39+
var tempDirPath = Path.GetFullPath(
40+
Path.Combine(publishDirPath, "../publish-macos-app-temp")
41+
);
42+
43+
// Ensure the temporary directory is clean before use in case a previous run crashed
44+
if (Directory.Exists(tempDirPath))
45+
Directory.Delete(tempDirPath, true);
46+
47+
var bundleDirPath = Path.Combine(tempDirPath, BundleName);
48+
var contentsDirPath = Path.Combine(bundleDirPath, "Contents");
49+
50+
try
51+
{
52+
// Copy icons into the .app's Resources folder
53+
Directory.CreateDirectory(Path.Combine(contentsDirPath, "Resources"));
54+
File.Copy(
55+
IconsFilePath,
56+
Path.Combine(contentsDirPath, "Resources", "AppIcon.icns"),
57+
true
58+
);
59+
60+
// Generate the Info.plist metadata file with the app information
61+
// lang=xml
62+
var plistContent = $"""
63+
<?xml version="1.0" encoding="UTF-8"?>
64+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
65+
<plist version="1.0">
66+
<dict>
67+
<key>CFBundleDisplayName</key>
68+
<string>{AppName}</string>
69+
<key>CFBundleName</key>
70+
<string>{AppName}</string>
71+
<key>CFBundleExecutable</key>
72+
<string>{AppName}</string>
73+
<key>NSHumanReadableCopyright</key>
74+
<string>{AppCopyright}</string>
75+
<key>CFBundleIdentifier</key>
76+
<string>{AppIdentifier}</string>
77+
<key>CFBundleSpokenName</key>
78+
<string>{AppSpokenName}</string>
79+
<key>CFBundleIconFile</key>
80+
<string>{AppIconName}</string>
81+
<key>CFBundleIconName</key>
82+
<string>{AppIconName}</string>
83+
<key>CFBundleVersion</key>
84+
<string>{FullVersion}</string>
85+
<key>CFBundleShortVersionString</key>
86+
<string>{ShortVersion}</string>
87+
<key>NSHighResolutionCapable</key>
88+
<true />
89+
<key>CFBundlePackageType</key>
90+
<string>APPL</string>
91+
</dict>
92+
</plist>
93+
""";
94+
95+
await File.WriteAllTextAsync(Path.Combine(contentsDirPath, "Info.plist"), plistContent);
96+
97+
// Delete the previous bundle if it exists
98+
var existingBundlePath = Path.Combine(publishDirPath, BundleName);
99+
if (Directory.Exists(existingBundlePath))
100+
Directory.Delete(existingBundlePath, true);
101+
102+
// Move all files from the publish directory into the MacOS directory
103+
Directory.CreateDirectory(Path.Combine(contentsDirPath, "MacOS"));
104+
foreach (var entryPath in Directory.GetFileSystemEntries(publishDirPath))
105+
{
106+
var destinationPath = Path.Combine(
107+
contentsDirPath,
108+
"MacOS",
109+
Path.GetFileName(entryPath)
110+
);
111+
112+
if (Directory.Exists(entryPath))
113+
Directory.Move(entryPath, destinationPath);
114+
else
115+
File.Move(entryPath, destinationPath);
116+
}
117+
118+
// Move the final bundle into the publish directory for upload
119+
Directory.Move(bundleDirPath, Path.Combine(publishDirPath, BundleName));
120+
}
121+
finally
122+
{
123+
// Clean up the temporary directory
124+
if (Directory.Exists(tempDirPath))
125+
Directory.Delete(tempDirPath, true);
126+
}
127+
}
128+
}

0 commit comments

Comments
 (0)