136 lines
4.8 KiB
Zig
136 lines
4.8 KiB
Zig
const std = @import("std");
|
|
const fs = std.fs;
|
|
const mem = std.mem;
|
|
const TrackInfo = @import("trackinfo.zig");
|
|
|
|
const TrackMap = std.StringHashMap(TrackInfo);
|
|
|
|
fn parse_musicdef(allocator: mem.Allocator, tracks: *TrackMap, file: []const u8) !void {
|
|
var lumps = mem.tokenizeSequence(u8, file, "Lump ");
|
|
|
|
while (lumps.next()) |lump| {
|
|
// Parse lump and add it to the tracklist
|
|
const info = try TrackInfo.parseLump(allocator, lump);
|
|
try tracks.put(try allocator.dupe(u8, info.id), info);
|
|
}
|
|
}
|
|
|
|
const forbidden_chars = "<>:\"/\\|?*!";
|
|
fn slugify(allocator: mem.Allocator, name: ?[]const u8, fallback: []const u8) ![]const u8 {
|
|
const notNullName = name orelse fallback;
|
|
|
|
// Make copy of name
|
|
var copiedName = try allocator.alloc(u8, notNullName.len);
|
|
|
|
for (0..copiedName.len) |i| {
|
|
if (std.mem.indexOfScalar(u8, forbidden_chars, notNullName[i])) |_| {
|
|
copiedName[i] = '-';
|
|
} else {
|
|
copiedName[i] = notNullName[i];
|
|
}
|
|
}
|
|
return copiedName;
|
|
}
|
|
|
|
pub fn main() !void {
|
|
// Get allocator
|
|
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
|
const allocator = gpa.allocator();
|
|
defer _ = gpa.deinit();
|
|
|
|
// Get args
|
|
const args = try std.process.argsAlloc(allocator);
|
|
defer std.process.argsFree(allocator, args);
|
|
|
|
if (args.len < 2) {
|
|
std.debug.print("Usage: {s} <path to extracted pk3>\n", .{args[0]});
|
|
std.process.exit(1);
|
|
}
|
|
|
|
// Extract folder from argv
|
|
const folder = args[1];
|
|
|
|
// Convert folder to absolute
|
|
const dir = try fs.cwd().openDir(folder, .{
|
|
.iterate = true,
|
|
});
|
|
|
|
// Prepare hashmap for storing song locations
|
|
var trackLocations = std.StringHashMap([]const u8).init(allocator);
|
|
defer trackLocations.deinit();
|
|
|
|
var trackInfos = TrackMap.init(allocator);
|
|
defer trackInfos.deinit();
|
|
|
|
var arenaAllocator = std.heap.ArenaAllocator.init(allocator);
|
|
defer arenaAllocator.deinit();
|
|
const keyAllocator = arenaAllocator.allocator();
|
|
|
|
// Iter through every file inside the directory (recursive)
|
|
var iter = try dir.walk(allocator);
|
|
defer iter.deinit();
|
|
while (try iter.next()) |entry| {
|
|
// Skip non-files
|
|
if (entry.kind != .file) {
|
|
continue;
|
|
}
|
|
|
|
// Remove extension
|
|
const extIndex = mem.indexOf(u8, entry.basename, ".");
|
|
const filename = if (extIndex) |index| entry.basename[0..index] else entry.basename;
|
|
|
|
// Check if it's a music definition file (and parse it if so)
|
|
if (mem.eql(u8, filename, "MUSICDEF")) {
|
|
const filedata = try dir.readFileAlloc(allocator, entry.path, 1048576);
|
|
defer allocator.free(filedata);
|
|
try parse_musicdef(keyAllocator, &trackInfos, filedata);
|
|
} else {
|
|
// Save file to hashmap of resolved files
|
|
try trackLocations.put(try keyAllocator.dupe(u8, filename), try keyAllocator.dupe(u8, entry.path));
|
|
}
|
|
}
|
|
|
|
// Make target dirs
|
|
const outputDir = try fs.cwd().makeOpenPath("target", .{});
|
|
const originalsDir = try outputDir.makeOpenPath("original", .{});
|
|
const othersDir = try outputDir.makeOpenPath("others", .{});
|
|
|
|
// Print detected songs
|
|
var tracks = trackInfos.valueIterator();
|
|
while (tracks.next()) |trackInfo| {
|
|
std.debug.print("- [{s}] {?s} ({?s}, {?s})\n", .{
|
|
trackInfo.id,
|
|
trackInfo.title,
|
|
trackInfo.author,
|
|
trackInfo.source,
|
|
});
|
|
// Extract all the qualifying IDs
|
|
var ids = mem.splitScalar(u8, trackInfo.id, ',');
|
|
while (ids.next()) |id| {
|
|
const actualID = mem.trimLeft(u8, id, "\\");
|
|
|
|
// Map to track location
|
|
var soundIDBuffer: [10]u8 = undefined;
|
|
const soundID = try std.fmt.bufPrint(&soundIDBuffer, "O_{s}", .{actualID});
|
|
const location = trackLocations.get(soundID) orelse {
|
|
std.debug.panic("{s} has no track assigned", .{soundID});
|
|
};
|
|
|
|
// Copy file over, dir depending on originality (I-I mean, I'm not trying to be mean)
|
|
const sourceDirName = try slugify(allocator, trackInfo.source, "unknown");
|
|
defer allocator.free(sourceDirName);
|
|
const targetDir = if (trackInfo.is_original) originalsDir else try othersDir.makeOpenPath(sourceDirName, .{});
|
|
|
|
const author = try slugify(allocator, trackInfo.author, "unknown");
|
|
defer allocator.free(author);
|
|
const title = try slugify(allocator, trackInfo.title, trackInfo.id);
|
|
defer allocator.free(title);
|
|
|
|
const filename = try std.fmt.allocPrint(allocator, "{s} - {s}.ogg", .{ author, title });
|
|
defer allocator.free(filename);
|
|
|
|
try dir.copyFile(location, targetDir, filename, .{});
|
|
}
|
|
}
|
|
}
|