remove swift-argument-parser - it was adding well over 1Mb of bloat to release builds
This commit is contained in:
+109
-48
@@ -1,63 +1,100 @@
|
||||
import ArgumentParser
|
||||
import CoreAudio
|
||||
import Foundation
|
||||
|
||||
struct AudioTee: ParsableCommand {
|
||||
static let configuration = CommandConfiguration(
|
||||
abstract: "Capture system audio and stream to stdout",
|
||||
discussion: """
|
||||
AudioTee captures system audio using Core Audio taps and streams it as structured output.
|
||||
|
||||
Output formats:
|
||||
• json: Base64-encoded audio in JSON messages (safe for terminals)
|
||||
• binary: Raw binary audio with JSON metadata headers (efficient for pipes)
|
||||
• auto: Automatically choose based on whether stdout is a terminal (default)
|
||||
|
||||
Process filtering:
|
||||
• include-processes: Only tap specified process IDs (empty = all processes)
|
||||
• exclude-processes: Tap all processes except specified ones
|
||||
• mute: How to handle processes being tapped
|
||||
|
||||
Examples:
|
||||
audiotee # Auto format, tap all processes
|
||||
audiotee --format=json # Always use JSON format
|
||||
audiotee --format=binary # Always use binary format
|
||||
audiotee --sample-rate=16000 # Convert to 16kHz mono for ASR
|
||||
audiotee --sample-rate=8000 # Convert to 8kHz for telephony
|
||||
audiotee --include-processes 1234 # Only tap process 1234
|
||||
audiotee --include-processes 1234 5678 9012 # Tap only these processes
|
||||
audiotee --exclude-processes 1234 5678 # Tap everything except these
|
||||
audiotee --mute # Mute processes being tapped
|
||||
"""
|
||||
)
|
||||
|
||||
@Option(name: .shortAndLong, help: "Output format")
|
||||
struct AudioTee {
|
||||
var format: OutputFormat = .auto
|
||||
|
||||
@Option(
|
||||
name: .long, help: "Process IDs to include (space-separated, empty = all processes)")
|
||||
var includeProcesses: [Int32] = []
|
||||
|
||||
@Option(
|
||||
name: .long, help: "Process IDs to exclude (space-separated)")
|
||||
var excludeProcesses: [Int32] = []
|
||||
|
||||
@Flag(name: .long, help: "Mute processes being tapped")
|
||||
var mute: Bool = false
|
||||
|
||||
@Option(
|
||||
name: .long,
|
||||
help: "Target sample rate (8000, 16000, 22050, 24000, 32000, 44100, 48000)")
|
||||
var sampleRate: Double?
|
||||
|
||||
@Option(
|
||||
name: .long,
|
||||
help: "Audio chunk duration in seconds (default: 0.2)")
|
||||
var chunkDuration: Double = 0.2
|
||||
|
||||
init() {}
|
||||
|
||||
static func main() {
|
||||
let parser = SimpleArgumentParser(
|
||||
programName: "audiotee",
|
||||
abstract: "Capture system audio and stream to stdout",
|
||||
discussion: """
|
||||
AudioTee captures system audio using Core Audio taps and streams it as structured output.
|
||||
|
||||
Output formats:
|
||||
• json: Base64-encoded audio in JSON messages (safe for terminals)
|
||||
• binary: Raw binary audio with JSON metadata headers (efficient for pipes)
|
||||
• auto: Automatically choose based on whether stdout is a terminal (default)
|
||||
|
||||
Process filtering:
|
||||
• include-processes: Only tap specified process IDs (empty = all processes)
|
||||
• exclude-processes: Tap all processes except specified ones
|
||||
• mute: How to handle processes being tapped
|
||||
|
||||
Examples:
|
||||
audiotee # Auto format, tap all processes
|
||||
audiotee --format=json # Always use JSON format
|
||||
audiotee --format=binary # Always use binary format
|
||||
audiotee --sample-rate=16000 # Convert to 16kHz mono for ASR
|
||||
audiotee --sample-rate=8000 # Convert to 8kHz for telephony
|
||||
audiotee --include-processes 1234 # Only tap process 1234
|
||||
audiotee --include-processes 1234 5678 9012 # Tap only these processes
|
||||
audiotee --exclude-processes 1234 5678 # Tap everything except these
|
||||
audiotee --mute # Mute processes being tapped
|
||||
"""
|
||||
)
|
||||
|
||||
// Configure arguments
|
||||
parser.addOption(name: "format", shortName: "f", help: "Output format", defaultValue: "auto")
|
||||
parser.addArrayOption(
|
||||
name: "include-processes",
|
||||
help: "Process IDs to include (space-separated, empty = all processes)")
|
||||
parser.addArrayOption(
|
||||
name: "exclude-processes", help: "Process IDs to exclude (space-separated)")
|
||||
parser.addFlag(name: "mute", help: "Mute processes being tapped")
|
||||
parser.addOption(
|
||||
name: "sample-rate",
|
||||
help: "Target sample rate (8000, 16000, 22050, 24000, 32000, 44100, 48000)")
|
||||
parser.addOption(
|
||||
name: "chunk-duration", help: "Audio chunk duration in seconds", defaultValue: "0.2")
|
||||
|
||||
// Parse arguments
|
||||
do {
|
||||
try parser.parse()
|
||||
|
||||
var audioTee = AudioTee()
|
||||
|
||||
// Extract values
|
||||
audioTee.format = try parser.getValue("format", as: OutputFormat.self)
|
||||
audioTee.includeProcesses = try parser.getArrayValue("include-processes", as: Int32.self)
|
||||
audioTee.excludeProcesses = try parser.getArrayValue("exclude-processes", as: Int32.self)
|
||||
audioTee.mute = parser.getFlag("mute")
|
||||
audioTee.sampleRate = try parser.getOptionalValue("sample-rate", as: Double.self)
|
||||
audioTee.chunkDuration = try parser.getValue("chunk-duration", as: Double.self)
|
||||
|
||||
// Validate
|
||||
try audioTee.validate()
|
||||
|
||||
// Run
|
||||
try audioTee.run()
|
||||
|
||||
} catch ArgumentParserError.helpRequested {
|
||||
parser.printHelp()
|
||||
exit(0)
|
||||
} catch ArgumentParserError.validationFailed(let message) {
|
||||
print("Error: \(message)", to: &standardError)
|
||||
exit(1)
|
||||
} catch let error as ArgumentParserError {
|
||||
print("Error: \(error.description)", to: &standardError)
|
||||
parser.printHelp()
|
||||
exit(1)
|
||||
} catch {
|
||||
print("Error: \(error)", to: &standardError)
|
||||
exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func validate() throws {
|
||||
if !includeProcesses.isEmpty && !excludeProcesses.isEmpty {
|
||||
throw ValidationError("Cannot specify both --include-processes and --exclude-processes")
|
||||
throw ArgumentParserError.validationFailed(
|
||||
"Cannot specify both --include-processes and --exclude-processes")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -159,3 +196,27 @@ struct AudioTee: ParsableCommand {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Helper for stderr output
|
||||
var standardError = FileHandle.standardError
|
||||
|
||||
extension FileHandle: @retroactive TextOutputStream {
|
||||
public func write(_ string: String) {
|
||||
let data = Data(string.utf8)
|
||||
self.write(data)
|
||||
}
|
||||
}
|
||||
|
||||
// Exit code handling
|
||||
enum ExitCode: Error {
|
||||
case failure
|
||||
}
|
||||
|
||||
extension ExitCode {
|
||||
var code: Int32 {
|
||||
switch self {
|
||||
case .failure:
|
||||
return 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user