Aeneas/SAB¶
Dido¶
Dido is a replacement for aeneas written in Go that doesn't suffer from all the idiosyncrasies and dependencies of the python version.
Drop-in replacement of aeneas doesn't work reliably, so we have to do a 2-part workflow for languages like Thai:
- Create the app in SAB (Ubuntu) and synchronize then just cancel the job (cause it doesn't support Thai).
- Copy the resultant phrase files to your OneDrive audio folder into a subfolder called
timings. - In Windows, cd to the audio dir, and run the Powershell script below:
powershell -ExecutionPolicy Bypass -File .\make_batch.ps1. - Run dido:
"H:\SGC_git\dido\dido.exe" --batch "batch.json"
Powershell script: make_batch.ps1
param(
[string]$AudioFolder = ".",
[string]$TextSubfolder = "timings",
[string]$OutputSubfolder = "timings",
[string]$BatchFilename = "batch.json",
[string]$Language = "tha",
[string]$TextType = "parsed",
[string]$OutputFormat = "tsv",
[string]$BoundaryAlgorithm = "percent",
[int]$BoundaryPercent = 50,
# For your files:
# Audio: "01 บทที่1 ..." -> use First
# Text: "C01-01-B001-15-aeneas.txt" -> use Last
[ValidateSet("First", "Last")]
[string]$AudioNumberPosition = "First",
[ValidateSet("First", "Last")]
[string]$TextNumberPosition = "Last"
)
$ErrorActionPreference = "Stop"
function Get-SequenceNumber {
param(
[string]$FileName,
[string]$NumberPosition
)
$matches = [regex]::Matches($FileName, '\d+')
if ($matches.Count -eq 0) {
return $null
}
if ($NumberPosition -eq "First") {
return [int]$matches[0].Value
}
return [int]$matches[$matches.Count - 1].Value
}
function Get-RelativePathForJson {
param(
[string]$BasePath,
[string]$FullPath,
[switch]$MustExist
)
$baseResolved = (Resolve-Path -LiteralPath $BasePath).Path
if ($MustExist) {
$fullResolved = (Resolve-Path -LiteralPath $FullPath).Path
}
else {
$fullResolved = [System.IO.Path]::GetFullPath($FullPath)
}
$baseUri = [System.Uri]($baseResolved.TrimEnd('\') + '\')
$fullUri = [System.Uri]$fullResolved
$relative = $baseUri.MakeRelativeUri($fullUri).ToString()
# Forward slashes are safe in Windows JSON paths and avoid escaping backslashes.
return [System.Uri]::UnescapeDataString($relative)
}
$AudioFolder = (Resolve-Path -LiteralPath $AudioFolder).Path
$TextFolder = Join-Path $AudioFolder $TextSubfolder
$OutputFolder = Join-Path $AudioFolder $OutputSubfolder
$BatchPath = Join-Path $AudioFolder $BatchFilename
if (!(Test-Path -LiteralPath $TextFolder)) {
throw "Could not find text folder: $TextFolder"
}
if (!(Test-Path -LiteralPath $OutputFolder)) {
New-Item -ItemType Directory -Path $OutputFolder | Out-Null
}
$parameters = "task_language=$Language|is_text_type=$TextType|os_task_file_format=$OutputFormat|task_adjust_boundary_algorithm=$BoundaryAlgorithm|task_adjust_boundary_percent_value=$BoundaryPercent"
# Index timing text files by sequence number.
$textFilesByNumber = @{}
Get-ChildItem -LiteralPath $TextFolder -Filter "*.txt" -File | ForEach-Object {
$num = Get-SequenceNumber -FileName $_.Name -NumberPosition $TextNumberPosition
if ($null -eq $num) {
Write-Warning "Ignoring text file with no sequence number: $($_.Name)"
return
}
if ($textFilesByNumber.ContainsKey($num)) {
Write-Warning "Duplicate text file number $num found. Keeping first: $($textFilesByNumber[$num].Name); ignoring: $($_.Name)"
return
}
$textFilesByNumber[$num] = $_
}
$mp3Files = Get-ChildItem -LiteralPath $AudioFolder -Filter "*.mp3" -File | Sort-Object Name
if ($mp3Files.Count -eq 0) {
throw "No MP3 files found in $AudioFolder"
}
$audioFilesByNumber = @{}
foreach ($mp3 in $mp3Files) {
$num = Get-SequenceNumber -FileName $mp3.Name -NumberPosition $AudioNumberPosition
if ($null -eq $num) {
Write-Warning "Skipping audio file with no sequence number: $($mp3.Name)"
continue
}
if ($audioFilesByNumber.ContainsKey($num)) {
Write-Warning "Duplicate audio file number $num found. Keeping first: $($audioFilesByNumber[$num].Name); ignoring: $($mp3.Name)"
continue
}
$audioFilesByNumber[$num] = $mp3
}
$tasks = @()
foreach ($num in ($audioFilesByNumber.Keys | Sort-Object)) {
$mp3 = $audioFilesByNumber[$num]
if (!$textFilesByNumber.ContainsKey($num)) {
Write-Warning "Skipping $($mp3.Name): no matching text file found for sequence number $num"
continue
}
$textFile = $textFilesByNumber[$num]
# $audioStem = [System.IO.Path]::GetFileNameWithoutExtension($mp3.Name)
# $outputFile = Join-Path $OutputFolder "$audioStem-timing.txt"
$textStem = [System.IO.Path]::GetFileNameWithoutExtension($textFile.Name)
# If the phrase file is named like C01-01-B001-01-aeneas.txt,
# the timing file becomes C01-01-B001-01-timing.tsv.
if ($textStem.EndsWith("-aeneas")) {
$textStem = $textStem.Substring(0, $textStem.Length - "-aeneas".Length)
}
$outputFile = Join-Path $OutputFolder "$textStem-timing.txt"
$description = ("B001 {0:D2}" -f $num)
$task = [ordered]@{
description = $description
audioFilename = Get-RelativePathForJson -BasePath $AudioFolder -FullPath $mp3.FullName -MustExist
phraseFilename = Get-RelativePathForJson -BasePath $AudioFolder -FullPath $textFile.FullName -MustExist
outputFilename = Get-RelativePathForJson -BasePath $AudioFolder -FullPath $outputFile
parameters = $parameters
}
$tasks += [pscustomobject]$task
Write-Host "Matched $description"
Write-Host " Audio: $($mp3.Name)"
Write-Host " Text: $($textFile.Name)"
Write-Host " Out: $([System.IO.Path]::GetFileName($outputFile))"
}
if ($tasks.Count -eq 0) {
throw "No matching audio/text pairs were found."
}
$json = $tasks | ConvertTo-Json -Depth 10
# UTF-8 without BOM, safer for Thai filenames.
[System.IO.File]::WriteAllText($BatchPath, $json, [System.Text.UTF8Encoding]::new($false))
Write-Host ""
Write-Host "Created batch file:"
Write-Host " $BatchPath"
Write-Host ""
Write-Host "Matched task count: $($tasks.Count)"
Write-Host ""
Espeak-ng¶
In order to get the latest version of espeak-ng (that supports languages like Thai) for Windows, visit the Github repo's Actions page and look for auto-compiled Artifacts. Or you can build it yourself on Linux.
Aeneas (OUTDATED)¶
- Install Ubuntu 22.04 on WSL
- Install aeneas dependencies
- Install aeneas itself
Note
You must use an older version of python because of a deprecated dependency (numpy misc_util) in newer versions.
sudo apt update && sudo apt upgrade -y
sudo apt install software-properties-common -y
sudo add-apt-repository ppa:deadsnakes/ppa
sudo apt update
sudo apt install espeak espeak-data libespeak1 libespeak-dev ffmpeg -y
sudo apt install python3.10 python3.10-venv libpython3.10-dev build-essential gcc libpq-dev -y
python3.10 -m venv aeneasenv2
source aeneasenv2/bin/activate
pip install numpy
pip install aeneas
Info
It looks like someone has forked aeneas and updated it to work with newer versions of numpy/python: https://github.com/naglis/aeneas
Scripture App Builder¶
Install scripture app builder from https://packages.sil.org/ :
(wget -O- https://packages.sil.org/keys/pso-keyring-2016.gpg | sudo tee /etc/apt/trusted.gpg.d/pso-keyring-2016.gpg)&>/dev/null
(. /etc/os-release && sudo tee /etc/apt/sources.list.d/packages-sil-org.list>/dev/null <<< "deb http://packages.sil.org/$ID $VERSION_CODENAME main")
sudo apt update
sudo apt install scripture-app-builder
Install WSL2 from the Windows Store to enable GUI programs, then run scripture-app-builder & to open Scripture App Builder.