feat: export vtt

This commit is contained in:
My-Responsitories
2026-06-13 12:00:02 +08:00
parent 3085d58508
commit 955fdc17e2

View File

@@ -1147,61 +1147,99 @@ class HeaderControlState extends State<HeaderControl>
); );
} }
Future<_SubtitleFormat?> _showFormatDialog() {
return showDialog<_SubtitleFormat>(
context: context,
builder: (context) => SimpleDialog(
title: const Text('选择格式'),
children: [
SimpleDialogOption(
onPressed: () => Get.back(result: _SubtitleFormat.json),
child: const Text('JSON'),
),
SimpleDialogOption(
onPressed: () => Get.back(result: _SubtitleFormat.vtt),
child: const Text('WEBVTT'),
),
],
),
);
}
void onExportSubtitle() { void onExportSubtitle() {
showDialog( showDialog(
context: context, context: context,
builder: (context) => SimpleDialog( builder: (context) {
clipBehavior: Clip.hardEdge, final subtitles = videoDetailCtr.subtitles;
contentPadding: const EdgeInsets.fromLTRB(0, 12, 0, 12), return SimpleDialog(
title: const Text('保存字幕'), clipBehavior: Clip.hardEdge,
children: videoDetailCtr.subtitles contentPadding: const EdgeInsets.fromLTRB(0, 12, 0, 12),
.map( title: const Text('保存字幕'),
(item) => SimpleDialogOption( children: List.generate(subtitles.length, (i) {
onPressed: () async { final item = subtitles[i];
Get.back(); return SimpleDialogOption(
final url = item.subtitleUrl; onPressed: () async {
if (url == null || url.isEmpty) return; Get.back();
try { final url = item.subtitleUrl;
final res = await Request.dio.get<Uint8List>( if (url == null || url.isEmpty) return;
url.http2https, final format = await _showFormatDialog();
options: Options( if (format == null) return;
responseType: ResponseType.bytes, try {
headers: Constants.baseHeaders, final Uint8List bytes;
extra: {'account': const NoAccount()}, switch (format) {
), case .vtt:
); var subtitle = videoDetailCtr.vttSubtitles[i];
if (res.statusCode == 200) { if (subtitle == null) {
final bytes = Uint8List.fromList( final res = await VideoHttp.vttSubtitles(
item.subtitleUrl!,
);
if (res == null) return;
subtitle = (isData: true, id: res);
videoDetailCtr.vttSubtitles[i] = subtitle;
}
bytes = utf8.encode(subtitle.id);
case .json:
final res = await Request.dio.get<Uint8List>(
url.http2https,
options: Options(
responseType: ResponseType.bytes,
headers: Constants.baseHeaders,
extra: {'account': const NoAccount()},
),
);
if (res.statusCode != 200) return;
bytes = Uint8List.fromList(
Request.responseBytesDecoder( Request.responseBytesDecoder(
res.data!, res.data!,
res.headers.map, res.headers.map,
), ),
); );
String name =
'${introController.videoDetail.value.title}-${videoDetailCtr.bvid}-${videoDetailCtr.cid.value}-${item.lanDoc}.json';
if (Platform.isWindows) {
// Reserved characters may not be used in file names. See: https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file#naming-conventions
name = name.replaceAll(
RegExp(r'[<>:/\\|?*"]'),
'',
);
}
StorageUtils.saveBytes2File(
name: name,
bytes: bytes,
allowedExtensions: const ['json'],
);
}
} catch (e, s) {
Utils.reportError(e, s);
SmartDialog.showToast(e.toString());
} }
}, String name =
child: Text(item.lanDoc!, style: const TextStyle(fontSize: 14)), '${introController.videoDetail.value.title}-${videoDetailCtr.bvid}-${videoDetailCtr.cid.value}-${item.lanDoc}.${format.name}';
// Reserved characters may not be used in file names. See: https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file#naming-conventions
name = name.replaceAll(
Platform.isWindows ? RegExp(r'[<>:/\\|?*"]') : '/',
'_',
);
StorageUtils.saveBytes2File(
name: name,
bytes: bytes,
allowedExtensions: [format.name],
);
} catch (e, s) {
Utils.reportError(e, s);
SmartDialog.showToast(e.toString());
}
},
child: Text(
item.lanDoc!,
style: const TextStyle(fontSize: 14),
), ),
) );
.toList(), }),
), );
},
); );
} }
@@ -2055,3 +2093,5 @@ class HeaderControlState extends State<HeaderControl>
); );
} }
} }
enum _SubtitleFormat { json, vtt }