main
dart 161 lines 4.99 KB
Raw
1 import 'dart:async';
2 import 'dart:io';
3
4 import 'package:dio/dio.dart';
5 import 'package:path_provider/path_provider.dart';
6 import 'package:wakelock_plus/wakelock_plus.dart';
7
8 import 'model_downloader.dart';
9 import 'piper_tts_model.dart';
10 import 'sherpa_bundle_downloader.dart';
11
12 class PiperTtsDownloader {
13 final Dio _dio;
14 final ModelDownloader _baseDownloader;
15 final Map<PiperTtsModelVariant, CancelToken> _cancelTokens = {};
16
17 PiperTtsDownloader() : _dio = Dio(), _baseDownloader = ModelDownloader();
18
19 Future<Directory> _getEngineDir() async {
20 final ttsDir = await _baseDownloader.getTtsDir();
21 final dir = Directory('${ttsDir.path}/piper');
22 if (!await dir.exists()) {
23 await dir.create(recursive: true);
24 }
25 return dir;
26 }
27
28 Future<Directory> _getVariantDir(PiperTtsModelVariant variant) async {
29 final engineDir = await _getEngineDir();
30 return Directory('${engineDir.path}/${variant.bundleDirName}');
31 }
32
33 Future<Set<PiperTtsModelVariant>> getDownloadedVariants() async {
34 final downloaded = <PiperTtsModelVariant>{};
35 for (final variant in PiperTtsModelVariant.values) {
36 final dir = await _getVariantDir(variant);
37 final modelFile = File('${dir.path}/${variant.modelFileName}');
38 final tokensFile = File('${dir.path}/${variant.tokensFileName}');
39 final dataDir = Directory('${dir.path}/${variant.dataDirName}');
40 if (await modelFile.exists() &&
41 await tokensFile.exists() &&
42 await dataDir.exists()) {
43 downloaded.add(variant);
44 }
45 }
46 return downloaded;
47 }
48
49 Future<bool> isVariantDownloaded(PiperTtsModelVariant variant) async {
50 final dir = await _getVariantDir(variant);
51 return await File('${dir.path}/${variant.modelFileName}').exists() &&
52 await File('${dir.path}/${variant.tokensFileName}').exists() &&
53 await Directory('${dir.path}/${variant.dataDirName}').exists();
54 }
55
56 Stream<PiperTtsFileProgress> downloadModel(PiperTtsModelVariant variant) {
57 return Stream<PiperTtsFileProgress>.multi((controller) async {
58 final cancelToken = CancelToken();
59 _cancelTokens[variant] = cancelToken;
60 final engineDir = await _getEngineDir();
61 await WakelockPlus.enable();
62
63 try {
64 final tempDir = await getTemporaryDirectory();
65 final archivePath = '${tempDir.path}/${variant.bundleDirName}.tar.bz2';
66 final archiveFile = File(archivePath);
67 final fileName = '${variant.bundleDirName}.tar.bz2';
68
69 controller.add(
70 PiperTtsFileProgress(
71 fileName: fileName,
72 receivedBytes: 0,
73 totalBytes: variant.totalSizeBytes,
74 isComplete: false,
75 ),
76 );
77
78 await _dio.download(
79 variant.tarballUrl,
80 archiveFile.path,
81 cancelToken: cancelToken,
82 onReceiveProgress: (received, total) {
83 final totalBytes = total > 0 ? total : variant.totalSizeBytes;
84 controller.add(
85 PiperTtsFileProgress(
86 fileName: fileName,
87 receivedBytes: received,
88 totalBytes: totalBytes,
89 isComplete: false,
90 ),
91 );
92 },
93 );
94
95 if (cancelToken.isCancelled) {
96 return;
97 }
98
99 await extractTarBz2(
100 archivePath: archiveFile.path,
101 targetDir: engineDir,
102 );
103
104 if (await archiveFile.exists()) {
105 await archiveFile.delete();
106 }
107
108 controller.add(
109 PiperTtsFileProgress(
110 fileName: fileName,
111 receivedBytes: variant.totalSizeBytes,
112 totalBytes: variant.totalSizeBytes,
113 isComplete: true,
114 ),
115 );
116 } catch (e, st) {
117 if (e is DioException && CancelToken.isCancel(e)) {
118 // Ignore user cancellations.
119 } else {
120 controller.addError(e, st);
121 }
122 } finally {
123 _cancelTokens.remove(variant);
124 if (_cancelTokens.isEmpty) {
125 await WakelockPlus.disable();
126 }
127 await controller.close();
128 }
129 });
130 }
131
132 void cancelDownload(PiperTtsModelVariant variant) {
133 _cancelTokens[variant]?.cancel('User cancelled');
134 _cancelTokens.remove(variant);
135 }
136
137 Future<void> deleteVariant(PiperTtsModelVariant variant) async {
138 final dir = await _getVariantDir(variant);
139 await deleteDirectoryIfExists(dir);
140 }
141
142 Future<String?> getModelPath(PiperTtsModelVariant variant) async {
143 final dir = await _getVariantDir(variant);
144 final file = File('${dir.path}/${variant.modelFileName}');
145 if (await file.exists()) return file.path;
146 return null;
147 }
148
149 Future<String?> getTokensPath(PiperTtsModelVariant variant) async {
150 final dir = await _getVariantDir(variant);
151 final file = File('${dir.path}/${variant.tokensFileName}');
152 if (await file.exists()) return file.path;
153 return null;
154 }
155
156 Future<Directory> getDataDir(PiperTtsModelVariant variant) async {
157 return Directory(
158 '${(await _getVariantDir(variant)).path}/${variant.dataDirName}',
159 );
160 }
161 }