<!DOCTYPE html>
<html lang="zh-cmn-Hans">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
<title>剪映 - 字幕导出工具</title>
<style type="text/css" rel="stylesheet">
label {
user-select: none;
font-weight: bold;
}
div {
margin: 4px 0;
}
.gap-8 {
gap: 8px;
}
.flex {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.flex.row {
flex-direction: row;
}
#main-container {
align-items: flex-start;
}
.hint-span {
color: red;
display: none;
margin-left: 8px;
}
</style>
</head>
<body>
<div class="flex">
<div id="main-container" class="flex">
<div>Windows 目录:C:\Users\Administrator\AppData\Local\JianyingPro\User Data\Projects\com.lveditor.draft\</div>
<div>Android 目录:/data/data/com.lemon.lv/files/newdrafts/</div>
<div>
<label>
设置换行符:
<select>
<option value="rn">\r\n (Windows)</option>
<option value="n">\n (Linux)</option>
</select>
<span id="rn-hint" class="hint-span">** 修改后需要重新生成</span>
</label>
</div>
<div>
<label>
选择时间单位:
<select>
<option value="milli">毫秒</option>
<option value="micro">微秒</option>
</select>
<span id="time-unit-hint">一般情况Windows是微秒,安卓是毫秒,如果时间不正常,可根据实际情况自行选择</span>
<span id="time-unit-reapply-hint" class="hint-span">** 修改后需要重新生成</span>
</label>
</div>
<div><label for="input-text">输入 JSON</label></div>
<div class="flex row gap-8">
<input id="input-file" type="file" />
<textarea id="input-text" cols="60" rows="15">
{"platform": {"os": ""},"materials":{"texts":[{"content":"轨道一文本","id":
"28824EDA-D841-4c81-B264-DC268957660B","type":"text"},{"content":"轨道二文本","id":"A4637647-EF07-418c-B2A4-6781EC9B6003",
"type":"text"}]},"tracks":[{"id":"A891C42E-A9F6-41fa-89E4-5DD250F2D7D1","segments":[{"id":"624A1A67-15F8-499b-BDF4-0C949FFB383A",
"material_id":"28824EDA-D841-4c81-B264-DC268957660B","target_timerange":{"duration":3000000,"start":0}}],"type":"text"},
{"id":"45336909-80D9-4320-93C2-1737989C45FC","segments":[{"id":"7729B6F5-8C3D-46ee-A0F4-3D3FC5B82823","material_id":
"A4637647-EF07-418c-B2A4-6781EC9B6003","target_timerange":{"duration":3000000,"start":2652233000}}],"type":"text"}]}</textarea
>
<button type="button">生成 SRT 文件</button>
<button type="button">清空</button>
</div>
<div><label>输出 SRT</label></div>
</div>
</div>
<script>
let mainContainer = document.getElementById('main-container')
// 文件选择
let inputFile = document.getElementById('input-file')
// 输入文本域
let inputText = document.getElementById('input-text')
// 换行符提示信息
let rnHintSpan = document.getElementById('rn-hint')
// 时间单位提示信息
let timeUnitHintSpan = document.getElementById('time-unit-hint')
// 时间单位重新应用提示信息
let timeUnitReapplyHintSpan = document.getElementById('time-unit-reapply-hint')
// 存储输出的 div 数组
let outputDivArray = []
// 换行符
let RN = '\r\n'
let timeUnit = 'milli'
function onRNChange(value) {
switch (value) {
case 'rn':
RN = '\r\n'
break
case 'n':
RN = '\n'
break
default:
}
if (outputDivArray.length) {
rnHintSpan.style.display = 'inline-block'
}
}
function onTimeUnitChange(value) {
timeUnit = value
if (outputDivArray.length) {
timeUnitHintSpan.style.display = 'none'
timeUnitReapplyHintSpan.style.display = 'inline-block'
}
}
function onFileSelected(el) {
let file = el.files[0]
if (file) {
let reader = new FileReader()
reader.readAsText(file)
reader.onload = function () {
inputText.value = this.result
}
}
}
function onClearClick() {
inputText.value = ''
inputFile.value = ''
}
function onGenerateClick() {
try {
rnHintSpan.style.display = 'none'
timeUnitHintSpan.style.display = 'inline-block'
timeUnitReapplyHintSpan.style.display = 'none'
// 如果上一次生成的 div 标签存在就移除掉
let temp
while (outputDivArray.length) {
temp = outputDivArray.pop()
temp.parentNode.removeChild(temp)
}
// 剪映 json 对象
temp = JSON.parse(inputText.value)
let srtFiles = convertJSON2SRT(temp)
let text,
i = 1
for (let k in srtFiles) {
text = srtFiles[k]
temp = document.createElement('div')
temp.className = 'flex row gap-8'
temp.innerHTML = `<textarea cols="60" rows="15" readonly>${text}</textarea>`
temp.appendChild(getDownloadLink(`下载轨道${i}`, `jy_track${i}_${k}.srt`, text))
mainContainer.appendChild(temp)
outputDivArray.push(temp)
i++
}
} catch (e) {
console.log(e)
alert('JSON 解析错误')
}
}
function convertJSON2SRT(jy) {
// 提取文本材料
// Map 结构 = {id1: text1, id2: text2, ...}
let texts = {},
temp = jy.materials.texts
for (let i in temp) {
texts[temp[i].id] = temp[i].content
}
// 轨道列表
let tracks = jy.tracks,
track
// SRT 文件 Map
let srtFiles = {}
for (let i in tracks) {
track = tracks[i]
temp = convertTrack2Srt(track, texts)
if (temp) {
srtFiles[track.id] = temp
}
}
return srtFiles
}
/**
* 将一条轨道转换为 srt 文本
* @param track 轨道
* @param texts 文本材料
* @return {string}
*/
function convertTrack2Srt(track, texts) {
let segments = track.segments,
segment
let srt = { content: null, start: null, end: null }
let srtText = '',
index = 0
for (let i in segments) {
segment = segments[i]
srt.content = texts[segment.material_id]
if (!srt.content) continue
srt.start = segment.target_timerange.start
srt.end = srt.start + segment.target_timerange.duration
srt.start = getSrtTimeText(srt.start)
srt.end = getSrtTimeText(srt.end)
index++
srtText += formatSrt(index, srt)
}
return srtText
}
/**
* 获取下载地址的标签
* @param text 文案
* @param fileName 文件名
* @param data 数据
* @returns {HTMLElement}
*/
function getDownloadLink(text, fileName, data) {
// 创建 a 标签
let a = document.createElement('a')
a.innerText = text
a.download = fileName
//生成一个 blob 二进制数据,内容为数据
let blob = new Blob([data], { type: 'application/octet-stream' })
//生成一个指向 blob 的 URL 地址,并赋值给 a 标签的 href 属性
a.href = URL.createObjectURL(blob)
return a
}
/**
* 获取 SRT 格式的时间文本
* @param time 时间,windows版本为微秒数
* @returns {string}
*/
function getSrtTimeText(time) {
// 1h1m1s111ms = 61m1s111ms = 3661s111ms = 3661111ms
if (timeUnit === 'micro') {
time = Math.floor(time / 1000)
}
// 余出的毫秒
let millisecond = time % 1000
time = Math.floor(time / 1000)
// 余出秒
let second = time % 60
time = Math.floor(time / 60)
// 余出分钟
let minute = time % 60
time = Math.floor(time / 60)
// 剩余时数
let hour = time
hour = formatDigit(hour, 2)
minute = formatDigit(minute, 2)
second = formatDigit(second, 2)
millisecond = formatDigit(millisecond, 3)
return `${hour}:${minute}:${second},${millisecond}`
}
/**
* 格式化为 SRT
* @param index 字幕序号,从 1 开始
* @param srt 字幕内容等信息
* @returns {string}
*/
function formatSrt(index, srt) {
return `${index}${RN}${srt.start} --> ${srt.end}${RN}${srt.content}${RN}${RN}`
}
/**
* 格式化数字
* @param digit 数字
* @param length 长度
* @returns {string}
*/
function formatDigit(digit, length) {
let str = digit.toString()
while (str.length < length) {
str = `0${str}`
}
return str
}
</script>
</body>
</html>