feat: reimplement WAV extraction in Go (replaces extract_wavs.sh)

extract.go replaces the bash script with idiomatic Go:
- akaiutil process communication via stdin/stdout pipes
- partition enumeration (df output parsing)
- volume enumeration (dir output parsing per partition)
- sample2wavall per volume with WAV count aggregation

runAkaiutil is a package-level var — tests stub it with canned
output to verify parsing without a real akaiutil binary or ISO.

Removed: findScript(), -tool flag on extract, os/exec from main.go
Kept: extract_wavs.sh as a standalone utility (no longer called)
This commit is contained in:
2026-06-21 22:17:24 -07:00
parent bc12130dd6
commit ea2bcde74b
4 changed files with 323 additions and 75 deletions
+202
View File
@@ -0,0 +1,202 @@
package main
import (
"fmt"
"io"
"os"
"os/exec"
"path/filepath"
"regexp"
"strconv"
"strings"
"time"
)
func findAkaiutil() (string, error) {
if env := os.Getenv("AKAIUTIL"); env != "" {
if fi, err := os.Stat(env); err == nil && !fi.IsDir() {
return env, nil
}
}
candidates := []string{
filepath.Join(filepath.Dir(os.Args[0]), "akaiutil"),
filepath.Join(filepath.Dir(os.Args[0]), "third_party", "akaiutil", "akaiutil"),
filepath.Join("third_party", "akaiutil", "akaiutil"),
"akaiutil",
}
for _, p := range candidates {
if fi, err := os.Stat(p); err == nil && !fi.IsDir() {
return p, nil
}
}
return "", fmt.Errorf("akaiutil not found — set AKAIUTIL env var or run: make akaiutil")
}
var runAkaiutil = func(isoPath, commands string) (string, error) {
bin, err := findAkaiutil()
if err != nil {
return "", err
}
cmd := exec.Command(bin, "-r", isoPath)
cmd.Stderr = nil
stdin, err := cmd.StdinPipe()
if err != nil {
return "", fmt.Errorf("stdin pipe: %w", err)
}
stdout, err := cmd.StdoutPipe()
if err != nil {
return "", fmt.Errorf("stdout pipe: %w", err)
}
if err := cmd.Start(); err != nil {
return "", fmt.Errorf("start akaiutil: %w", err)
}
stdin.Write([]byte(commands))
stdin.Close()
output, _ := io.ReadAll(stdout)
cmd.Wait()
return string(output), nil
}
type volInfo struct {
Part string
Name string
}
func listPartitions(isoPath string) ([]string, error) {
out, err := runAkaiutil(isoPath, "df\nq\n")
if err != nil {
return nil, err
}
re := regexp.MustCompile(`^\s+([A-Z])\s+`)
var parts []string
for _, line := range strings.Split(out, "\n") {
if m := re.FindStringSubmatch(line); m != nil {
parts = append(parts, m[1])
}
}
if len(parts) == 0 {
return []string{"A"}, nil
}
return parts, nil
}
func listVolumes(isoPath string, partitions []string) ([]volInfo, error) {
var cmds strings.Builder
for _, p := range partitions {
fmt.Fprintf(&cmds, "cd /disk0/%s\n", p)
fmt.Fprintf(&cmds, "dir\n")
}
cmds.WriteString("q\n")
out, err := runAkaiutil(isoPath, cmds.String())
if err != nil {
return nil, err
}
rePrompt := regexp.MustCompile(`/disk0/([A-Z])\s*>`)
reEntry := regexp.MustCompile(`^\s+\d+\s+(.+?)\s+-\s+`)
var vols []volInfo
currentPart := ""
for _, line := range strings.Split(out, "\n") {
if m := rePrompt.FindStringSubmatch(line); m != nil {
currentPart = m[1]
continue
}
if m := reEntry.FindStringSubmatch(line); m != nil {
name := strings.TrimRight(m[1], " ")
if currentPart != "" && name != "" {
vols = append(vols, volInfo{Part: currentPart, Name: name})
}
}
}
return vols, nil
}
func runExtraction(isoPath, baseDir string, vols []volInfo) (int, error) {
var cmds strings.Builder
for _, v := range vols {
safe := sanitiseISOName(v.Name)
if safe == "" {
safe = "volume"
}
volDir := filepath.Join(baseDir, safe)
os.MkdirAll(volDir, 0755)
nav := strings.ReplaceAll(v.Name, " ", "_")
fmt.Fprintf(&cmds, "lcd %s\n", volDir)
fmt.Fprintf(&cmds, "cd /disk0/%s/%s\n", v.Part, nav)
fmt.Fprintf(&cmds, "sample2wavall\n")
}
fmt.Fprintf(&cmds, "lcd /\nq\n")
out, err := runAkaiutil(isoPath, cmds.String())
if err != nil {
return 0, err
}
re := regexp.MustCompile(`exported\s+(\d+)\s+file`)
total := 0
for _, line := range strings.Split(out, "\n") {
if m := re.FindStringSubmatch(line); m != nil {
n, _ := strconv.Atoi(m[1])
total += n
}
}
return total, nil
}
func extractWAVs(isoPath, outDir string) (int, error) {
parts, err := listPartitions(isoPath)
if err != nil {
return 0, fmt.Errorf("enumerate partitions: %w", err)
}
vols, err := listVolumes(isoPath, parts)
if err != nil {
return 0, fmt.Errorf("enumerate volumes: %w", err)
}
if len(vols) == 0 {
return 0, nil
}
isoBase := sanitiseISOName(filepath.Base(isoPath))
baseDir := filepath.Join(outDir, isoBase)
os.MkdirAll(baseDir, 0755)
return runExtraction(isoPath, baseDir, vols)
}
func extractISO(isoPath, outDir string) (string, error) {
isoOutDir := filepath.Join(outDir, sanitiseISOName(filepath.Base(isoPath)))
if fi, err := os.Stat(isoOutDir); err == nil && fi.IsDir() {
var wavCount int
filepath.WalkDir(isoOutDir, func(p string, d os.DirEntry, err error) error {
if err == nil && !d.IsDir() && strings.HasSuffix(strings.ToLower(d.Name()), ".wav") {
wavCount++
}
return nil
})
if wavCount > 0 {
return fmt.Sprintf("skipped (%d WAVs exist)", wavCount), nil
}
}
start := time.Now()
total, err := extractWAVs(isoPath, outDir)
elapsed := time.Since(start).Round(time.Second)
if err != nil {
return "", err
}
return fmt.Sprintf("Total WAV files: %d (%s)", total, elapsed), nil
}
+117
View File
@@ -0,0 +1,117 @@
package main
import (
"testing"
)
func TestListPartitions(t *testing.T) {
orig := runAkaiutil
defer func() { runAkaiutil = orig }()
runAkaiutil = func(isoPath, commands string) (string, error) {
if commands != "df\nq\n" {
t.Fatalf("unexpected commands: %q", commands)
}
return `
Disk /disk0
Total blocks: 12288 (8 KB each) 98304 KB
Partition table:
A - S1000 HD (3840 blocks)
B - S1000 HD (5120 blocks)
C - S1000 HD (3328 blocks)
`, nil
}
parts, err := listPartitions("/fake/disk.iso")
if err != nil {
t.Fatal(err)
}
if len(parts) != 3 {
t.Fatalf("expected 3 partitions, got %d: %v", len(parts), parts)
}
if parts[0] != "A" || parts[1] != "B" || parts[2] != "C" {
t.Fatalf("unexpected partitions: %v", parts)
}
}
func TestListPartitionsEmpty(t *testing.T) {
orig := runAkaiutil
defer func() { runAkaiutil = orig }()
runAkaiutil = func(isoPath, commands string) (string, error) {
return "No partitions found\n", nil
}
parts, err := listPartitions("/fake/disk.iso")
if err != nil {
t.Fatal(err)
}
if len(parts) != 1 || parts[0] != "A" {
t.Fatalf("expected fallback to [A], got %v", parts)
}
}
func TestListVolumes(t *testing.T) {
orig := runAkaiutil
defer func() { runAkaiutil = orig }()
runAkaiutil = func(isoPath, commands string) (string, error) {
return `
/disk0/A > cd /disk0/A
/disk0/A > dir
1 DRUM KIT 1 - VOLUME (10 files)
2 BASS SAMPLES - VOLUME (5 files)
/disk0/A > cd /disk0/B
/disk0/B > dir
3 SYNTH PADS - VOLUME (20 files)
/disk0/B >
`, nil
}
vols, err := listVolumes("/fake/disk.iso", []string{"A", "B"})
if err != nil {
t.Fatal(err)
}
if len(vols) != 3 {
t.Fatalf("expected 3 volumes, got %d: %v", len(vols), vols)
}
if vols[0].Part != "A" || vols[0].Name != "DRUM KIT 1" {
t.Fatalf("unexpected vol 0: %+v", vols[0])
}
if vols[1].Part != "A" || vols[1].Name != "BASS SAMPLES" {
t.Fatalf("unexpected vol 1: %+v", vols[1])
}
if vols[2].Part != "B" || vols[2].Name != "SYNTH PADS" {
t.Fatalf("unexpected vol 2: %+v", vols[2])
}
}
func TestRunExtractionCount(t *testing.T) {
orig := runAkaiutil
defer func() { runAkaiutil = orig }()
calls := 0
runAkaiutil = func(isoPath, commands string) (string, error) {
calls++
return `
exported 10 file(s)
exported 5 file(s)
exported 0 file(s)
`, nil
}
vols := []volInfo{
{Part: "A", Name: "DRUMS"},
{Part: "A", Name: "BASS"},
}
total, err := runExtraction("/fake/disk.iso", "/tmp/out", vols)
if err != nil {
t.Fatal(err)
}
if total != 15 {
t.Fatalf("expected 15 total WAVs, got %d", total)
}
if calls != 1 {
t.Fatalf("expected 1 akaiutil call, got %d", calls)
}
}
+2 -71
View File
@@ -8,7 +8,6 @@ import (
"net/http"
"net/url"
"os"
"os/exec"
"path/filepath"
"sort"
"strconv"
@@ -103,7 +102,6 @@ Download flags:
Extract flags:
-dir "./isos" directory containing ISO files
-out "./wavs" output directory for WAV files
-tool "./extract_wavs.sh" path to extraction script
-jobs 2 concurrent extractions (default: 2)
Pipeline flags:
@@ -382,15 +380,9 @@ func cmdExtract(args []string) {
fs := flag.NewFlagSet("extract", flag.ExitOnError)
isoDir := fs.String("dir", ".", "directory with ISO files")
outDir := fs.String("out", "./wavs", "output directory for WAVs")
toolFlag := fs.String("tool", "", "path to extraction script (default: auto-detect)")
jobs := fs.Int("jobs", 2, "concurrent extractions")
_ = fs.Parse(args)
toolPath := *toolFlag
if toolPath == "" {
toolPath = findScript("extract_wavs.sh")
}
os.MkdirAll(*outDir, 0755)
entries, err := os.ReadDir(*isoDir)
@@ -414,8 +406,6 @@ func cmdExtract(args []string) {
sem := make(chan struct{}, *jobs)
var wg sync.WaitGroup
var mu sync.Mutex
results := map[string]string{}
for _, iso := range isos {
wg.Add(1)
@@ -425,10 +415,7 @@ func cmdExtract(args []string) {
defer func() { <-sem }()
out := *outDir
result, err := extractISO(toolPath, isoPath, out)
mu.Lock()
results[filepath.Base(isoPath)] = result
mu.Unlock()
result, err := extractISO(isoPath, out)
if err != nil {
fmt.Printf(" FAIL %-40s %v\n", filepath.Base(isoPath), err)
} else {
@@ -439,46 +426,6 @@ func cmdExtract(args []string) {
wg.Wait()
}
func extractISO(toolPath, isoPath, outDir string) (string, error) {
absTool, err := filepath.Abs(toolPath)
if err != nil {
return "", err
}
if _, err := os.Stat(absTool); os.IsNotExist(err) {
return "", fmt.Errorf("script not found: %s", absTool)
}
isoOutDir := filepath.Join(outDir, sanitiseISOName(filepath.Base(isoPath)))
if _, err := os.Stat(isoOutDir); err == nil {
var wavCount int
filepath.WalkDir(isoOutDir, func(p string, d os.DirEntry, err error) error {
if err == nil && !d.IsDir() && strings.HasSuffix(strings.ToLower(d.Name()), ".wav") {
wavCount++
}
return nil
})
if wavCount > 0 {
return fmt.Sprintf("skipped (%d WAVs exist)", wavCount), nil
}
}
cmd := exec.Command("bash", absTool, isoPath, outDir)
cmd.Stderr = nil
output, err := cmd.Output()
if err != nil {
return "", fmt.Errorf("extract: %w", err)
}
lines := strings.Split(string(output), "\n")
for _, line := range lines {
if strings.Contains(line, "Total WAV files:") {
return strings.TrimSpace(line), nil
}
}
return "extracted (check output)", nil
}
func sanitiseISOName(path string) string {
name := path
if ext := filepath.Ext(name); ext != "" {
@@ -520,7 +467,6 @@ func cmdPipeline(args []string) {
isoDir := filepath.Join(*baseDir, "isos")
wavDir := filepath.Join(*baseDir, "wavs")
toolPath := findScript("extract_wavs.sh")
// Remaining args after flags are treated as explicit identifiers
identifiers := fs.Args()
@@ -566,28 +512,13 @@ func cmdPipeline(args []string) {
cmdDownload([]string{"-f", idFile, "-dir", isoDir, "-jobs", fmt.Sprintf("%d", *dlJobs)})
fmt.Println("\n=== Phase 3: Extract ===")
cmdExtract([]string{"-dir", isoDir, "-out", wavDir, "-tool", toolPath, "-jobs", fmt.Sprintf("%d", *extJobs)})
cmdExtract([]string{"-dir", isoDir, "-out", wavDir, "-jobs", fmt.Sprintf("%d", *extJobs)})
fmt.Printf("\n=== Pipeline Complete ===\n")
fmt.Printf(" ISOs: %s\n", isoDir)
fmt.Printf(" WAVs: %s\n", wavDir)
}
func findScript(name string) string {
candidates := []string{
filepath.Join(filepath.Dir(os.Args[0]), name),
filepath.Join(filepath.Dir(os.Args[0]), "scripts", name),
name,
filepath.Join("scripts", name),
}
for _, p := range candidates {
if _, err := os.Stat(p); err == nil {
return p
}
}
return name
}
// ─── Helpers ───────────────────────────────────────────────────────────
func fatal(format string, args ...any) {
+1 -3
View File
@@ -289,8 +289,6 @@ func handleAPIExtract(w http.ResponseWriter, r *http.Request) {
os.MkdirAll(outDir, 0755)
toolPath := findScript("extract_wavs.sh")
entries, err := os.ReadDir(isoDir)
if err != nil {
writeJSON(w, map[string]any{"error": err.Error()})
@@ -311,7 +309,7 @@ func handleAPIExtract(w http.ResponseWriter, r *http.Request) {
var messages []string
for _, isoPath := range isos {
result, err := extractISO(toolPath, isoPath, outDir)
result, err := extractISO(isoPath, outDir)
if err != nil {
messages = append(messages, fmt.Sprintf("FAIL %s: %v", filepath.Base(isoPath), err))
} else {