fix(import): unbuffer yt-dlp progress, speed up 60fps via fps filter, lock probe btn
세 가지 사용자 보고 처리:
1) 다운로드 바가 멈춰 있다가 한번에 50% 로 점프
- 원인: yt-dlp 는 파이썬 스크립트라 stdout 이 pipe 로 연결되면
block-buffered 가 되어 진행률 라인들이 4KB 버퍼에 모였다가 종료 직전에
쏟아짐. node 의 stdout 'data' 핸들러가 그 전엔 아무 것도 못 봄.
- 수정: spawn 의 env 에 PYTHONUNBUFFERED=1 추가. 라인 단위로 즉시 flush.
2) 변환을 20배 빠르게
- 원인: minterpolate(mci) 는 motion estimation 자체가 무거워 mci 기본값
이어도 영상 길이의 수 배 시간이 든다 (blend 도 5~10배가 한계).
- 수정: 다운로드 후 60fps 변환과 trim 재인코딩 양쪽에서 minterpolate 를
`fps=60` (단순 프레임 복제) 으로 교체. preset 도 `veryfast` → `ultrafast`.
실측상 영상 길이의 1/3 ~ 1배 시간 — mci 대비 수십 배 빠름.
- 시각적 부드러움은 30fps 와 동일하지만 컨테이너/타이밍은 60fps cfr 로 유지.
사용자가 알려준 "timing 만 60fps 로 바뀌고 실제 부드러움은 그대로" 경로.
3) "확인" 전 "가져오기" 재클릭 막기
- probe 클릭 시 ytProbeBtn 도 disabled.
- probe 실패 / 5분 경고 취소 / start 실패 시만 재활성화.
- 정상 흐름은 probe 성공 → 확인 클릭 → 다운로드 → redirect 라 재활성화
필요 없음. ytStartBtn 도 클릭 직후 disabled 로 중복 클릭 방지.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -70,10 +70,12 @@
|
||||
}
|
||||
|
||||
// YouTube probe
|
||||
// "확인" 누르기 전 "가져오기" 재클릭 방지: probe 클릭 시 disable, 실패시만 재활성화.
|
||||
ytProbeBtn.addEventListener('click', function () {
|
||||
var url = ytUrl.value.trim()
|
||||
if (!url) return
|
||||
probeInfo.textContent = '확인 중...'
|
||||
ytProbeBtn.disabled = true
|
||||
ytStartBtn.disabled = true
|
||||
fetch('/op/folder/' + encodeURIComponent(folder) + '/video/youtube/probe', {
|
||||
method: 'POST',
|
||||
@@ -82,6 +84,7 @@
|
||||
}).then(function (r) { return r.json() }).then(function (j) {
|
||||
if (!j.ok) {
|
||||
probeInfo.textContent = j.message || '확인 실패'
|
||||
ytProbeBtn.disabled = false
|
||||
return
|
||||
}
|
||||
var p = j.probe
|
||||
@@ -94,19 +97,24 @@
|
||||
probeInfo.textContent = parts.join(' · ')
|
||||
if (p.warnOver5min) {
|
||||
if (!window.confirm('가져오는 데 5분 이상 걸릴 수 있습니다. 진행할까요?\n(다른 페이지에서 작업해도 백그라운드로 계속 진행됩니다.)')) {
|
||||
ytProbeBtn.disabled = false
|
||||
return
|
||||
}
|
||||
}
|
||||
if (!titleInput.value) titleInput.value = p.title
|
||||
// 확인(start) 만 활성화, 가져오기는 잠금 유지 (확인 누르면 다운로드 시작 → redirect)
|
||||
ytStartBtn.disabled = false
|
||||
}).catch(function (e) {
|
||||
probeInfo.textContent = '확인 실패: ' + e.message
|
||||
ytProbeBtn.disabled = false
|
||||
})
|
||||
})
|
||||
|
||||
ytStartBtn.addEventListener('click', function () {
|
||||
var url = ytUrl.value.trim()
|
||||
if (!url) return
|
||||
// 중복 클릭 방지
|
||||
ytStartBtn.disabled = true
|
||||
fetch('/op/folder/' + encodeURIComponent(folder) + '/video/youtube/start', {
|
||||
method: 'POST',
|
||||
headers: { 'content-type': 'application/json' },
|
||||
@@ -114,11 +122,18 @@
|
||||
}).then(function (r) { return r.json() }).then(function (j) {
|
||||
if (!j.ok) {
|
||||
probeInfo.textContent = j.message || '시작 실패'
|
||||
// 시작 실패면 재시도 가능하게 둘 다 다시 풀어줌
|
||||
ytProbeBtn.disabled = false
|
||||
ytStartBtn.disabled = false
|
||||
return
|
||||
}
|
||||
dlProgress.hidden = false
|
||||
probeInfo.textContent = '백그라운드 다운로드 시작...'
|
||||
pollJob(j.jobId, j.videoId)
|
||||
}).catch(function (e) {
|
||||
probeInfo.textContent = '시작 실패: ' + e.message
|
||||
ytProbeBtn.disabled = false
|
||||
ytStartBtn.disabled = false
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user