chore: bump 0.2.32 and apply plugin loading override
This commit is contained in:
@@ -42,7 +42,8 @@ class FfmpegHlsDownloader(BaseDownloader):
|
||||
if not filename:
|
||||
filename = f"download_{int(__import__('time').time())}.mp4"
|
||||
|
||||
filepath = os.path.join(save_path, filename)
|
||||
filepath = os.path.abspath(os.path.join(save_path, filename))
|
||||
filepath = os.path.normpath(filepath)
|
||||
|
||||
# ffmpeg 명령어 구성
|
||||
ffmpeg_path = options.get('ffmpeg_path', 'ffmpeg')
|
||||
@@ -173,7 +174,16 @@ class FfmpegHlsDownloader(BaseDownloader):
|
||||
"""다운로드 취소"""
|
||||
super().cancel()
|
||||
if self._process:
|
||||
try:
|
||||
# [FIX] 파이프 명시적으로 닫기
|
||||
if self._process.stdout: self._process.stdout.close()
|
||||
if self._process.stderr: self._process.stderr.close()
|
||||
|
||||
self._process.terminate()
|
||||
# 짧은 대기 후 여전히 살아있으면 kill
|
||||
try: self._process.wait(timeout=1)
|
||||
except: self._process.kill()
|
||||
except: pass
|
||||
|
||||
def _get_duration(self, url: str, ffprobe_path: str, headers: Dict) -> float:
|
||||
"""ffprobe로 영상 길이 획득"""
|
||||
|
||||
@@ -38,7 +38,8 @@ class HttpDirectDownloader(BaseDownloader):
|
||||
if not filename:
|
||||
filename = url.split('/')[-1].split('?')[0] or f"download_{int(__import__('time').time())}"
|
||||
|
||||
filepath = os.path.join(save_path, filename)
|
||||
filepath = os.path.abspath(os.path.join(save_path, filename))
|
||||
filepath = os.path.normpath(filepath)
|
||||
|
||||
# 헤더 설정
|
||||
headers = options.get('headers', {})
|
||||
|
||||
@@ -40,11 +40,13 @@ class YtdlpAria2Downloader(BaseDownloader):
|
||||
try:
|
||||
os.makedirs(save_path, exist_ok=True)
|
||||
|
||||
# 출력 템플릿
|
||||
if filename:
|
||||
output_template = os.path.normpath(os.path.join(save_path, filename))
|
||||
else:
|
||||
output_template = os.path.normpath(os.path.join(save_path, '%(title)s.%(ext)s'))
|
||||
# 출력 템플릿 (outtmpl 옵션 우선 처리)
|
||||
raw_outtmpl = options.get('outtmpl') or filename or '%(title)s.%(ext)s'
|
||||
|
||||
# 경로와 템플릿 결합 후 정규화
|
||||
output_template = os.path.abspath(os.path.join(save_path, raw_outtmpl))
|
||||
# 윈도우/리눅스 구분 없이 중복 슬래시 제거 및 절대 경로 확보
|
||||
output_template = os.path.normpath(output_template)
|
||||
|
||||
# yt-dlp 명령어 구성
|
||||
cmd = [
|
||||
@@ -58,6 +60,10 @@ class YtdlpAria2Downloader(BaseDownloader):
|
||||
cmd.extend(['--print', 'before_dl:GDM_FIX:title:%(title)s'])
|
||||
cmd.extend(['--print', 'before_dl:GDM_FIX:thumb:%(thumbnail)s'])
|
||||
|
||||
# 속도 제한 설정
|
||||
max_rate = P.ModelSetting.get('max_download_rate')
|
||||
rate_limited = bool(max_rate and max_rate != '0')
|
||||
|
||||
# aria2c 사용 (설치되어 있으면)
|
||||
aria2c_path = options.get('aria2c_path', 'aria2c')
|
||||
connections = options.get('connections', 4)
|
||||
@@ -65,21 +71,18 @@ class YtdlpAria2Downloader(BaseDownloader):
|
||||
if self._check_aria2c(aria2c_path):
|
||||
cmd.extend(['--external-downloader', aria2c_path])
|
||||
# aria2c 설정: -x=연결수, -s=분할수, -j=병렬, -k=조각크기, --console-log-level=notice로 진행률 출력
|
||||
cmd.extend(['--external-downloader-args', f'aria2c:-x{connections} -s{connections} -j{connections} -k1M --summary-interval=1 --console-log-level=notice'])
|
||||
aria2_args = f'aria2c:-x{connections} -s{connections} -j{connections} -k1M --summary-interval=1 --console-log-level=notice'
|
||||
if rate_limited:
|
||||
aria2_args = f'{aria2_args} --max-download-limit={max_rate}'
|
||||
cmd.extend(['--external-downloader-args', aria2_args])
|
||||
logger.info(f'[GDM] Using aria2c for multi-threaded download (connections: {connections})')
|
||||
|
||||
# 진행률 템플릿 추가 (yt-dlp native downloader)
|
||||
cmd.extend(['--progress-template', 'download:GDM_PROGRESS:%(progress._percent_str)s:%(progress._speed_str)s:%(progress._eta_str)s'])
|
||||
|
||||
# 속도 제한 설정
|
||||
max_rate = P.ModelSetting.get('max_download_rate')
|
||||
if max_rate == '0':
|
||||
max_rate_arg = ''
|
||||
log_rate_msg = '무제한'
|
||||
else:
|
||||
max_rate_arg = f'--max-download-limit={max_rate}'
|
||||
log_rate_msg = max_rate
|
||||
cmd.extend(['--limit-rate', max_rate]) # Native downloader limit
|
||||
# yt-dlp native downloader 제한 (external-downloader 미사용/보조 경로)
|
||||
if rate_limited:
|
||||
cmd.extend(['--limit-rate', max_rate])
|
||||
|
||||
# 포맷 선택
|
||||
format_spec = options.get('format')
|
||||
@@ -147,14 +150,6 @@ class YtdlpAria2Downloader(BaseDownloader):
|
||||
if options.get('add_metadata'):
|
||||
cmd.append('--add-metadata')
|
||||
|
||||
if options.get('outtmpl'):
|
||||
# outtmpl 옵션이 별도로 전달된 경우 덮어쓰기 (output_template는 -o가 이미 차지함)
|
||||
# 하지만 yt-dlp -o 옵션이 곧 outtmpl임.
|
||||
# 파일명 템플릿 문제 해결을 위해 filename 인자 대신 outtmpl 옵션을 우선시
|
||||
# 위에서 -o output_template를 이미 넣었으므로, 여기서 다시 넣으면 중복될 수 있음.
|
||||
# 따라서 로직 수정: filename 없이 outtmpl만 온 경우
|
||||
pass
|
||||
|
||||
# URL 추가
|
||||
cmd.append(url)
|
||||
|
||||
@@ -314,7 +309,16 @@ class YtdlpAria2Downloader(BaseDownloader):
|
||||
"""다운로드 취소"""
|
||||
super().cancel()
|
||||
if self._process:
|
||||
try:
|
||||
# [FIX] 파이프 명시적으로 닫기
|
||||
if self._process.stdout: self._process.stdout.close()
|
||||
if self._process.stderr: self._process.stderr.close()
|
||||
|
||||
self._process.terminate()
|
||||
# 짧은 대기 후 여전히 살아있으면 kill
|
||||
try: self._process.wait(timeout=1)
|
||||
except: self._process.kill()
|
||||
except: pass
|
||||
|
||||
def _check_aria2c(self, aria2c_path: str) -> bool:
|
||||
"""aria2c 설치 확인"""
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
title: "GDM"
|
||||
package_name: gommi_downloader_manager
|
||||
version: '0.2.31'
|
||||
version: '0.2.33'
|
||||
description: FlaskFarm 범용 다운로더 큐 - YouTube, 애니24, 링크애니, Anilife 지원
|
||||
developer: projectdx
|
||||
home: https://gitea.yommi.duckdns.org/projectdx/gommi_downloader_manager
|
||||
|
||||
10
mod_queue.py
10
mod_queue.py
@@ -297,13 +297,13 @@ class ModuleQueue(PluginModuleBase):
|
||||
|
||||
# 1. Git Pull
|
||||
cmd = ['git', '-C', plugin_path, 'pull']
|
||||
process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
|
||||
stdout, stderr = process.communicate()
|
||||
result = subprocess.run(cmd, capture_output=True, text=True, timeout=60)
|
||||
|
||||
if process.returncode != 0:
|
||||
raise Exception(f"Git pull 실패: {stderr}")
|
||||
if result.returncode != 0:
|
||||
raise Exception(f"Git pull 실패: {result.stderr}")
|
||||
|
||||
self.P.logger.info(f"Git pull 결과: {stdout}")
|
||||
self.P.logger.info(f"Git pull 결과: {result.stdout}")
|
||||
stdout = result.stdout
|
||||
|
||||
# 2. 모듈 리로드 (Hot-Reload)
|
||||
self.reload_plugin()
|
||||
|
||||
@@ -46,7 +46,63 @@
|
||||
background-color: #3e5770 !important;
|
||||
}
|
||||
|
||||
#loading { display: none !important; }
|
||||
/* Plugin-owned loading override (independent from Flaskfarm default loader assets) */
|
||||
#loading,
|
||||
#modal_loading {
|
||||
display: none;
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 3000010 !important;
|
||||
background: rgba(10, 22, 36, 0.46);
|
||||
backdrop-filter: blur(2px);
|
||||
}
|
||||
|
||||
#loading img,
|
||||
#modal_loading img {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
#loading::before,
|
||||
#modal_loading::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
width: 58px;
|
||||
height: 58px;
|
||||
margin-left: -29px;
|
||||
margin-top: -29px;
|
||||
border-radius: 50%;
|
||||
border: 3px solid rgba(255, 255, 255, 0.24);
|
||||
border-top-color: var(--accent-primary);
|
||||
border-right-color: var(--accent-secondary);
|
||||
animation: gdm-loader-spin 0.9s linear infinite;
|
||||
}
|
||||
|
||||
#loading::after,
|
||||
#modal_loading::after {
|
||||
content: "LOADING";
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: calc(50% + 44px);
|
||||
transform: translateX(-50%);
|
||||
color: var(--text-main);
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.12em;
|
||||
}
|
||||
|
||||
#loading[style*="display: block"],
|
||||
#loading[style*="display: inline-block"],
|
||||
#modal_loading[style*="display: block"],
|
||||
#modal_loading[style*="display: inline-block"] {
|
||||
display: block !important;
|
||||
}
|
||||
|
||||
@keyframes gdm-loader-spin {
|
||||
from { transform: rotate(0deg); }
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
#gommi_download_manager_queue_list {
|
||||
font-family: var(--font-sans);
|
||||
@@ -143,6 +199,24 @@
|
||||
}
|
||||
}
|
||||
|
||||
/* Mobile 5px Padding - Maximum Screen Usage */
|
||||
@media (max-width: 768px) {
|
||||
.container, .container-fluid, #main_container, #gommi_download_manager_queue_list {
|
||||
padding-left: 5px !important;
|
||||
padding-right: 5px !important;
|
||||
margin-left: 0 !important;
|
||||
margin-right: 0 !important;
|
||||
}
|
||||
.row {
|
||||
margin-left: 0 !important;
|
||||
margin-right: 0 !important;
|
||||
}
|
||||
[class*="col-"] {
|
||||
padding-left: 4px !important;
|
||||
padding-right: 4px !important;
|
||||
}
|
||||
}
|
||||
|
||||
.page-title {
|
||||
font-size: 1.75rem;
|
||||
font-weight: 700;
|
||||
|
||||
@@ -23,6 +23,64 @@
|
||||
--font-sans: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
||||
}
|
||||
|
||||
/* Plugin-owned loading override (independent from Flaskfarm default loader assets) */
|
||||
#loading,
|
||||
#modal_loading {
|
||||
display: none;
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 3000010 !important;
|
||||
background: rgba(7, 16, 35, 0.46);
|
||||
backdrop-filter: blur(2px);
|
||||
}
|
||||
|
||||
#loading img,
|
||||
#modal_loading img {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
#loading::before,
|
||||
#modal_loading::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
width: 58px;
|
||||
height: 58px;
|
||||
margin-left: -29px;
|
||||
margin-top: -29px;
|
||||
border-radius: 50%;
|
||||
border: 3px solid rgba(255, 255, 255, 0.24);
|
||||
border-top-color: var(--accent-primary);
|
||||
border-right-color: var(--accent-secondary);
|
||||
animation: gdm-loader-spin 0.9s linear infinite;
|
||||
}
|
||||
|
||||
#loading::after,
|
||||
#modal_loading::after {
|
||||
content: "LOADING";
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: calc(50% + 44px);
|
||||
transform: translateX(-50%);
|
||||
color: var(--text-main);
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.12em;
|
||||
}
|
||||
|
||||
#loading[style*="display: block"],
|
||||
#loading[style*="display: inline-block"],
|
||||
#modal_loading[style*="display: block"],
|
||||
#modal_loading[style*="display: inline-block"] {
|
||||
display: block !important;
|
||||
}
|
||||
|
||||
@keyframes gdm-loader-spin {
|
||||
from { transform: rotate(0deg); }
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
#gommi_download_manager_queue_setting {
|
||||
font-family: var(--font-sans);
|
||||
color: var(--text-main);
|
||||
@@ -237,6 +295,10 @@
|
||||
<option value="1M" {% if arg['max_download_rate'] == '1M' %}selected{% endif %}>1 MB/s</option>
|
||||
<option value="3M" {% if arg['max_download_rate'] == '3M' %}selected{% endif %}>3 MB/s</option>
|
||||
<option value="5M" {% if arg['max_download_rate'] == '5M' %}selected{% endif %}>5 MB/s</option>
|
||||
<option value="6M" {% if arg['max_download_rate'] == '6M' %}selected{% endif %}>6 MB/s</option>
|
||||
<option value="7M" {% if arg['max_download_rate'] == '7M' %}selected{% endif %}>7 MB/s</option>
|
||||
<option value="8M" {% if arg['max_download_rate'] == '8M' %}selected{% endif %}>8 MB/s</option>
|
||||
<option value="9M" {% if arg['max_download_rate'] == '9M' %}selected{% endif %}>9 MB/s</option>
|
||||
<option value="10M" {% if arg['max_download_rate'] == '10M' %}selected{% endif %}>10 MB/s</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user