TTS Model (Piper VITS): - MODEL_CARD: Voice model information - tokens.txt: Phoneme tokenization - onnx.json: Model configuration - Model: en_US-lessac-medium (60MB ONNX - not in git) Documentation: - APP_REVIEW_NOTES.txt: App Store review notes - specs/: Feature specifications - plugins/: Expo config plugins .gitignore updates: - Exclude large ONNX models (60MB+) - Exclude espeak-ng-data (phoneme data) - Exclude credentials.json - Exclude store-screenshots/ Note: TTS models must be downloaded separately. See specs/ for setup instructions.
97 lines
3.2 KiB
JavaScript
97 lines
3.2 KiB
JavaScript
/**
|
|
* Expo Config Plugin to bundle TTS model files for iOS
|
|
* Uses a Run Script build phase to copy models during build
|
|
*/
|
|
const { withXcodeProject } = require('@expo/config-plugins');
|
|
const path = require('path');
|
|
const fs = require('fs');
|
|
|
|
const withTTSModels = (config) => {
|
|
return withXcodeProject(config, async (config) => {
|
|
const projectRoot = config.modRequest.projectRoot;
|
|
const xcodeProject = config.modResults;
|
|
|
|
// Source model directory (relative to project root)
|
|
const modelSrcDir = path.join(projectRoot, 'assets', 'tts-models');
|
|
|
|
if (!fs.existsSync(modelSrcDir)) {
|
|
console.warn('[withTTSModels] ⚠️ Model directory not found:', modelSrcDir);
|
|
return config;
|
|
}
|
|
|
|
console.log('[withTTSModels] Found models at:', modelSrcDir);
|
|
|
|
// Get the first target
|
|
const target = xcodeProject.getFirstTarget();
|
|
if (!target) {
|
|
console.error('[withTTSModels] ❌ No target found');
|
|
return config;
|
|
}
|
|
|
|
// Path relative to ios directory (where Xcode project lives)
|
|
const iosDir = path.join(projectRoot, 'ios');
|
|
const relativeModelPath = path.relative(iosDir, modelSrcDir);
|
|
|
|
console.log('[withTTSModels] Relative path from ios/:', relativeModelPath);
|
|
|
|
// Create a Run Script build phase to copy ONLY Lessac model during build
|
|
const scriptName = '[TTS] Copy Model Files';
|
|
const scriptContent = [
|
|
'# Copy TTS model files to app bundle (ONLY Lessac voice)',
|
|
'echo "📦 Copying TTS model to app bundle..."',
|
|
'',
|
|
'SOURCE_DIR="${SRCROOT}/' + relativeModelPath + '"',
|
|
'DEST_DIR="${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.app/assets/tts-models"',
|
|
'LESSAC_MODEL="vits-piper-en_US-lessac-medium"',
|
|
'',
|
|
'# Only copy Lessac model',
|
|
'if [ -d "$SOURCE_DIR/$LESSAC_MODEL" ]; then',
|
|
' mkdir -p "$DEST_DIR"',
|
|
' cp -R "$SOURCE_DIR/$LESSAC_MODEL" "$DEST_DIR/"',
|
|
' MODEL_SIZE=$(du -sh "$SOURCE_DIR/$LESSAC_MODEL" | cut -f1)',
|
|
' echo "✅ Lessac TTS model copied successfully ($MODEL_SIZE)"',
|
|
' echo " From: $SOURCE_DIR/$LESSAC_MODEL"',
|
|
' echo " To: $DEST_DIR/$LESSAC_MODEL"',
|
|
'else',
|
|
' echo "⚠️ Lessac model not found at: $SOURCE_DIR/$LESSAC_MODEL"',
|
|
' exit 1',
|
|
'fi'
|
|
].join('\n');
|
|
|
|
// Check if script already exists
|
|
const buildPhases = xcodeProject.hash.project.objects.PBXShellScriptBuildPhase || {};
|
|
let scriptExists = false;
|
|
|
|
for (const key in buildPhases) {
|
|
if (buildPhases[key].name === scriptName) {
|
|
scriptExists = true;
|
|
console.log('[withTTSModels] Run Script phase already exists');
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!scriptExists) {
|
|
// Add the Run Script build phase
|
|
xcodeProject.addBuildPhase(
|
|
[],
|
|
'PBXShellScriptBuildPhase',
|
|
scriptName,
|
|
target.uuid,
|
|
{
|
|
shellPath: '/bin/sh',
|
|
shellScript: scriptContent,
|
|
// Run before Copy Bundle Resources phase
|
|
runOnlyForDeploymentPostprocessing: 0
|
|
}
|
|
);
|
|
|
|
console.log('[withTTSModels] ✅ Added Run Script build phase');
|
|
}
|
|
|
|
console.log('[withTTSModels] ✅ Plugin configured successfully');
|
|
return config;
|
|
});
|
|
};
|
|
|
|
module.exports = withTTSModels;
|