Add subtitle support for video downloads and playback

This commit is contained in:
Moustapha Kodjo Amadou 2025-07-09 13:11:04 +01:00
parent b2fa09c214
commit 8eaeca2123
5 changed files with 81 additions and 4 deletions

View file

@ -268,7 +268,7 @@ class _AnimeStreamPageState extends riv.ConsumerState<AnimeStreamPage>
);
final file = defaultTrack.file ?? "";
final label = defaultTrack.label;
final track = file.startsWith("http")
final track = (file.startsWith("http") || file.startsWith("file"))
? SubtitleTrack.uri(file, title: label, language: label)
: SubtitleTrack.data(file, title: label, language: label);
_player.setSubtitleTrack(track);

View file

@ -10,6 +10,7 @@ import 'package:mangayomi/main.dart';
import 'package:mangayomi/models/chapter.dart';
import 'package:mangayomi/models/download.dart';
import 'package:mangayomi/models/settings.dart';
import 'package:mangayomi/models/video.dart';
import 'package:mangayomi/modules/manga/download/providers/convert_to_cbz.dart';
import 'package:mangayomi/modules/more/settings/downloads/providers/downloads_state_provider.dart';
import 'package:mangayomi/providers/l10n_providers.dart';
@ -75,7 +76,7 @@ Future<void> downloadChapter(
final mangaMainDirectory = await storageProvider.getMangaMainDirectory(
chapter,
);
List<Track>? subtitles;
bool isOk = false;
final manga = chapter.manga.value!;
final chapterName = chapter.name!.replaceForbiddenCharacters(' ');
@ -199,11 +200,13 @@ Future<void> downloadChapter(
hasM3U8File = nonM3U8File ? false : m3u8Urls.isNotEmpty;
final videosUrls = nonM3U8File ? nonM3u8Urls : m3u8Urls;
if (videosUrls.isNotEmpty) {
subtitles = videosUrls.first.subtitles;
if (hasM3U8File) {
m3u8Downloader = M3u8Downloader(
m3u8Url: videosUrls.first.url,
downloadDir: chapterDirectory.path,
headers: videosUrls.first.headers ?? {},
subtitles: subtitles,
fileName: p.join(mangaMainDirectory!.path, "$chapterName.mp4"),
chapter: chapter,
);
@ -339,7 +342,12 @@ Future<void> downloadChapter(
});
} else {
savePageUrls();
await MDownloader(chapter: chapter, pageUrls: pages).download((progress) {
await MDownloader(
chapter: chapter,
pageUrls: pages,
subtitles: subtitles,
subDownloadDir: chapterDirectory.path,
).download((progress) {
setProgress(progress);
});
}

View file

@ -6,6 +6,7 @@ import 'dart:isolate';
import 'package:flutter/foundation.dart';
import 'package:http/http.dart';
import 'package:mangayomi/models/chapter.dart';
import 'package:mangayomi/models/video.dart';
import 'package:mangayomi/services/http/m_client.dart';
import 'package:mangayomi/services/http/rhttp/src/model/settings.dart';
import 'package:mangayomi/services/download_manager/m3u8/models/download.dart';
@ -25,6 +26,7 @@ class M3u8Downloader {
final String fileName;
final int concurrentDownloads;
final Chapter chapter;
final List<Track>? subtitles;
Isolate? _isolate;
ReceivePort? _receivePort;
static var httpClient = MClient.httpClient(
@ -40,6 +42,7 @@ class M3u8Downloader {
this.headers,
required this.chapter,
this.concurrentDownloads = 2,
required this.subtitles,
});
void _log(String message) {
@ -139,6 +142,26 @@ class M3u8Downloader {
mediaSequence,
onProgress,
);
for (var element in subtitles ?? <Track>[]) {
final subtitleFile = File(
path.join('${downloadDir}_subtitles', '${element.label}.srt'),
);
if (subtitleFile.existsSync()) {
_log('Subtitle file already exists: ${element.label}');
continue;
}
_log('Downloading subtitle file: ${element.label}');
subtitleFile.createSync(recursive: true);
final response = await _withRetry(
() => httpClient.get(Uri.parse(element.file ?? ''), headers: headers),
);
if (response.statusCode != 200) {
_log('Warning: Failed to download subtitle file: ${element.label}');
continue;
}
_log('Subtitle file downloaded: ${element.label}');
await subtitleFile.writeAsBytes(response.bodyBytes);
}
} catch (e) {
throw M3u8DownloaderException('Download failed', e);
} finally {

View file

@ -8,16 +8,20 @@ import 'package:http/http.dart';
import 'package:mangayomi/models/chapter.dart';
import 'package:mangayomi/models/manga.dart';
import 'package:mangayomi/models/page.dart';
import 'package:mangayomi/models/video.dart';
import 'package:mangayomi/services/http/m_client.dart';
import 'package:mangayomi/services/http/rhttp/src/model/settings.dart';
import 'package:mangayomi/services/download_manager/m3u8/m3u8_downloader.dart';
import 'package:mangayomi/services/download_manager/m3u8/models/download.dart';
import 'package:mangayomi/src/rust/frb_generated.dart';
import 'package:path/path.dart' as path;
class MDownloader {
List<PageUrl> pageUrls;
final int concurrentDownloads;
final Chapter chapter;
final List<Track>? subtitles;
final String? subDownloadDir;
Isolate? _isolate;
ReceivePort? _receivePort;
static var httpClient = MClient.httpClient(
@ -29,6 +33,8 @@ class MDownloader {
MDownloader({
required this.chapter,
required this.pageUrls,
required this.subtitles,
required this.subDownloadDir,
this.concurrentDownloads = 2,
});
@ -76,6 +82,27 @@ class MDownloader {
Future<void> download(void Function(DownloadProgress) onProgress) async {
try {
await _downloadFilesWithProgress(pageUrls, onProgress);
for (var element in subtitles ?? <Track>[]) {
final subtitleFile = File(
path.join('${subDownloadDir}_subtitles', '${element.label}.srt'),
);
if (subtitleFile.existsSync()) {
_log('Subtitle file already exists: ${element.label}');
continue;
}
_log('Downloading subtitle file: ${element.label}');
subtitleFile.createSync(recursive: true);
final response = await _withRetryStatic(
() => httpClient.get(Uri.parse(element.file ?? '')),
3,
);
if (response.statusCode != 200) {
_log('Warning: Failed to download subtitle file: ${element.label}');
continue;
}
_log('Subtitle file downloaded: ${element.label}');
await subtitleFile.writeAsBytes(response.bodyBytes);
}
} catch (e) {
throw MDownloaderException('Download failed', e);
} finally {

View file

@ -28,9 +28,28 @@ Future<(List<Video>, bool, List<String>)> getVideoList(
);
List<String> infoHashes = [];
if (await File(mp4animePath).exists() || isLocalArchive) {
final chapterDirectory = (await storageProvider.getMangaChapterDirectory(
episode,
mangaMainDirectory: mangaDirectory,
))!;
final path = isLocalArchive ? episode.archivePath : mp4animePath;
final subtitlesDir = Directory(
p.join('${chapterDirectory.path}_subtitles'),
);
List<Track> subtitles = [];
if (subtitlesDir.existsSync()) {
for (var element in subtitlesDir.listSync()) {
if (element is File) {
final subtitle = Track(
label: element.uri.pathSegments.last.replaceAll('.srt', ''),
file: element.uri.toString(),
);
subtitles.add(subtitle);
}
}
}
return (
[Video(path!, episode.name!, path, subtitles: [])],
[Video(path!, episode.name!, path, subtitles: subtitles)],
true,
infoHashes,
);