diff --git a/Vendor/SwiftSoup b/Vendor/SwiftSoup deleted file mode 160000 index 855ac2e..0000000 --- a/Vendor/SwiftSoup +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 855ac2e62729e7259f41f79c09f712cc99daf9b2 diff --git a/Vendor/ZipFoundation b/Vendor/ZipFoundation deleted file mode 160000 index d6e0da4..0000000 --- a/Vendor/ZipFoundation +++ /dev/null @@ -1 +0,0 @@ -Subproject commit d6e0da4509c22274b2775b0e8c741518194acba1 diff --git a/Vorleser.xcodeproj/project.pbxproj b/Vorleser.xcodeproj/project.pbxproj deleted file mode 100644 index 2099994..0000000 --- a/Vorleser.xcodeproj/project.pbxproj +++ /dev/null @@ -1,703 +0,0 @@ -// !$*UTF8*$! -{ - archiveVersion = 1; - classes = { - }; - objectVersion = 77; - objects = { - -/* Begin PBXBuildFile section */ - 1173DA36CC81A687FCD9ADEF /* AudioPlaybackService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 607049D947C8E99A2EFEAE2B /* AudioPlaybackService.swift */; }; - 16A7B21A1735D83F72ABE479 /* TextChunker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85269B5CD0BEC628EE701F6D /* TextChunker.swift */; }; - 2873719E4068119DC4AF8E6E /* MacContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FEC91648998FFBEA606BB391 /* MacContentView.swift */; }; - 34166D992482C7B7426B5522 /* VorleserApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5CA6531783A31383ABBC5D2 /* VorleserApp.swift */; }; - 4CE6864A9ED8CC539C8B7CE5 /* TextChunker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06CD128D7B6A97B4FCE4029C /* TextChunker.swift */; }; - 509AA480B488B31919F29088 /* SynthesisWorker.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3FFA134DDAED316C5CFB082 /* SynthesisWorker.swift */; }; - 548CB9DAF59F86B24974F8E4 /* SwiftSoup in Frameworks */ = {isa = PBXBuildFile; productRef = B0D36FF6F6C2B26774D08DC7 /* SwiftSoup */; }; - 5922CC46A3F17E81D35176AA /* am_michael.f32 in Resources */ = {isa = PBXBuildFile; fileRef = E81F0CBA9F875D46BC1526E3 /* am_michael.f32 */; }; - 5D711DB48ABE34775D5FDB98 /* ZIPFoundation in Frameworks */ = {isa = PBXBuildFile; productRef = C1752C03CDF5CAD6901643D1 /* ZIPFoundation */; }; - 610C2F9387026F62F61C8CF2 /* DocumentPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DA26953AA44A2E7FD5A02BE /* DocumentPicker.swift */; }; - 6215D254F0FFF5DA2B20AECA /* EPUBService.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01E3A445F73A6332FD740DC /* EPUBService.swift */; }; - 6553CBE1C2A85C0E3B5AAB32 /* ReaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A0658A4F43BB4E96F5F4FFEE /* ReaderView.swift */; }; - 6665F2B72ACD3728EB59EAF0 /* LibraryViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = A25386256250D132B07A40A8 /* LibraryViewModel.swift */; }; - 68907CD0425F824EDEAA474B /* ModelDownloadManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 095B8213401737EBE5BD051E /* ModelDownloadManager.swift */; }; - 68B454E96B643A49C8575B1F /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF069F9DF9CB378E19213C1A /* ContentView.swift */; }; - 774433CFD75BDD0BD3FD7CE3 /* KokoroPipeline.swift in Sources */ = {isa = PBXBuildFile; fileRef = 81B1729BB2EEFB2F938A4ED1 /* KokoroPipeline.swift */; }; - 7F7A8848F3F6A6305B69F207 /* ZIPFoundation in Frameworks */ = {isa = PBXBuildFile; productRef = EFE297A06C9DB8F3A03CC722 /* ZIPFoundation */; }; - 857BA97045E2D5DC1BE459CA /* am_michael_256.f32 in Resources */ = {isa = PBXBuildFile; fileRef = 2261729AEC15D2904FC451D7 /* am_michael_256.f32 */; }; - 8B60D8666A4A93F9E89E3D9A /* BookItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B83FC4EDCDD4B8E955E50F5 /* BookItem.swift */; }; - 8F70FF75CF4252218AB7D9B9 /* config.json in Resources */ = {isa = PBXBuildFile; fileRef = B96222D416F28D8ADABB8690 /* config.json */; }; - 91D5040FD66AC881D66737AC /* SwiftSoup in Frameworks */ = {isa = PBXBuildFile; productRef = 40DA6F30AF230EF31BE90E5D /* SwiftSoup */; }; - 9C0EE7F98FDCBE710E0FABF2 /* EPUBService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1736FACE2FDE0410DA52FDCF /* EPUBService.swift */; }; - 9C182CD3A6163FB87780D9B6 /* AssetVerifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4BBAF6BE51E77F99FA99D6A /* AssetVerifier.swift */; }; - 9CAD6CF7B191D03B2DF31D1C /* kokoro_decoder_only_3s.mlpackage in Sources */ = {isa = PBXBuildFile; fileRef = 1305807ABD715C606FEA6DFA /* kokoro_decoder_only_3s.mlpackage */; }; - A2FEFDB8B74FBB03FFAC8E98 /* kokoro_f0n_10s.mlpackage in Sources */ = {isa = PBXBuildFile; fileRef = A337EE4E3A4036F440E4FD9C /* kokoro_f0n_10s.mlpackage */; }; - A70470255CE4892795B128A2 /* KokoroTokenizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9565A2BC053068E43C4023E8 /* KokoroTokenizer.swift */; }; - A7BFB668CD8C8D9BF53970A8 /* am_michael.pt in Resources */ = {isa = PBXBuildFile; fileRef = E3DB5C54ADDC336BDEE52370 /* am_michael.pt */; }; - ADD9720B3F47ADA5662A1252 /* ModelManifest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84CAEC669F2D50C02A202551 /* ModelManifest.swift */; }; - AFB35F64AFB50B3ED9569E5A /* kokoro_duration.mlpackage in Sources */ = {isa = PBXBuildFile; fileRef = 69C5DD780EBC730C24A7EAD2 /* kokoro_duration.mlpackage */; }; - BF4C90C35F076CFB1374688D /* AudioSynthesisService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 996137D48F183F1E4C91800B /* AudioSynthesisService.swift */; }; - C37859F5789D350B87B6992F /* kokoro_decoder_only_10s.mlpackage in Sources */ = {isa = PBXBuildFile; fileRef = B1895F72980568E5F7EFA85E /* kokoro_decoder_only_10s.mlpackage */; }; - C6AFD11DD6725CA9422C419E /* MacLibraryViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8865DCEC0F532253D25B3D97 /* MacLibraryViewModel.swift */; }; - D1CE101DCF0DE54F6F301C95 /* EspeakPhonemizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 63F44ED11BB4C142F40FB9C7 /* EspeakPhonemizer.swift */; }; - D577A9313BC7656040C53FD9 /* VorleserMacApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CFB7D317E65F1324C34D3F0 /* VorleserMacApp.swift */; }; - E8BDFA760B9CFE0B8FAFF8B8 /* ReaderViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEBD2C99C256768BB156EFCC /* ReaderViewModel.swift */; }; - EC040FC1433D4B3628D69765 /* kokoro_f0n_3s.mlpackage in Sources */ = {isa = PBXBuildFile; fileRef = 7B8464C0EC10657806B6E626 /* kokoro_f0n_3s.mlpackage */; }; -/* End PBXBuildFile section */ - -/* Begin PBXFileReference section */ - 0655C39862135E3CC301233A /* SwiftSoup */ = {isa = PBXFileReference; lastKnownFileType = folder; name = SwiftSoup; path = Vendor/SwiftSoup; sourceTree = SOURCE_ROOT; }; - 06CD128D7B6A97B4FCE4029C /* TextChunker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextChunker.swift; sourceTree = ""; }; - 095B8213401737EBE5BD051E /* ModelDownloadManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ModelDownloadManager.swift; sourceTree = ""; }; - 0DA26953AA44A2E7FD5A02BE /* DocumentPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DocumentPicker.swift; sourceTree = ""; }; - 1305807ABD715C606FEA6DFA /* kokoro_decoder_only_3s.mlpackage */ = {isa = PBXFileReference; path = kokoro_decoder_only_3s.mlpackage; sourceTree = ""; }; - 1736FACE2FDE0410DA52FDCF /* EPUBService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EPUBService.swift; sourceTree = ""; }; - 2261729AEC15D2904FC451D7 /* am_michael_256.f32 */ = {isa = PBXFileReference; path = am_michael_256.f32; sourceTree = ""; }; - 3857F14DFCD4924A4340F42B /* Signing.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Signing.xcconfig; sourceTree = ""; }; - 3CFB7D317E65F1324C34D3F0 /* VorleserMacApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VorleserMacApp.swift; sourceTree = ""; }; - 607049D947C8E99A2EFEAE2B /* AudioPlaybackService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioPlaybackService.swift; sourceTree = ""; }; - 63F44ED11BB4C142F40FB9C7 /* EspeakPhonemizer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EspeakPhonemizer.swift; sourceTree = ""; }; - 69C5DD780EBC730C24A7EAD2 /* kokoro_duration.mlpackage */ = {isa = PBXFileReference; path = kokoro_duration.mlpackage; sourceTree = ""; }; - 7241D29C019B5B111851F09E /* VorleserMac.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = VorleserMac.app; sourceTree = BUILT_PRODUCTS_DIR; }; - 78B456910B098FF29B2C3DCC /* ZipFoundation */ = {isa = PBXFileReference; lastKnownFileType = folder; name = ZipFoundation; path = Vendor/ZipFoundation; sourceTree = SOURCE_ROOT; }; - 7B83FC4EDCDD4B8E955E50F5 /* BookItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookItem.swift; sourceTree = ""; }; - 7B8464C0EC10657806B6E626 /* kokoro_f0n_3s.mlpackage */ = {isa = PBXFileReference; path = kokoro_f0n_3s.mlpackage; sourceTree = ""; }; - 81B1729BB2EEFB2F938A4ED1 /* KokoroPipeline.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KokoroPipeline.swift; sourceTree = ""; }; - 84CAEC669F2D50C02A202551 /* ModelManifest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ModelManifest.swift; sourceTree = ""; }; - 85269B5CD0BEC628EE701F6D /* TextChunker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextChunker.swift; sourceTree = ""; }; - 8865DCEC0F532253D25B3D97 /* MacLibraryViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MacLibraryViewModel.swift; sourceTree = ""; }; - 9565A2BC053068E43C4023E8 /* KokoroTokenizer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KokoroTokenizer.swift; sourceTree = ""; }; - 996137D48F183F1E4C91800B /* AudioSynthesisService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioSynthesisService.swift; sourceTree = ""; }; - A0658A4F43BB4E96F5F4FFEE /* ReaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReaderView.swift; sourceTree = ""; }; - A25386256250D132B07A40A8 /* LibraryViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibraryViewModel.swift; sourceTree = ""; }; - A337EE4E3A4036F440E4FD9C /* kokoro_f0n_10s.mlpackage */ = {isa = PBXFileReference; path = kokoro_f0n_10s.mlpackage; sourceTree = ""; }; - B1895F72980568E5F7EFA85E /* kokoro_decoder_only_10s.mlpackage */ = {isa = PBXFileReference; path = kokoro_decoder_only_10s.mlpackage; sourceTree = ""; }; - B5CA6531783A31383ABBC5D2 /* VorleserApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VorleserApp.swift; sourceTree = ""; }; - B96222D416F28D8ADABB8690 /* config.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = config.json; sourceTree = ""; }; - BF069F9DF9CB378E19213C1A /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; - C3FFA134DDAED316C5CFB082 /* SynthesisWorker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SynthesisWorker.swift; sourceTree = ""; }; - D01E3A445F73A6332FD740DC /* EPUBService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EPUBService.swift; sourceTree = ""; }; - E3DB5C54ADDC336BDEE52370 /* am_michael.pt */ = {isa = PBXFileReference; path = am_michael.pt; sourceTree = ""; }; - E4BBAF6BE51E77F99FA99D6A /* AssetVerifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AssetVerifier.swift; sourceTree = ""; }; - E81F0CBA9F875D46BC1526E3 /* am_michael.f32 */ = {isa = PBXFileReference; path = am_michael.f32; sourceTree = ""; }; - EEBD2C99C256768BB156EFCC /* ReaderViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReaderViewModel.swift; sourceTree = ""; }; - F602A780D06CF08113B0B040 /* Vorleser.app */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = wrapper.application; path = Vorleser.app; sourceTree = BUILT_PRODUCTS_DIR; }; - FEC91648998FFBEA606BB391 /* MacContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MacContentView.swift; sourceTree = ""; }; -/* End PBXFileReference section */ - -/* Begin PBXFrameworksBuildPhase section */ - 1252E70F7090988D4BD7BFEE /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - 5D711DB48ABE34775D5FDB98 /* ZIPFoundation in Frameworks */, - 548CB9DAF59F86B24974F8E4 /* SwiftSoup in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 59B4BBC05DBAE06B85C55206 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - 7F7A8848F3F6A6305B69F207 /* ZIPFoundation in Frameworks */, - 91D5040FD66AC881D66737AC /* SwiftSoup in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXFrameworksBuildPhase section */ - -/* Begin PBXGroup section */ - 00DDDCA9D08C8871DFE5E2C6 /* Services */ = { - isa = PBXGroup; - children = ( - E4BBAF6BE51E77F99FA99D6A /* AssetVerifier.swift */, - 607049D947C8E99A2EFEAE2B /* AudioPlaybackService.swift */, - 1736FACE2FDE0410DA52FDCF /* EPUBService.swift */, - 63F44ED11BB4C142F40FB9C7 /* EspeakPhonemizer.swift */, - 81B1729BB2EEFB2F938A4ED1 /* KokoroPipeline.swift */, - 9565A2BC053068E43C4023E8 /* KokoroTokenizer.swift */, - 8865DCEC0F532253D25B3D97 /* MacLibraryViewModel.swift */, - C3FFA134DDAED316C5CFB082 /* SynthesisWorker.swift */, - 06CD128D7B6A97B4FCE4029C /* TextChunker.swift */, - ); - path = Services; - sourceTree = ""; - }; - 0EB0E6D095C1B61F22CFCA3A /* Config */ = { - isa = PBXGroup; - children = ( - B96222D416F28D8ADABB8690 /* config.json */, - ); - path = Config; - sourceTree = ""; - }; - 453A1AEE6DFF6B0971E3C127 /* Resources */ = { - isa = PBXGroup; - children = ( - 0EB0E6D095C1B61F22CFCA3A /* Config */, - 967C242FA01032FE1F4FD74A /* Models */, - CB478F6D37690D8B63AC03E6 /* Voices */, - ); - path = Resources; - sourceTree = ""; - }; - 5C5F841A908AC94F515A14DF /* Services */ = { - isa = PBXGroup; - children = ( - 996137D48F183F1E4C91800B /* AudioSynthesisService.swift */, - D01E3A445F73A6332FD740DC /* EPUBService.swift */, - A25386256250D132B07A40A8 /* LibraryViewModel.swift */, - 095B8213401737EBE5BD051E /* ModelDownloadManager.swift */, - 84CAEC669F2D50C02A202551 /* ModelManifest.swift */, - EEBD2C99C256768BB156EFCC /* ReaderViewModel.swift */, - 85269B5CD0BEC628EE701F6D /* TextChunker.swift */, - ); - path = Services; - sourceTree = ""; - }; - 967C242FA01032FE1F4FD74A /* Models */ = { - isa = PBXGroup; - children = ( - 1305807ABD715C606FEA6DFA /* kokoro_decoder_only_3s.mlpackage */, - B1895F72980568E5F7EFA85E /* kokoro_decoder_only_10s.mlpackage */, - 69C5DD780EBC730C24A7EAD2 /* kokoro_duration.mlpackage */, - 7B8464C0EC10657806B6E626 /* kokoro_f0n_3s.mlpackage */, - A337EE4E3A4036F440E4FD9C /* kokoro_f0n_10s.mlpackage */, - ); - path = Models; - sourceTree = ""; - }; - AA2551B713A3837EBFFE36ED = { - isa = PBXGroup; - children = ( - CA0FE0B06C7F73AE98CB9C0E /* Config */, - B6DABD163E3B1A42BA34A10D /* Packages */, - C91060BFBE7C457FBA96A1D3 /* Vorleser */, - B53E7A13A3D61433250080D0 /* VorleserMac */, - FEFF49B59E5B20C71387C257 /* Products */, - ); - sourceTree = ""; - }; - B53E7A13A3D61433250080D0 /* VorleserMac */ = { - isa = PBXGroup; - children = ( - FEC91648998FFBEA606BB391 /* MacContentView.swift */, - 3CFB7D317E65F1324C34D3F0 /* VorleserMacApp.swift */, - 453A1AEE6DFF6B0971E3C127 /* Resources */, - 00DDDCA9D08C8871DFE5E2C6 /* Services */, - ); - path = VorleserMac; - sourceTree = ""; - }; - B6DABD163E3B1A42BA34A10D /* Packages */ = { - isa = PBXGroup; - children = ( - 0655C39862135E3CC301233A /* SwiftSoup */, - 78B456910B098FF29B2C3DCC /* ZipFoundation */, - ); - name = Packages; - sourceTree = ""; - }; - B7584ED8034338CD7C617867 /* Views */ = { - isa = PBXGroup; - children = ( - 0DA26953AA44A2E7FD5A02BE /* DocumentPicker.swift */, - A0658A4F43BB4E96F5F4FFEE /* ReaderView.swift */, - ); - path = Views; - sourceTree = ""; - }; - BF849F2658E55D7F49854887 /* Models */ = { - isa = PBXGroup; - children = ( - 7B83FC4EDCDD4B8E955E50F5 /* BookItem.swift */, - ); - path = Models; - sourceTree = ""; - }; - C91060BFBE7C457FBA96A1D3 /* Vorleser */ = { - isa = PBXGroup; - children = ( - BF069F9DF9CB378E19213C1A /* ContentView.swift */, - B5CA6531783A31383ABBC5D2 /* VorleserApp.swift */, - BF849F2658E55D7F49854887 /* Models */, - 5C5F841A908AC94F515A14DF /* Services */, - B7584ED8034338CD7C617867 /* Views */, - ); - path = Vorleser; - sourceTree = ""; - }; - CA0FE0B06C7F73AE98CB9C0E /* Config */ = { - isa = PBXGroup; - children = ( - 3857F14DFCD4924A4340F42B /* Signing.xcconfig */, - ); - path = Config; - sourceTree = ""; - }; - CB478F6D37690D8B63AC03E6 /* Voices */ = { - isa = PBXGroup; - children = ( - 2261729AEC15D2904FC451D7 /* am_michael_256.f32 */, - E81F0CBA9F875D46BC1526E3 /* am_michael.f32 */, - E3DB5C54ADDC336BDEE52370 /* am_michael.pt */, - ); - path = Voices; - sourceTree = ""; - }; - FEFF49B59E5B20C71387C257 /* Products */ = { - isa = PBXGroup; - children = ( - F602A780D06CF08113B0B040 /* Vorleser.app */, - 7241D29C019B5B111851F09E /* VorleserMac.app */, - ); - name = Products; - sourceTree = ""; - }; -/* End PBXGroup section */ - -/* Begin PBXNativeTarget section */ - 15368B3CC5F10505A5542431 /* VorleserMac */ = { - isa = PBXNativeTarget; - buildConfigurationList = 910E34933F556E9FD04522FB /* Build configuration list for PBXNativeTarget "VorleserMac" */; - buildPhases = ( - 421B395AF45EAB2389E36EDA /* Sources */, - 1FAD9B3D3D372A7193D268DA /* Resources */, - 59B4BBC05DBAE06B85C55206 /* Frameworks */, - 19C2D6A4838A08DD26BA48F9 /* Bundle espeak-ng */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = VorleserMac; - packageProductDependencies = ( - EFE297A06C9DB8F3A03CC722 /* ZIPFoundation */, - 40DA6F30AF230EF31BE90E5D /* SwiftSoup */, - ); - productName = VorleserMac; - productReference = 7241D29C019B5B111851F09E /* VorleserMac.app */; - productType = "com.apple.product-type.application"; - }; - F8874C75585C039A65C14A8F /* Vorleser */ = { - isa = PBXNativeTarget; - buildConfigurationList = 08279EC5D81ED3A0A3B8E7C8 /* Build configuration list for PBXNativeTarget "Vorleser" */; - buildPhases = ( - 6B2D8F9CBB5D255FABC2DF25 /* Sources */, - 1252E70F7090988D4BD7BFEE /* Frameworks */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = Vorleser; - packageProductDependencies = ( - C1752C03CDF5CAD6901643D1 /* ZIPFoundation */, - B0D36FF6F6C2B26774D08DC7 /* SwiftSoup */, - ); - productName = Vorleser; - productReference = F602A780D06CF08113B0B040 /* Vorleser.app */; - productType = "com.apple.product-type.application"; - }; -/* End PBXNativeTarget section */ - -/* Begin PBXProject section */ - 883E55BF581DFC7ED329F0DA /* Project object */ = { - isa = PBXProject; - attributes = { - BuildIndependentTargetsInParallel = YES; - LastUpgradeCheck = 1430; - TargetAttributes = { - 15368B3CC5F10505A5542431 = { - DevelopmentTeam = NG5W75WE8U; - ProvisioningStyle = Automatic; - }; - F8874C75585C039A65C14A8F = { - DevelopmentTeam = NG5W75WE8U; - ProvisioningStyle = Automatic; - }; - }; - }; - buildConfigurationList = 5C0E46E98AB9768E0EDC7F1B /* Build configuration list for PBXProject "Vorleser" */; - compatibilityVersion = "Xcode 14.0"; - developmentRegion = en; - hasScannedForEncodings = 0; - knownRegions = ( - Base, - en, - ); - mainGroup = AA2551B713A3837EBFFE36ED; - minimizedProjectReferenceProxies = 1; - packageReferences = ( - 7F54DACBE04DB2E26884B508 /* XCLocalSwiftPackageReference "Vendor/SwiftSoup" */, - 675AD79D8C32F0F3F21CB7B5 /* XCLocalSwiftPackageReference "Vendor/ZipFoundation" */, - ); - preferredProjectObjectVersion = 77; - projectDirPath = ""; - projectRoot = ""; - targets = ( - F8874C75585C039A65C14A8F /* Vorleser */, - 15368B3CC5F10505A5542431 /* VorleserMac */, - ); - }; -/* End PBXProject section */ - -/* Begin PBXResourcesBuildPhase section */ - 1FAD9B3D3D372A7193D268DA /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 5922CC46A3F17E81D35176AA /* am_michael.f32 in Resources */, - A7BFB668CD8C8D9BF53970A8 /* am_michael.pt in Resources */, - 857BA97045E2D5DC1BE459CA /* am_michael_256.f32 in Resources */, - 8F70FF75CF4252218AB7D9B9 /* config.json in Resources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXResourcesBuildPhase section */ - -/* Begin PBXShellScriptBuildPhase section */ - 19C2D6A4838A08DD26BA48F9 /* Bundle espeak-ng */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - ); - name = "Bundle espeak-ng"; - outputFileListPaths = ( - ); - outputPaths = ( - "${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.app/Contents/Resources/Tools/espeak-ng", - "${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.app/Contents/Resources/Tools/espeak-ng-data/phondata", - "${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.app/Contents/Frameworks/libespeak-ng.dylib", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "set -euo pipefail\nBIN_SRC=\"/opt/homebrew/bin/espeak-ng\"\nDATA_SRC=\"/opt/homebrew/share/espeak-ng-data\"\nDYLIB_SRC=\"/opt/homebrew/lib/libespeak-ng.dylib\"\n\nif [ ! -x \"$BIN_SRC\" ]; then\n echo \"espeak-ng not found at $BIN_SRC. Install via: brew install espeak-ng\"\n exit 1\nfi\nif [ ! -d \"$DATA_SRC\" ]; then\n echo \"espeak-ng data not found at $DATA_SRC\"\n exit 1\nfi\nif [ ! -f \"$DYLIB_SRC\" ]; then\n echo \"libespeak-ng.dylib not found at $DYLIB_SRC\"\n exit 1\nfi\n\nRES_DIR=\"${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.app/Contents/Resources\"\nTOOL_DIR=\"${RES_DIR}/Tools\"\nFRAMEWORK_DIR=\"${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.app/Contents/Frameworks\"\nmkdir -p \"$TOOL_DIR\" \"$FRAMEWORK_DIR\"\n\ncp -f \"$BIN_SRC\" \"$TOOL_DIR/espeak-ng\"\nchmod +x \"$TOOL_DIR/espeak-ng\"\nrsync -a \"$DATA_SRC/\" \"$TOOL_DIR/espeak-ng-data/\"\ncp -f \"$DYLIB_SRC\" \"$FRAMEWORK_DIR/libespeak-ng.dylib\"\n\ninstall_name_tool -id \"@rpath/libespeak-ng.dylib\" \"$FRAMEWORK_DIR/libespeak-ng.dylib\"\ninstall_name_tool -change \"$DYLIB_SRC\" \"@rpath/libespeak-ng.dylib\" \"$TOOL_DIR/espeak-ng\"\ninstall_name_tool -add_rpath \"@executable_path/../Frameworks\" \"$TOOL_DIR/espeak-ng\" || true\n\ncodesign --force --sign - --timestamp=none \"$FRAMEWORK_DIR/libespeak-ng.dylib\"\ncodesign --force --sign - --timestamp=none \"$TOOL_DIR/espeak-ng\"\n"; - }; -/* End PBXShellScriptBuildPhase section */ - -/* Begin PBXSourcesBuildPhase section */ - 421B395AF45EAB2389E36EDA /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 9C182CD3A6163FB87780D9B6 /* AssetVerifier.swift in Sources */, - 1173DA36CC81A687FCD9ADEF /* AudioPlaybackService.swift in Sources */, - 9C0EE7F98FDCBE710E0FABF2 /* EPUBService.swift in Sources */, - D1CE101DCF0DE54F6F301C95 /* EspeakPhonemizer.swift in Sources */, - 774433CFD75BDD0BD3FD7CE3 /* KokoroPipeline.swift in Sources */, - A70470255CE4892795B128A2 /* KokoroTokenizer.swift in Sources */, - 2873719E4068119DC4AF8E6E /* MacContentView.swift in Sources */, - C6AFD11DD6725CA9422C419E /* MacLibraryViewModel.swift in Sources */, - 509AA480B488B31919F29088 /* SynthesisWorker.swift in Sources */, - 4CE6864A9ED8CC539C8B7CE5 /* TextChunker.swift in Sources */, - D577A9313BC7656040C53FD9 /* VorleserMacApp.swift in Sources */, - C37859F5789D350B87B6992F /* kokoro_decoder_only_10s.mlpackage in Sources */, - 9CAD6CF7B191D03B2DF31D1C /* kokoro_decoder_only_3s.mlpackage in Sources */, - AFB35F64AFB50B3ED9569E5A /* kokoro_duration.mlpackage in Sources */, - A2FEFDB8B74FBB03FFAC8E98 /* kokoro_f0n_10s.mlpackage in Sources */, - EC040FC1433D4B3628D69765 /* kokoro_f0n_3s.mlpackage in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 6B2D8F9CBB5D255FABC2DF25 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - BF4C90C35F076CFB1374688D /* AudioSynthesisService.swift in Sources */, - 8B60D8666A4A93F9E89E3D9A /* BookItem.swift in Sources */, - 68B454E96B643A49C8575B1F /* ContentView.swift in Sources */, - 610C2F9387026F62F61C8CF2 /* DocumentPicker.swift in Sources */, - 6215D254F0FFF5DA2B20AECA /* EPUBService.swift in Sources */, - 6665F2B72ACD3728EB59EAF0 /* LibraryViewModel.swift in Sources */, - 68907CD0425F824EDEAA474B /* ModelDownloadManager.swift in Sources */, - ADD9720B3F47ADA5662A1252 /* ModelManifest.swift in Sources */, - 6553CBE1C2A85C0E3B5AAB32 /* ReaderView.swift in Sources */, - E8BDFA760B9CFE0B8FAFF8B8 /* ReaderViewModel.swift in Sources */, - 16A7B21A1735D83F72ABE479 /* TextChunker.swift in Sources */, - 34166D992482C7B7426B5522 /* VorleserApp.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXSourcesBuildPhase section */ - -/* Begin XCBuildConfiguration section */ - 0A73B635C02BCC20E4C867D1 /* Release */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 3857F14DFCD4924A4340F42B /* Signing.xcconfig */; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - GENERATE_INFOPLIST_FILE = YES; - INFOPLIST_KEY_CFBundleShortVersionString = 0.1.0; - INFOPLIST_KEY_CFBundleVersion = 1; - INFOPLIST_KEY_LSSupportsOpeningDocumentsInPlace = YES; - INFOPLIST_KEY_UIBackgroundModes = ( - audio, - processing, - ); - INFOPLIST_KEY_UIFileSharingEnabled = YES; - IPHONEOS_DEPLOYMENT_TARGET = 26.0; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - ); - PRODUCT_BUNDLE_IDENTIFIER = de.felixfoertsch.vorleser; - SDKROOT = iphoneos; - TARGETED_DEVICE_FAMILY = "1,2"; - }; - name = Release; - }; - 127706133EE774A829656100 /* Release */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 3857F14DFCD4924A4340F42B /* Signing.xcconfig */; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - COMBINE_HIDPI_IMAGES = YES; - GENERATE_INFOPLIST_FILE = YES; - INFOPLIST_KEY_CFBundleShortVersionString = 0.1.0; - INFOPLIST_KEY_CFBundleVersion = 1; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/../Frameworks", - ); - MACOSX_DEPLOYMENT_TARGET = 15.0; - PRODUCT_BUNDLE_IDENTIFIER = de.felixfoertsch.vorleser.mac; - SDKROOT = macosx; - }; - name = Release; - }; - 6511FD010BF67ACEA7CA9239 /* Debug */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 3857F14DFCD4924A4340F42B /* Signing.xcconfig */; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - GENERATE_INFOPLIST_FILE = YES; - INFOPLIST_KEY_CFBundleShortVersionString = 0.1.0; - INFOPLIST_KEY_CFBundleVersion = 1; - INFOPLIST_KEY_LSSupportsOpeningDocumentsInPlace = YES; - INFOPLIST_KEY_UIBackgroundModes = ( - audio, - processing, - ); - INFOPLIST_KEY_UIFileSharingEnabled = YES; - IPHONEOS_DEPLOYMENT_TARGET = 26.0; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - ); - PRODUCT_BUNDLE_IDENTIFIER = de.felixfoertsch.vorleser; - SDKROOT = iphoneos; - TARGETED_DEVICE_FAMILY = "1,2"; - }; - name = Debug; - }; - 8C1B3BB72A7DF57561B03D25 /* Debug */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 3857F14DFCD4924A4340F42B /* Signing.xcconfig */; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - COMBINE_HIDPI_IMAGES = YES; - GENERATE_INFOPLIST_FILE = YES; - INFOPLIST_KEY_CFBundleShortVersionString = 0.1.0; - INFOPLIST_KEY_CFBundleVersion = 1; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/../Frameworks", - ); - MACOSX_DEPLOYMENT_TARGET = 15.0; - PRODUCT_BUNDLE_IDENTIFIER = de.felixfoertsch.vorleser.mac; - SDKROOT = macosx; - }; - name = Debug; - }; - B99A49578EB57D09FC154DE2 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_NONNULL = YES; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_ENABLE_OBJC_WEAK = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 1; - DEBUG_INFORMATION_FORMAT = dwarf; - ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_TESTABILITY = YES; - GCC_C_LANGUAGE_STANDARD = gnu11; - GCC_DYNAMIC_NO_PIC = NO; - GCC_NO_COMMON_BLOCKS = YES; - GCC_OPTIMIZATION_LEVEL = 0; - GCC_PREPROCESSOR_DEFINITIONS = ( - "$(inherited)", - "DEBUG=1", - ); - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 26.0; - MACOSX_DEPLOYMENT_TARGET = 15.0; - MARKETING_VERSION = 0.1.0; - MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; - MTL_FAST_MATH = YES; - ONLY_ACTIVE_ARCH = YES; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 5.9; - }; - name = Debug; - }; - CC4A9125F315FD4E9A1D3873 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_NONNULL = YES; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_ENABLE_OBJC_WEAK = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 1; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - ENABLE_NS_ASSERTIONS = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - GCC_C_LANGUAGE_STANDARD = gnu11; - GCC_NO_COMMON_BLOCKS = YES; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 26.0; - MACOSX_DEPLOYMENT_TARGET = 15.0; - MARKETING_VERSION = 0.1.0; - MTL_ENABLE_DEBUG_INFO = NO; - MTL_FAST_MATH = YES; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_COMPILATION_MODE = wholemodule; - SWIFT_OPTIMIZATION_LEVEL = "-O"; - SWIFT_VERSION = 5.9; - }; - name = Release; - }; -/* End XCBuildConfiguration section */ - -/* Begin XCConfigurationList section */ - 08279EC5D81ED3A0A3B8E7C8 /* Build configuration list for PBXNativeTarget "Vorleser" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 6511FD010BF67ACEA7CA9239 /* Debug */, - 0A73B635C02BCC20E4C867D1 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Debug; - }; - 5C0E46E98AB9768E0EDC7F1B /* Build configuration list for PBXProject "Vorleser" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - B99A49578EB57D09FC154DE2 /* Debug */, - CC4A9125F315FD4E9A1D3873 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Debug; - }; - 910E34933F556E9FD04522FB /* Build configuration list for PBXNativeTarget "VorleserMac" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 8C1B3BB72A7DF57561B03D25 /* Debug */, - 127706133EE774A829656100 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Debug; - }; -/* End XCConfigurationList section */ - -/* Begin XCLocalSwiftPackageReference section */ - 675AD79D8C32F0F3F21CB7B5 /* XCLocalSwiftPackageReference "Vendor/ZipFoundation" */ = { - isa = XCLocalSwiftPackageReference; - relativePath = Vendor/ZipFoundation; - }; - 7F54DACBE04DB2E26884B508 /* XCLocalSwiftPackageReference "Vendor/SwiftSoup" */ = { - isa = XCLocalSwiftPackageReference; - relativePath = Vendor/SwiftSoup; - }; -/* End XCLocalSwiftPackageReference section */ - -/* Begin XCSwiftPackageProductDependency section */ - 40DA6F30AF230EF31BE90E5D /* SwiftSoup */ = { - isa = XCSwiftPackageProductDependency; - productName = SwiftSoup; - }; - B0D36FF6F6C2B26774D08DC7 /* SwiftSoup */ = { - isa = XCSwiftPackageProductDependency; - productName = SwiftSoup; - }; - C1752C03CDF5CAD6901643D1 /* ZIPFoundation */ = { - isa = XCSwiftPackageProductDependency; - productName = ZIPFoundation; - }; - EFE297A06C9DB8F3A03CC722 /* ZIPFoundation */ = { - isa = XCSwiftPackageProductDependency; - productName = ZIPFoundation; - }; -/* End XCSwiftPackageProductDependency section */ - }; - rootObject = 883E55BF581DFC7ED329F0DA /* Project object */; -} diff --git a/Vorleser.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/Vorleser.xcodeproj/project.xcworkspace/contents.xcworkspacedata deleted file mode 100644 index 919434a..0000000 --- a/Vorleser.xcodeproj/project.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1,7 +0,0 @@ - - - - - diff --git a/Vorleser.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Vorleser.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved deleted file mode 100644 index 8cb6cda..0000000 --- a/Vorleser.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ /dev/null @@ -1,23 +0,0 @@ -{ - "pins" : [ - { - "identity" : "lrucache", - "kind" : "remoteSourceControl", - "location" : "https://github.com/nicklockwood/LRUCache.git", - "state" : { - "revision" : "cb5b2bd0da83ad29c0bec762d39f41c8ad0eaf3e", - "version" : "1.2.1" - } - }, - { - "identity" : "swift-atomics", - "kind" : "remoteSourceControl", - "location" : "https://github.com/apple/swift-atomics.git", - "state" : { - "revision" : "b601256eab081c0f92f059e12818ac1d4f178ff7", - "version" : "1.3.0" - } - } - ], - "version" : 2 -} diff --git a/Vorleser/ContentView.swift b/Vorleser/ContentView.swift deleted file mode 100644 index 4d8ebc7..0000000 --- a/Vorleser/ContentView.swift +++ /dev/null @@ -1,94 +0,0 @@ -import SwiftUI - -struct ContentView: View { - @StateObject private var viewModel = LibraryViewModel() - @State private var selectedItem: BookItem? - - var body: some View { - NavigationStack { - VStack(alignment: .leading, spacing: 16) { - header - - if viewModel.items.isEmpty { - emptyState - } else { - bookList - } - - Spacer() - } - .padding(20) - .navigationTitle("Vorleser") - .toolbar { - ToolbarItem(placement: .topBarTrailing) { - Button("Import EPUB") { - viewModel.isImporting = true - } - } - } - .sheet(isPresented: $viewModel.isImporting) { - DocumentPicker(onPick: { url in - viewModel.isImporting = false - viewModel.importPicked(url: url) - }, onCancel: { - viewModel.isImporting = false - }) - } - .alert("Import failed", isPresented: .constant(viewModel.lastErrorMessage != nil), actions: { - Button("OK", role: .cancel) { - viewModel.lastErrorMessage = nil - } - }, message: { - Text(viewModel.lastErrorMessage ?? "") - }) - } - } - - private var header: some View { - VStack(alignment: .leading, spacing: 8) { - Text("Local EPUB reader") - .font(.title2.weight(.semibold)) - Text("On-device, high-quality narration powered by CoreML.") - .foregroundStyle(.secondary) - } - } - - private var emptyState: some View { - VStack(alignment: .leading, spacing: 12) { - Text("No books yet") - .font(.headline) - Text("Import an EPUB from Files or iCloud to start preparing narration.") - .foregroundStyle(.secondary) - Button("Import EPUB") { - viewModel.isImporting = true - } - .buttonStyle(.borderedProminent) - } - .frame(maxWidth: .infinity, alignment: .leading) - .padding(16) - .background(.thinMaterial) - .clipShape(RoundedRectangle(cornerRadius: 16, style: .continuous)) - } - - private var bookList: some View { - List(viewModel.items) { item in - NavigationLink(value: item) { - VStack(alignment: .leading, spacing: 4) { - Text(item.title) - .font(.headline) - Text(item.sourceURL.lastPathComponent) - .font(.caption) - .foregroundStyle(.secondary) - } - } - } - .listStyle(.plain) - .navigationDestination(for: BookItem.self) { item in - ReaderView(item: item) - } - } -} - -#Preview { - ContentView() -} diff --git a/Vorleser/Models/BookItem.swift b/Vorleser/Models/BookItem.swift deleted file mode 100644 index 7919b45..0000000 --- a/Vorleser/Models/BookItem.swift +++ /dev/null @@ -1,15 +0,0 @@ -import Foundation - -struct BookItem: Identifiable, Hashable { - let id: UUID - let title: String - let sourceURL: URL - let addedAt: Date - - init(title: String, sourceURL: URL, addedAt: Date = Date()) { - self.id = UUID() - self.title = title - self.sourceURL = sourceURL - self.addedAt = addedAt - } -} diff --git a/Vorleser/Services/AudioSynthesisService.swift b/Vorleser/Services/AudioSynthesisService.swift deleted file mode 100644 index 37141b6..0000000 --- a/Vorleser/Services/AudioSynthesisService.swift +++ /dev/null @@ -1,42 +0,0 @@ -import Foundation -import AVFoundation -import CoreML - -final class AudioSynthesisService { - struct SynthesisChunk { - let pcmData: Data - let sampleRate: Double - let channels: AVAudioChannelCount - } - - struct KokoroModelPaths { - let durationModel: URL - let decoder3s: URL - let decoder10s: URL - let voicesDirectory: URL - let tokenizerConfig: URL - } - - private(set) var isPrepared = false - - func prepareIfNeeded(paths: KokoroModelPaths) async throws { - guard !isPrepared else { return } - _ = try await compileIfNeeded(at: paths.durationModel) - _ = try await compileIfNeeded(at: paths.decoder3s) - _ = try await compileIfNeeded(at: paths.decoder10s) - isPrepared = true - } - - func synthesize(text: String) async throws -> SynthesisChunk { - // TODO: Kokoro CoreML inference goes here. - throw NSError(domain: "AudioSynthesisService", code: -1, userInfo: [NSLocalizedDescriptionKey: "Kokoro pipeline not implemented yet."]) - } - - private func compileIfNeeded(at modelURL: URL) async throws -> URL { - if modelURL.pathExtension == "mlmodelc" { - return modelURL - } - let compiledURL = try await MLModel.compileModel(at: modelURL) - return compiledURL - } -} diff --git a/Vorleser/Services/EPUBService.swift b/Vorleser/Services/EPUBService.swift deleted file mode 100644 index 93012f6..0000000 --- a/Vorleser/Services/EPUBService.swift +++ /dev/null @@ -1,190 +0,0 @@ -import Foundation -import ZIPFoundation -import SwiftSoup - -struct EPUBChapter: Identifiable, Hashable { - let id: UUID - let title: String - let rawText: String - - init(title: String, rawText: String) { - self.id = UUID() - self.title = title - self.rawText = rawText - } -} - -final class EPUBService { - enum EPUBError: Error, LocalizedError { - case missingContainer - case missingRootfile - case missingOPF - case invalidOPF - case missingSpine - - var errorDescription: String? { - switch self { - case .missingContainer: - return "Missing META-INF/container.xml" - case .missingRootfile: - return "container.xml did not include a rootfile" - case .missingOPF: - return "OPF file missing" - case .invalidOPF: - return "OPF parsing failed" - case .missingSpine: - return "OPF spine is empty" - } - } - } - - func extractChapters(from epubURL: URL) throws -> [EPUBChapter] { - let tmpDir = try createTempDirectory() - try unzip(epubURL, to: tmpDir) - - let containerURL = tmpDir.appendingPathComponent("META-INF/container.xml") - guard FileManager.default.fileExists(atPath: containerURL.path) else { - throw EPUBError.missingContainer - } - - let containerData = try Data(contentsOf: containerURL) - let rootfilePath = try parseContainer(data: containerData) - guard let rootfilePath else { - throw EPUBError.missingRootfile - } - - let opfURL = tmpDir.appendingPathComponent(rootfilePath) - guard FileManager.default.fileExists(atPath: opfURL.path) else { - throw EPUBError.missingOPF - } - - let opfData = try Data(contentsOf: opfURL) - let opfResult = try parseOPF(data: opfData) - guard !opfResult.spine.isEmpty else { - throw EPUBError.missingSpine - } - - let baseURL = opfURL.deletingLastPathComponent() - var chapters: [EPUBChapter] = [] - - for idref in opfResult.spine { - guard let href = opfResult.manifest[idref] else { continue } - let contentURL = baseURL.appendingPathComponent(href) - guard FileManager.default.fileExists(atPath: contentURL.path) else { continue } - - let html = try String(contentsOf: contentURL, encoding: .utf8) - let text = try SwiftSoup.parse(html).text() - let title = try extractTitle(from: html) ?? contentURL.deletingPathExtension().lastPathComponent - let cleaned = cleanText(text) - if cleaned.isEmpty { continue } - chapters.append(EPUBChapter(title: title, rawText: cleaned)) - } - - return chapters - } - - private func unzip(_ url: URL, to destination: URL) throws { - let archive = try Archive(url: url, accessMode: .read) - for entry in archive { - let entryURL = destination.appendingPathComponent(entry.path) - let parent = entryURL.deletingLastPathComponent() - if !FileManager.default.fileExists(atPath: parent.path) { - try FileManager.default.createDirectory(at: parent, withIntermediateDirectories: true) - } - _ = try archive.extract(entry, to: entryURL) - } - } - - private func createTempDirectory() throws -> URL { - let base = URL(fileURLWithPath: NSTemporaryDirectory()) - let dir = base.appendingPathComponent("epub_\(UUID().uuidString)", isDirectory: true) - try FileManager.default.createDirectory(at: dir, withIntermediateDirectories: true) - return dir - } - - private func parseContainer(data: Data) throws -> String? { - let parser = XMLParser(data: data) - let delegate = ContainerParser() - parser.delegate = delegate - parser.parse() - return delegate.rootfilePath - } - - private func parseOPF(data: Data) throws -> OPFParseResult { - let parser = XMLParser(data: data) - let delegate = OPFParser() - parser.delegate = delegate - parser.parse() - return OPFParseResult(title: delegate.title?.trimmingCharacters(in: .whitespacesAndNewlines), manifest: delegate.manifest, spine: delegate.spine) - } - - private func extractTitle(from html: String) throws -> String? { - let doc = try SwiftSoup.parse(html) - if let h1 = try doc.select("h1").first() { - return try h1.text() - } - if let title = try doc.select("title").first() { - return try title.text() - } - return nil - } - - private func cleanText(_ text: String) -> String { - let collapsed = text.replacingOccurrences(of: "\\s+", with: " ", options: .regularExpression) - return collapsed.trimmingCharacters(in: .whitespacesAndNewlines) - } -} - -private struct OPFParseResult { - let title: String? - let manifest: [String: String] - let spine: [String] -} - -private final class ContainerParser: NSObject, XMLParserDelegate { - var rootfilePath: String? - - func parser(_ parser: XMLParser, didStartElement elementName: String, namespaceURI: String?, qualifiedName qName: String?, attributes attributeDict: [String : String] = [:]) { - guard elementName.lowercased().contains("rootfile") else { return } - if let path = attributeDict["full-path"] { - rootfilePath = path - } - } -} - -private final class OPFParser: NSObject, XMLParserDelegate { - var manifest: [String: String] = [:] - var spine: [String] = [] - var title: String? - - private var isCollectingTitle = false - private var titleBuffer = "" - - func parser(_ parser: XMLParser, didStartElement elementName: String, namespaceURI: String?, qualifiedName qName: String?, attributes attributeDict: [String : String] = [:]) { - let name = elementName.lowercased() - if name == "item", let id = attributeDict["id"], let href = attributeDict["href"] { - manifest[id] = href - } else if name == "itemref", let idref = attributeDict["idref"] { - spine.append(idref) - } else if name.hasSuffix("title"), title == nil { - isCollectingTitle = true - titleBuffer = "" - } - } - - func parser(_ parser: XMLParser, foundCharacters string: String) { - if isCollectingTitle { - titleBuffer.append(string) - } - } - - func parser(_ parser: XMLParser, didEndElement elementName: String, namespaceURI: String?, qualifiedName qName: String?) { - if isCollectingTitle, elementName.lowercased().hasSuffix("title") { - let trimmed = titleBuffer.trimmingCharacters(in: .whitespacesAndNewlines) - if !trimmed.isEmpty { - title = trimmed - } - isCollectingTitle = false - } - } -} diff --git a/Vorleser/Services/LibraryViewModel.swift b/Vorleser/Services/LibraryViewModel.swift deleted file mode 100644 index 7d554f0..0000000 --- a/Vorleser/Services/LibraryViewModel.swift +++ /dev/null @@ -1,16 +0,0 @@ -import Foundation -import UniformTypeIdentifiers -import SwiftUI - -@MainActor -final class LibraryViewModel: ObservableObject { - @Published private(set) var items: [BookItem] = [] - @Published var isImporting: Bool = false - @Published var lastErrorMessage: String? - - func importPicked(url: URL) { - let title = url.deletingPathExtension().lastPathComponent - let item = BookItem(title: title, sourceURL: url) - items.insert(item, at: 0) - } -} diff --git a/Vorleser/Services/ModelDownloadManager.swift b/Vorleser/Services/ModelDownloadManager.swift deleted file mode 100644 index 1d328b0..0000000 --- a/Vorleser/Services/ModelDownloadManager.swift +++ /dev/null @@ -1,66 +0,0 @@ -import Foundation - -@MainActor -final class ModelDownloadManager: ObservableObject { - enum DownloadState: Equatable { - case idle - case downloading(String) - case completed - case failed(String) - } - - @Published private(set) var state: DownloadState = .idle - @Published private(set) var progress: Double = 0 - - func downloadAll(manifest: ModelManifest) async { - state = .downloading("Preparing download") - progress = 0 - - let allAssets = manifest.assets + manifest.voices + [manifest.tokenizer] - let total = Double(allAssets.count) - var completed = 0.0 - - do { - for asset in allAssets { - state = .downloading("Downloading \(asset.name)") - _ = try await download(asset: asset) - completed += 1 - progress = completed / total - } - state = .completed - } catch { - state = .failed(error.localizedDescription) - } - } - - func localURL(for asset: ModelManifest.Asset) throws -> URL { - let destination = try Self.modelsDirectory().appendingPathComponent(asset.localFolderName, isDirectory: true) - return destination.appendingPathComponent(asset.remoteURL.lastPathComponent) - } - - func download(asset: ModelManifest.Asset) async throws -> URL { - let destination = try Self.modelsDirectory().appendingPathComponent(asset.localFolderName, isDirectory: true) - try FileManager.default.createDirectory(at: destination, withIntermediateDirectories: true) - - let (tempURL, response) = try await URLSession.shared.download(from: asset.remoteURL) - guard let http = response as? HTTPURLResponse, (200..<300).contains(http.statusCode) else { - throw URLError(.badServerResponse) - } - - let finalURL = destination.appendingPathComponent(asset.remoteURL.lastPathComponent) - if FileManager.default.fileExists(atPath: finalURL.path) { - try FileManager.default.removeItem(at: finalURL) - } - try FileManager.default.moveItem(at: tempURL, to: finalURL) - return finalURL - } - - static func modelsDirectory() throws -> URL { - let base = try FileManager.default.url(for: .applicationSupportDirectory, in: .userDomainMask, appropriateFor: nil, create: true) - let folder = base.appendingPathComponent("Models", isDirectory: true) - if !FileManager.default.fileExists(atPath: folder.path) { - try FileManager.default.createDirectory(at: folder, withIntermediateDirectories: true) - } - return folder - } -} diff --git a/Vorleser/Services/ModelManifest.swift b/Vorleser/Services/ModelManifest.swift deleted file mode 100644 index cf6a3bf..0000000 --- a/Vorleser/Services/ModelManifest.swift +++ /dev/null @@ -1,33 +0,0 @@ -import Foundation - -struct ModelManifest { - struct Asset: Hashable { - let name: String - let remoteURL: URL - let localFolderName: String - } - - let modelName: String - let assets: [Asset] - let voices: [Asset] - let tokenizer: Asset - - static func kokoro() -> ModelManifest { - // TODO: Replace URLs with your hosted files. - let base = URL(string: "https://example.com/kokoro/")! - - let assets = [ - Asset(name: "duration", remoteURL: base.appendingPathComponent("kokoro_duration.mlpackage"), localFolderName: "kokoro"), - Asset(name: "decoder_3s", remoteURL: base.appendingPathComponent("KokoroDecoder_HAR_3s.mlpackage"), localFolderName: "kokoro"), - Asset(name: "decoder_10s", remoteURL: base.appendingPathComponent("KokoroDecoder_HAR_10s.mlpackage"), localFolderName: "kokoro") - ] - - let voices = [ - Asset(name: "am_michael", remoteURL: base.appendingPathComponent("voices/am_michael.pt"), localFolderName: "kokoro/voices") - ] - - let tokenizer = Asset(name: "tokenizer", remoteURL: base.appendingPathComponent("checkpoints/config.json"), localFolderName: "kokoro") - - return ModelManifest(modelName: "Kokoro", assets: assets, voices: voices, tokenizer: tokenizer) - } -} diff --git a/Vorleser/Services/ReaderViewModel.swift b/Vorleser/Services/ReaderViewModel.swift deleted file mode 100644 index eb2a544..0000000 --- a/Vorleser/Services/ReaderViewModel.swift +++ /dev/null @@ -1,61 +0,0 @@ -import Foundation -import SwiftUI -import Combine - -@MainActor -final class ReaderViewModel: ObservableObject { - enum PreparationState: Equatable { - case idle - case parsing - case ready(Int) - case failed(String) - } - - @Published private(set) var preparationState: PreparationState = .idle - @Published private(set) var chapters: [EPUBChapter] = [] - @Published var isPreparingModel: Bool = false - - let book: BookItem - let downloadManager = ModelDownloadManager() - - private let epubService = EPUBService() - private var cancellables = Set() - - var modelDownloadState: ModelDownloadManager.DownloadState { - downloadManager.state - } - - var modelDownloadProgress: Double { - downloadManager.progress - } - - init(book: BookItem) { - self.book = book - downloadManager.objectWillChange - .sink { [weak self] _ in - self?.objectWillChange.send() - } - .store(in: &cancellables) - } - - func parseBook() { - preparationState = .parsing - Task { - do { - let chapters = try epubService.extractChapters(from: book.sourceURL) - self.chapters = chapters - preparationState = .ready(chapters.count) - } catch { - preparationState = .failed(error.localizedDescription) - } - } - } - - func downloadModel() { - Task { - isPreparingModel = true - await downloadManager.downloadAll(manifest: .kokoro()) - isPreparingModel = false - } - } -} diff --git a/Vorleser/Services/TextChunker.swift b/Vorleser/Services/TextChunker.swift deleted file mode 100644 index 9dafabc..0000000 --- a/Vorleser/Services/TextChunker.swift +++ /dev/null @@ -1,40 +0,0 @@ -import Foundation -import NaturalLanguage - -struct TextChunker { - let maxCharacters: Int - - init(maxCharacters: Int = 900) { - self.maxCharacters = maxCharacters - } - - func chunk(_ text: String) -> [String] { - let tokenizer = NLTokenizer(unit: .sentence) - tokenizer.string = text - - var chunks: [String] = [] - var current = "" - - tokenizer.enumerateTokens(in: text.startIndex.. maxCharacters { - if !current.isEmpty { - chunks.append(current.trimmingCharacters(in: .whitespacesAndNewlines)) - current = "" - } - } - - current.append(sentence) - current.append(" ") - return true - } - - if !current.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty { - chunks.append(current.trimmingCharacters(in: .whitespacesAndNewlines)) - } - - return chunks - } -} diff --git a/Vorleser/Views/DocumentPicker.swift b/Vorleser/Views/DocumentPicker.swift deleted file mode 100644 index 042286c..0000000 --- a/Vorleser/Views/DocumentPicker.swift +++ /dev/null @@ -1,41 +0,0 @@ -import SwiftUI -import UniformTypeIdentifiers - -struct DocumentPicker: UIViewControllerRepresentable { - let onPick: (URL) -> Void - let onCancel: () -> Void - - func makeUIViewController(context: Context) -> UIDocumentPickerViewController { - let types: [UTType] = [UTType(filenameExtension: "epub")].compactMap { $0 } - let controller = UIDocumentPickerViewController(forOpeningContentTypes: types, asCopy: true) - controller.allowsMultipleSelection = false - controller.delegate = context.coordinator - return controller - } - - func updateUIViewController(_ uiViewController: UIDocumentPickerViewController, context: Context) { - } - - func makeCoordinator() -> Coordinator { - Coordinator(onPick: onPick, onCancel: onCancel) - } - - final class Coordinator: NSObject, UIDocumentPickerDelegate { - private let onPick: (URL) -> Void - private let onCancel: () -> Void - - init(onPick: @escaping (URL) -> Void, onCancel: @escaping () -> Void) { - self.onPick = onPick - self.onCancel = onCancel - } - - func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) { - guard let url = urls.first else { return } - onPick(url) - } - - func documentPickerWasCancelled(_ controller: UIDocumentPickerViewController) { - onCancel() - } - } -} diff --git a/Vorleser/Views/ReaderView.swift b/Vorleser/Views/ReaderView.swift deleted file mode 100644 index 4e09221..0000000 --- a/Vorleser/Views/ReaderView.swift +++ /dev/null @@ -1,96 +0,0 @@ -import SwiftUI - -struct ReaderView: View { - @StateObject private var viewModel: ReaderViewModel - - init(item: BookItem) { - _viewModel = StateObject(wrappedValue: ReaderViewModel(book: item)) - } - - var body: some View { - VStack(alignment: .leading, spacing: 16) { - Text(viewModel.book.title) - .font(.title2.weight(.semibold)) - - modelSection - - Divider() - - parsingSection - - Spacer() - } - .padding(20) - .navigationTitle("Reader") - .navigationBarTitleDisplayMode(.inline) - } - - private var modelSection: some View { - VStack(alignment: .leading, spacing: 8) { - Text("Kokoro Model") - .font(.headline) - Text(modelStatusText) - .foregroundStyle(.secondary) - - switch viewModel.modelDownloadState { - case .idle, .failed: - Button("Download Model") { - viewModel.downloadModel() - } - .buttonStyle(.borderedProminent) - case .downloading: - ProgressView(value: viewModel.modelDownloadProgress) - case .completed: - EmptyView() - } - } - .frame(maxWidth: .infinity, alignment: .leading) - .padding(16) - .background(.thinMaterial) - .clipShape(RoundedRectangle(cornerRadius: 16, style: .continuous)) - } - - private var parsingSection: some View { - VStack(alignment: .leading, spacing: 8) { - Text("EPUB Parsing") - .font(.headline) - Text(parsingStatusText) - .foregroundStyle(.secondary) - - Button("Parse EPUB") { - viewModel.parseBook() - } - .buttonStyle(.bordered) - } - .frame(maxWidth: .infinity, alignment: .leading) - .padding(16) - .background(.thinMaterial) - .clipShape(RoundedRectangle(cornerRadius: 16, style: .continuous)) - } - - private var modelStatusText: String { - switch viewModel.modelDownloadState { - case .idle: - return "Model not downloaded" - case .downloading(let message): - return message - case .completed: - return "Model ready" - case .failed(let message): - return "Download failed: \(message)" - } - } - - private var parsingStatusText: String { - switch viewModel.preparationState { - case .idle: - return "Ready to parse" - case .parsing: - return "Parsing EPUB" - case .ready(let count): - return "Parsed \(count) chapters" - case .failed(let message): - return "Parsing failed: \(message)" - } - } -} diff --git a/Vorleser/VorleserApp.swift b/Vorleser/VorleserApp.swift deleted file mode 100644 index bc0ac50..0000000 --- a/Vorleser/VorleserApp.swift +++ /dev/null @@ -1,10 +0,0 @@ -import SwiftUI - -@main -struct VorleserApp: App { - var body: some Scene { - WindowGroup { - ContentView() - } - } -} diff --git a/VorleserMac/MacContentView.swift b/VorleserMac/MacContentView.swift deleted file mode 100644 index 1331b1a..0000000 --- a/VorleserMac/MacContentView.swift +++ /dev/null @@ -1,100 +0,0 @@ -import SwiftUI - -struct MacContentView: View { - @StateObject private var viewModel = MacLibraryViewModel() - - var body: some View { - VStack(alignment: .leading, spacing: 16) { - Text("Vorleser (macOS)") - .font(.title.bold()) - - HStack(spacing: 12) { - Button("Import EPUB") { - viewModel.pickEPUB() - } - .buttonStyle(.borderedProminent) - - Button("Load Test Text") { - viewModel.loadTestText() - } - .buttonStyle(.bordered) - - Button("Load Debug Phonemes") { - viewModel.loadDebugPhonemes() - } - .buttonStyle(.bordered) - - Button("Verify Bundled Assets") { - viewModel.updateStatus(AssetVerifier.verify().message) - } - .buttonStyle(.bordered) - - Button("Export Last Audio") { - viewModel.exportLastAudio() - } - .buttonStyle(.bordered) - - Button("Play") { - viewModel.playFirstChunk() - } - .buttonStyle(.bordered) - } - - if let url = viewModel.selectedURL { - Text("Selected: \(url.lastPathComponent)") - .font(.callout) - } - - if !viewModel.statusMessage.isEmpty { - Text(viewModel.statusMessage) - .font(.callout) - .foregroundStyle(.secondary) - } - - Divider() - - if viewModel.chapters.isEmpty { - Text("No chapters loaded yet.") - .foregroundStyle(.secondary) - } else { - Picker("Chapter", selection: $viewModel.selectedChapterIndex) { - ForEach(Array(viewModel.chapters.enumerated()), id: \.offset) { index, chapter in - Text(chapter.title).tag(index) - } - } - .onChange(of: viewModel.selectedChapterIndex) { - viewModel.selectChapter(index: viewModel.selectedChapterIndex) - } - - List(selection: $viewModel.selectedChunkIndex) { - ForEach(Array(viewModel.chunks.enumerated()), id: \.offset) { index, chunk in - HStack(alignment: .top, spacing: 12) { - VStack(alignment: .leading, spacing: 6) { - Text("Chunk \(index + 1)") - .font(.headline) - Text(chunk.prefix(240)) - .font(.caption) - .foregroundStyle(.secondary) - } - Spacer() - Button("Play") { - viewModel.playChunk(at: index) - } - .buttonStyle(.bordered) - } - .tag(index) - } - } - .listStyle(.plain) - } - - Spacer() - } - .padding(20) - .frame(minWidth: 720, minHeight: 520) - } -} - -#Preview { - MacContentView() -} diff --git a/VorleserMac/Resources/Config/config.json b/VorleserMac/Resources/Config/config.json deleted file mode 100644 index 14a726e..0000000 --- a/VorleserMac/Resources/Config/config.json +++ /dev/null @@ -1,150 +0,0 @@ -{ - "istftnet": { - "upsample_kernel_sizes": [20, 12], - "upsample_rates": [10, 6], - "gen_istft_hop_size": 5, - "gen_istft_n_fft": 20, - "resblock_dilation_sizes": [ - [1, 3, 5], - [1, 3, 5], - [1, 3, 5] - ], - "resblock_kernel_sizes": [3, 7, 11], - "upsample_initial_channel": 512 - }, - "dim_in": 64, - "dropout": 0.2, - "hidden_dim": 512, - "max_conv_dim": 512, - "max_dur": 50, - "multispeaker": true, - "n_layer": 3, - "n_mels": 80, - "n_token": 178, - "style_dim": 128, - "text_encoder_kernel_size": 5, - "plbert": { - "hidden_size": 768, - "num_attention_heads": 12, - "intermediate_size": 2048, - "max_position_embeddings": 512, - "num_hidden_layers": 12, - "dropout": 0.1 - }, - "vocab": { - ";": 1, - ":": 2, - ",": 3, - ".": 4, - "!": 5, - "?": 6, - "—": 9, - "…": 10, - "\"": 11, - "(": 12, - ")": 13, - "“": 14, - "”": 15, - " ": 16, - "\u0303": 17, - "ʣ": 18, - "ʥ": 19, - "ʦ": 20, - "ʨ": 21, - "ᵝ": 22, - "\uAB67": 23, - "A": 24, - "I": 25, - "O": 31, - "Q": 33, - "S": 35, - "T": 36, - "W": 39, - "Y": 41, - "ᵊ": 42, - "a": 43, - "b": 44, - "c": 45, - "d": 46, - "e": 47, - "f": 48, - "h": 50, - "i": 51, - "j": 52, - "k": 53, - "l": 54, - "m": 55, - "n": 56, - "o": 57, - "p": 58, - "q": 59, - "r": 60, - "s": 61, - "t": 62, - "u": 63, - "v": 64, - "w": 65, - "x": 66, - "y": 67, - "z": 68, - "ɑ": 69, - "ɐ": 70, - "ɒ": 71, - "æ": 72, - "β": 75, - "ɔ": 76, - "ɕ": 77, - "ç": 78, - "ɖ": 80, - "ð": 81, - "ʤ": 82, - "ə": 83, - "ɚ": 85, - "ɛ": 86, - "ɜ": 87, - "ɟ": 90, - "ɡ": 92, - "ɥ": 99, - "ɨ": 101, - "ɪ": 102, - "ʝ": 103, - "ɯ": 110, - "ɰ": 111, - "ŋ": 112, - "ɳ": 113, - "ɲ": 114, - "ɴ": 115, - "ø": 116, - "ɸ": 118, - "θ": 119, - "œ": 120, - "ɹ": 123, - "ɾ": 125, - "ɻ": 126, - "ʁ": 128, - "ɽ": 129, - "ʂ": 130, - "ʃ": 131, - "ʈ": 132, - "ʧ": 133, - "ʊ": 135, - "ʋ": 136, - "ʌ": 138, - "ɣ": 139, - "ɤ": 140, - "χ": 142, - "ʎ": 143, - "ʒ": 147, - "ʔ": 148, - "ˈ": 156, - "ˌ": 157, - "ː": 158, - "ʰ": 162, - "ʲ": 164, - "↓": 169, - "→": 171, - "↗": 172, - "↘": 173, - "ᵻ": 177 - } -} \ No newline at end of file diff --git a/VorleserMac/Resources/Models/kokoro_decoder_only_10s.mlpackage/Data/com.apple.CoreML/model.mlmodel b/VorleserMac/Resources/Models/kokoro_decoder_only_10s.mlpackage/Data/com.apple.CoreML/model.mlmodel deleted file mode 100644 index f6b1817..0000000 Binary files a/VorleserMac/Resources/Models/kokoro_decoder_only_10s.mlpackage/Data/com.apple.CoreML/model.mlmodel and /dev/null differ diff --git a/VorleserMac/Resources/Models/kokoro_decoder_only_10s.mlpackage/Data/com.apple.CoreML/weights/weight.bin b/VorleserMac/Resources/Models/kokoro_decoder_only_10s.mlpackage/Data/com.apple.CoreML/weights/weight.bin deleted file mode 100644 index e469ebe..0000000 Binary files a/VorleserMac/Resources/Models/kokoro_decoder_only_10s.mlpackage/Data/com.apple.CoreML/weights/weight.bin and /dev/null differ diff --git a/VorleserMac/Resources/Models/kokoro_decoder_only_10s.mlpackage/Manifest.json b/VorleserMac/Resources/Models/kokoro_decoder_only_10s.mlpackage/Manifest.json deleted file mode 100644 index 4c2f7f0..0000000 --- a/VorleserMac/Resources/Models/kokoro_decoder_only_10s.mlpackage/Manifest.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "fileFormatVersion": "1.0.0", - "itemInfoEntries": { - "10A41D50-9A6A-46B4-9EE3-17F40605768E": { - "author": "com.apple.CoreML", - "description": "CoreML Model Specification", - "name": "model.mlmodel", - "path": "com.apple.CoreML/model.mlmodel" - }, - "7DC183FF-5C6F-454C-BA3E-8D5898495BEF": { - "author": "com.apple.CoreML", - "description": "CoreML Model Weights", - "name": "weights", - "path": "com.apple.CoreML/weights" - } - }, - "rootModelIdentifier": "10A41D50-9A6A-46B4-9EE3-17F40605768E" -} diff --git a/VorleserMac/Resources/Models/kokoro_decoder_only_3s.mlpackage/Data/com.apple.CoreML/model.mlmodel b/VorleserMac/Resources/Models/kokoro_decoder_only_3s.mlpackage/Data/com.apple.CoreML/model.mlmodel deleted file mode 100644 index 37b2790..0000000 Binary files a/VorleserMac/Resources/Models/kokoro_decoder_only_3s.mlpackage/Data/com.apple.CoreML/model.mlmodel and /dev/null differ diff --git a/VorleserMac/Resources/Models/kokoro_decoder_only_3s.mlpackage/Data/com.apple.CoreML/weights/weight.bin b/VorleserMac/Resources/Models/kokoro_decoder_only_3s.mlpackage/Data/com.apple.CoreML/weights/weight.bin deleted file mode 100644 index e469ebe..0000000 Binary files a/VorleserMac/Resources/Models/kokoro_decoder_only_3s.mlpackage/Data/com.apple.CoreML/weights/weight.bin and /dev/null differ diff --git a/VorleserMac/Resources/Models/kokoro_decoder_only_3s.mlpackage/Manifest.json b/VorleserMac/Resources/Models/kokoro_decoder_only_3s.mlpackage/Manifest.json deleted file mode 100644 index e6703be..0000000 --- a/VorleserMac/Resources/Models/kokoro_decoder_only_3s.mlpackage/Manifest.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "fileFormatVersion": "1.0.0", - "itemInfoEntries": { - "BB7DA7FA-4D92-4B0F-BC42-EA198FC64A57": { - "author": "com.apple.CoreML", - "description": "CoreML Model Specification", - "name": "model.mlmodel", - "path": "com.apple.CoreML/model.mlmodel" - }, - "DC159FC3-A48E-4DB6-9790-A78DE3F5BBD8": { - "author": "com.apple.CoreML", - "description": "CoreML Model Weights", - "name": "weights", - "path": "com.apple.CoreML/weights" - } - }, - "rootModelIdentifier": "BB7DA7FA-4D92-4B0F-BC42-EA198FC64A57" -} diff --git a/VorleserMac/Resources/Models/kokoro_duration.mlpackage/Data/com.apple.CoreML/model.mlmodel b/VorleserMac/Resources/Models/kokoro_duration.mlpackage/Data/com.apple.CoreML/model.mlmodel deleted file mode 100644 index 0f40907..0000000 Binary files a/VorleserMac/Resources/Models/kokoro_duration.mlpackage/Data/com.apple.CoreML/model.mlmodel and /dev/null differ diff --git a/VorleserMac/Resources/Models/kokoro_duration.mlpackage/Data/com.apple.CoreML/weights/weight.bin b/VorleserMac/Resources/Models/kokoro_duration.mlpackage/Data/com.apple.CoreML/weights/weight.bin deleted file mode 100644 index 78af251..0000000 Binary files a/VorleserMac/Resources/Models/kokoro_duration.mlpackage/Data/com.apple.CoreML/weights/weight.bin and /dev/null differ diff --git a/VorleserMac/Resources/Models/kokoro_duration.mlpackage/Manifest.json b/VorleserMac/Resources/Models/kokoro_duration.mlpackage/Manifest.json deleted file mode 100644 index 64669d5..0000000 --- a/VorleserMac/Resources/Models/kokoro_duration.mlpackage/Manifest.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "fileFormatVersion": "1.0.0", - "itemInfoEntries": { - "CF41DE8E-0A19-4F3F-8885-F07154AF4A4B": { - "author": "com.apple.CoreML", - "description": "CoreML Model Specification", - "name": "model.mlmodel", - "path": "com.apple.CoreML/model.mlmodel" - }, - "FAF77E4F-EDB7-4B8C-B0ED-E7F8A42CFE0A": { - "author": "com.apple.CoreML", - "description": "CoreML Model Weights", - "name": "weights", - "path": "com.apple.CoreML/weights" - } - }, - "rootModelIdentifier": "CF41DE8E-0A19-4F3F-8885-F07154AF4A4B" -} diff --git a/VorleserMac/Resources/Models/kokoro_f0n_10s.mlpackage/Data/com.apple.CoreML/model.mlmodel b/VorleserMac/Resources/Models/kokoro_f0n_10s.mlpackage/Data/com.apple.CoreML/model.mlmodel deleted file mode 100644 index fbd3e09..0000000 Binary files a/VorleserMac/Resources/Models/kokoro_f0n_10s.mlpackage/Data/com.apple.CoreML/model.mlmodel and /dev/null differ diff --git a/VorleserMac/Resources/Models/kokoro_f0n_10s.mlpackage/Data/com.apple.CoreML/weights/weight.bin b/VorleserMac/Resources/Models/kokoro_f0n_10s.mlpackage/Data/com.apple.CoreML/weights/weight.bin deleted file mode 100644 index ec4aa5c..0000000 Binary files a/VorleserMac/Resources/Models/kokoro_f0n_10s.mlpackage/Data/com.apple.CoreML/weights/weight.bin and /dev/null differ diff --git a/VorleserMac/Resources/Models/kokoro_f0n_10s.mlpackage/Manifest.json b/VorleserMac/Resources/Models/kokoro_f0n_10s.mlpackage/Manifest.json deleted file mode 100644 index 65495a2..0000000 --- a/VorleserMac/Resources/Models/kokoro_f0n_10s.mlpackage/Manifest.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "fileFormatVersion": "1.0.0", - "itemInfoEntries": { - "5BBD3EBA-A41D-40C8-8D40-76912154E555": { - "author": "com.apple.CoreML", - "description": "CoreML Model Weights", - "name": "weights", - "path": "com.apple.CoreML/weights" - }, - "B66B79D4-8D3F-4589-A5D1-459812919B86": { - "author": "com.apple.CoreML", - "description": "CoreML Model Specification", - "name": "model.mlmodel", - "path": "com.apple.CoreML/model.mlmodel" - } - }, - "rootModelIdentifier": "B66B79D4-8D3F-4589-A5D1-459812919B86" -} diff --git a/VorleserMac/Resources/Models/kokoro_f0n_3s.mlpackage/Data/com.apple.CoreML/model.mlmodel b/VorleserMac/Resources/Models/kokoro_f0n_3s.mlpackage/Data/com.apple.CoreML/model.mlmodel deleted file mode 100644 index a7313c0..0000000 Binary files a/VorleserMac/Resources/Models/kokoro_f0n_3s.mlpackage/Data/com.apple.CoreML/model.mlmodel and /dev/null differ diff --git a/VorleserMac/Resources/Models/kokoro_f0n_3s.mlpackage/Data/com.apple.CoreML/weights/weight.bin b/VorleserMac/Resources/Models/kokoro_f0n_3s.mlpackage/Data/com.apple.CoreML/weights/weight.bin deleted file mode 100644 index ec4aa5c..0000000 Binary files a/VorleserMac/Resources/Models/kokoro_f0n_3s.mlpackage/Data/com.apple.CoreML/weights/weight.bin and /dev/null differ diff --git a/VorleserMac/Resources/Models/kokoro_f0n_3s.mlpackage/Manifest.json b/VorleserMac/Resources/Models/kokoro_f0n_3s.mlpackage/Manifest.json deleted file mode 100644 index e2b632b..0000000 --- a/VorleserMac/Resources/Models/kokoro_f0n_3s.mlpackage/Manifest.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "fileFormatVersion": "1.0.0", - "itemInfoEntries": { - "041E5B67-A9C9-4116-85BC-5726D2583C21": { - "author": "com.apple.CoreML", - "description": "CoreML Model Weights", - "name": "weights", - "path": "com.apple.CoreML/weights" - }, - "2FFC5529-2C09-4D1D-AD15-888AD1E09277": { - "author": "com.apple.CoreML", - "description": "CoreML Model Specification", - "name": "model.mlmodel", - "path": "com.apple.CoreML/model.mlmodel" - } - }, - "rootModelIdentifier": "2FFC5529-2C09-4D1D-AD15-888AD1E09277" -} diff --git a/VorleserMac/Resources/Voices/am_michael.f32 b/VorleserMac/Resources/Voices/am_michael.f32 deleted file mode 100644 index 593ea50..0000000 Binary files a/VorleserMac/Resources/Voices/am_michael.f32 and /dev/null differ diff --git a/VorleserMac/Resources/Voices/am_michael.pt b/VorleserMac/Resources/Voices/am_michael.pt deleted file mode 100644 index b755169..0000000 Binary files a/VorleserMac/Resources/Voices/am_michael.pt and /dev/null differ diff --git a/VorleserMac/Resources/Voices/am_michael_256.f32 b/VorleserMac/Resources/Voices/am_michael_256.f32 deleted file mode 100644 index d3697cb..0000000 Binary files a/VorleserMac/Resources/Voices/am_michael_256.f32 and /dev/null differ diff --git a/VorleserMac/Services/AssetVerifier.swift b/VorleserMac/Services/AssetVerifier.swift deleted file mode 100644 index a59f6b8..0000000 --- a/VorleserMac/Services/AssetVerifier.swift +++ /dev/null @@ -1,57 +0,0 @@ -import Foundation - -struct AssetVerifier { - struct Result { - let ok: Bool - let message: String - } - - static func verify() -> Result { - let bundleRoot = Bundle.main.resourceURL?.path ?? "(unknown)" - let modelCandidates = [ - "kokoro_duration.mlmodelc", - "kokoro_decoder_only_3s.mlmodelc", - "kokoro_duration.mlpackage", - "kokoro_decoder_only_3s.mlpackage" - ] - - let voiceName = "am_michael.pt" - let configName = "config.json" - - var missing: [String] = [] - - let modelFound = modelCandidates.filter { name in - Bundle.main.url(forResource: name, withExtension: nil, subdirectory: "Models") != nil || - Bundle.main.url(forResource: name, withExtension: nil, subdirectory: nil) != nil - } - - if modelFound.isEmpty { - missing.append("Models or root: kokoro_duration + kokoro_decoder_only_3s") - } - - if Bundle.main.url(forResource: voiceName, withExtension: nil, subdirectory: "Voices") == nil, - Bundle.main.url(forResource: voiceName, withExtension: nil, subdirectory: nil) == nil { - missing.append("Voices or root: \(voiceName)") - } - - if Bundle.main.url(forResource: configName, withExtension: nil, subdirectory: "Config") == nil, - Bundle.main.url(forResource: configName, withExtension: nil, subdirectory: nil) == nil { - missing.append("Config or root: \(configName)") - } - - if missing.isEmpty { - return Result(ok: true, message: "All bundled assets found at:\n\(bundleRoot)") - } - - return Result( - ok: false, - message: """ - Missing assets: - - \(missing.joined(separator: "\n- ")) - - Bundle resources path: - \(bundleRoot) - """ - ) - } -} diff --git a/VorleserMac/Services/AudioPlaybackService.swift b/VorleserMac/Services/AudioPlaybackService.swift deleted file mode 100644 index f74f638..0000000 --- a/VorleserMac/Services/AudioPlaybackService.swift +++ /dev/null @@ -1,99 +0,0 @@ -import Foundation -import AVFoundation - -final class AudioPlaybackService { - private let engine = AVAudioEngine() - private let player = AVAudioPlayerNode() - private var isConfigured = false - private var fallbackPlayer: AVAudioPlayer? - private var lastSamples: [Float] = [] - - init() { - configureEngine() - } - - func play(samples: [Float]) { - print("AudioPlaybackService: playing \(samples.count) samples (\(Double(samples.count)/24000.0) seconds)") - lastSamples = samples - if !isConfigured { - configureEngine() - } - let frameCount = AVAudioFrameCount(samples.count) - guard let format = AVAudioFormat(standardFormatWithSampleRate: 24000, channels: 1), - let buffer = AVAudioPCMBuffer(pcmFormat: format, frameCapacity: frameCount) else { - return - } - - buffer.frameLength = frameCount - let channel = buffer.floatChannelData![0] - for i in 0.. URL? { - guard !lastSamples.isEmpty else { return nil } - let frameCount = AVAudioFrameCount(lastSamples.count) - guard let format = AVAudioFormat(standardFormatWithSampleRate: 24000, channels: 1), - let buffer = AVAudioPCMBuffer(pcmFormat: format, frameCapacity: frameCount) else { - return nil - } - buffer.frameLength = frameCount - let channel = buffer.floatChannelData![0] - for i in 0.. [EPUBChapter] { - let tmpDir = try createTempDirectory() - try unzip(epubURL, to: tmpDir) - - let containerURL = tmpDir.appendingPathComponent("META-INF/container.xml") - guard FileManager.default.fileExists(atPath: containerURL.path) else { - throw EPUBError.missingContainer - } - - let containerData = try Data(contentsOf: containerURL) - let rootfilePath = try parseContainer(data: containerData) - guard let rootfilePath else { - throw EPUBError.missingRootfile - } - - let opfURL = tmpDir.appendingPathComponent(rootfilePath) - guard FileManager.default.fileExists(atPath: opfURL.path) else { - throw EPUBError.missingOPF - } - - let opfData = try Data(contentsOf: opfURL) - let opfResult = try parseOPF(data: opfData) - guard !opfResult.spine.isEmpty else { - throw EPUBError.missingSpine - } - - let baseURL = opfURL.deletingLastPathComponent() - var chapters: [EPUBChapter] = [] - - for idref in opfResult.spine { - guard let href = opfResult.manifest[idref] else { continue } - let contentURL = baseURL.appendingPathComponent(href) - guard FileManager.default.fileExists(atPath: contentURL.path) else { continue } - - let html = try String(contentsOf: contentURL, encoding: .utf8) - let text = try SwiftSoup.parse(html).text() - let title = try extractTitle(from: html) ?? contentURL.deletingPathExtension().lastPathComponent - let cleaned = cleanText(text) - if cleaned.isEmpty { continue } - chapters.append(EPUBChapter(title: title, rawText: cleaned)) - } - - return chapters - } - - private func unzip(_ url: URL, to destination: URL) throws { - let archive = try Archive(url: url, accessMode: .read) - for entry in archive { - let entryURL = destination.appendingPathComponent(entry.path) - let parent = entryURL.deletingLastPathComponent() - if !FileManager.default.fileExists(atPath: parent.path) { - try FileManager.default.createDirectory(at: parent, withIntermediateDirectories: true) - } - _ = try archive.extract(entry, to: entryURL) - } - } - - private func createTempDirectory() throws -> URL { - let base = URL(fileURLWithPath: NSTemporaryDirectory()) - let dir = base.appendingPathComponent("epub_\(UUID().uuidString)", isDirectory: true) - try FileManager.default.createDirectory(at: dir, withIntermediateDirectories: true) - return dir - } - - private func parseContainer(data: Data) throws -> String? { - let parser = XMLParser(data: data) - let delegate = ContainerParser() - parser.delegate = delegate - parser.parse() - return delegate.rootfilePath - } - - private func parseOPF(data: Data) throws -> OPFParseResult { - let parser = XMLParser(data: data) - let delegate = OPFParser() - parser.delegate = delegate - parser.parse() - return OPFParseResult(title: delegate.title?.trimmingCharacters(in: .whitespacesAndNewlines), manifest: delegate.manifest, spine: delegate.spine) - } - - private func extractTitle(from html: String) throws -> String? { - let doc = try SwiftSoup.parse(html) - if let h1 = try doc.select("h1").first() { - return try h1.text() - } - if let title = try doc.select("title").first() { - return try title.text() - } - return nil - } - - private func cleanText(_ text: String) -> String { - let collapsed = text.replacingOccurrences(of: "\\s+", with: " ", options: .regularExpression) - return collapsed.trimmingCharacters(in: .whitespacesAndNewlines) - } -} - -private struct OPFParseResult { - let title: String? - let manifest: [String: String] - let spine: [String] -} - -private final class ContainerParser: NSObject, XMLParserDelegate { - var rootfilePath: String? - - func parser(_ parser: XMLParser, didStartElement elementName: String, namespaceURI: String?, qualifiedName qName: String?, attributes attributeDict: [String : String] = [:]) { - guard elementName.lowercased().contains("rootfile") else { return } - if let path = attributeDict["full-path"] { - rootfilePath = path - } - } -} - -private final class OPFParser: NSObject, XMLParserDelegate { - var manifest: [String: String] = [:] - var spine: [String] = [] - var title: String? - - private var isCollectingTitle = false - private var titleBuffer = "" - - func parser(_ parser: XMLParser, didStartElement elementName: String, namespaceURI: String?, qualifiedName qName: String?, attributes attributeDict: [String : String] = [:]) { - let name = elementName.lowercased() - if name == "item", let id = attributeDict["id"], let href = attributeDict["href"] { - manifest[id] = href - } else if name == "itemref", let idref = attributeDict["idref"] { - spine.append(idref) - } else if name.hasSuffix("title"), title == nil { - isCollectingTitle = true - titleBuffer = "" - } - } - - func parser(_ parser: XMLParser, foundCharacters string: String) { - if isCollectingTitle { - titleBuffer.append(string) - } - } - - func parser(_ parser: XMLParser, didEndElement elementName: String, namespaceURI: String?, qualifiedName qName: String?) { - if isCollectingTitle, elementName.lowercased().hasSuffix("title") { - let trimmed = titleBuffer.trimmingCharacters(in: .whitespacesAndNewlines) - if !trimmed.isEmpty { - title = trimmed - } - isCollectingTitle = false - } - } -} diff --git a/VorleserMac/Services/EspeakPhonemizer.swift b/VorleserMac/Services/EspeakPhonemizer.swift deleted file mode 100644 index 5dc96bd..0000000 --- a/VorleserMac/Services/EspeakPhonemizer.swift +++ /dev/null @@ -1,136 +0,0 @@ -import Foundation - -final class EspeakPhonemizer { - enum PhonemizerError: Error, LocalizedError { - case binaryNotFound - case failed(String) - - var errorDescription: String? { - switch self { - case .binaryNotFound: - return "espeak-ng binary not found." - case .failed(let message): - return message - } - } - } - - func phonemize(_ text: String) throws -> String { - let binary = try resolveBinary() - - let process = Process() - process.executableURL = binary - process.arguments = ["-q", "--ipa=3", "-v", "en-us", "--stdin"] - let dataPath = resolveDataPath() - if let dataPath { - let testFile = dataPath.appendingPathComponent("phontab") - if !FileManager.default.fileExists(atPath: testFile.path) { - throw PhonemizerError.failed("espeak-ng data missing phontab at \(testFile.path)") - } - } else { - throw PhonemizerError.failed("espeak-ng data path not found") - } - let frameworksPath = resolveFrameworksPath() - var env = ProcessInfo.processInfo.environment - env["ESPEAK_DATA_PATH"] = dataPath!.path - if let frameworksPath { - let existing = env["DYLD_FALLBACK_LIBRARY_PATH"] ?? "" - let merged = existing.isEmpty ? frameworksPath.path : "\(frameworksPath.path):\(existing)" - env["DYLD_FALLBACK_LIBRARY_PATH"] = merged - } - env["LC_ALL"] = "en_US.UTF-8" - env["LANG"] = "en_US.UTF-8" - process.environment = env - - let inputPipe = Pipe() - let outputPipe = Pipe() - let errorPipe = Pipe() - process.standardInput = inputPipe - process.standardOutput = outputPipe - process.standardError = errorPipe - - try process.run() - - if let data = text.data(using: .utf8) { - inputPipe.fileHandleForWriting.write(data) - } - inputPipe.fileHandleForWriting.closeFile() - - let outputData = outputPipe.fileHandleForReading.readDataToEndOfFile() - let errorData = errorPipe.fileHandleForReading.readDataToEndOfFile() - - process.waitUntilExit() - - if process.terminationStatus != 0 { - let errorMessage = String(data: errorData, encoding: .utf8) ?? "" - let outputMessage = String(data: outputData, encoding: .utf8) ?? "" - let combined = [errorMessage, outputMessage].filter { !$0.isEmpty }.joined(separator: "\n") - let pathInfo = "binary=\(binary.path), data=\(dataPath?.path ?? "nil"), frameworks=\(frameworksPath?.path ?? "nil")" - let fallback = combined.isEmpty ? "espeak-ng failed with exit code \(process.terminationStatus) (\(pathInfo))" : "\(combined)\n(\(pathInfo))" - throw PhonemizerError.failed(fallback) - } - - let phonemes = String(data: outputData, encoding: .utf8) ?? "" - let cleaned = phonemes - .replacingOccurrences(of: "\\r", with: "") - .replacingOccurrences(of: "\\n", with: " ") - .trimmingCharacters(in: .whitespacesAndNewlines) - .replacingOccurrences(of: "\\s+", with: " ", options: .regularExpression) - let filtered = cleaned.unicodeScalars.filter { $0.properties.generalCategory != .format } - let final = String(String.UnicodeScalarView(filtered)) - if final.isEmpty { - throw PhonemizerError.failed("espeak-ng returned empty output") - } - return final - } - - private func resolveBinary() throws -> URL { - if let bundled = Bundle.main.url(forResource: "espeak-ng", withExtension: nil, subdirectory: "Tools") { - return bundled - } - - let candidates = [ - "/opt/homebrew/bin/espeak-ng", - "/usr/local/bin/espeak-ng", - "/usr/bin/espeak-ng" - ] - - for path in candidates { - if FileManager.default.isExecutableFile(atPath: path) { - return URL(fileURLWithPath: path) - } - } - - throw PhonemizerError.binaryNotFound - } - - private func resolveDataPath() -> URL? { - if let bundled = Bundle.main.resourceURL?.appendingPathComponent("Tools/espeak-ng-data"), - FileManager.default.fileExists(atPath: bundled.path) { - return bundled - } - let candidates = [ - "/opt/homebrew/share/espeak-ng-data", - "/usr/local/share/espeak-ng-data", - "/usr/share/espeak-ng-data" - ] - for path in candidates { - if FileManager.default.fileExists(atPath: path) { - return URL(fileURLWithPath: path) - } - } - return nil - } - - private func resolveFrameworksPath() -> URL? { - if let frameworks = Bundle.main.privateFrameworksURL, - FileManager.default.fileExists(atPath: frameworks.path) { - return frameworks - } - let fallback = Bundle.main.bundleURL.appendingPathComponent("Contents/Frameworks") - if FileManager.default.fileExists(atPath: fallback.path) { - return fallback - } - return nil - } -} diff --git a/VorleserMac/Services/KokoroPipeline.swift b/VorleserMac/Services/KokoroPipeline.swift deleted file mode 100644 index 1526c11..0000000 --- a/VorleserMac/Services/KokoroPipeline.swift +++ /dev/null @@ -1,419 +0,0 @@ -import Foundation -import CoreML - -final class KokoroPipeline { - enum PipelineError: Error, LocalizedError { - case modelNotFound(String) - case outputMissing(String) - - var errorDescription: String? { - switch self { - case .modelNotFound(let name): - return "Missing model: \(name)" - case .outputMissing(let name): - return "Missing output: \(name)" - } - } - } - - private let phonemizer = EspeakPhonemizer() - private let tokenizer: KokoroTokenizer - private let durationModel: MLModel - private let decoderModel: MLModel - private let f0nModel: MLModel? - private let bucketFrames: Int - private let bucketF0Frames: Int - private let playbackSpeed: Float = 0.7 - private let f0Scale: Float = 1.25 - private let outputGain: Float = 2.0 - private let voicePack: [Float] - private let voicePackCount: Int - - init() throws { - tokenizer = try KokoroTokenizer.loadFromBundle() - durationModel = try KokoroPipeline.loadModel(named: "kokoro_duration") - // Use 3s bucket for debugging signal quality. - decoderModel = try KokoroPipeline.loadModel(named: "kokoro_decoder_only_3s") - f0nModel = try? KokoroPipeline.loadModel(named: "kokoro_f0n_3s") - bucketFrames = 120 - bucketF0Frames = 240 - voicePack = try KokoroPipeline.loadVoicePack() - voicePackCount = max(1, voicePack.count / 256) - } - - func synthesize(text: String) throws -> [Float] { - // 3s bucket can handle ~45 phonemes at normal speed - let (phonemes, tokenIdsAll, unknown, phonemeScalarCount) = try prepareTokens(from: text, maxTokens: 45) - if !unknown.isEmpty { - print("KokoroTokenizer unknown tokens: \(unknown.prefix(20))") - } - print("Phonemes (preview): \(phonemes.prefix(200))") - return try synthesizeTokenIds(tokenIdsAll, phonemeScalarCount: phonemeScalarCount) - } - - func synthesizeLong(text: String) throws -> [Float] { - let (phonemes, tokenIdsAll, unknown, _) = try prepareTokens(from: text, maxTokens: 4096) - if !unknown.isEmpty { - print("KokoroTokenizer unknown tokens: \(unknown.prefix(20))") - } - print("Phonemes (preview): \(phonemes.prefix(200))") - - let chunkSize = 70 - var chunks: [[Int]] = [] - var idx = 0 - while idx < tokenIdsAll.count { - let end = min(tokenIdsAll.count, idx + chunkSize) - chunks.append(Array(tokenIdsAll[idx.. (String, [Int], [String], Int) { - let phonemes: String - if text.hasPrefix("[PHONEMES]") { - phonemes = text.replacingOccurrences(of: "[PHONEMES]", with: "").trimmingCharacters(in: .whitespacesAndNewlines) - } else { - phonemes = try phonemizer.phonemize(text) - } - let (tokenIdsAll, unknown, phonemeScalarCount) = tokenizer.tokenize(phonemes, maxTokens: maxTokens) - if tokenIdsAll.count > maxTokens { - print("Token count truncated from \(tokenIdsAll.count) to \(maxTokens) for bucket") - } - return (phonemes, tokenIdsAll, unknown, phonemeScalarCount) - } - - private func synthesizeTokenIds(_ tokenIdsAll: [Int], phonemeScalarCount: Int) throws -> [Float] { - let tokenIds = [0] + tokenIdsAll + [0] - print("Token count: \(tokenIds.count) (input: \(tokenIdsAll.count) + 2 for start/end)") - - let inputIds = try MLMultiArray(shape: [1, 128], dataType: .int32) - let attention = try MLMultiArray(shape: [1, 128], dataType: .int32) - for i in 0..<128 { - inputIds[i] = 0 - attention[i] = 0 - } - for (idx, id) in tokenIds.prefix(128).enumerated() { - inputIds[idx] = NSNumber(value: id) - attention[idx] = 1 - } - - let refS = try MLMultiArray(shape: [1, 256], dataType: .float32) - let voiceIndex = max(0, min(voicePackCount - 1, max(1, phonemeScalarCount) - 1)) - let voiceOffset = voiceIndex * 256 - for i in 0..<256 { - refS[i] = NSNumber(value: voicePack[voiceOffset + i]) - } - - let speed = try MLMultiArray(shape: [1], dataType: .float32) - speed[0] = NSNumber(value: playbackSpeed) - - let durationProvider = try MLDictionaryFeatureProvider(dictionary: [ - "input_ids": MLFeatureValue(multiArray: inputIds), - "attention_mask": MLFeatureValue(multiArray: attention), - "ref_s": MLFeatureValue(multiArray: refS), - "speed": MLFeatureValue(multiArray: speed) - ]) - let durationOutput = try durationModel.prediction(from: durationProvider) - - guard let d = durationOutput.featureValue(for: "d")?.multiArrayValue else { - throw PipelineError.outputMissing("d") - } - guard let tEn = durationOutput.featureValue(for: "t_en")?.multiArrayValue else { - throw PipelineError.outputMissing("t_en") - } - guard let s = durationOutput.featureValue(for: "s")?.multiArrayValue else { - throw PipelineError.outputMissing("s") - } - guard let predDur = durationOutput.featureValue(for: "pred_dur")?.multiArrayValue else { - throw PipelineError.outputMissing("pred_dur") - } - let refSOut = durationOutput.featureValue(for: "ref_s_out")?.multiArrayValue ?? refS - - let tokenCount = min(tokenIds.count, 128) - let durations = makeDurations(predDur: predDur, tokenCount: tokenCount, targetFrames: bucketFrames) - print("Durations sum: \(durations.reduce(0, +)) for tokenCount \(tokenCount)") - let predAln = buildAlignment(tokenCount: tokenCount, targetFrames: bucketFrames, durations: durations) - let asr = buildAligned(tensor: tEn, channels: 512, tokenCount: tokenCount, alignment: predAln, tokenAxis: 2, channelAxis: 1, frameCount: bucketFrames) - let en = buildAligned(tensor: d, channels: 640, tokenCount: tokenCount, alignment: predAln, tokenAxis: 1, channelAxis: 2, frameCount: bucketFrames) - - let (f0, n) = try predictF0N(en: en, s: s) - - print("d min/max: \(minMax(d))") - print("d shape/strides: \(d.shape) / \(d.strides)") - print("t_en min/max: \(minMax(tEn))") - print("t_en shape/strides: \(tEn.shape) / \(tEn.strides)") - print("s min/max: \(minMax(s))") - print("s shape/strides: \(s.shape) / \(s.strides)") - print("asr min/max: \(minMax(asr))") - print("asr shape/strides: \(asr.shape) / \(asr.strides)") - print("en min/max: \(minMax(en))") - print("en shape/strides: \(en.shape) / \(en.strides)") - print("F0 min/max: \(minMax(f0))") - print("F0 shape/strides: \(f0.shape) / \(f0.strides)") - print("N min/max: \(minMax(n))") - print("N shape/strides: \(n.shape) / \(n.strides)") - - let decoderProvider = try MLDictionaryFeatureProvider(dictionary: [ - "asr": MLFeatureValue(multiArray: asr), - "F0_pred": MLFeatureValue(multiArray: f0), - "N_pred": MLFeatureValue(multiArray: n), - "ref_s": MLFeatureValue(multiArray: refSOut) - ]) - let decoderOutput = try decoderModel.prediction(from: decoderProvider) - - guard let waveform = decoderOutput.featureValue(for: "waveform")?.multiArrayValue else { - throw PipelineError.outputMissing("waveform") - } - print("waveform shape/strides: \(waveform.shape) / \(waveform.strides) count=\(waveform.count)") - - var samples = waveformToArray(waveform) - applyGain(&samples, gain: outputGain) - if let min = samples.min(), let max = samples.max() { - print("Waveform min/max: \(min) / \(max)") - } - return samples - } - - private func concatenateWithCrossfade(chunks: [[Float]], crossfade: Int) -> [Float] { - guard var output = chunks.first else { return [] } - for chunk in chunks.dropFirst() { - let fadeCount = min(crossfade, min(output.count, chunk.count)) - let start = output.count - fadeCount - for i in 0.. fadeCount { - output.append(contentsOf: chunk[fadeCount...]) - } - } - return output - } - - private static func loadModel(named name: String) throws -> MLModel { - if let url = Bundle.main.url(forResource: name, withExtension: "mlmodelc", subdirectory: "Models") ?? - Bundle.main.url(forResource: name, withExtension: "mlmodelc", subdirectory: nil) { - return try MLModel(contentsOf: url) - } - throw PipelineError.modelNotFound(name) - } - - private func makeDurations(predDur: MLMultiArray, tokenCount: Int, targetFrames: Int) -> [Int] { - var raw: [Int] = [] - raw.reserveCapacity(tokenCount) - for i in 0.. targetFrames { - var i = scaled.count - 1 - while current > targetFrames && i >= 0 { - if scaled[i] > 1 { - scaled[i] -= 1 - current -= 1 - } else { - i -= 1 - } - } - } else if current < targetFrames, let last = scaled.indices.last { - scaled[last] += (targetFrames - current) - } - return scaled - } - - private func buildAlignment(tokenCount: Int, targetFrames: Int, durations: [Int]) -> [[Float]] { - var alignment = Array(repeating: Array(repeating: Float(0), count: targetFrames), count: tokenCount) - var cursor = 0 - for i in 0..= targetFrames { break } - for f in cursor.. MLMultiArray { - let output = try! MLMultiArray(shape: [1, NSNumber(value: channels), NSNumber(value: frameCount)], dataType: .float32) - let shape = tensor.shape.map { $0.intValue } - let strides = tensor.strides.map { $0.intValue } - let availableTokens = min(tokenCount, shape[tokenAxis]) - let availableChannels = min(channels, shape[channelAxis]) - - func offset(channel: Int, token: Int) -> Int { - // assumes batch dimension = 1 at axis 0 - if strides.count >= 3 { - var off = 0 - if channelAxis == 1 && tokenAxis == 2 { - off = channel * strides[1] + token * strides[2] - } else if tokenAxis == 1 && channelAxis == 2 { - off = token * strides[1] + channel * strides[2] - } else { - // fallback for unexpected layouts - off = channel * strides[max(channelAxis, 1)] + token * strides[max(tokenAxis, 1)] - } - return off - } - if strides.count == 2 { - return channel * strides[0] + token * strides[1] - } - return channel * (shape.last ?? 0) + token - } - - // First pass: compute weighted sum - for h in 0..= tensor.count { continue } - let value = tensor[idx].floatValue - if !value.isFinite { continue } - sum += value * weight - } - let outIndex = h * frameCount + f - output[outIndex] = NSNumber(value: sum) - } - } - - // Second pass: per-channel min-max normalization - var channelMin = [Float](repeating: Float.greatestFiniteMagnitude, count: availableChannels) - var channelMax = [Float](repeating: -Float.greatestFiniteMagnitude, count: availableChannels) - - for h in 0.. channelMax[h] { channelMax[h] = value } - } - } - } - - for h in 0.. 1e-6 { - for f in 0.. (MLMultiArray, MLMultiArray) { - guard let f0nModel else { - let f0 = try MLMultiArray(shape: [1, NSNumber(value: bucketF0Frames)], dataType: .float32) - let n = try MLMultiArray(shape: [1, NSNumber(value: bucketF0Frames)], dataType: .float32) - for i in 0.. String { - var minVal = Float.greatestFiniteMagnitude - var maxVal = -Float.greatestFiniteMagnitude - for i in 0.. maxVal { maxVal = v } - } - if minVal == Float.greatestFiniteMagnitude { return "n/a" } - return "\(minVal) / \(maxVal)" - } - - private static func loadVoicePack() throws -> [Float] { - guard let url = Bundle.main.url(forResource: "am_michael", withExtension: "f32", subdirectory: "Voices") ?? - Bundle.main.url(forResource: "am_michael", withExtension: "f32", subdirectory: nil) else { - throw CocoaError(.fileNoSuchFile) - } - let data = try Data(contentsOf: url) - let count = data.count / MemoryLayout.size - return data.withUnsafeBytes { rawPtr in - let buffer = rawPtr.bindMemory(to: Float.self) - return Array(buffer.prefix(count)) - } - } - - private func waveformToArray(_ array: MLMultiArray) -> [Float] { - let count = array.count - var samples = [Float](repeating: 0, count: count) - for i in 0.. 1 { v = 1 } - if v < -1 { v = -1 } - samples[i] = v - } - } -} diff --git a/VorleserMac/Services/KokoroTokenizer.swift b/VorleserMac/Services/KokoroTokenizer.swift deleted file mode 100644 index 89f29ef..0000000 --- a/VorleserMac/Services/KokoroTokenizer.swift +++ /dev/null @@ -1,53 +0,0 @@ -import Foundation - -struct KokoroTokenizer { - let vocab: [String: Int] - - init(vocab: [String: Int]) { - self.vocab = vocab - } - - static func loadFromBundle() throws -> KokoroTokenizer { - guard let url = Bundle.main.url(forResource: "config", withExtension: "json", subdirectory: "Config") ?? - Bundle.main.url(forResource: "config", withExtension: "json", subdirectory: nil) else { - throw CocoaError(.fileNoSuchFile) - } - - let data = try Data(contentsOf: url) - let object = try JSONSerialization.jsonObject(with: data) - guard - let dict = object as? [String: Any], - let vocab = dict["vocab"] as? [String: Int] - else { - throw CocoaError(.fileReadCorruptFile) - } - - return KokoroTokenizer(vocab: vocab) - } - - func tokenize(_ phonemes: String, maxTokens: Int = 128) -> (ids: [Int], unknown: [String], scalarCount: Int) { - let cleanedScalars = phonemes.unicodeScalars.filter { $0.properties.generalCategory != .format } - let cleanedView = String.UnicodeScalarView(cleanedScalars) - var ids: [Int] = [] - var unknown: [String] = [] - - for scalar in cleanedView { - let token = String(scalar) - if let id = vocab[token] { - ids.append(id) - } else { - unknown.append(token) - } - if ids.count >= maxTokens { - break - } - } - - if !unknown.isEmpty { - print("Tokenizer: \(unknown.count) unknown tokens: \(unknown.prefix(30))") - } - print("Tokenizer: \(ids.count) ids from \(cleanedView.count) scalars, unknown: \(unknown.count)") - - return (ids, unknown, cleanedView.count) - } -} diff --git a/VorleserMac/Services/MacLibraryViewModel.swift b/VorleserMac/Services/MacLibraryViewModel.swift deleted file mode 100644 index 2196c09..0000000 --- a/VorleserMac/Services/MacLibraryViewModel.swift +++ /dev/null @@ -1,140 +0,0 @@ -import Foundation -import AppKit - -@MainActor -final class MacLibraryViewModel: ObservableObject { - @Published private(set) var selectedURL: URL? - @Published private(set) var chapters: [EPUBChapter] = [] - @Published private(set) var chunks: [String] = [] - @Published var selectedChapterIndex: Int = 0 - @Published var selectedChunkIndex: Int? - @Published private(set) var chunkedCount: Int = 0 - @Published private(set) var statusMessage: String = "" - - private let epubService = EPUBService() - private let chunker = TextChunker(maxCharacters: 80) - private let audioPlayer = AudioPlaybackService() - private let synthesisWorker = SynthesisWorker() - - func pickEPUB() { - let panel = NSOpenPanel() - panel.allowedContentTypes = [.init(filenameExtension: "epub")].compactMap { $0 } - panel.allowsMultipleSelection = false - panel.canChooseDirectories = false - panel.canChooseFiles = true - - panel.begin { [weak self] result in - guard result == .OK, let url = panel.urls.first else { return } - self?.loadEPUB(url: url) - } - } - - func loadTestText() { - selectedURL = nil - let chapter = EPUBChapter( - title: "Test Chapter", - rawText: """ - When magic returns to the Earth, its power calls Sam Verner. As Sam searches for his sister through the slick and scary streets of 2050, his quest leads him across the ocean to England, where druids guard secrets older than cities. - - He follows rumors into crowded markets and dark alleyways, bargaining with strangers and tracing clues in a language he barely understands. Every answer creates another question, and every friend might also be a rival. In a world where technology and spellcraft collide, survival requires equal parts courage and caution. - """ - ) - chapters = [chapter] - selectedChapterIndex = 0 - chunks = chunker.chunk(chapter.rawText) - selectedChunkIndex = chunks.isEmpty ? nil : 0 - chunkedCount = chunks.count - statusMessage = "Loaded test text (1 chapter, \(chunkedCount) chunks)" - } - - func loadDebugPhonemes() { - selectedURL = nil - let phonemes = "[PHONEMES]hˌW ɑɹ ju tədˈA? ˌI ɐm dˈuɪŋ ɹˈizənəbli wˈɛl, θˈæŋk ju fɔɹ ˈæskɪŋ" - let chapter = EPUBChapter( - title: "Debug Phonemes", - rawText: phonemes - ) - chapters = [chapter] - selectedChapterIndex = 0 - chunks = chunker.chunk(chapter.rawText) - selectedChunkIndex = chunks.isEmpty ? nil : 0 - chunkedCount = chunks.count - statusMessage = "Loaded debug phonemes (1 chapter, \(chunkedCount) chunks)" - } - - func updateStatus(_ message: String) { - statusMessage = message - } - - private func loadEPUB(url: URL) { - selectedURL = url - statusMessage = "Parsing EPUB…" - - Task { [weak self] in - do { - guard let self else { return } - let chapters = try self.epubService.extractChapters(from: url) - let chunkCount = chapters.reduce(0) { total, chapter in - total + self.chunker.chunk(chapter.rawText).count - } - - self.chapters = chapters - self.selectedChapterIndex = 0 - self.chunks = chapters.first.map { self.chunker.chunk($0.rawText) } ?? [] - self.selectedChunkIndex = self.chunks.isEmpty ? nil : 0 - self.chunkedCount = chunkCount - self.statusMessage = "Parsed \(chapters.count) chapters, \(chunkCount) chunks" - } catch { - self?.statusMessage = "Failed: \(error.localizedDescription)" - } - } - } - - func selectChapter(index: Int) { - guard index >= 0 && index < chapters.count else { return } - selectedChapterIndex = index - chunks = chunker.chunk(chapters[index].rawText) - selectedChunkIndex = chunks.isEmpty ? nil : 0 - } - - func synthesizeSelectedChunk() { - guard !chunks.isEmpty else { - statusMessage = "No chunks to synthesize." - return - } - - let index = min(selectedChunkIndex ?? 0, chunks.count - 1) - let text = chunks[index] - print("DEBUG: Playing chunk \(index + 1)/\(chunks.count), text: \(text)") - statusMessage = "Phonemizing and synthesizing chunk \(index + 1)/\(chunks.count)…" - - Task { - do { - let samples = try await synthesisWorker.synthesize(text: text) - audioPlayer.play(samples: samples) - statusMessage = "Playing synthesized audio." - } catch { - statusMessage = "Synthesis failed: \(error.localizedDescription) (\(String(describing: error)))" - } - } - } - - func playFirstChunk() { - selectedChunkIndex = 0 - synthesizeSelectedChunk() - } - - func playChunk(at index: Int) { - guard index >= 0 && index < chunks.count else { return } - selectedChunkIndex = index - synthesizeSelectedChunk() - } - - func exportLastAudio() { - if let url = audioPlayer.exportLastWav() { - statusMessage = "Exported last audio to \(url.path)" - } else { - statusMessage = "No audio to export yet." - } - } -} diff --git a/VorleserMac/Services/SynthesisWorker.swift b/VorleserMac/Services/SynthesisWorker.swift deleted file mode 100644 index c2c8c2d..0000000 --- a/VorleserMac/Services/SynthesisWorker.swift +++ /dev/null @@ -1,34 +0,0 @@ -import Foundation - -actor SynthesisWorker { - enum WorkerError: Error, LocalizedError { - case pipelineUnavailable - - var errorDescription: String? { - switch self { - case .pipelineUnavailable: - return "Kokoro pipeline failed to initialize." - } - } - } - - private let pipeline: KokoroPipeline? - - init() { - pipeline = try? KokoroPipeline() - } - - func synthesize(text: String) throws -> [Float] { - guard let pipeline else { - throw WorkerError.pipelineUnavailable - } - return try pipeline.synthesize(text: text) - } - - func synthesizeLong(text: String) throws -> [Float] { - guard let pipeline else { - throw WorkerError.pipelineUnavailable - } - return try pipeline.synthesizeLong(text: text) - } -} diff --git a/VorleserMac/Services/TextChunker.swift b/VorleserMac/Services/TextChunker.swift deleted file mode 100644 index 997f71e..0000000 --- a/VorleserMac/Services/TextChunker.swift +++ /dev/null @@ -1,85 +0,0 @@ -import Foundation -import NaturalLanguage - -struct TextChunker { - let maxCharacters: Int - - init(maxCharacters: Int = 900) { - self.maxCharacters = maxCharacters - } - - func chunk(_ text: String) -> [String] { - let tokenizer = NLTokenizer(unit: .sentence) - tokenizer.string = text - - var chunks: [String] = [] - var current = "" - - tokenizer.enumerateTokens(in: text.startIndex.. maxCharacters { - if !current.isEmpty { - chunks.append(current.trimmingCharacters(in: .whitespacesAndNewlines)) - current = "" - } - } - current.append(part) - current.append(" ") - } - return true - } - - if !current.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty { - chunks.append(current.trimmingCharacters(in: .whitespacesAndNewlines)) - } - - return chunks - } - - private func splitIfNeeded(_ sentence: String) -> [String] { - if sentence.count <= maxCharacters { - return [sentence] - } - let clauseSeparators = CharacterSet(charactersIn: ",;:") - var clauses: [String] = [] - var current = "" - for scalar in sentence.unicodeScalars { - current.unicodeScalars.append(scalar) - if clauseSeparators.contains(scalar) { - clauses.append(current.trimmingCharacters(in: .whitespacesAndNewlines)) - current = "" - } - } - if !current.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty { - clauses.append(current.trimmingCharacters(in: .whitespacesAndNewlines)) - } - if clauses.allSatisfy({ $0.count <= maxCharacters }) { - return clauses - } - return splitByWords(sentence) - } - - private func splitByWords(_ sentence: String) -> [String] { - let words = sentence.split(whereSeparator: { $0.isWhitespace }) - var parts: [String] = [] - var current = "" - for word in words { - if current.count + word.count + 1 > maxCharacters { - if !current.isEmpty { - parts.append(current.trimmingCharacters(in: .whitespacesAndNewlines)) - current = "" - } - } - current.append(contentsOf: word) - current.append(" ") - } - if !current.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty { - parts.append(current.trimmingCharacters(in: .whitespacesAndNewlines)) - } - return parts - } -} diff --git a/VorleserMac/VorleserMacApp.swift b/VorleserMac/VorleserMacApp.swift deleted file mode 100644 index a7c7dd1..0000000 --- a/VorleserMac/VorleserMacApp.swift +++ /dev/null @@ -1,11 +0,0 @@ -import SwiftUI - -@main -struct VorleserMacApp: App { - var body: some Scene { - WindowGroup { - MacContentView() - } - .windowStyle(.titleBar) - } -} diff --git a/project.yml b/project.yml index 932c0b9..2e54948 100644 --- a/project.yml +++ b/project.yml @@ -31,6 +31,8 @@ targets: settings: base: PRODUCT_BUNDLE_IDENTIFIER: de.felixfoertsch.vorleser + MARKETING_VERSION: "2026.03.13" + CURRENT_PROJECT_VERSION: "1" INFOPLIST_VALUES: >- UIBackgroundModes=(audio); UILaunchScreen={}; @@ -61,6 +63,8 @@ targets: settings: base: PRODUCT_BUNDLE_IDENTIFIER: de.felixfoertsch.vorleser.mac + MARKETING_VERSION: "2026.03.13" + CURRENT_PROJECT_VERSION: "1" resources: - path: Resources/Models buildPhase: resources