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:
+202
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user