feat: implement custom path support for youtube engines

This commit is contained in:
Kingkor Roy Tirtho 2025-02-08 21:39:07 +06:00
parent f8663875e3
commit fc9121b488
11 changed files with 245 additions and 66 deletions

View File

@ -45,4 +45,11 @@ gensums:
sh -c scripts/gensums.sh
migrate:
dart run drift_dev make-migrations
dart run drift_dev make-migrations
dmg:
flutter build macos &&\
if [ -f dist/Spotube-macos-universal.dmg ];\
then rm dist/Spotube-macos-universal.dmg;\
fi &&\
appdmg appdmg.json dist/Spotube-macos-universal.dmg

View File

@ -1,9 +1,12 @@
import 'dart:io';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:shadcn_flutter/shadcn_flutter.dart';
import 'package:spotube/models/database/database.dart';
import 'package:spotube/modules/settings/youtube_engine_not_installed_dialog.dart';
import 'package:spotube/provider/user_preferences/user_preferences_provider.dart';
import 'package:spotube/services/kv_store/kv_store.dart';
import 'package:spotube/services/youtube_engine/yt_dlp_engine.dart';
void useCheckYtDlpInstalled(WidgetRef ref) {
@ -17,8 +20,12 @@ void useCheckYtDlpInstalled(WidgetRef ref) {
),
);
final customPath =
KVStoreService.getYoutubeEnginePath(YoutubeClientEngine.ytDlp);
if (youtubeEngine == YoutubeClientEngine.ytDlp &&
!await YtDlpEngine.isInstalled() &&
(customPath == null || !await File(customPath).exists()) &&
context.mounted) {
await showDialog(
context: context,

View File

@ -418,6 +418,9 @@
"no_logs_found": "No logs found",
"youtube_engine": "YouTube Engine",
"youtube_engine_not_installed_title": "{engine} is not installed",
"youtube_engine_not_installed_message": "{engine} is not installed in your system.\nPlease install it and make sure it's available in the PATH variable\n\nAfter installing, restart the app",
"download": "Download"
"youtube_engine_not_installed_message": "{engine} is not installed in your system.",
"youtube_engine_set_path": "Make sure it's available in the PATH variable or\nset the absolute path to the {engine} executable below",
"youtube_engine_unix_issue_message": "In macOS/Linux/unix like OS's, setting path on .zshrc/.bashrc/.bash_profile etc. won't work.\nYou need to set the path in the shell configuration file",
"download": "Download",
"file_not_found": "File not found"
}

View File

@ -84,10 +84,15 @@ Future<void> main(List<String> rawArgs) async {
MetadataGod.initialize();
}
await KVStoreService.initialize();
if (kIsDesktop) {
await windowManager.setPreventClose(true);
await YtDlp.instance
.setBinaryLocation("yt-dlp${kIsWindows ? '.exe' : ''}")
.setBinaryLocation(
KVStoreService.getYoutubeEnginePath(YoutubeClientEngine.ytDlp) ??
"yt-dlp${kIsWindows ? '.exe' : ''}",
)
.catchError((e, stack) => null);
await FlutterDiscordRPC.initialize(Env.discordAppId);
}
@ -96,7 +101,6 @@ Future<void> main(List<String> rawArgs) async {
await SMTCWindows.initialize();
}
await KVStoreService.initialize();
await EncryptedKvStoreService.initialize();
final database = AppDatabase();

View File

@ -1,9 +1,18 @@
import 'dart:io';
import 'package:flutter_form_builder/flutter_form_builder.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:shadcn_flutter/shadcn_flutter.dart';
import 'package:shadcn_flutter/shadcn_flutter_extension.dart';
import 'package:spotube/collections/spotube_icons.dart';
import 'package:spotube/components/form/text_form_field.dart';
import 'package:spotube/extensions/context.dart';
import 'package:spotube/models/database/database.dart';
import 'package:spotube/services/kv_store/kv_store.dart';
import 'package:spotube/utils/platform.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:yt_dlp_dart/yt_dlp_dart.dart';
const engineDownloadUrls = {
YoutubeClientEngine.ytDlp:
@ -19,6 +28,9 @@ class YouTubeEngineNotInstalledDialog extends HookConsumerWidget {
@override
Widget build(BuildContext context, ref) {
final controller = useTextEditingController();
final formKey = useMemoized(() => GlobalKey<FormBuilderState>(), []);
return AlertDialog(
title: Row(
spacing: 8,
@ -31,33 +43,77 @@ class YouTubeEngineNotInstalledDialog extends HookConsumerWidget {
),
],
),
content: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
spacing: 10,
children: [
Text(
context.l10n.youtube_engine_not_installed_message(engine.label),
),
if (engineDownloadUrls[engine] != null)
Row(
mainAxisSize: MainAxisSize.min,
children: [
Text("${context.l10n.download}:"),
Button.link(
child: Text(engineDownloadUrls[engine]!.split("?").first),
onPressed: () async {
launchUrl(Uri.parse(engineDownloadUrls[engine]!));
},
),
],
content: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 400),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
spacing: 8,
children: [
Text(
context.l10n.youtube_engine_not_installed_message(engine.label),
),
],
if (engineDownloadUrls[engine] != null)
Row(
mainAxisSize: MainAxisSize.min,
children: [
Text("${context.l10n.download}:"),
Button.link(
child: Text(engineDownloadUrls[engine]!.split("?").first),
onPressed: () async {
launchUrl(Uri.parse(engineDownloadUrls[engine]!));
},
),
],
),
Text(context.l10n.youtube_engine_set_path(engine.label)),
const Gap(8),
FormBuilder(
key: formKey,
child: TextFormBuilderField(
name: "path",
controller: controller,
placeholder: Text(switch (context.theme.platform) {
TargetPlatform.macOS => "e.g. /opt/homebrew/bin/yt-dlp",
TargetPlatform.windows =>
r"e.g. C:\Program Files\yt-dlp\yt-dlp.exe",
_ => "e.g. /home/user/.local/bin/yt-dlp",
}),
),
),
if (kIsMacOS || kIsLinux)
Text(context.l10n.youtube_engine_unix_issue_message),
],
),
),
actions: [
Button.text(
onPressed: () {
if (!context.mounted) return;
Navigator.of(context).pop(false);
},
child: Text(context.l10n.cancel),
),
Button.secondary(
onPressed: () => Navigator.of(context).pop(),
child: Text(context.l10n.ok),
onPressed: () async {
if (controller.text.isNotEmpty) {
if (!await File(controller.text).exists() && context.mounted) {
formKey.currentState?.fields["path"]
?.invalidate(context.l10n.file_not_found);
return;
}
await KVStoreService.setYoutubeEnginePath(
engine,
controller.text,
);
if (engine == YoutubeClientEngine.ytDlp) {
await YtDlp.instance.setBinaryLocation(controller.text);
}
}
if (!context.mounted) return;
Navigator.of(context).pop(true);
},
child: Text(context.l10n.save),
),
],
);

View File

@ -214,12 +214,12 @@ class SettingsPlaybackSection extends HookConsumerWidget {
if (value == YoutubeClientEngine.ytDlp &&
!await YtDlpEngine.isInstalled() &&
context.mounted) {
await showDialog(
final hasInstalled = await showDialog<bool>(
context: context,
builder: (context) =>
YouTubeEngineNotInstalledDialog(engine: value),
);
return;
if (hasInstalled != true) return;
}
preferencesNotifier.setYoutubeClientEngine(value);
},

View File

@ -2,6 +2,7 @@ import 'dart:convert';
import 'package:encrypt/encrypt.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:spotube/models/database/database.dart';
import 'package:spotube/services/wm_tools/wm_tools.dart';
import 'package:uuid/uuid.dart';
@ -87,4 +88,31 @@ abstract class KVStoreService {
sharedPreferences.getBool('hasMigratedToDrift') ?? false;
static Future<void> setHasMigratedToDrift(bool value) async =>
await sharedPreferences.setBool('hasMigratedToDrift', value);
static Map<String, dynamic>? get _youtubeEnginePaths {
final jsonRaw = sharedPreferences.getString('ytDlpPath');
if (jsonRaw == null) {
return null;
}
return jsonDecode(jsonRaw);
}
static String? getYoutubeEnginePath(YoutubeClientEngine engine) {
return _youtubeEnginePaths?[engine.name];
}
static Future<void> setYoutubeEnginePath(
YoutubeClientEngine engine,
String path,
) async {
await sharedPreferences.setString(
'ytDlpPath',
jsonEncode({
...?_youtubeEnginePaths,
engine.name: path,
}),
);
}
}

View File

@ -2737,11 +2737,9 @@ packages:
yt_dlp_dart:
dependency: "direct main"
description:
path: "."
ref: "4199bb019542bae361fbb38b3448b3583fbca022"
resolved-ref: "4199bb019542bae361fbb38b3448b3583fbca022"
url: "https://github.com/KRTirtho/yt_dlp_dart.git"
source: git
path: "../yt_dlp_dart"
relative: true
source: path
version: "1.0.0"
sdks:
dart: ">=3.6.1 <4.0.0"

View File

@ -141,9 +141,7 @@ dependencies:
url: https://github.com/Hexer10/youtube_explode_dart.git
ref: e519db65ad0b0a40b12f69285932f9db509da3cf
yt_dlp_dart:
git:
url: https://github.com/KRTirtho/yt_dlp_dart.git
ref: 4199bb019542bae361fbb38b3448b3583fbca022
path: ../yt_dlp_dart
dev_dependencies:
build_runner: ^2.4.13

View File

@ -19,7 +19,10 @@
"youtube_engine",
"youtube_engine_not_installed_title",
"youtube_engine_not_installed_message",
"download"
"youtube_engine_set_path",
"youtube_engine_unix_issue_message",
"download",
"file_not_found"
],
"bn": [
@ -42,7 +45,10 @@
"youtube_engine",
"youtube_engine_not_installed_title",
"youtube_engine_not_installed_message",
"download"
"youtube_engine_set_path",
"youtube_engine_unix_issue_message",
"download",
"file_not_found"
],
"ca": [
@ -65,7 +71,10 @@
"youtube_engine",
"youtube_engine_not_installed_title",
"youtube_engine_not_installed_message",
"download"
"youtube_engine_set_path",
"youtube_engine_unix_issue_message",
"download",
"file_not_found"
],
"cs": [
@ -88,7 +97,10 @@
"youtube_engine",
"youtube_engine_not_installed_title",
"youtube_engine_not_installed_message",
"download"
"youtube_engine_set_path",
"youtube_engine_unix_issue_message",
"download",
"file_not_found"
],
"de": [
@ -111,7 +123,10 @@
"youtube_engine",
"youtube_engine_not_installed_title",
"youtube_engine_not_installed_message",
"download"
"youtube_engine_set_path",
"youtube_engine_unix_issue_message",
"download",
"file_not_found"
],
"es": [
@ -134,7 +149,10 @@
"youtube_engine",
"youtube_engine_not_installed_title",
"youtube_engine_not_installed_message",
"download"
"youtube_engine_set_path",
"youtube_engine_unix_issue_message",
"download",
"file_not_found"
],
"eu": [
@ -157,7 +175,10 @@
"youtube_engine",
"youtube_engine_not_installed_title",
"youtube_engine_not_installed_message",
"download"
"youtube_engine_set_path",
"youtube_engine_unix_issue_message",
"download",
"file_not_found"
],
"fa": [
@ -180,7 +201,10 @@
"youtube_engine",
"youtube_engine_not_installed_title",
"youtube_engine_not_installed_message",
"download"
"youtube_engine_set_path",
"youtube_engine_unix_issue_message",
"download",
"file_not_found"
],
"fi": [
@ -203,7 +227,10 @@
"youtube_engine",
"youtube_engine_not_installed_title",
"youtube_engine_not_installed_message",
"download"
"youtube_engine_set_path",
"youtube_engine_unix_issue_message",
"download",
"file_not_found"
],
"fr": [
@ -226,7 +253,10 @@
"youtube_engine",
"youtube_engine_not_installed_title",
"youtube_engine_not_installed_message",
"download"
"youtube_engine_set_path",
"youtube_engine_unix_issue_message",
"download",
"file_not_found"
],
"hi": [
@ -249,7 +279,10 @@
"youtube_engine",
"youtube_engine_not_installed_title",
"youtube_engine_not_installed_message",
"download"
"youtube_engine_set_path",
"youtube_engine_unix_issue_message",
"download",
"file_not_found"
],
"id": [
@ -272,7 +305,10 @@
"youtube_engine",
"youtube_engine_not_installed_title",
"youtube_engine_not_installed_message",
"download"
"youtube_engine_set_path",
"youtube_engine_unix_issue_message",
"download",
"file_not_found"
],
"it": [
@ -295,7 +331,10 @@
"youtube_engine",
"youtube_engine_not_installed_title",
"youtube_engine_not_installed_message",
"download"
"youtube_engine_set_path",
"youtube_engine_unix_issue_message",
"download",
"file_not_found"
],
"ja": [
@ -318,7 +357,10 @@
"youtube_engine",
"youtube_engine_not_installed_title",
"youtube_engine_not_installed_message",
"download"
"youtube_engine_set_path",
"youtube_engine_unix_issue_message",
"download",
"file_not_found"
],
"ka": [
@ -341,7 +383,10 @@
"youtube_engine",
"youtube_engine_not_installed_title",
"youtube_engine_not_installed_message",
"download"
"youtube_engine_set_path",
"youtube_engine_unix_issue_message",
"download",
"file_not_found"
],
"ko": [
@ -364,7 +409,10 @@
"youtube_engine",
"youtube_engine_not_installed_title",
"youtube_engine_not_installed_message",
"download"
"youtube_engine_set_path",
"youtube_engine_unix_issue_message",
"download",
"file_not_found"
],
"ne": [
@ -387,7 +435,10 @@
"youtube_engine",
"youtube_engine_not_installed_title",
"youtube_engine_not_installed_message",
"download"
"youtube_engine_set_path",
"youtube_engine_unix_issue_message",
"download",
"file_not_found"
],
"nl": [
@ -410,7 +461,10 @@
"youtube_engine",
"youtube_engine_not_installed_title",
"youtube_engine_not_installed_message",
"download"
"youtube_engine_set_path",
"youtube_engine_unix_issue_message",
"download",
"file_not_found"
],
"pl": [
@ -433,7 +487,10 @@
"youtube_engine",
"youtube_engine_not_installed_title",
"youtube_engine_not_installed_message",
"download"
"youtube_engine_set_path",
"youtube_engine_unix_issue_message",
"download",
"file_not_found"
],
"pt": [
@ -456,7 +513,10 @@
"youtube_engine",
"youtube_engine_not_installed_title",
"youtube_engine_not_installed_message",
"download"
"youtube_engine_set_path",
"youtube_engine_unix_issue_message",
"download",
"file_not_found"
],
"ru": [
@ -479,7 +539,10 @@
"youtube_engine",
"youtube_engine_not_installed_title",
"youtube_engine_not_installed_message",
"download"
"youtube_engine_set_path",
"youtube_engine_unix_issue_message",
"download",
"file_not_found"
],
"th": [
@ -502,7 +565,10 @@
"youtube_engine",
"youtube_engine_not_installed_title",
"youtube_engine_not_installed_message",
"download"
"youtube_engine_set_path",
"youtube_engine_unix_issue_message",
"download",
"file_not_found"
],
"tr": [
@ -525,7 +591,10 @@
"youtube_engine",
"youtube_engine_not_installed_title",
"youtube_engine_not_installed_message",
"download"
"youtube_engine_set_path",
"youtube_engine_unix_issue_message",
"download",
"file_not_found"
],
"uk": [
@ -548,7 +617,10 @@
"youtube_engine",
"youtube_engine_not_installed_title",
"youtube_engine_not_installed_message",
"download"
"youtube_engine_set_path",
"youtube_engine_unix_issue_message",
"download",
"file_not_found"
],
"vi": [
@ -571,7 +643,10 @@
"youtube_engine",
"youtube_engine_not_installed_title",
"youtube_engine_not_installed_message",
"download"
"youtube_engine_set_path",
"youtube_engine_unix_issue_message",
"download",
"file_not_found"
],
"zh": [
@ -594,6 +669,9 @@
"youtube_engine",
"youtube_engine_not_installed_title",
"youtube_engine_not_installed_message",
"download"
"youtube_engine_set_path",
"youtube_engine_unix_issue_message",
"download",
"file_not_found"
]
}

View File

@ -22,8 +22,8 @@ SetupIconFile={{SETUP_ICON_FILE}}
WizardStyle=modern
WizardSmallImageFile="..\\..\\assets\\spotube-logo.bmp"
PrivilegesRequired={{PRIVILEGES_REQUIRED}}
ArchitecturesAllowed=x64
ArchitecturesInstallIn64BitMode=x64
ArchitecturesAllowed=x64compatible
ArchitecturesInstallIn64BitMode=x64compatible
[Languages]
{% for locale in LOCALES %}