Gui update & Services

This commit is contained in:
VineFeeder
2025-11-11 15:19:35 +00:00
parent c71c631dc5
commit 4e9300197d
5 changed files with 385 additions and 30 deletions
+190
View File
@@ -0,0 +1,190 @@
# -*- coding: utf-8 -*-
from PyQt6.QtWidgets import QWidget, QVBoxLayout, QPushButton, QFrame, QLabel
from PyQt6.QtCore import QProcess
from PyQt6.QtCore import Qt
import sys
import os
import subprocess
import shutil
CFG = os.path.abspath("./packages/envied/src/envied/envied.yaml")
class EnviedPanel(QFrame):
def __init__(self, parent=None):
super().__init__(parent)
self.setWindowTitle("Envied Control ")
# Frame styling: 1px white border (no pink)
self.setObjectName("enviedFrame")
self.setStyleSheet("#enviedFrame { background-color: #2D2D2D; border: 1px solid pink; }")
# ("color: #f5c2e7; border: none; background-color:#1E1E2E;padding: 5px;")
# Layout
layout = QVBoxLayout(self)
layout.setContentsMargins(5, 5, 5, 5)
layout.setSpacing(5)
# Helper to make consistently styled buttons
def make_btn(text):
b = QPushButton(text)
b.setStyleSheet(
"background-color: #1E1E2E; \
color: #f5c2e7; \
border-width: 2px;\
border-color: pink;\
font: 14px;\
min-width: 10em;\
padding: 5px;"
)
b.setAutoFillBackground(True)
b.setAttribute(Qt.WidgetAttribute.WA_StyledBackground, True)
return b
# Buttons
btn_run = make_btn("run envied")
btn_check = make_btn("env check")
btn_info = make_btn("env info")
btn_cfg = make_btn("config")
layout.addWidget(btn_run)
layout.addWidget(btn_check)
layout.addWidget(btn_info)
layout.addWidget(btn_cfg)
# --- Wire up clicks ---
# Option A: non-blocking (recommended): use QProcess so the GUI stays responsive
btn_run.clicked.connect(lambda: open_prefilled_terminal())
btn_check.clicked.connect(lambda: self.run_cmd(["uv", "run", "envied", "env", "check"]))
btn_info.clicked.connect(lambda: self.run_cmd(["uv", "run", "envied", "env", "info"]))
'''btn_cfg.clicked.connect(
lambda: os.system('nano ./packages/envied/src/envied/envied.yaml') if os.name == 'posix'
else os.system('notepad.exe ./packages/envied/src/envied/envied.yaml')
)'''
btn_cfg.clicked.connect(open_config_detached)
# ---- Helpers ----
def run_cmd(self, argv):
"""
Non-blocking run using QProcess.
"""
proc = QProcess(self)
# Optional: forward output to your terminal
proc.setProgram(argv[0])
proc.setArguments(argv[1:])
# Show output in your launching terminal (detach these if not needed)
proc.readyReadStandardOutput.connect(lambda p=proc: print(p.readAllStandardOutput().data().decode(), end=""))
proc.readyReadStandardError.connect(lambda p=proc: print(p.readAllStandardError().data().decode(), end=""))
proc.start()
def run_script(self, what):
"""
Placeholder hook for your 'config' action.
e.g., open a config dialog or run a script.
"""
print(f"[EnviedPanel] run_script({what}) not implemented yet.")
def get_terminal():
terminals = ["gnome-terminal", "xterm", "konsole", "lxterminal", "xfce4-terminal"]
for term in terminals:
if shutil.which(term):
return term
elif os.__name__ =="nt":
TERMINALS = ["WindowsTerminal.exe", "OpenConsole.exe","powershell.exe", "Terminal.exe", "cmd.exe"]
for TERMINAL in TERMINALS:
if shutil.which(TERMINAL):
return TERMINAL
raise EnvironmentError("No suitable terminal emulator found.")
def open_prefilled_terminal():
prefill = 'uv run envied dl --select-titles '
term = get_terminal()
if term == "gnome-terminal":
# -l starts login shell; -c runs the command, then we keep the shell open with 'exec bash'
cmd = [
"gnome-terminal",
"--",
"bash", "-lc",
# read: -e enables readline, -i sets initial text
f'read -e -i "{prefill}" cmd; eval "$cmd"; exec bash'
]
elif term == "konsole":
cmd = [
"konsole", "-e",
"bash", "-lc",
f'read -e -i "{prefill}" cmd; eval "$cmd"; exec bash'
]
elif term =="lxterminal": # xterm fallback
cmd = [
"xterm", "-e",
"bash", "-lc",
f'read -e -i "{prefill}" cmd; eval "$cmd"; exec bash'
]
elif os.name == "nt":
# --- Windows: open PowerShell (or Windows Terminal -> PowerShell)
# and pre-fill the command line using PSReadLine.
# This requires PSReadLine (present by default on modern Windows).
ps_prefill = prefill.replace('"', r'`"') # escape quotes for PowerShell
if shutil.which("wt.exe"):
# Windows Terminal present: open a new tab running PowerShell
cmd = [
"wt.exe",
"powershell",
"-NoExit",
"-Command",
f"[Microsoft.PowerShell.PSConsoleReadLine]::Insert(\"{ps_prefill}\")"
]
elif shutil.which("powershell.exe"):
# Fallback: plain PowerShell
cmd = [
"powershell.exe",
"-NoExit",
"-Command",
f"[Microsoft.PowerShell.PSConsoleReadLine]::Insert(\"{ps_prefill}\")"
]
else:
# Last resort: cmd.exe can't truly prefill. Keep the window open and hint user.
cmd = [
"cmd.exe", "/K",
f'echo Please use PowerShell for prefill. Intended command: {prefill}'
]
subprocess.Popen(cmd)
def open_config_detached():
if os.name == "nt":
QProcess.startDetached("notepad.exe", [CFG])
return
# POSIX
# Easiest: open in the default GUI editor
if shutil.which("xdg-open"):
QProcess.startDetached("xdg-open", [CFG])
return
# If you insist on nano, run it *inside* a terminal emulator
# (xterm is very common and works out of the box)
if shutil.which("xterm"):
QProcess.startDetached("xterm", ["-e", "nano", CFG])
return
# Last resort (may fail without a TTY)
QProcess.startDetached("nano", [CFG])
if __name__ == "__main__":
from PyQt6.QtWidgets import QApplication
app = QApplication(sys.argv)
envied_panel = EnviedPanel()
window = envied_panel
window.show()
sys.exit(app.exec())
subprocess.Popen(cmd)
+153
View File
@@ -0,0 +1,153 @@
# Group or Username to postfix to the end of all download fienames following a dash
#tag: ''
# Set terminal background color (custom option not in CONFIG.md)
set_terminal_bg: false
# Muxing configuration
muxing:
set_title: false
# shakapackager or mp4decrypt
decryption: shakapackager
scene_naming: false
series_year: false
# Widevine pssh display; fold, crop or ellipsis
pssh_display: ellipsis
# Login credentials for each Service
credentials:
ALL4: angela.slaney@gmail.com:Nood13sh
ROKU: moretonplumber@yandex.com:$WPJ~Mdh2:X@EtB
TVNZ: moretonplumber@yandex.com:penumbra
TPTV: moretonplumber@yandex.com:penumbra1234
CBC: moretonplumber@yandex.com:penumbra
# Override default directories used across unshackle
directories:
cache: Cache
cookies: Cookies
dcsl: DCSL # Device Certificate Status List
downloads: /home/angela/Downloads/devine/
logs: Logs
temp: Temp
wvds: WVDs
prds: PRDs
# Additional directories that can be configured:
# commands: Commands
#services:
# - ./envied/services/
vaults: vaults/
# fonts: Fonts
# Pre-define which Widevine or PlayReady device to use for each Service
cdm:
default: device
# Use pywidevine Serve-compliant Remote CDMs
remote_cdm:
- name: "CDRM_Project_API"
Device Type: 'ANDROID'
System ID: 22590
Security Level: 3
Host: https://cdrm-project.com/remotecdm/widevine
Secret: 'CDRM'
Device Name: public
# Key Vaults store your obtained Content Encryption Keys (CEKs)
key_vaults:
- type: SQLite
name: Local vault
path: /home/angela/Programming/gits/devine-services/key_store.db
- type: HTTPAPI
name: drmlab
host: http://api.drmlab.io/vault/
password: gEX75q7I5YVkvgF5SUkcNd41IbGrDtTT
#- type: API
# name: "Online Vault"
# uri: "https://cdrm-project.com/api/cache"
# token: "CDRM"
# Choose what software to use to download data
#downloader: n_m3u8dl_re
# Options: requests | aria2c | curl_impersonate | n_m3u8dl_re
# Can also be a mapping:
downloader:
# NF: requests
# AMZN: n_m3u8dl_re
ALL4: n_m3u8dl_re
default: n_m3u8dl_re
# aria2c downloader configuration
aria2c:
max_concurrent_downloads: 4
max_connection_per_server: 3
split: 5
file_allocation: falloc # none | prealloc | falloc | trunc
# N_m3u8DL-RE downloader configuration
n_m3u8dl_re:
thread_count: 16
ad_keyword: "advertisement"
use_proxy: true
# curl_impersonate downloader configuration
curl_impersonate:
browser: chrome120
# Pre-define default options and switches of the dl command
dl:
best: true
sub_format: srt
downloads: 4
workers: 16
# Chapter Name to use when exporting a Chapter without a Name
chapter_fallback_name: "Chapter {j:02}"
# Case-Insensitive dictionary of headers for all Services
headers:
Accept-Language: "en-US,en;q=0.8"
User-Agent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.75 Safari/537.36"
# Override default filenames used across unshackle
filenames:
log: "envied_{name}_{time}.log"
config: "config.yaml"
root_config: "envied.yaml"
chapters: "Chapters_{title}_{random}.txt"
subtitle: "Subtitle_{id}_{language}.srt"
# API key for The Movie Database (TMDB)
tmdb_api_key: ""
# conversion_method:
# - auto (default): Smart routing - subby for WebVTT/SAMI, standard for others
# - subby: Always use subby with advanced processing
# - pycaption: Use only pycaption library (no SubtitleEdit, no subby)
# - subtitleedit: Prefer SubtitleEdit when available, fall back to pycaption
subtitle:
conversion_method: auto
sdh_method: auto
# Configuration data for each Service
services:
# Service-specific configuration goes here
# EXAMPLE:
# api_key: "service_specific_key"
# Legacy NordVPN configuration (use proxy_providers instead)
proxy_providers:
nordvpn:
username: gBM9ejjmY1J15jccH12VT7Qy
password: 1kmuCrRXnT55uAjiUTd8twv9
server_map:
us: 6918
uk: 2613
nz: 100
@@ -87,7 +87,7 @@ class TPTV(Service):
self.profile = ctx.parent.params.get("profile")
if not self.profile:
self.profile = "default"
self.session = requests.session()
self.session.headers.update(self.config["headers"])
def authenticate(self, cookies: Optional[MozillaCookieJar] = None, credential: Optional[Credential] = None) -> None:
@@ -147,7 +147,7 @@ class TPTV(Service):
tokens = res
self.log.info(" + Acquired tokens...")
cache.set(tokens)
# cache.set(tokens)
self.authorization = tokens
+38 -27
View File
@@ -1,5 +1,5 @@
from PyQt6.QtWidgets import (QApplication,QWidget,QVBoxLayout,QLabel,QLineEdit,QPushButton,QCheckBox,QFrame,)
from PyQt6.QtCore import Qt
from PyQt6.QtCore import Qt, QProcess
from PyQt6.QtGui import QPalette, QColor
from PyQt6.QtCore import QTimer
from PyQt6.QtWidgets import QHBoxLayout, QSlider
@@ -61,23 +61,6 @@ _CFG_NAME = "config.yaml"
'''def load_config_with_fallback() -> tuple[dict, Path | None]:
"""
Returns (config_dict, user_path_if_used_or_None).
Prefers user config; falls back to package-bundled default.
"""
user_path = _user_config_path()
if user_path.exists():
data = yaml.safe_load(user_path.read_text(encoding="utf-8")) or {}
return data, user_path
# packaged default
with resources.files(_PKG_NAME).joinpath(_CFG_NAME).open("rb") as f:
data = yaml.safe_load(f) or {}
return data, None'''
def derive_loader_class_name(service_modname: str) -> str:
"""
Derive a class name from a service module name.
@@ -319,16 +302,15 @@ class VineFeeder(QWidget):
# Set button text to white in dark mode, remove red border
for i in range(self.highlighted_layout.count()):
button = self.highlighted_layout.itemAt(i).widget()
if isinstance(button, QPushButton):
button.setStyleSheet("""
b = self.highlighted_layout.itemAt(i).widget()
if isinstance(b, QPushButton) and b.objectName() != "enviedButton": # <-- skip Envied
b.setStyleSheet("""
color: white;
background-color:#1E1E2E;
border: none;
padding: 5px;
""")
button.repaint() # Force update of the button's appearance
b.repaint()
else:
self.setPalette(QApplication.palette())
self.search_url_label.setStyleSheet("color: black;")
@@ -336,15 +318,15 @@ class VineFeeder(QWidget):
self.batch_label.setStyleSheet("color: black;")
for i in range(self.highlighted_layout.count()):
button = self.highlighted_layout.itemAt(i).widget()
if isinstance(button, QPushButton):
button.setStyleSheet("""
b = self.highlighted_layout.itemAt(i).widget()
if isinstance(b, QPushButton) and b.objectName() != "enviedButton": # <-- skip Envied
b.setStyleSheet("""
color: black;
background-color: #aeaeae;
border: none;
padding: 5px;
""")
button.repaint() # Force update of the button's appearance
b.repaint()
# Update batch_label based on dark mode and batch mode state
if self.batch_slider.value() == 1:
self.batch_label.setStyleSheet("color: lightgreen; padding-left: 5px; border: none;")
@@ -477,7 +459,36 @@ class VineFeeder(QWidget):
button.clicked.connect(
lambda: self.run_script("./gui.py"))
self.highlighted_layout.addWidget(button)
if os.name == "posix": # linux
button = QPushButton("Envied")
button.setObjectName("enviedButton") # <-- add this
button.setStyleSheet("color: #f5c2e7; border: none; background-color:#1E1E2E;padding: 5px;") # your pink
button.clicked.connect(lambda: self.run_script("./envied_gui.py"))
elif os.name == "nt":
button = QPushButton("Envied Config")
button.setObjectName("enviedButton") # <-- add this
button.setStyleSheet("color: #f5c2e7; border: none; background-color:#1E1E2E;padding: 5px;") # your pink
CFG = os.path.abspath("./packages/envied/src/envied/envied.yaml")
button.clicked.connect(lambda: QProcess.startDetached("notepad.exe", [CFG]))
self.highlighted_layout.addWidget(button)
def run_cmd(self, argv):
"""
Non-blocking run using QProcess.
"""
proc = QProcess(self)
# Optional: forward output to your terminal
proc.setProgram(argv[0])
proc.setArguments(argv[1:])
# Show output in your launching terminal (detach these if not needed)
proc.readyReadStandardOutput.connect(lambda p=proc: print(p.readAllStandardOutput().data().decode(), end=""))
proc.readyReadStandardError.connect(lambda p=proc: print(p.readAllStandardError().data().decode(), end=""))
proc.start()
def run_script(self, path: str):
"""
@@ -1,2 +1,3 @@
BATCH_DOWNLOAD: false
TERMINAL_RESET: true
TERMINAL_RESET: false
TERMINAL: gnome-terminal