Skip to content

Commit 748ecd0

Browse files
authored
Merge pull request #58 from z1nc0r3/feat-automatic-update-for-ytdlp-pkg
[FEAT] Automatic update logic for yt-dlp pip package
2 parents df3affa + 4c0dc57 commit 748ecd0

File tree

3 files changed

+172
-8
lines changed

3 files changed

+172
-8
lines changed

plugin/main.py

Lines changed: 61 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@
2222
verify_ffmpeg_zip,
2323
extract_ffmpeg,
2424
get_binaries_paths,
25+
check_ytdlp_update_needed,
26+
update_ytdlp_library,
2527
)
2628
from results import (
2729
init_results,
@@ -32,10 +34,13 @@
3234
download_ffmpeg_result,
3335
ffmpeg_setup_result,
3436
ffmpeg_not_found_result,
37+
update_ytdlp_result,
38+
ytdlp_update_in_progress_result,
3539
)
3640
from ytdlp import CustomYoutubeDL
3741

3842
PLUGIN_ROOT = os.path.dirname(os.path.abspath(__file__))
43+
LIB_PATH = os.path.abspath(os.path.join(PLUGIN_ROOT, "..", "lib"))
3944
EXE_PATH = os.path.join(PLUGIN_ROOT, "yt-dlp.exe")
4045
CHECK_INTERVAL_DAYS = 5
4146
DEFAULT_DOWNLOAD_PATH = str(Path.home() / "Downloads")
@@ -92,6 +97,33 @@ def query(query: str) -> ResultResponse:
9297
return send_results([invalid_result()])
9398

9499
query = query.replace("https://", "http://")
100+
101+
# Check if yt-dlp library needs update before processing
102+
update_lock = os.path.join(LIB_PATH, ".ytdlp_updating")
103+
104+
# Check if update is in progress, but ignore stale locks
105+
if os.path.exists(update_lock):
106+
try:
107+
lock_age = datetime.now() - datetime.fromtimestamp(os.path.getmtime(update_lock))
108+
if lock_age < timedelta(minutes=5):
109+
return send_results([ytdlp_update_in_progress_result()])
110+
else:
111+
try:
112+
os.remove(update_lock)
113+
except Exception:
114+
# Best-effort cleanup of stale update lock; ignore failures as they are non-fatal.
115+
pass
116+
except Exception:
117+
# If we can't check lock age, assume update is in progress to be safe
118+
return send_results([ytdlp_update_in_progress_result()])
119+
120+
if check_ytdlp_update_needed(CHECK_INTERVAL_DAYS):
121+
try:
122+
import yt_dlp
123+
current_version = yt_dlp.version.__version__
124+
except:
125+
current_version = None
126+
return send_results([update_ytdlp_result(current_version)])
95127

96128
ydl_opts = {
97129
"quiet": True,
@@ -126,7 +158,7 @@ def query(query: str) -> ResultResponse:
126158
formats = sort_by_tbr(formats)
127159
elif sort == "FPS":
128160
formats = sort_by_fps(formats)
129-
161+
130162
results = []
131163

132164
if not verify_ffmpeg_binaries():
@@ -181,20 +213,42 @@ def download_ffmpeg_binaries(PLUGIN_ROOT) -> None:
181213
if not os.path.exists(FFMPEG_ZIP):
182214
return
183215

184-
zip_ok, zip_reason = verify_ffmpeg_zip(return_reason=True)
216+
zip_ok, _ = verify_ffmpeg_zip(return_reason=True)
185217
if not zip_ok:
186-
if zip_reason:
187-
print(f"FFmpeg download validation failed: {zip_reason}")
188218
try:
189219
os.remove(FFMPEG_ZIP)
190220
except Exception:
191221
pass
192222
return
193223

194-
extracted, extract_reason = extract_ffmpeg()
195-
if not extracted and extract_reason:
196-
print(f"FFmpeg extraction failed: {extract_reason}")
224+
extract_ffmpeg()
225+
finally:
226+
try:
227+
if os.path.exists(lock_path):
228+
os.remove(lock_path)
229+
except Exception:
230+
pass
231+
232+
233+
@plugin.on_method
234+
def update_ytdlp_library_action() -> None:
235+
"""Update the yt-dlp library when user clicks the update prompt."""
236+
lock_path = os.path.join(LIB_PATH, ".ytdlp_updating")
237+
238+
# Create lock file to prevent concurrent updates
239+
try:
240+
os.makedirs(LIB_PATH, exist_ok=True)
241+
with open(lock_path, "w") as lock_file:
242+
lock_file.write("in-progress")
243+
except Exception as _:
244+
return
245+
246+
try:
247+
update_ytdlp_library()
248+
except Exception as _:
249+
return
197250
finally:
251+
# Always remove lock file, even if update fails or is interrupted
198252
try:
199253
if os.path.exists(lock_path):
200254
os.remove(lock_path)

plugin/results.py

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,12 +55,47 @@ def ffmpeg_setup_result(issue) -> Result:
5555
)
5656

5757

