Files

264 lines
6.2 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)
}
if _, err := stdin.Write([]byte(commands)); err != nil {
return "", fmt.Errorf("write stdin: %w", err)
}
if err := stdin.Close(); err != nil {
return "", fmt.Errorf("close stdin: %w", err)
}
output, _ := io.ReadAll(stdout)
if err := cmd.Wait(); err != nil {
return "", fmt.Errorf("wait akaiutil: %w", err)
}
return string(output), nil
}
type volInfo struct {
Part string `json:"part"`
Name string `json:"name"`
}
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+`)
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+`)
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)
if err := os.MkdirAll(volDir, 0755); err != nil {
return 0, fmt.Errorf("mkdir %s: %w", volDir, err)
}
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)
if err := os.MkdirAll(baseDir, 0755); err != nil {
return 0, fmt.Errorf("mkdir %s: %w", baseDir, err)
}
return runExtraction(isoPath, baseDir, vols)
}
var extractISO = func(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
}
type TagInfo struct {
Index int `json:"index"`
Name string `json:"name"`
}
func listTags(isoPath, part, volName string) ([]TagInfo, error) {
var cmds strings.Builder
fmt.Fprintf(&cmds, "df\n")
fmt.Fprintf(&cmds, "cd /disk0/%s\n", part)
nav := strings.ReplaceAll(volName, " ", "_")
fmt.Fprintf(&cmds, "cd /disk0/%s/%s\n", part, nav)
fmt.Fprintf(&cmds, "lstags\n")
cmds.WriteString("q\n")
out, err := runAkaiutil(isoPath, cmds.String())
if err != nil {
return nil, fmt.Errorf("list tags: %w", err)
}
return parseTags(out), nil
}
func parseTags(output string) []TagInfo {
reTag := regexp.MustCompile(`^\s*(\d+)\s+(\S.*)`)
tags := []TagInfo{}
inTags := false
for _, line := range strings.Split(output, "\n") {
trimmed := strings.TrimRight(line, " \r")
if strings.Contains(trimmed, "tag name") {
inTags = true
continue
}
if !inTags {
continue
}
if strings.HasPrefix(trimmed, "---") && len(tags) > 0 {
break
}
if m := reTag.FindStringSubmatch(trimmed); m != nil {
idx, _ := strconv.Atoi(m[1])
name := strings.TrimSpace(m[2])
if name != "" {
tags = append(tags, TagInfo{Index: idx, Name: name})
}
}
}
return tags
}