const std = @import("std"); pub fn build(b: *std.Build) void { const target = b.standardTargetOptions(.{}); const optimize = b.standardOptimizeOption(.{}); const vosk_dep = b.dependency("vosk", .{}); const zlib_dep = b.dependency("zlib", .{ .target = target, .optimize = .ReleaseFast, }); const sdl_dep = b.dependency("SDL", .{ .target = target, .optimize = .ReleaseFast, }); // We need to use curl for this as the domain doesn't work with zig TLS const model_step = ModelDownloadStep.create(b); // Install the model to the output directory const install_model = b.addInstallDirectory(.{ .source_dir = model_step.getOutputPath(), .install_dir = .bin, .install_subdir = "vosk-model-small-en-us-0.15", }); install_model.step.dependOn(&model_step.step); b.getInstallStep().dependOn(&install_model.step); const exe = b.addExecutable(.{ .name = "stt", .root_module = b.createModule(.{ .root_source_file = b.path("src/main.zig"), .target = target, .optimize = optimize, }), }); exe.linkLibC(); exe.addIncludePath(vosk_dep.path("")); exe.addLibraryPath(vosk_dep.path("")); exe.linkLibrary(zlib_dep.artifact("z")); exe.linkLibrary(sdl_dep.artifact("SDL2")); exe.linkSystemLibrary("vosk"); exe.linkSystemLibrary("asound"); b.installArtifact(exe); const run_step = b.step("run", "Run the app"); const run_cmd = b.addRunArtifact(exe); run_step.dependOn(&run_cmd.step); run_cmd.step.dependOn(b.getInstallStep()); if (b.args) |args| { run_cmd.addArgs(args); } } const ModelDownloadStep = struct { step: std.Build.Step, builder: *std.Build, pub fn create(builder: *std.Build) *ModelDownloadStep { const self = builder.allocator.create(ModelDownloadStep) catch @panic("OOM"); self.* = .{ .step = std.Build.Step.init(.{ .id = .custom, .name = "download-model", .owner = builder, .makeFn = make, }), .builder = builder, }; return self; } pub fn getOutputPath(self: *ModelDownloadStep) std.Build.LazyPath { var hasher = std.hash.Wyhash.init(0); hasher.update("https://alphacephei.com/vosk/models/vosk-model-small-en-us-0.15.zip"); const cache_hash = hasher.final(); var cache_dir_buf: [std.fs.max_path_bytes]u8 = undefined; const cache_dir = std.fmt.bufPrint(&cache_dir_buf, "{s}/o/{x}/vosk-model-small-en-us-0.15", .{ self.builder.cache_root.path.?, cache_hash }) catch @panic("path too long"); return .{ .cwd_relative = self.builder.allocator.dupe(u8, cache_dir) catch @panic("OOM") }; } fn make(step: *std.Build.Step, options: std.Build.Step.MakeOptions) anyerror!void { _ = options; const self: *ModelDownloadStep = @fieldParentPtr("step", step); const model_dir = "vosk-model-small-en-us-0.15"; // Create a cache hash based on the URL var hasher = std.hash.Wyhash.init(0); hasher.update("https://alphacephei.com/vosk/models/vosk-model-small-en-us-0.15.zip"); const cache_hash = hasher.final(); var cache_dir_buf: [std.fs.max_path_bytes]u8 = undefined; const cache_dir = std.fmt.bufPrint(&cache_dir_buf, "{s}/o/{x}", .{ self.builder.cache_root.path.?, cache_hash }) catch @panic("path too long"); const cached_model_dir = std.fmt.allocPrint(self.builder.allocator, "{s}/{s}", .{ cache_dir, model_dir }) catch @panic("OOM"); defer self.builder.allocator.free(cached_model_dir); // Check if already cached if (std.fs.cwd().access(cached_model_dir, .{})) |_| { step.result_cached = true; return; } else |_| {} // Not cached, need to download std.fs.cwd().makePath(cache_dir) catch {}; const model_zip = std.fmt.allocPrint(self.builder.allocator, "{s}/model.zip", .{cache_dir}) catch @panic("OOM"); defer self.builder.allocator.free(model_zip); // Download const download_result = std.process.Child.run(.{ .allocator = self.builder.allocator, .argv = &.{ "curl", "-s", "-o", model_zip, "https://alphacephei.com/vosk/models/vosk-model-small-en-us-0.15.zip" }, }) catch return error.DownloadFailed; if (download_result.term.Exited != 0) return error.DownloadFailed; // Extract to cache using stdlib var zip_file = std.fs.cwd().openFile(model_zip, .{}) catch return error.UnzipFailed; defer zip_file.close(); var cache_dir_handle = std.fs.cwd().openDir(cache_dir, .{}) catch return error.UnzipFailed; defer cache_dir_handle.close(); var zip_file_buffer: [4096]u8 = undefined; var zip_file_reader = zip_file.reader(&zip_file_buffer); std.zip.extract(cache_dir_handle, &zip_file_reader, .{}) catch return error.UnzipFailed; step.result_cached = false; } };