From 8e022743af303371c54ed6c1d60a47f2f2ca695e Mon Sep 17 00:00:00 2001 From: ro Date: Fri, 1 Aug 2025 22:34:31 -0700 Subject: [PATCH] feat: fix ffmpeg linking on windows + other fixes --- .gitignore | 2 + build.zig | 45 +++++++++++++-- src/main.zig | 154 +++++++++++++++++++++++++-------------------------- 3 files changed, 118 insertions(+), 83 deletions(-) diff --git a/.gitignore b/.gitignore index eabac4b..b3d5679 100644 --- a/.gitignore +++ b/.gitignore @@ -26,3 +26,5 @@ test_output/ # releases to make it less annoying to work with multiple branches. zig-cache/ release_assets/ + +raddbg* diff --git a/build.zig b/build.zig index 8bd3936..49e2719 100644 --- a/build.zig +++ b/build.zig @@ -18,11 +18,13 @@ const BuildOptions = struct { video: *Module, img: *Module, version: Version, + av_root: []const u8, }; pub fn build(b: *std.Build) !void { const optimize = b.standardOptimizeOption(.{}); const strip = b.option(bool, "strip", "Omit debug information") orelse false; + const av_root = b.option([]const u8, "av_root", "Path to ffmpeg root") orelse "C:/ProgramData/chocolatey/lib/ffmpeg-shared/tools/ffmpeg-7.1-full_build-shared"; const target = b.standardTargetOptions(.{}); const dep_stb = b.dependency("stb", .{}); @@ -40,7 +42,7 @@ pub fn build(b: *std.Build) !void { .target = target, .optimize = optimize, }); - linkFfmpeg(b, target, av_module); + linkFfmpeg(b, target, av_module, av_root); const libglyph = b.addModule("libglyph", .{ .root_source_file = b.path("src/core.zig"), .target = target, @@ -87,6 +89,7 @@ pub fn build(b: *std.Build) !void { .libav = av_module, .video = video_module, .version = version, + .av_root = av_root, }; try runZig( @@ -153,10 +156,42 @@ fn setupTest( return unit_test; } -fn linkFfmpeg(b: *std.Build, target: std.Build.ResolvedTarget, lib: *Module) void { +fn copyDlls(b: *std.Build, target: std.Build.ResolvedTarget, av_root: []const u8) void { + if (target.result.os.tag != .windows) return; + + var path_buf: [std.fs.max_path_bytes]u8 = undefined; + const ffmpeg_bin_path = std.fmt.bufPrint(&path_buf, "{s}/bin", .{av_root}) catch |err| { + std.debug.print("Failed to format ffmpeg bin path: {any}\n", .{err}); + return; + }; + + // We can't walk the directory due to permissions with winget packages. + // Instead, we'll copy the DLLs we know we need by name. + // These names are for FFmpeg 7.x. + const dlls = [_][]const u8{ + "avcodec-61.dll", + "avformat-61.dll", + "avutil-59.dll", + "swresample-5.dll", + "swscale-8.dll", + }; + + for (dlls) |dll_name| { + var dll_path_buf: [std.fs.max_path_bytes]u8 = undefined; + const dll_path = std.fmt.bufPrint(&dll_path_buf, "{s}/{s}", .{ ffmpeg_bin_path, dll_name }) catch |err| { + std.debug.print("Failed to format src path: {any}\n", .{err}); + return; + }; + b.getInstallStep().dependOn(&b.addInstallBinFile(.{ .cwd_relative = dll_path }, dll_name).step); + } +} + +fn linkFfmpeg(b: *std.Build, target: std.Build.ResolvedTarget, lib: *Module, av_root: []const u8) void { if (target.result.os.tag == .windows) { - const ffmpeg_include_path = getEnvOrDefault(b, "INCLUDE", "C:/ProgramData/chocolatey/lib/ffmpeg-shared/tools/ffmpeg-7.1-full_build-shared/include"); - const ffmpeg_lib_path = getEnvOrDefault(b, "LIB", "C:/ProgramData/chocolatey/lib/ffmpeg-shared/tools/ffmpeg-7.1-full_build-shared/lib"); + const ffmpeg_include_path = std.fs.path.join(b.allocator, &.{ av_root, "include" }) catch unreachable; + const ffmpeg_lib_path = std.fs.path.join(b.allocator, &.{ av_root, "lib" }) catch unreachable; + defer b.allocator.free(ffmpeg_include_path); + defer b.allocator.free(ffmpeg_lib_path); lib.addLibraryPath(.{ .cwd_relative = ffmpeg_lib_path }); lib.addIncludePath(.{ .cwd_relative = ffmpeg_include_path }); @@ -181,6 +216,8 @@ fn runZig( optimize: std.builtin.OptimizeMode, strip: bool, ) !void { + copyDlls(b, target, self.av_root); + const exe = try setupExecutable( self, b, diff --git a/src/main.zig b/src/main.zig index caeb16e..8122e85 100644 --- a/src/main.zig +++ b/src/main.zig @@ -14,7 +14,79 @@ const default_block = " .:coPO?@█"; const default_ascii = " .:-=+*%@#"; const full_characters = " .-:=+iltIcsv1x%7aejorzfnuCJT3*69LYpqy25SbdgFGOVXkPhmw48AQDEHKUZR@B#NW0M"; -fn parseArgs(allocator: std.mem.Allocator) !core.CoreParams { +fn getDefaultChars(symbols: ?[]const u8) []const u8 { + const symbol_type = if (symbols) |s| + if (std.mem.eql(u8, s, "ascii")) core.SymbolType.Ascii else core.SymbolType.Block + else + core.SymbolType.Ascii; + return switch (symbol_type) { + .Ascii => default_ascii, + .Block => default_block, + }; +} + +fn sortCharsBySize(allocator: std.mem.Allocator, input: []const u8) ![]const u8 { + const CharInfo = struct { + char: []const u8, + size: usize, + }; + + var char_infos = std.ArrayList(CharInfo).init(allocator); + defer char_infos.deinit(); + + var it = std.unicode.Utf8Iterator{ .bytes = input, .i = 0 }; + while (it.nextCodepoint()) |codepoint| { + const len = std.unicode.utf8CodepointSequenceLength(codepoint) catch continue; + const char_start = it.i - len; + const char = input[char_start..it.i]; + + const bm = bitmap.getCharSet(char) catch continue; + var size: usize = 0; + + for (bm) |row| { + size += @popCount(row); + } + + if (size == 0 and !std.mem.eql(u8, char, " ")) continue; // Skip zero-size characters except space + + try char_infos.append(.{ .char = char, .size = size }); + } + + // Sort characters by size + std.mem.sort(CharInfo, char_infos.items, {}, struct { + fn lessThan(_: void, a: CharInfo, b: CharInfo) bool { + return a.size < b.size; + } + }.lessThan); + + // Create the sorted string + var result = std.ArrayList(u8).init(allocator); + for (char_infos.items) |char_info| { + try result.appendSlice(char_info.char); + } + + return result.toOwnedSlice(); +} + +fn hexToRgb(hex: []const u8) ![3]u8 { + if (hex[0] != '#') return error.InvalidHexString; + if (hex.len != 7) return error.InvalidHexString; + const r = try std.fmt.parseInt(u8, hex[1..3], 16); + const g = try std.fmt.parseInt(u8, hex[3..5], 16); + const b = try std.fmt.parseInt(u8, hex[5..7], 16); + return .{ r, g, b }; +} + +// ----------------------- +// MAIN ENTRYPOINT AND HELPER FUNCTIONS +// ----------------------- + +pub fn main() !void { + var gpa = std.heap.GeneralPurposeAllocator(.{}){}; + const allocator = gpa.allocator(); + var arena = std.heap.ArenaAllocator.init(allocator); + defer arena.deinit(); + const params = comptime clap.parseParamsComptime( \\-h, --help Print this help message and exit \\-v, --version Prints the version and exit @@ -45,7 +117,7 @@ fn parseArgs(allocator: std.mem.Allocator) !core.CoreParams { ); var diag = clap.Diagnostic{}; - var res = clap.parse(clap.Help, ¶ms, clap.parsers.default, .{ + const res = clap.parse(clap.Help, ¶ms, clap.parsers.default, .{ .allocator = allocator, .diagnostic = &diag, }) catch |err| { @@ -136,7 +208,7 @@ fn parseArgs(allocator: std.mem.Allocator) !core.CoreParams { } else break :blk null; }; - return core.CoreParams{ + const args = core.CoreParams{ .input = res.args.input.?, .output_type = output_type, .output = res.args.output, @@ -161,82 +233,6 @@ fn parseArgs(allocator: std.mem.Allocator) !core.CoreParams { .fg_color = fg_color, .bg_color = bg_color, }; -} - -fn getDefaultChars(symbols: ?[]const u8) []const u8 { - const symbol_type = if (symbols) |s| - if (std.mem.eql(u8, s, "ascii")) core.SymbolType.Ascii else core.SymbolType.Block - else - core.SymbolType.Ascii; - return switch (symbol_type) { - .Ascii => default_ascii, - .Block => default_block, - }; -} - -fn sortCharsBySize(allocator: std.mem.Allocator, input: []const u8) ![]const u8 { - const CharInfo = struct { - char: []const u8, - size: usize, - }; - - var char_infos = std.ArrayList(CharInfo).init(allocator); - defer char_infos.deinit(); - - var it = std.unicode.Utf8Iterator{ .bytes = input, .i = 0 }; - while (it.nextCodepoint()) |codepoint| { - const len = std.unicode.utf8CodepointSequenceLength(codepoint) catch continue; - const char_start = it.i - len; - const char = input[char_start..it.i]; - - const bm = bitmap.getCharSet(char) catch continue; - var size: usize = 0; - - for (bm) |row| { - size += @popCount(row); - } - - if (size == 0 and !std.mem.eql(u8, char, " ")) continue; // Skip zero-size characters except space - - try char_infos.append(.{ .char = char, .size = size }); - } - - // Sort characters by size - std.mem.sort(CharInfo, char_infos.items, {}, struct { - fn lessThan(_: void, a: CharInfo, b: CharInfo) bool { - return a.size < b.size; - } - }.lessThan); - - // Create the sorted string - var result = std.ArrayList(u8).init(allocator); - for (char_infos.items) |char_info| { - try result.appendSlice(char_info.char); - } - - return result.toOwnedSlice(); -} - -fn hexToRgb(hex: []const u8) ![3]u8 { - if (hex[0] != '#') return error.InvalidHexString; - if (hex.len != 7) return error.InvalidHexString; - const r = try std.fmt.parseInt(u8, hex[1..3], 16); - const g = try std.fmt.parseInt(u8, hex[3..5], 16); - const b = try std.fmt.parseInt(u8, hex[5..7], 16); - return .{ r, g, b }; -} - -// ----------------------- -// MAIN ENTRYPOINT AND HELPER FUNCTIONS -// ----------------------- - -pub fn main() !void { - var gpa = std.heap.GeneralPurposeAllocator(.{}){}; - const allocator = gpa.allocator(); - var arena = std.heap.ArenaAllocator.init(allocator); - defer arena.deinit(); - - const args = try parseArgs(allocator); if (video.isVideoFile(args.input)) { try video.processVideo(allocator, args);