ea2bcde74b
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)
203 lines
4.6 KiB
Go
203 lines
4.6 KiB
Go
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
|
|
}
|