mirror of
https://github.com/nimbold/Firelink.git
synced 2026-08-29 20:17:09 +00:00
feat: enhance media engine settings with cookie extraction and update checks
- Added `mediaCookieSource` to `AppSettings` to let users choose the browser for cookie extraction. - Refactored `EngineSettingsPane` to display `yt-dlp` and `ffmpeg` statuses. - Added a manual 'Check for Updates' button with UI loading feedback. - Passed `--cookies-from-browser` to both metadata extraction and download engines if configured. - Added `--ignore-no-formats-error` to `yt-dlp` metadata extraction to prevent crashes on restricted videos.
This commit is contained in:
@@ -20,6 +20,15 @@ enum ProxyMode: String, Codable, CaseIterable, Sendable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum BrowserCookieSource: String, Codable, CaseIterable, Sendable {
|
||||||
|
case none = "None"
|
||||||
|
case safari = "Safari"
|
||||||
|
case chrome = "Chrome"
|
||||||
|
case firefox = "Firefox"
|
||||||
|
case edge = "Edge"
|
||||||
|
case brave = "Brave"
|
||||||
|
}
|
||||||
|
|
||||||
enum ProxyType: String, Codable, CaseIterable, Sendable {
|
enum ProxyType: String, Codable, CaseIterable, Sendable {
|
||||||
case http
|
case http
|
||||||
case https
|
case https
|
||||||
@@ -141,6 +150,10 @@ final class AppSettings: ObservableObject {
|
|||||||
didSet { save() }
|
didSet { save() }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Published var mediaCookieSource: BrowserCookieSource {
|
||||||
|
didSet { save() }
|
||||||
|
}
|
||||||
|
|
||||||
@Published var message = ""
|
@Published var message = ""
|
||||||
|
|
||||||
private let defaults: UserDefaults
|
private let defaults: UserDefaults
|
||||||
@@ -161,6 +174,7 @@ final class AppSettings: ObservableObject {
|
|||||||
preventsSleepWhileDownloading = stored.preventsSleepWhileDownloading
|
preventsSleepWhileDownloading = stored.preventsSleepWhileDownloading
|
||||||
proxySettings = stored.proxySettings?.normalized ?? ProxySettings()
|
proxySettings = stored.proxySettings?.normalized ?? ProxySettings()
|
||||||
siteLogins = stored.siteLogins
|
siteLogins = stored.siteLogins
|
||||||
|
mediaCookieSource = stored.mediaCookieSource ?? .none
|
||||||
downloadDirectories = Self.decodeDirectories(stored.downloadDirectories)
|
downloadDirectories = Self.decodeDirectories(stored.downloadDirectories)
|
||||||
} else {
|
} else {
|
||||||
appTheme = .system
|
appTheme = .system
|
||||||
@@ -172,6 +186,7 @@ final class AppSettings: ObservableObject {
|
|||||||
preventsSleepWhileDownloading = true
|
preventsSleepWhileDownloading = true
|
||||||
proxySettings = ProxySettings()
|
proxySettings = ProxySettings()
|
||||||
siteLogins = []
|
siteLogins = []
|
||||||
|
mediaCookieSource = .none
|
||||||
downloadDirectories = Self.defaultDirectories()
|
downloadDirectories = Self.defaultDirectories()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -287,7 +302,8 @@ final class AppSettings: ObservableObject {
|
|||||||
preventsSleepWhileDownloading: preventsSleepWhileDownloading,
|
preventsSleepWhileDownloading: preventsSleepWhileDownloading,
|
||||||
proxySettings: proxySettings.normalized,
|
proxySettings: proxySettings.normalized,
|
||||||
downloadDirectories: Dictionary(uniqueKeysWithValues: downloadDirectories.map { ($0.key.rawValue, $0.value) }),
|
downloadDirectories: Dictionary(uniqueKeysWithValues: downloadDirectories.map { ($0.key.rawValue, $0.value) }),
|
||||||
siteLogins: siteLogins
|
siteLogins: siteLogins,
|
||||||
|
mediaCookieSource: mediaCookieSource
|
||||||
)
|
)
|
||||||
let defaults = self.defaults
|
let defaults = self.defaults
|
||||||
let storageKey = self.storageKey
|
let storageKey = self.storageKey
|
||||||
@@ -358,4 +374,5 @@ private struct StoredSettings: Codable {
|
|||||||
var proxySettings: ProxySettings?
|
var proxySettings: ProxySettings?
|
||||||
var downloadDirectories: [String: String]
|
var downloadDirectories: [String: String]
|
||||||
var siteLogins: [SiteLogin]
|
var siteLogins: [SiteLogin]
|
||||||
|
var mediaCookieSource: BrowserCookieSource?
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -55,6 +55,14 @@ final class MediaDownloadEngine: @unchecked Sendable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add cookies if configured
|
||||||
|
if let storedData = UserDefaults.standard.data(forKey: "Firelink.AppSettings.v1"),
|
||||||
|
let json = try? JSONSerialization.jsonObject(with: storedData) as? [String: Any],
|
||||||
|
let cookieSourceStr = json["mediaCookieSource"] as? String,
|
||||||
|
cookieSourceStr != "None" {
|
||||||
|
arguments.append(contentsOf: ["--cookies-from-browser", cookieSourceStr.lowercased()])
|
||||||
|
}
|
||||||
|
|
||||||
arguments.append(item.url.absoluteString)
|
arguments.append(item.url.absoluteString)
|
||||||
process.arguments = arguments
|
process.arguments = arguments
|
||||||
|
|
||||||
|
|||||||
@@ -57,7 +57,19 @@ enum MediaExtractionEngine {
|
|||||||
|
|
||||||
let process = Process()
|
let process = Process()
|
||||||
process.executableURL = URL(fileURLWithPath: ytDlpPath)
|
process.executableURL = URL(fileURLWithPath: ytDlpPath)
|
||||||
process.arguments = ["-J", "--no-warnings", url.absoluteString]
|
|
||||||
|
var args = ["-J", "--no-warnings", "--ignore-no-formats-error"]
|
||||||
|
|
||||||
|
// Add cookies if configured
|
||||||
|
if let storedData = UserDefaults.standard.data(forKey: "Firelink.AppSettings.v1"),
|
||||||
|
let json = try? JSONSerialization.jsonObject(with: storedData) as? [String: Any],
|
||||||
|
let cookieSourceStr = json["mediaCookieSource"] as? String,
|
||||||
|
cookieSourceStr != "None" {
|
||||||
|
args.append(contentsOf: ["--cookies-from-browser", cookieSourceStr.lowercased()])
|
||||||
|
}
|
||||||
|
|
||||||
|
args.append(url.absoluteString)
|
||||||
|
process.arguments = args
|
||||||
|
|
||||||
let pipe = Pipe()
|
let pipe = Pipe()
|
||||||
let errorPipe = Pipe()
|
let errorPipe = Pipe()
|
||||||
|
|||||||
@@ -2,15 +2,20 @@ import SwiftUI
|
|||||||
import AppKit
|
import AppKit
|
||||||
|
|
||||||
struct EngineSettingsPane: View {
|
struct EngineSettingsPane: View {
|
||||||
|
@EnvironmentObject private var settings: AppSettings
|
||||||
|
@StateObject private var engineManager = MediaEngineManager.shared
|
||||||
@State private var version = "Checking..."
|
@State private var version = "Checking..."
|
||||||
|
|
||||||
|
@State private var isCheckingForUpdates = false
|
||||||
|
@State private var updateCheckResult: String?
|
||||||
|
|
||||||
private var executableURL: URL? {
|
private var executableURL: URL? {
|
||||||
Aria2DownloadEngine.findExecutable()
|
Aria2DownloadEngine.findExecutable()
|
||||||
}
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
Form {
|
Form {
|
||||||
Section {
|
Section("Aria2 (HTTP/FTP)") {
|
||||||
LabeledContent("Status") {
|
LabeledContent("Status") {
|
||||||
Label(
|
Label(
|
||||||
executableURL == nil ? "Missing" : "Ready",
|
executableURL == nil ? "Missing" : "Ready",
|
||||||
@@ -37,10 +42,92 @@ struct EngineSettingsPane: View {
|
|||||||
.foregroundStyle(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Section("Media Engine (yt-dlp & ffmpeg)") {
|
||||||
|
addonStatusRow(title: "yt-dlp", state: engineManager.ytDlpState)
|
||||||
|
addonStatusRow(title: "ffmpeg", state: engineManager.ffmpegState)
|
||||||
|
|
||||||
|
HStack(spacing: 12) {
|
||||||
|
Button("Check for Updates") {
|
||||||
|
Task {
|
||||||
|
isCheckingForUpdates = true
|
||||||
|
updateCheckResult = nil
|
||||||
|
try? await Task.sleep(nanoseconds: 800_000_000)
|
||||||
|
|
||||||
|
do {
|
||||||
|
try await engineManager.ensureInstalled()
|
||||||
|
updateCheckResult = "Up to date."
|
||||||
|
} catch {
|
||||||
|
updateCheckResult = "Update failed."
|
||||||
|
}
|
||||||
|
|
||||||
|
isCheckingForUpdates = false
|
||||||
|
try? await Task.sleep(nanoseconds: 3_000_000_000)
|
||||||
|
if !isCheckingForUpdates {
|
||||||
|
updateCheckResult = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.disabled(isDownloadingMediaEngines || isCheckingForUpdates)
|
||||||
|
|
||||||
|
if isCheckingForUpdates {
|
||||||
|
ProgressView().controlSize(.small)
|
||||||
|
Text("Checking...")
|
||||||
|
.foregroundStyle(.secondary)
|
||||||
|
.font(.subheadline)
|
||||||
|
} else if let result = updateCheckResult {
|
||||||
|
Text(result)
|
||||||
|
.foregroundStyle(.secondary)
|
||||||
|
.font(.subheadline)
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
}
|
||||||
|
|
||||||
|
Picker("Extract Cookies from", selection: $settings.mediaCookieSource) {
|
||||||
|
ForEach(BrowserCookieSource.allCases, id: \.self) { source in
|
||||||
|
Text(source.rawValue).tag(source)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Text("Allows downloading restricted media from sites requiring login. Requires Full Disk Access for Safari.")
|
||||||
|
.font(.caption)
|
||||||
|
.foregroundStyle(.secondary)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.formStyle(.grouped)
|
.formStyle(.grouped)
|
||||||
.task {
|
.task {
|
||||||
version = await Aria2DownloadEngine.versionString() ?? "Unavailable"
|
version = await Aria2DownloadEngine.versionString() ?? "Unavailable"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var isDownloadingMediaEngines: Bool {
|
||||||
|
if case .downloading = engineManager.ytDlpState { return true }
|
||||||
|
if case .downloading = engineManager.ffmpegState { return true }
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
@ViewBuilder
|
||||||
|
private func addonStatusRow(title: String, state: AddonState) -> some View {
|
||||||
|
LabeledContent(title) {
|
||||||
|
switch state {
|
||||||
|
case .notInstalled:
|
||||||
|
Label("Missing", systemImage: "xmark.circle.fill")
|
||||||
|
.foregroundStyle(.orange)
|
||||||
|
case .downloading(let progress):
|
||||||
|
HStack(spacing: 6) {
|
||||||
|
ProgressView(value: progress)
|
||||||
|
.frame(width: 60)
|
||||||
|
Text("\(Int(progress * 100))%")
|
||||||
|
.monospacedDigit()
|
||||||
|
}
|
||||||
|
case .installed(let version):
|
||||||
|
Label("v\(version)", systemImage: "checkmark.seal.fill")
|
||||||
|
.foregroundStyle(.green)
|
||||||
|
.font(.system(.body, design: .monospaced))
|
||||||
|
case .failed(let error):
|
||||||
|
Label("Failed: \(error)", systemImage: "exclamationmark.triangle.fill")
|
||||||
|
.foregroundStyle(.red)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user