mirror of
https://github.com/FluidInference/FluidAudio.git
synced 2026-06-11 20:24:36 +00:00
## Summary Adds comprehensive Japanese ASR support to FluidAudio with benchmark datasets and CLI commands. ## Changes ### Core Japanese ASR Support - **CtcJaManager.swift** - Japanese CTC transcription manager (actor-based) - **CtcJaModels.swift** - Japanese model loading and management - **ModelNames.swift** - Added Japanese model registry (`parakeetCtcJa`, `CTCJa` enum) - **AsrModels.swift** - Added `.ctcJa` model version (3,072 vocab, 1,024 hidden, blank_id=3072) - **AsrManager.swift** - Added `.ctcJa` case with error directing to `CtcJaManager` ### CLI Commands - **JapaneseAsrBenchmark.swift** (459 lines) - New `ja-benchmark` command - JSUT basic5000 dataset support - Mozilla Common Voice (MCV) test set support - Auto-download capability - CER (Character Error Rate) evaluation - **DownloadCommand.swift** - Added JSUT and MCV Japanese dataset downloads - **TranscribeCommand.swift** - Added `.ctcJa` model version support - **AsrBenchmark.swift** - Added `.ctcJa` switch case ### Dataset Support - **JapaneseDatasetDownloader.swift** (387 lines) - Dataset download and parsing - JSUT basic5000 (5,000 sentences, clean studio recordings) - Mozilla Common Voice Japanese test split - Efficient streaming downloads - Metadata extraction and validation ## Usage ### CLI Commands ```bash # Benchmark on JSUT basic5000 (100 samples) swift run fluidaudiocli ja-benchmark --dataset jsut --samples 100 # Benchmark on Common Voice test (500 samples, auto-download) swift run fluidaudiocli ja-benchmark --dataset cv-test --samples 500 --auto-download # Download datasets swift run fluidaudiocli download --dataset jsut swift run fluidaudiocli download --dataset cv-ja-test ``` ### Swift API ```swift // Load and use Japanese CTC transcription let manager = try await CtcJaManager.load() let text = try manager.transcribe(audioURL: japaneseAudioFile) ``` ## Model Info - **Repo**: `FluidInference/parakeet-ctc-0.6b-ja-coreml` - **Architecture**: 600M parameter CTC-only - **Vocabulary**: 3,072 Japanese SentencePiece tokens + 1 blank (id: 3072) - **Encoder**: 1,024 hidden size - **Expected CER**: 6.5% on JSUT basic5000, 13.3% on MCV 16.1 test ## Testing - ✅ Builds successfully (`swift build`) - ✅ Model loading integration tested - ✅ CLI commands compile and link correctly - ⏳ Runtime benchmark testing pending (requires model download) ## Related - Mobius PR #39: Japanese CTC CoreML conversion (https://github.com/FluidInference/mobius/pull/39) 🤖 Generated with Claude Code <!-- devin-review-badge-begin --> --- <a href="https://app.devin.ai/review/fluidinference/fluidaudio/pull/478" target="_blank"> <picture> <source media="(prefers-color-scheme: dark)" srcset="https://static.devin.ai/assets/gh-open-in-devin-review-dark.svg?v=1"> <img src="https://static.devin.ai/assets/gh-open-in-devin-review-light.svg?v=1" alt="Open with Devin"> </picture> </a> <!-- devin-review-badge-end --> ---------
194 lines
7.9 KiB
Swift
194 lines
7.9 KiB
Swift
#if os(macOS)
|
|
import AVFoundation
|
|
import FluidAudio
|
|
import Foundation
|
|
import MachTaskSelfWrapper
|
|
|
|
// Using @main instead of main.swift for Swift 6 compatibility.
|
|
// This provides an explicit async context and clear isolation semantics.
|
|
@main
|
|
struct FluidAudioCLI {
|
|
static let cliLogger = AppLogger(category: "Main")
|
|
|
|
static func main() async {
|
|
let arguments = CommandLine.arguments
|
|
|
|
guard arguments.count > 1 else {
|
|
printUsage()
|
|
exitWithPeakMemory(1)
|
|
}
|
|
|
|
// Log system information once at application startup
|
|
await SystemInfo.logOnce(using: cliLogger)
|
|
|
|
let command = arguments[1]
|
|
|
|
defer {
|
|
logPeakMemoryUsage()
|
|
}
|
|
|
|
switch command {
|
|
case "vad-benchmark":
|
|
await VadBenchmark.runVadBenchmark(arguments: Array(arguments.dropFirst(2)))
|
|
case "vad-analyze":
|
|
await VadAnalyzeCommand.run(arguments: Array(arguments.dropFirst(2)))
|
|
case "asr-benchmark":
|
|
await ASRBenchmark.runASRBenchmark(arguments: Array(arguments.dropFirst(2)))
|
|
case "fleurs-benchmark":
|
|
await FLEURSBenchmark.runCLI(arguments: Array(arguments.dropFirst(2)))
|
|
case "transcribe":
|
|
await TranscribeCommand.run(arguments: Array(arguments.dropFirst(2)))
|
|
case "multi-stream":
|
|
await MultiStreamCommand.run(arguments: Array(arguments.dropFirst(2)))
|
|
case "tts":
|
|
await TTS.run(arguments: Array(arguments.dropFirst(2)))
|
|
case "diarization-benchmark":
|
|
await StreamDiarizationBenchmark.run(arguments: Array(arguments.dropFirst(2)))
|
|
case "process":
|
|
await ProcessCommand.run(arguments: Array(arguments.dropFirst(2)))
|
|
case "download":
|
|
await DownloadCommand.run(arguments: Array(arguments.dropFirst(2)))
|
|
case "parakeet-eou":
|
|
await ParakeetEouCommand.main(Array(arguments.dropFirst(2)))
|
|
case "ctc-earnings-benchmark":
|
|
await CtcEarningsBenchmark.runCLI(arguments: Array(arguments.dropFirst(2)))
|
|
case "sortformer":
|
|
await SortformerCommand.run(arguments: Array(arguments.dropFirst(2)))
|
|
case "sortformer-benchmark":
|
|
await SortformerBenchmark.run(arguments: Array(arguments.dropFirst(2)))
|
|
case "lseend":
|
|
await LSEENDCommand.run(arguments: Array(arguments.dropFirst(2)))
|
|
case "lseend-benchmark":
|
|
await LSEENDBenchmark.run(arguments: Array(arguments.dropFirst(2)))
|
|
case "qwen3-benchmark":
|
|
await Qwen3AsrBenchmark.runCLI(arguments: Array(arguments.dropFirst(2)))
|
|
case "qwen3-transcribe":
|
|
await Qwen3TranscribeCommand.run(arguments: Array(arguments.dropFirst(2)))
|
|
case "g2p-benchmark":
|
|
await G2PBenchmark.run(arguments: Array(arguments.dropFirst(2)))
|
|
case "nemotron-benchmark":
|
|
await NemotronBenchmark.run(arguments: Array(arguments.dropFirst(2)))
|
|
case "nemotron-transcribe":
|
|
await NemotronTranscribe.run(arguments: Array(arguments.dropFirst(2)))
|
|
case "ctc-zh-cn-transcribe":
|
|
await CtcZhCnTranscribeCommand.run(arguments: Array(arguments.dropFirst(2)))
|
|
case "ctc-zh-cn-benchmark":
|
|
await CtcZhCnBenchmark.run(arguments: Array(arguments.dropFirst(2)))
|
|
case "ja-benchmark":
|
|
await JapaneseAsrBenchmark.run(arguments: Array(arguments.dropFirst(2)))
|
|
case "help", "--help", "-h":
|
|
printUsage()
|
|
default:
|
|
cliLogger.error("Unknown command: \(command)")
|
|
printUsage()
|
|
exit(1)
|
|
}
|
|
}
|
|
|
|
static func printUsage() {
|
|
cliLogger.info(
|
|
"""
|
|
FluidAudio CLI
|
|
|
|
Usage: fluidaudio <command> [options]
|
|
|
|
Commands:
|
|
process Process a single audio file for diarization
|
|
diarization-benchmark Run diarization benchmark
|
|
vad-benchmark Run VAD-specific benchmark
|
|
vad-analyze Inspect VAD segmentation and streaming events
|
|
asr-benchmark Run ASR benchmark on LibriSpeech
|
|
fleurs-benchmark Run multilingual ASR benchmark on FLEURS dataset
|
|
transcribe Transcribe audio file using streaming ASR
|
|
multi-stream Transcribe multiple audio files in parallel
|
|
tts Synthesize speech from text using Kokoro TTS
|
|
parakeet-eou Run Parakeet EOU Streaming ASR on a single file
|
|
ctc-earnings-benchmark Run CTC keyword spotting benchmark on Earnings22
|
|
sortformer Run Sortformer streaming diarization
|
|
sortformer-benchmark Run Sortformer benchmark on AMI dataset
|
|
lseend Run LS-EEND diarization on a single file
|
|
lseend-benchmark Run LS-EEND benchmark on AMI dataset
|
|
qwen3-benchmark Run Qwen3 ASR benchmark
|
|
qwen3-transcribe Transcribe using Qwen3 ASR
|
|
g2p-benchmark Run multilingual G2P benchmark
|
|
nemotron-benchmark Run Nemotron 0.6B streaming ASR benchmark
|
|
nemotron-transcribe Transcribe custom audio files with Nemotron
|
|
ctc-zh-cn-transcribe Transcribe Mandarin Chinese audio with Parakeet CTC
|
|
ctc-zh-cn-benchmark Run CTC zh-CN benchmark on THCHS-30 dataset
|
|
ja-benchmark Run Japanese ASR benchmark on JSUT/Common Voice
|
|
download Download evaluation datasets
|
|
help Show this help message
|
|
|
|
Run 'fluidaudio <command> --help' for command-specific options.
|
|
|
|
Examples:
|
|
fluidaudio process audio.wav --output results.json
|
|
|
|
fluidaudio diarization-benchmark --single-file ES2004a
|
|
|
|
fluidaudio asr-benchmark --subset test-clean --max-files 100
|
|
|
|
fluidaudio fleurs-benchmark --languages en_us,fr_fr --samples 10
|
|
|
|
fluidaudio transcribe audio.wav --low-latency
|
|
|
|
fluidaudio multi-stream audio1.wav audio2.wav
|
|
|
|
fluidaudio tts "Hello world" --output hello.wav
|
|
|
|
fluidaudio vad-analyze audio.wav --streaming
|
|
|
|
fluidaudio download --dataset ami-sdm
|
|
|
|
fluidaudio ja-benchmark --dataset jsut --samples 100
|
|
|
|
fluidaudio ja-benchmark --dataset cv-test --samples 500 --auto-download
|
|
"""
|
|
)
|
|
}
|
|
|
|
static func fetchPeakMemoryUsageBytes() -> UInt64? {
|
|
var info = task_vm_info_data_t()
|
|
var count =
|
|
mach_msg_type_number_t(MemoryLayout<task_vm_info_data_t>.size)
|
|
/ mach_msg_type_number_t(MemoryLayout<natural_t>.size)
|
|
|
|
let result = withUnsafeMutablePointer(to: &info) {
|
|
$0.withMemoryRebound(to: integer_t.self, capacity: Int(count)) {
|
|
task_info(
|
|
get_current_task_port(),
|
|
task_flavor_t(TASK_VM_INFO),
|
|
$0,
|
|
&count)
|
|
}
|
|
}
|
|
|
|
guard result == KERN_SUCCESS else {
|
|
return nil
|
|
}
|
|
|
|
return info.resident_size_peak
|
|
}
|
|
|
|
static func logPeakMemoryUsage() {
|
|
guard let peakBytes = fetchPeakMemoryUsageBytes() else {
|
|
cliLogger.error("Unable to determine peak memory usage")
|
|
return
|
|
}
|
|
|
|
let peakGigabytes = Double(peakBytes) / 1024.0 / 1024.0 / 1024.0
|
|
let formatted = String(format: "%.3f", peakGigabytes)
|
|
cliLogger.info(
|
|
"Peak memory usage (process-wide): \(formatted) GB"
|
|
)
|
|
}
|
|
|
|
static func exitWithPeakMemory(_ code: Int32) -> Never {
|
|
logPeakMemoryUsage()
|
|
exit(code)
|
|
}
|
|
}
|
|
#else
|
|
#error("FluidAudioCLI is only supported on macOS")
|
|
#endif
|