시계열 행이 늘어나면 영상 길이도 같이 늘어납니다.
기본 규칙을 알면 “몇 줄짜리 표를 만들지” 쉽게 결정할 수 있습니다.
총 프레임 수 = 150 (인트로) + (행 수 - 1) × durationPerRow + 300 (엔딩)
기본 durationPerRow = 90 프레임 (30fps 기준 3초)
행 10개일 때
총 프레임 ≈ 150 + 9 × 90 + 300 = 1,260 → 약 42초
행 20개일 때
총 프레임 ≈ 150 + 19 × 90 + 300 = 2,160 → 약 72초
행 40개일 때
총 프레임 ≈ 150 + 39 × 90 + 300 = 3,960 → 약 132초
parseCount / parseRate / dateToMinutes / minutesToDateLabel · YoutubeStatsRowParsed
/** 구독자/조회수/좋아요/댓글 숫자 파싱 (표시용 원본 유지, 보간용 숫자) */
function parseCount(s: string): number {
if (!s || s === '-' || s === '채널 개설') return 0;
const t = s.replace(/^~\s*/, '').trim().replace(/,/g, '');
const man = t.match(/^([\d.]+)\s*만$/);
if (man) return Math.round(parseFloat(man[1]) * 10000);
const n = parseFloat(t.replace(/[^\d.]/g, ''));
return Number.isNaN(n) ? 0 : Math.round(n);
}
/** 구독률 파싱 */
function parseRate(s: string): number {
if (!s || s === '-') return 0;
const n = parseFloat(s.replace('%', ''));
return Number.isNaN(n) ? 0 : n;
}
/** 일시 문자열 → 경과 분 (03. 02. 0:00 기준) */
function dateToMinutes(dateStr: string): number {
if (!dateStr) return 0;
const withTime = dateStr.match(/03\.\s*(\d{1,2})\.\s*(\d{1,2}):(\d{1,2})/);
if (withTime) {
const day = parseInt(withTime[1], 10);
const hour = parseInt(withTime[2], 10);
const min = parseInt(withTime[3], 10);
return (day - 2) * 24 * 60 + hour * 60 + min;
}
const dayOnly = dateStr.match(/03\.\s*(\d{1,2})\.\s*[\(\d]/);
if (dayOnly) {
const day = parseInt(dayOnly[1], 10);
return (day - 2) * 24 * 60;
}
return 0;
}
/** 경과 분 → "03. DD. HH:mm" */
function minutesToDateLabel(totalMinutes: number): string {
const day = 2 + Math.floor(totalMinutes / (24 * 60));
const remainder = Math.max(0, totalMinutes % (24 * 60));
const hour = Math.floor(remainder / 60);
const min = Math.round(remainder % 60);
return `03. ${String(day).padStart(2, '0')}. ${String(hour).padStart(2, '0')}:${String(min).padStart(2, '0')}`;
}
export interface YoutubeStatsRowParsed {
date: string;
minutesFromStart: number;
subscribers: number;
views: number;
rate: number;
likes: number;
comments: number;
}