feat: Refine UI content loading with fade-in effects and lazy image handling, expand M3U8 URL detection to include gcdn.app, and enhance yt-dlp download progress
This commit is contained in:
@@ -246,7 +246,7 @@ class FfmpegQueue(object):
|
||||
logger.info(f"=== END COMMAND ===")
|
||||
|
||||
# m3u8 URL인 경우 다운로드 방법 설정에 따라 분기
|
||||
if video_url.endswith('.m3u8') or 'master.txt' in video_url:
|
||||
if video_url.endswith('.m3u8') or 'master.txt' in video_url or 'gcdn.app' in video_url:
|
||||
# 다운로드 방법 설정 확인
|
||||
download_method = P.ModelSetting.get(f"{self.name}_download_method")
|
||||
|
||||
|
||||
@@ -135,8 +135,14 @@ class YtdlpDownloader:
|
||||
bufsize=1
|
||||
)
|
||||
|
||||
# 여러 진행률 형식 매칭
|
||||
# [download] 10.5% of ~100.00MiB at 2.45MiB/s
|
||||
prog_re = re.compile(r'\[download\]\s+(?P<percent>[\d\.]+)%\s+of\s+.*?\s+at\s+(?P<speed>.*?)(\s+ETA|$)')
|
||||
# [download] 10.5% of 100.00MiB at 2.45MiB/s ETA 00:30
|
||||
# [download] 100% of 100.00MiB
|
||||
prog_patterns = [
|
||||
re.compile(r'\[download\]\s+(?P<percent>[\d\.]+)%\s+of\s+.*?(?:\s+at\s+(?P<speed>[\d\.]+\s*\w+/s))?'),
|
||||
re.compile(r'\[download\]\s+(?P<percent>[\d\.]+)%'),
|
||||
]
|
||||
|
||||
for line in self.process.stdout:
|
||||
if self.cancelled:
|
||||
@@ -146,18 +152,27 @@ class YtdlpDownloader:
|
||||
line = line.strip()
|
||||
if not line: continue
|
||||
|
||||
match = prog_re.search(line)
|
||||
if match:
|
||||
try:
|
||||
self.percent = float(match.group('percent'))
|
||||
self.current_speed = match.group('speed').strip()
|
||||
if self.start_time:
|
||||
elapsed = time.time() - self.start_time
|
||||
self.elapsed_time = self.format_time(elapsed)
|
||||
if self.callback:
|
||||
self.callback(percent=int(self.percent), current=int(self.percent), total=100, speed=self.current_speed, elapsed=self.elapsed_time)
|
||||
except: pass
|
||||
elif 'error' in line.lower() or 'security' in line.lower() or 'unable' in line.lower():
|
||||
# 디버깅: 모든 출력 로깅 (너무 많으면 주석 해제)
|
||||
if '[download]' in line or 'fragment' in line.lower():
|
||||
logger.debug(f"yt-dlp: {line}")
|
||||
|
||||
for prog_re in prog_patterns:
|
||||
match = prog_re.search(line)
|
||||
if match:
|
||||
try:
|
||||
self.percent = float(match.group('percent'))
|
||||
speed_group = match.groupdict().get('speed')
|
||||
if speed_group:
|
||||
self.current_speed = speed_group.strip()
|
||||
if self.start_time:
|
||||
elapsed = time.time() - self.start_time
|
||||
self.elapsed_time = self.format_time(elapsed)
|
||||
if self.callback:
|
||||
self.callback(percent=int(self.percent), current=int(self.percent), total=100, speed=self.current_speed, elapsed=self.elapsed_time)
|
||||
except: pass
|
||||
break # 한 패턴이 매칭되면 중단
|
||||
|
||||
if 'error' in line.lower() or 'security' in line.lower() or 'unable' in line.lower():
|
||||
logger.warning(f"yt-dlp output notice: {line}")
|
||||
self.error_output.append(line)
|
||||
|
||||
|
||||
@@ -21,15 +21,17 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<form id="program_list">
|
||||
{{ macros.setting_input_text_and_buttons('code', '작품 Code',
|
||||
[['analysis_btn', '분석'], ['go_anilife_btn', 'Go 애니라이프']], desc='예)
|
||||
"https://anilife.live/g/l?id=f6e83ec6-bd25-4d6c-9428-c10522687604" 이나 "f6e83ec6-bd25-4d6c-9428-c10522687604"')
|
||||
}}
|
||||
</form>
|
||||
<form id="program_auto_form">
|
||||
<div id="episode_list"></div>
|
||||
</form>
|
||||
<div id="main_content" style="display: none; opacity: 0; transition: opacity 0.3s ease-in;">
|
||||
<form id="program_list">
|
||||
{{ macros.setting_input_text_and_buttons('code', '작품 Code',
|
||||
[['analysis_btn', '분석'], ['go_anilife_btn', 'Go 애니라이프']], desc='예)
|
||||
"https://anilife.live/g/l?id=f6e83ec6-bd25-4d6c-9428-c10522687604" 이나 "f6e83ec6-bd25-4d6c-9428-c10522687604"')
|
||||
}}
|
||||
</form>
|
||||
<form id="program_auto_form">
|
||||
<div id="episode_list"></div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<!--전체-->
|
||||
<script src="{{ url_for('.static', filename='js/sjva_ui14.js') }}"></script>
|
||||
@@ -123,6 +125,13 @@
|
||||
function make_program(data) {
|
||||
current_data = data;
|
||||
// console.log("current_data::", current_data)
|
||||
|
||||
// 에피소드 목록을 완전히 숨긴 상태로 시작 (visibility로 레이아웃 시프트 방지)
|
||||
const episodeList = document.getElementById("episode_list");
|
||||
episodeList.style.visibility = 'hidden';
|
||||
episodeList.style.opacity = '0';
|
||||
episodeList.style.transition = 'opacity 0.3s ease-in';
|
||||
|
||||
str = '';
|
||||
tmp = '<div class="form-inline">'
|
||||
tmp += m_button('check_download_btn', '선택 다운로드 추가', []);
|
||||
@@ -146,7 +155,7 @@
|
||||
if (data.image && data.image.includes('cdn.anilife.live')) {
|
||||
proxyImgSrc = '/' + package_name + '/ajax/' + sub + '/proxy_image?image_url=' + encodeURIComponent(data.image);
|
||||
}
|
||||
tmp = '<img src="' + proxyImgSrc + '" class="img-fluid" onerror="this.src=\'../static/img_loader_x200.svg\'">';
|
||||
tmp = '<img src="' + proxyImgSrc + '" class="img-fluid series-main-img" onerror="this.src=\'../static/img_loader_x200.svg\'">';
|
||||
}
|
||||
str += m_col(3, tmp)
|
||||
tmp = ''
|
||||
@@ -182,7 +191,7 @@
|
||||
str += '<div class="episode-card">';
|
||||
str += '<div class="episode-thumb">';
|
||||
if (epThumbSrc) {
|
||||
str += '<img src="' + epThumbSrc + '" onerror="this.src=\'../static/img_loader_x200.svg\'">';
|
||||
str += '<img src="' + epThumbSrc + '" loading="lazy" onerror="this.src=\'../static/img_loader_x200.svg\'">';
|
||||
}
|
||||
str += '<span class="episode-num">' + data.episode[i].ep_num + '화</span>';
|
||||
str += '</div>';
|
||||
@@ -199,8 +208,50 @@
|
||||
str += '</div>';
|
||||
}
|
||||
str += '</div>';
|
||||
document.getElementById("episode_list").innerHTML = str;
|
||||
$('input[id^="checkbox_"]').bootstrapToggle()
|
||||
episodeList.innerHTML = str;
|
||||
$('input[id^="checkbox_"]').bootstrapToggle();
|
||||
|
||||
// 이미지 로딩 완료 후 표시 (최대 2초 대기)
|
||||
const images = episodeList.querySelectorAll('img');
|
||||
let loadedCount = 0;
|
||||
const totalImages = images.length;
|
||||
|
||||
function showContent() {
|
||||
episodeList.style.visibility = 'visible';
|
||||
episodeList.style.opacity = '1';
|
||||
}
|
||||
|
||||
if (totalImages === 0) {
|
||||
showContent();
|
||||
} else {
|
||||
// 최대 2초 후 강제 표시
|
||||
const forceShowTimeout = setTimeout(showContent, 2000);
|
||||
|
||||
images.forEach(img => {
|
||||
if (img.complete) {
|
||||
loadedCount++;
|
||||
if (loadedCount >= totalImages) {
|
||||
clearTimeout(forceShowTimeout);
|
||||
showContent();
|
||||
}
|
||||
} else {
|
||||
img.addEventListener('load', function() {
|
||||
loadedCount++;
|
||||
if (loadedCount >= totalImages) {
|
||||
clearTimeout(forceShowTimeout);
|
||||
showContent();
|
||||
}
|
||||
});
|
||||
img.addEventListener('error', function() {
|
||||
loadedCount++;
|
||||
if (loadedCount >= totalImages) {
|
||||
clearTimeout(forceShowTimeout);
|
||||
showContent();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
$(function () {
|
||||
@@ -240,11 +291,25 @@
|
||||
})
|
||||
|
||||
$(document).ready(function () {
|
||||
$("#loader").css("display", 'none')
|
||||
// DOM 로딩 완료 후 콘텐츠 표시
|
||||
const mainContent = document.getElementById('main_content');
|
||||
const preloader = document.getElementById('preloader');
|
||||
|
||||
// 메인 콘텐츠 보이기 (fade-in 효과)
|
||||
mainContent.style.display = 'block';
|
||||
setTimeout(function() {
|
||||
mainContent.style.opacity = '1';
|
||||
// preloader 숨기기
|
||||
if (preloader) {
|
||||
preloader.style.opacity = '0';
|
||||
setTimeout(function() {
|
||||
preloader.style.display = 'none';
|
||||
}, 300);
|
||||
}
|
||||
}, 100);
|
||||
|
||||
$("#loader").css("display", 'none');
|
||||
console.log({{ arg['code'] }})
|
||||
// console.log('wr_id::', params.wr_id)
|
||||
|
||||
|
||||
});
|
||||
|
||||
$("#analysis_btn").unbind("click").bind('click', function (e) {
|
||||
|
||||
Reference in New Issue
Block a user