58+
def update_ytdlp_result(current_version=None) -> Result:
59+
subtitle = "Click to update yt-dlp library to the latest version."
60+
if current_version:
61+
subtitle = f"Current version: {current_version}. Click to update."
62+
return Result(
63+
Title="yt-dlp library update available!",
64+
SubTitle=subtitle,
65+
IcoPath="Images/app.png",
66+
JsonRPCAction={"method": "update_ytdlp_library_action", "parameters": []},
67+
)
68+
69+
70+
def ytdlp_update_in_progress_result() -> Result:
71+
return Result(
72+
Title="yt-dlp update in progress...",
73+
SubTitle="Please wait a moment and try again.",
74+
IcoPath="Images/app.png",
75+
)
76+
77+
5878
def query_result(
5979
query, thumbnail, title, format, download_path, pref_video_path, pref_audio_path
6080
) -> Result:
81+
# Build subtitle with consistent spacing
82+
subtitle_parts = [f"Res: {format['resolution']}"]
83+
84+
if format.get('tbr') is not None:
85+
subtitle_parts.append(f"({round(format['tbr'], 2)} kbps)")
86+
87+
if format.get('filesize'):
88+
size_mb = round(format['filesize'] / 1024 / 1024, 2)
89+
subtitle_parts.append(f"Size: {size_mb}MB")
90+
91+
if format.get('fps'):
92+
subtitle_parts.append(f"FPS: {int(format['fps'])}")
93+
94+
subtitle = " ┃ ".join(subtitle_parts)
95+
6196
return Result(
6297
Title=title,
63-
SubTitle=f"Res: {format['resolution']} ({round(format['tbr'], 2)} kbps) {'┃ Size: ' + str(round(format['filesize'] / 1024 / 1024, 2)) + 'MB' if format.get('filesize') else ''} {'┃ FPS: ' + str(int(format['fps'])) if format.get('fps') else ''}",
98+
SubTitle=subtitle,
6499
IcoPath=thumbnail or "Images/app.png",
65100
JsonRPCAction={
66101
"method": "download",

plugin/utils.py

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
import re
22
import os
33
import zipfile
4+
import subprocess
5+
import sys
6+
from datetime import datetime, timedelta
47

58
PLUGIN_ROOT = os.path.dirname(os.path.abspath(__file__))
9+
LIB_PATH = os.path.abspath(os.path.join(PLUGIN_ROOT, "..", "lib"))
610
FFMPEG_SETUP_LOCK = os.path.join(PLUGIN_ROOT, "ffmpeg_setup.lock")
711
URL_REGEX = (
812
"((http|https)://)(www.)?"
@@ -280,3 +284,74 @@ def extract_ffmpeg():
280284
return False, binaries_reason
281285

282286
return True, None
287+
288+
289+
def check_ytdlp_update_needed(check_interval_days=5):
290+
"""
291+
Check if yt-dlp library update is needed based on the last update timestamp.
292+
293+
Args:
294+
check_interval_days (int): Number of days between update checks.
295+
296+
Returns:
297+
bool: True if update is needed, False otherwise.
298+
"""
299+
300+
# Path to yt-dlp package in lib folder
301+
lib_ytdlp_path = os.path.join(LIB_PATH, "yt_dlp")
302+
update_marker = os.path.join(LIB_PATH, ".ytdlp_last_update")
303+
304+
# If yt-dlp doesn't exist in lib, update is needed
305+
if not os.path.exists(lib_ytdlp_path):
306+
return True
307+
308+
# Check the update marker file
309+
if os.path.exists(update_marker):
310+
try:
311+
last_update = datetime.fromtimestamp(os.path.getmtime(update_marker))
312+
if datetime.now() - last_update < timedelta(days=check_interval_days):
313+
return False
314+
except Exception:
315+
# If we can't read the marker, assume update is needed
316+
return True
317+
318+
return True
319+
320+
321+
def update_ytdlp_library():
322+
"""
323+
Update the bundled yt-dlp library in the lib folder.
324+
325+
Returns:
326+
tuple: (success: bool, message: str)
327+
"""
328+
329+
update_marker = os.path.join(LIB_PATH, ".ytdlp_last_update")
330+
331+
# Try different pip commands in order of preference
332+
pip_commands = [
333+
[sys.executable, "-m", "pip", "install", "--upgrade", "--target", LIB_PATH, "yt-dlp"],
334+
["python", "-m", "pip", "install", "--upgrade", "--target", LIB_PATH, "yt-dlp"],
335+
["pip", "install", "--upgrade", "--target", LIB_PATH, "yt-dlp"],
336+
]
337+
338+
last_error = None
339+
for cmd in pip_commands:
340+
try:
341+
subprocess.run(cmd, check=True, timeout=120)
342+
343+
# Create/update the marker file
344+
os.makedirs(LIB_PATH, exist_ok=True)
345+
with open(update_marker, "w") as f:
346+
f.write("updated")
347+
348+
return True, "yt-dlp library updated successfully"
349+
except (subprocess.CalledProcessError, subprocess.TimeoutExpired, FileNotFoundError) as e:
350+
last_error = e
351+
continue
352+
353+
# All commands failed
354+
if isinstance(last_error, subprocess.TimeoutExpired):
355+
return False, "Update timed out after 2 minutes"
356+
else:
357+
return False, f"All pip commands failed. Last error: {str(last_error)}"

0 commit comments

Comments
 (0)