mirror of
https://github.com/nimbold/Firelink.git
synced 2026-07-28 04:49:39 +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 {
|
||||
case http
|
||||
case https
|
||||
@@ -141,6 +150,10 @@ final class AppSettings: ObservableObject {
|
||||
didSet { save() }
|
||||
}
|
||||
|
||||
@Published var mediaCookieSource: BrowserCookieSource {
|
||||
didSet { save() }
|
||||
}
|
||||
|
||||
@Published var message = ""
|
||||
|
||||
private let defaults: UserDefaults
|
||||
@@ -161,6 +174,7 @@ final class AppSettings: ObservableObject {
|
||||
preventsSleepWhileDownloading = stored.preventsSleepWhileDownloading
|
||||
proxySettings = stored.proxySettings?.normalized ?? ProxySettings()
|
||||
siteLogins = stored.siteLogins
|
||||
mediaCookieSource = stored.mediaCookieSource ?? .none
|
||||
downloadDirectories = Self.decodeDirectories(stored.downloadDirectories)
|
||||
} else {
|
||||
appTheme = .system
|
||||
@@ -172,6 +186,7 @@ final class AppSettings: ObservableObject {
|
||||
preventsSleepWhileDownloading = true
|
||||
proxySettings = ProxySettings()
|
||||
siteLogins = []
|
||||
mediaCookieSource = .none
|
||||
downloadDirectories = Self.defaultDirectories()
|
||||
}
|
||||
|
||||
@@ -287,7 +302,8 @@ final class AppSettings: ObservableObject {
|
||||
preventsSleepWhileDownloading: preventsSleepWhileDownloading,
|
||||
proxySettings: proxySettings.normalized,
|
||||
downloadDirectories: Dictionary(uniqueKeysWithValues: downloadDirectories.map { ($0.key.rawValue, $0.value) }),
|
||||
siteLogins: siteLogins
|
||||
siteLogins: siteLogins,
|
||||
mediaCookieSource: mediaCookieSource
|
||||
)
|
||||
let defaults = self.defaults
|
||||
let storageKey = self.storageKey
|
||||
@@ -358,4 +374,5 @@ private struct StoredSettings: Codable {
|
||||
var proxySettings: ProxySettings?
|
||||
var downloadDirectories: [String: String]
|
||||
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)
|
||||
process.arguments = arguments
|
||||
|
||||
|
||||
@@ -57,7 +57,19 @@ enum MediaExtractionEngine {
|
||||
|
||||
let process = Process()
|
||||
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 errorPipe = Pipe()
|
||||
|
||||
@@ -2,15 +2,20 @@ import SwiftUI
|
||||
import AppKit
|
||||
|
||||
struct EngineSettingsPane: View {
|
||||
@EnvironmentObject private var settings: AppSettings
|
||||
@StateObject private var engineManager = MediaEngineManager.shared
|
||||
@State private var version = "Checking..."
|
||||
|
||||
@State private var isCheckingForUpdates = false
|
||||
@State private var updateCheckResult: String?
|
||||
|
||||
private var executableURL: URL? {
|
||||
Aria2DownloadEngine.findExecutable()
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
Form {
|
||||
Section {
|
||||
Section("Aria2 (HTTP/FTP)") {
|
||||
LabeledContent("Status") {
|
||||
Label(
|
||||
executableURL == nil ? "Missing" : "Ready",
|
||||
@@ -37,10 +42,92 @@ struct EngineSettingsPane: View {
|
||||
.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)
|
||||
.task {
|
||||
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