211 lines
9.2 KiB
Dart
211 lines
9.2 KiB
Dart
import 'dart:io';
|
|
|
|
import 'package:path/path.dart' as p;
|
|
|
|
import '../platform/common/media_kit_full_player.dart';
|
|
import '../platform/common/media_kit_player.dart';
|
|
import '../app/ai_upscale.dart' show AiUpscaler;
|
|
import '../app/settings.dart';
|
|
import '../app/vault_location.dart';
|
|
import '../core/shizuku.dart' show PrivilegedFs;
|
|
import '../core/download_detect.dart';
|
|
import '../core/lan_scan.dart' show LanHost, deviceMetered;
|
|
import '../platform/common/nllb_translator.dart';
|
|
import '../platform/common/opensubtitles_provider.dart';
|
|
import '../platform/common/whisper_recognizer.dart';
|
|
import '../platform/android/android_download_tools.dart';
|
|
import '../platform/android/android_shell.dart';
|
|
import '../platform/android/android_storage.dart';
|
|
import '../platform/android/android_updater.dart';
|
|
import '../platform/android/ffmpeg_kit_media_tool.dart';
|
|
import '../platform/windows/aria2_backend.dart';
|
|
import '../platform/windows/desktop_shell.dart';
|
|
import '../platform/windows/desktop_storage_service.dart';
|
|
import '../platform/windows/start_menu.dart';
|
|
import '../platform/windows/github_updater.dart';
|
|
import '../platform/windows/process_media_tool.dart';
|
|
import '../platform/windows/windows_usage.dart';
|
|
import '../platform/windows/ytdlp_backend.dart';
|
|
import 'ai_services.dart';
|
|
import 'app_shell.dart';
|
|
import 'downloader.dart';
|
|
import 'media_player.dart';
|
|
import 'media_tool.dart';
|
|
import 'model_store.dart';
|
|
import 'platform_info.dart';
|
|
import 'preview_player.dart';
|
|
import 'storage_service.dart';
|
|
import 'subtitle_provider.dart';
|
|
import 'system_usage.dart';
|
|
import 'updater.dart';
|
|
import '../l10n/tr.dart';
|
|
import 'package:wakelock_plus/wakelock_plus.dart';
|
|
|
|
/// 플랫폼별 구현을 한 곳에서 고른다.
|
|
/// Android 이식 시 이 파일에 분기만 추가하면 된다.
|
|
class PlatformServices {
|
|
final MediaTool mediaTool;
|
|
final StorageService storage;
|
|
|
|
/// 영상 재생기 생성 (없으면 영상 없이 자막만 편집)
|
|
final PreviewPlayer Function()? createPlayer;
|
|
|
|
/// 동영상 플레이어 (재생 목록 · 트랙)
|
|
final MediaPlayer Function()? createMediaPlayer;
|
|
|
|
/// AI 자막 (없으면 AI 자막 기능 숨김)
|
|
final SpeechRecognizer Function()? createRecognizer;
|
|
final Translator Function()? createTranslator;
|
|
final ModelStore models;
|
|
|
|
/// 다운로드 엔진 (yt-dlp, aria2). 설정값(형식 · 화질)을 읽는 함수를 받는다.
|
|
final List<DownloadBackend> Function(AppSettings Function() settings)? createDownloadBackends;
|
|
|
|
/// 트레이 · 전역 단축키 · 창 닫기
|
|
final AppShell shell;
|
|
|
|
/// 새 버전 확인 · 설치 (없으면 업데이트 기능 숨김)
|
|
final Updater? updater;
|
|
|
|
/// PC 의 CPU · 메모리 사용량 (없으면 표시 숨김)
|
|
final SystemUsage? usage;
|
|
|
|
/// 엔진 · 모델을 이미 정해 둔 해상도 올리기 (없으면 설정대로 준비하고, 없는 엔진 · 모델은 그 자리에서 받는다)
|
|
final AiUpscaler Function()? createUpscaler;
|
|
|
|
/// 같은 망의 SMB 서버 찾기 (없으면 이 기기의 사설망을 실제로 훑는다 - smb_settings)
|
|
final Future<List<LanHost>> Function({bool Function()? cancelled, void Function(int done, int total)? onProgress})? findLanSmb;
|
|
|
|
/// 디스크 남은 · 전체 바이트 재기 (없으면 실제 디스크 - copy_center 의 diskSpace). 작업 현황의 "대상 디스크" 줄
|
|
final Future<(int, int)?> Function(String path)? diskSpace;
|
|
|
|
/// 지금 시각 (없으면 실제 시계). 작업 기록의 [시:분:초]
|
|
final DateTime Function()? clock;
|
|
|
|
/// 암호 보관함 자리 (설계 M3). 앱 조립 ([PlatformServices.create]) 은 실제 자리 ([VaultLocation.device]),
|
|
/// 손으로 조립하면 처음 값은 앱 임시 폴더 아래 ([VaultLocation.under] - 사용자 보관함 · 앱 데이터를 건드리지 않음)
|
|
final VaultLocation vault;
|
|
|
|
/// 시스템 폴더 (Shizuku) · 루트 (su) 읽기 (설계 M3). 있으면 AppController 가 켤 때 PrivilegedFs.use 로 건다 - Android 조립만 넘기고,
|
|
/// 없으면 PrivilegedFs.current 를 그대로 둔다 (시험이 건 가짜를 덮지 않게)
|
|
final PrivilegedFs? privileged;
|
|
|
|
/// 지금 쓰는 망이 데이터 요금제인지 (원격 썸네일 "Wi-Fi 에서만" - 설계 M3 ThumbPolicy). 처음 값 = 실제 (Android 만 묻고 PC 는 false)
|
|
final Future<bool> Function() isMetered;
|
|
|
|
/// 화면을 켜 둔다 / 놓는다 (보기 화면의 자동 넘기기 - 설계 M4 WakeLock). 처음 값 = WakelockPlus
|
|
final Future<void> Function(bool on) keepScreenOn;
|
|
|
|
/// 시작 메뉴 바로 가기 · jjmkvmaker:// 연결 (Windows, 설계 M4). 앱 조립은 [StartMenu.user] (사용자 영역),
|
|
/// 손으로 조립하면 처음 값은 [StartMenu.locked] (사용자 시작 메뉴 · HKCU 를 건드리지 않음 - 시험이 실제 바로 가기를 만들거나 지우지 않게)
|
|
final StartMenu startMenu;
|
|
|
|
/// 이 기기 (Android · Windows · 메모리) - 기기마다 다르게 도는 곳은 이것을 본다 (M1, 처음 값 = 이 기기)
|
|
final PlatformInfo platform;
|
|
|
|
/// 인터넷 자막 사이트 (설정값을 읽는 함수를 받아 만든다)
|
|
final List<SubtitleProvider> Function(AppSettings Function() settings)? createSubtitleProviders;
|
|
|
|
PlatformServices({
|
|
required this.mediaTool,
|
|
required this.storage,
|
|
this.createPlayer,
|
|
this.createMediaPlayer,
|
|
this.createRecognizer,
|
|
this.createTranslator,
|
|
this.createDownloadBackends,
|
|
this.createSubtitleProviders,
|
|
this.createUpscaler,
|
|
this.findLanSmb,
|
|
this.diskSpace,
|
|
this.clock,
|
|
this.updater,
|
|
this.usage,
|
|
ModelStore? models,
|
|
AppShell? shell,
|
|
PlatformInfo? platform,
|
|
VaultLocation? vault,
|
|
this.privileged,
|
|
Future<bool> Function()? isMetered,
|
|
Future<void> Function(bool on)? keepScreenOn,
|
|
StartMenu? startMenu,
|
|
}) : models = models ?? ModelStore(),
|
|
vault = vault ?? VaultLocation.under(storage.tempDirectory),
|
|
isMetered = isMetered ?? deviceMetered,
|
|
keepScreenOn = keepScreenOn ?? _wakeLock,
|
|
startMenu = startMenu ?? StartMenu.locked(Directory.systemTemp.path),
|
|
platform = platform ?? PlatformInfo.host,
|
|
shell = shell ?? NoopShell();
|
|
|
|
factory PlatformServices.create() {
|
|
if (Platform.isWindows || Platform.isLinux || Platform.isMacOS) {
|
|
MediaKitPreviewPlayer.ensureInitialized();
|
|
final tool = ProcessMediaTool.locate();
|
|
final ffmpegDir = File(tool.ffmpeg).existsSync() ? p.dirname(tool.ffmpeg) : null;
|
|
return PlatformServices(
|
|
vault: VaultLocation.device,
|
|
startMenu: StartMenu.user,
|
|
mediaTool: tool,
|
|
storage: DesktopStorageService(),
|
|
createPlayer: MediaKitPreviewPlayer.new,
|
|
createMediaPlayer: MediaKitFullPlayer.new,
|
|
createRecognizer: WhisperRecognizer.new,
|
|
createTranslator: NllbTranslator.new,
|
|
createDownloadBackends: (s) => [
|
|
YtDlpBackend(
|
|
ffmpegDir: ffmpegDir,
|
|
formatArgs: () => ytDlpFormatArgs(s().download.ytContainer, s().download.ytQuality, preferH264: s().download.ytPreferH264),
|
|
cookieArgs: () => ytDlpCookieArgs(
|
|
browser: s().download.ytCookiesBrowser,
|
|
file: s().download.ytCookiesFile,
|
|
internalProfile: s().internalCookieProfile,
|
|
internalCookieFile: s().internalCookieFile),
|
|
// 고른 브라우저의 쿠키를 못 읽으면 앱 안 브라우저의 쿠키로
|
|
fallbackCookieArgs: () => ytDlpCookieArgs(
|
|
browser: internalBrowserCookies, internalCookieFile: s().internalCookieFile),
|
|
),
|
|
Aria2Backend(stallAfter: () => Duration(seconds: s().download.torrentStallSeconds)),
|
|
],
|
|
shell: DesktopShell(),
|
|
updater: GitHubUpdater(),
|
|
usage: Platform.isWindows ? WindowsUsage() : null,
|
|
createSubtitleProviders: (s) => [
|
|
OpenSubtitlesProvider(
|
|
apiKey: () => s().mkv.openSubtitlesKey,
|
|
username: () => s().mkv.openSubtitlesUser,
|
|
password: () => s().mkv.openSubtitlesPassword,
|
|
),
|
|
],
|
|
);
|
|
}
|
|
if (Platform.isAndroid) {
|
|
// 탐색기 연결은 데스크톱 전용. 업데이트는 APK 를 받아 Android 설치 화면으로. 다운로드는 앱에 넣은 yt-dlp · aria2c (youtubedl-android)
|
|
MediaKitPreviewPlayer.ensureInitialized();
|
|
final storage = AndroidStorageService();
|
|
return PlatformServices(
|
|
vault: VaultLocation.device,
|
|
privileged: PrivilegedFs.device(storage.tempDirectory),
|
|
mediaTool: FfmpegKitMediaTool(),
|
|
storage: storage,
|
|
updater: AndroidUpdater(storage),
|
|
createPlayer: MediaKitPreviewPlayer.new,
|
|
createMediaPlayer: MediaKitFullPlayer.new,
|
|
createRecognizer: WhisperRecognizer.new,
|
|
createTranslator: NllbTranslator.new,
|
|
createDownloadBackends: AndroidDownloadTools.backends,
|
|
shell: AndroidShell(),
|
|
createSubtitleProviders: (s) => [
|
|
OpenSubtitlesProvider(
|
|
apiKey: () => s().mkv.openSubtitlesKey,
|
|
username: () => s().mkv.openSubtitlesUser,
|
|
password: () => s().mkv.openSubtitlesPassword,
|
|
),
|
|
],
|
|
);
|
|
}
|
|
throw UnsupportedError(trf('아직 지원하지 않는 플랫폼입니다: {0}', [Platform.operatingSystem]));
|
|
}
|
|
}
|
|
|
|
Future<void> _wakeLock(bool on) => on ? WakelockPlus.enable() : WakelockPlus.disable();
|