最近使用tg2drive自动追剧到云盘,但是对于码率比较高的电视剧在观看时会很卡,几乎一秒一卡,于是有了本脚本的出现。
通过监控tg2drive生成的strm文件夹,通过关键词匹配下载指定电视剧,实现观看时不再卡顿,自动跳过已下载文件,由于监控的是生成到本地的strm文件,因此全程无云盘风控风险,扫描间隔在青龙面板配置想多短就多短。
对于其他strm方案只要strm内容是http直链均可正常使用,如果需要的人多也可以改一版webdav挂载方案。
至于为什么使用青龙面板而不是发一个docker,一来功能简单,二来实在是内存不够了
#!/usr/bin/env python3
"""
青龙面板定时脚本:扫描 strm 文件夹,下载关键词匹配的文件,保持目录结构
"""
import os
import re
import time
import logging
import shutil
from urllib.parse import unquote, urlparse
from pathlib import Path
import requests
# ---------- 配置(根据实际情况修改)----------
STRM_DIR = "/volume1/docker/tg2drive/strm/123云盘/library/剧集"
DOWNLOAD_DIR = "/volume5/workspace/云盘下载"
KEYWORDS = ["小芳"] # 文件名包含任意一个关键词即下载
RECURSIVE = True # 是否递归扫描子目录
DOWNLOAD_TIMEOUT = 300 # 下载超时(秒)
RETRY_TIMES = 3 # 重试次数
# --------------------------------------------
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)
def ensure_dir(path):
"""确保目录存在"""
os.makedirs(path, exist_ok=True)
def extract_filename_from_url(url):
"""从 URL 中提取文件名"""
parsed = urlparse(url)
path = unquote(parsed.path)
filename = os.path.basename(path)
if not filename:
# 尝试从查询参数中提取(某些链接带 filename 参数)
query = parsed.query
match = re.search(r'filename=([^&]+)', query)
if match:
filename = unquote(match.group(1))
return filename
def should_download(filename):
"""根据关键词判断是否应该下载"""
if not filename:
return False
for kw in KEYWORDS:
if kw in filename:
return True
return False
def download_file(url, dest_path):
"""下载文件到指定路径,带重试(每10%打印一次进度)"""
for attempt in range(1, RETRY_TIMES + 1):
try:
logger.info(f"开始下载 [{attempt}/{RETRY_TIMES}]: {dest_path.name}")
with requests.get(url, stream=True, timeout=DOWNLOAD_TIMEOUT) as r:
r.raise_for_status()
total_size = int(r.headers.get('content-length', 0))
temp_path = str(dest_path) + ".tmp"
with open(temp_path, 'wb') as f:
downloaded = 0
last_percent = -1
for chunk in r.iter_content(chunk_size=8192):
if chunk:
f.write(chunk)
downloaded += len(chunk)
if total_size:
percent = int((downloaded / total_size) * 100)
if percent >= last_percent + 10:
logger.info(f"下载进度: {dest_path.name} {percent}%")
last_percent = percent
shutil.move(temp_path, str(dest_path))
logger.info(f"下载完成: {dest_path.name} (大小: {total_size} bytes)")
return True
except Exception as e:
logger.error(f"下载失败 (尝试 {attempt}): {e}")
temp_path = str(dest_path) + ".tmp"
if os.path.exists(temp_path):
os.remove(temp_path)
if attempt == RETRY_TIMES:
logger.error(f"下载最终失败: {dest_path.name}")
return False
time.sleep(2 ** attempt)
return False
def process_strm_file(strm_path):
"""
处理单个 strm 文件
返回:
1 = 下载成功
0 = 文件已存在,跳过
-1 = 下载失败
-2 = 关键词不匹配,跳过
"""
# 读取 URL
try:
with open(strm_path, 'r', encoding='utf-8') as f:
url = f.readline().strip()
except Exception as e:
logger.error(f"读取 strm 文件失败 {strm_path}: {e}")
return -1
if not url:
logger.warning(f"strm 文件内容为空: {strm_path}")
return -1
# 提取文件名
filename = extract_filename_from_url(url)
if not filename:
filename = Path(strm_path).stem
logger.warning(f"无法从 URL 提取文件名,使用 strm 文件名: {filename}")
# 关键词过滤
if not should_download(filename):
return -2 # 关键词不匹配
# 计算目标路径
rel_path = os.path.relpath(strm_path, STRM_DIR)
rel_dir = os.path.dirname(rel_path)
target_dir = Path(DOWNLOAD_DIR) / rel_dir
dest_path = target_dir / filename
# 检查是否已存在
if dest_path.exists():
logger.debug(f"文件已存在,跳过: {dest_path}") # debug 级别,不显示
return 0
# 确保目录存在
ensure_dir(target_dir)
# 下载
success = download_file(url, dest_path)
if success:
logger.info(f"成功下载: {filename} 到 {dest_path}")
return 1
else:
logger.error(f"下载失败: {filename}")
return -1
def main():
logger.info("===== 开始扫描 strm 文件 =====")
ensure_dir(DOWNLOAD_DIR)
strm_files = []
if RECURSIVE:
for root, _, files in os.walk(STRM_DIR):
for f in files:
if f.lower().endswith('.strm'):
strm_files.append(os.path.join(root, f))
else:
for f in os.listdir(STRM_DIR):
if f.lower().endswith('.strm'):
strm_files.append(os.path.join(STRM_DIR, f))
total = len(strm_files)
logger.info(f"共发现 {total} 个 strm 文件")
# 统计
skipped_exist = 0
skipped_keyword = 0
downloaded = 0
failed = 0
for strm_path in strm_files:
status = process_strm_file(strm_path)
if status == 1:
downloaded += 1
elif status == 0:
skipped_exist += 1
elif status == -2:
skipped_keyword += 1
else: # -1
failed += 1
# 最终统计信息
logger.info("===== 扫描完成 =====")
logger.info(f"总计扫描 strm 文件: {total}")
logger.info(f"已存在跳过: {skipped_exist}")
logger.info(f"关键词不匹配跳过: {skipped_keyword}")
logger.info(f"下载成功: {downloaded}")
logger.info(f"下载失败: {failed}")
if __name__ == "__main__":
main()