YouTubeのチャプター機能の結果をシミュレートし、わかりやすく表示するツール。
- 各チャプターの時間を表示
- 10秒未満のチャプターはエラー
- 100以上のチャプターはエラー(2021年1月5日 3時47分の時点で確認した仕様)
YouTubeのチャプター機能の結果をシミュレートし、わかりやすく表示するツール。
#!/bin/bash | |
echo '$' npx standard --fix | |
npx standard --fix | |
echo '$' npx prettier --write '*.css' | |
npx prettier --write '*.css' |
<!DOCTYPE html> | |
<html lang=ja> | |
<meta charset=utf-8> | |
<meta name=viewport content="width=device-width,initial-scale=1"> | |
<meta name=format-detection content="telephone=no,email=no,address=no"> | |
<title>YouTubeのチャプターの簡易デバッグツール</title> | |
<link rel=stylesheet href="./main.css"> | |
<h1>YouTubeのチャプターの簡易デバッグツール</h1> | |
<main> | |
<p><textarea id=input></textarea></p> | |
<ol id=output></ol> | |
</main> | |
<footer> | |
<h2>Gist</h2> | |
<p><a href="https://gist.github.com/sounisi5011/0cdf426d24128176d0acbbeda1b77a2e">gist.github.com<wbr>/sounisi5011<wbr>/0cdf426d24128176d0acbbeda1b77a2e</a> | |
</footer> | |
<script src="https://unpkg.com/[email protected]/min.js" integrity="sha384-5//czxK1xsldAV41C5VrZbhEJLPVlOxJg4vPh9flJbmIkiB6BL89PhjudfyNQw1Z" crossorigin=anonymous defer></script> | |
<script src="./main.js" defer></script> |
#input { | |
box-sizing: border-box; | |
width: 100%; | |
height: 10em; | |
font-family: Roboto, Noto, sans-serif; | |
} | |
#output li { | |
padding: 0.15em 0; | |
} | |
#output li:nth-child(2n) { | |
background-color: lightgray; | |
} | |
#output .time, | |
#output .diff { | |
margin-right: 0.25em; | |
border: solid 1px black; | |
border-radius: 0.3em; | |
padding: 0.1em 0.25em; | |
font-family: monospace; | |
vertical-align: text-bottom; | |
} | |
#output .time { | |
color: cyan; | |
background-color: gray; | |
} | |
#output .diff { | |
color: lime; | |
background-color: gray; | |
} | |
#output .warn { | |
background-color: orangered; | |
} |
const SESSION_STORAGE_KEY = 'gist.github.com/sounisi5011/0cdf426d24128176d0acbbeda1b77a2e' | |
const inputElem = document.getElementById('input') | |
const outputElem = document.getElementById('output') | |
inputElem.addEventListener('input', inputListener) | |
init() | |
// ----- ----- ----- ----- ----- // | |
function last (array) { | |
return array[array.length - 1] | |
} | |
function sec2time (sec) { | |
if (!Number.isFinite(sec)) return String(sec) | |
const sign = sec < 0 ? '-' : '' | |
const absSec = Math.abs(sec) | |
const s = absSec % 60 | |
const m = Math.trunc(absSec / 60) % 60 | |
const h = Math.trunc(absSec / 60 ** 2) | |
return `${sign}${h}:${String(m).padStart(2, 0)}:${String(s).padStart(2, 0)}` | |
} | |
function init () { | |
if (inputElem.value === '') { | |
const savedData = sessionStorage.getItem(SESSION_STORAGE_KEY) | |
if (typeof savedData === 'string') { | |
inputElem.value = savedData | |
} | |
} | |
inputListener() | |
} | |
function inputListener () { | |
const inputText = inputElem.value | |
const chapterList = inputText | |
.split(/(?:\r\n?|\n)+/) | |
.map(line => { | |
const match = /^\s*(?:(\d+):)?(\d+):(\d+)\s+(.+)$/s.exec(line) | |
if (!match) return null | |
const [, hour = '0', min, sec, title] = match | |
const time = (Number(hour) * 60 + Number(min)) * 60 + Number(sec) | |
return { time, title } | |
}) | |
.filter(Boolean) | |
.reduce( | |
(chapterList, chapterItem) => | |
(chapterList.length < 1 && chapterItem.time === 0) || | |
(chapterList.length >= 1 && last(chapterList).time < chapterItem.time) | |
? [...chapterList, chapterItem] | |
: chapterList, | |
[] | |
) | |
const outputHTML = chapterList | |
.map(({ time, title }, index) => { | |
const nextTime = chapterList[index + 1]?.time ?? Infinity | |
const diffTime = nextTime - time | |
return [ | |
`<li class="${index >= 100 ? 'warn' : ''}">`, | |
`<span class=time>${sec2time(time)}</span>`, | |
`<span class=${diffTime < 10 ? '"diff warn"' : 'diff'}>${sec2time(diffTime)}</span>`, | |
`<span class=title>${html.escape(title)}</span>`, | |
'</li>' | |
].join('') | |
}) | |
.join('') | |
outputElem.innerHTML = outputHTML | |
try { | |
sessionStorage.setItem(SESSION_STORAGE_KEY, inputText) | |
} finally {} | |
} |