feat(playlist): per-entry/bulk trim, preview, and trim editor on import

플레이리스트 가져오기 화면에 다음을 추가:
- 항목별 제목 입력을 절반으로 줄이고 오른쪽에 시작/종료 시간 입력 추가
  (기본 시작 0, 종료는 영상 길이)
- 정렬 옵션 옆에 일괄 시작/종료 입력과 적용/초기화 버튼 추가
  (초기화 = 시작 0, 종료 = 영상 길이)
- 항목 썸네일 클릭 시 YouTube 임베드로 미리보기 재생
- 항목 우클릭 → 자르기 메뉴 → /video/editor 식 타임라인 모달(YouTube IFrame)
  에서 구간을 정하면 항목의 시작/종료 입력에 반영
- 백엔드: playlist/start 가 항목별 startSec/endSec 를 받아 다운로드 잡에
  실어 보내고, 60fps 후처리 후 ffmpeg 로 해당 구간을 잘라 편집본 생성

Co-Authored-By: Claude Opus 4 <noreply@anthropic.com>
This commit is contained in:
Claude (owner)
2026-06-02 00:50:04 +09:00
parent 066ae6b112
commit 34c040c15d
5 changed files with 596 additions and 12 deletions

View File

@@ -39,6 +39,13 @@
<input type="checkbox" id="zeroPadToggle" /> 자릿수 채우기 (예: 001)
</label>
</div>
<div class="bulkTrimBox">
<span class="idModeLabel">일괄 구간</span>
<label>시작 <input type="text" id="bulkStart" placeholder="0:00" /></label>
<label>종료 <input type="text" id="bulkEnd" placeholder="끝까지" /></label>
<button type="button" class="secondaryButton" id="bulkApplyBtn">적용</button>
<button type="button" class="secondaryButton" id="bulkResetBtn">초기화</button>
</div>
<div class="playlistSummary">
<span id="entriesCount" class="muted">0 항목</span>
</div>
@@ -53,12 +60,63 @@
</section>
</main>
<!-- 항목 우클릭 메뉴 -->
<div class="ctxMenu" id="entryCtxMenu" hidden>
<button type="button" data-action="trim">자르기</button>
</div>
<!-- 미리보기 재생 모달 -->
<div class="playerOverlay" id="previewOverlay" hidden>
<div class="playerModal" role="dialog" aria-modal="true">
<button type="button" class="playerClose" id="previewClose" aria-label="닫기">✕</button>
<div class="playerTitle" id="previewTitle"></div>
<div class="ytEmbedWrap"><div id="previewPlayer"></div></div>
</div>
</div>
<!-- 자르기(트림) 모달 — /video/editor 타임라인을 YouTube 임베드로 재현 -->
<div class="playerOverlay" id="trimOverlay" hidden>
<div class="playerModal trimModal" role="dialog" aria-modal="true">
<button type="button" class="playerClose" id="trimClose" aria-label="닫기">✕</button>
<div class="playerTitle" id="trimTitle"></div>
<div class="ytEmbedWrap"><div id="trimPlayer"></div></div>
<div class="trimTimeline">
<div class="trimTimelineHeader">
<span class="timeReadout" id="plTimeReadout">00:00.0 / 00:00.0</span>
<div class="trimTimelineActions">
<button type="button" data-pmark="start" title="현재 시점을 시작점으로">[&nbsp;시작점</button>
<button type="button" data-pmark="end" title="현재 시점을 끝점으로">끝점&nbsp;]</button>
<button type="button" data-paction="playSelection" title="선택 구간만 재생">▶ 선택 재생</button>
<button type="button" data-paction="reset" title="자르기 초기화">초기화</button>
</div>
</div>
<div class="trimBar" id="plTrimBar">
<div class="trimRangeFill" id="plTrimRangeFill"></div>
<div class="trimHandle trimHandleStart" id="plTrimHandleStart" data-phandle="start" title="시작 핸들"></div>
<div class="trimHandle trimHandleEnd" id="plTrimHandleEnd" data-phandle="end" title="끝 핸들"></div>
<div class="trimPlayhead" id="plTrimPlayhead"></div>
</div>
<div class="trimNumericRow">
<label>시작(초) <input type="number" id="plStartSec" step="0.1" min="0" value="0" /></label>
<span class="trimDuration" id="plTrimDuration">선택: 0.0초</span>
<label>끝(초, 비우면 끝까지) <input type="number" id="plEndSec" step="0.1" min="0" value="" /></label>
</div>
<p class="muted">재생바의 파란 핸들을 끌어 구간을 정합니다. 적용하면 항목의 시작/종료 시간이 갱신되고, 다운로드 완료 후 이 구간으로 잘립니다.</p>
</div>
<div class="modalActions">
<button type="button" class="secondaryButton" id="trimCancel">취소</button>
<button type="button" class="primaryButton" id="trimApply">적용</button>
</div>
</div>
</div>
<script>
window.__PLAYLIST__ = {
folderPath: <%- jsonForScript(breadcrumb) %>,
folderId: <%- jsonForScript(folder.id) %>
}
</script>
<script src="https://www.youtube.com/iframe_api"></script>
<script src="/static/playlist.js"></script>
</body>
</html>