diff --git a/SentryReplay.Tests/PackageManagerTests.cs b/SentryReplay.Tests/PackageManagerTests.cs
new file mode 100644
index 0000000..230095f
--- /dev/null
+++ b/SentryReplay.Tests/PackageManagerTests.cs
@@ -0,0 +1,29 @@
+using System.Runtime.InteropServices;
+using Shouldly;
+
+namespace SentryReplay.Tests;
+
+///
+/// Tests for PackageManager functionality.
+///
+public class PackageManagerTests
+{
+ [Fact]
+ public void GetFFmpegDownloadUrl_ReturnsValidUrl()
+ {
+ // We can't directly test the private method, but we can verify the logic indirectly
+ // by checking that the current architecture is supported
+ var architecture = RuntimeInformation.ProcessArchitecture;
+
+ // Verify that the architecture is one we support
+ architecture.ShouldBeOneOf(Architecture.X64, Architecture.Arm64, Architecture.X86, Architecture.Arm);
+ }
+
+ [Fact]
+ public void FindFFmpegDirectories_ReturnsEnumerable()
+ {
+ // Test that the method returns an enumerable (even if empty)
+ var directories = PackageManager.FindFFmpegDirectories(".");
+ directories.ShouldNotBeNull();
+ }
+}
diff --git a/SentryReplay/PackageManager.cs b/SentryReplay/PackageManager.cs
index 50bd70a..d07b4e3 100644
--- a/SentryReplay/PackageManager.cs
+++ b/SentryReplay/PackageManager.cs
@@ -1,6 +1,7 @@
using System.IO;
using System.IO.Compression;
using System.Net.Http;
+using System.Runtime.InteropServices;
using Serilog;
namespace SentryReplay;
@@ -9,35 +10,82 @@ public static class PackageManager
{
private static async Task DownloadFile(string url, string savePath)
{
- using var client = new HttpClient();
- var response = await client.GetAsync(url);
- if (response.IsSuccessStatusCode)
- {
- using var fileStream = File.Create(savePath);
- await response.Content.CopyToAsync(fileStream);
- }
+ using var client = new HttpClient { Timeout = TimeSpan.FromMinutes(5) };
+ var response = await client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead);
+ response.EnsureSuccessStatusCode();
+
+ using var fileStream = File.Create(savePath);
+ await response.Content.CopyToAsync(fileStream);
}
private static void ExtractZipFile(string zipFilePath, string extractPath)
{
- ZipFile.ExtractToDirectory(zipFilePath, extractPath, true);
+ // Extract and flatten nested directories - the zip contains a bin directory with ffmpeg.exe and required DLLs
+ using var archive = ZipFile.OpenRead(zipFilePath);
+
+ foreach (var entry in archive.Entries)
+ {
+ if (string.IsNullOrEmpty(entry.Name))
+ continue;
+
+ // Find bin directory in the zip structure
+ var entryPath = entry.FullName.Replace('/', Path.DirectorySeparatorChar);
+ var binDirectory = "bin" + Path.DirectorySeparatorChar;
+ var binIndex = entryPath.IndexOf(binDirectory, StringComparison.OrdinalIgnoreCase);
+
+ if (binIndex >= 0)
+ {
+ // Extract files from bin directory to root of extractPath
+ var relativePath = entryPath.Substring(binIndex + binDirectory.Length);
+ var destPath = Path.Combine(extractPath, relativePath);
+
+ Directory.CreateDirectory(Path.GetDirectoryName(destPath)!);
+ entry.ExtractToFile(destPath, overwrite: true);
+ }
+ }
}
public static async Task DownloadAndExtractFFmpeg()
{
var outputFolder = Path.GetFullPath("ffmpeg");
- var url = "https://github.com/GyanD/codexffmpeg/releases/download/7.0/ffmpeg-7.0-full_build-shared.zip"; // TODO: ARM64 builds?
+ var url = GetFFmpegDownloadUrl();
var tempPath = Path.GetTempFileName();
- Log.Information("Getting ffmpeg");
+ try
+ {
+ Log.Information("Downloading FFmpeg...");
- Log.Debug($"Downloading ffmpeg to {tempPath} from {url}");
- await DownloadFile(url, tempPath);
+ Log.Debug($"Downloading FFmpeg to {tempPath} from {url}");
+ await DownloadFile(url, tempPath);
- Log.Debug($"Extracting ffmpeg to {outputFolder}");
- ExtractZipFile(tempPath, outputFolder);
+ Log.Information("Extracting FFmpeg...");
+ Log.Debug($"Extracting FFmpeg to {outputFolder}");
+
+ if (Directory.Exists(outputFolder))
+ Directory.Delete(outputFolder, true);
+ Directory.CreateDirectory(outputFolder);
+
+ ExtractZipFile(tempPath, outputFolder);
+
+ Log.Information("FFmpeg downloaded and extracted successfully");
+ }
+ finally
+ {
+ if (File.Exists(tempPath))
+ File.Delete(tempPath);
+ }
+ }
- File.Delete(tempPath);
+ private static string GetFFmpegDownloadUrl()
+ {
+ var architecture = RuntimeInformation.ProcessArchitecture;
+
+ return architecture switch
+ {
+ Architecture.Arm64 => "https://github.com/BtbN/FFmpeg-Builds/releases/download/latest/ffmpeg-master-latest-winarm64-gpl-shared.zip",
+ Architecture.X64 => "https://github.com/BtbN/FFmpeg-Builds/releases/download/latest/ffmpeg-master-latest-win64-gpl-shared.zip",
+ _ => throw new PlatformNotSupportedException($"Unsupported architecture: {architecture}. Only x64 and ARM64 are supported.")
+ };
}
public static IEnumerable FindFFmpegDirectories(string searchDirectory = ".")