264 lines
6.2 KiB
Go
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
|
|
}
|