Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,5 @@ test_output/
# releases to make it less annoying to work with multiple branches.
zig-cache/
release_assets/

raddbg*
45 changes: 41 additions & 4 deletions build.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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", .{});

Expand All @@ -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,
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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 });
Expand All @@ -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,
Expand Down
154 changes: 75 additions & 79 deletions src/main.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -45,7 +117,7 @@ fn parseArgs(allocator: std.mem.Allocator) !core.CoreParams {
);

var diag = clap.Diagnostic{};
var res = clap.parse(clap.Help, &params, clap.parsers.default, .{
const res = clap.parse(clap.Help, &params, clap.parsers.default, .{
.allocator = allocator,
.diagnostic = &diag,
}) catch |err| {
Expand Down Expand Up @@ -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,
Expand All @@ -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);
Expand Down
Loading