Final Cut ProのタイトルからYouTubeの字幕を作成

Final Cut ProのタイトルからYouTubeの字幕を作成

Final Cut Proで出力できる.fcpxmlファイルから、タイトル情報を読み取り、YouTubeに入力できる.srt形式に変換するスクリプトです。動作はブラウザのJavaScriptのみで行われますので、データがサーバにアップされることはありません。

 

下のテキストエリアに.fcpxmlファイルをドロップ

まずFCPXのファイルから「XMLを書き出す」。

fcpxmlバージョン1.10から.fcpxmlファイルは.fcpxmldの「パッケージの内容を表示」内にあるようです。

 

code

xml内のtitleタグから時間を算出してだけなので、改造も比較的簡単かと思います。

window.onload = function () {
    document.querySelectorAll('.drop').forEach(function (target) {
        target.addEventListener('dragover', handleDragOver, false)
        target.addEventListener('drop', handleFileSelect, false)
    })
    function handleFileSelect(evt) {
        evt.stopPropagation()
        evt.preventDefault()

        let files = evt.dataTransfer.files
        let load = files[0]

        if (load !== undefined) {
            let reader = new FileReader()
            reader.onload = function (evt) {
                let text = evt.target.result
                let out = extract(text)
                document.getElementById('out').value = out
                let filename = load.name.match(/([^/]+)\./)[1]
                save(`${filename}.srt`, out)
            };
            reader.readAsText(load)
        }
    }
    function extract(input) {
        const parser = new DOMParser();
        let xmlData = parser.parseFromString(input, 'text/xml');
        let titles = xmlData.querySelectorAll('title')
        let counter = 0, out = ''

        for (title of titles) {
            counter++
            let parent = title.parentNode
            let parentOffset = parent.getAttribute('offset')
            let parentStart = parent.getAttribute('start')

            let name = title.getAttribute('name')
            let offset = title.getAttribute('offset')
            let duration = title.getAttribute('duration')

            let startTime = getTime(parentOffset) + getTime(offset) - getTime(parentStart)
            let endTime = startTime + getTime(duration)

            let body = ''
            let texts = title.querySelectorAll('text')
            for (text of texts) {
                let styles = text.querySelectorAll('text-style')
                for (style of styles) {
                    body += style.textContent.replace(/\r?\n/g, '')
                }
            }

            if (body ==='') body = name

            out += counter + '\n'
            out += toHmsMs(startTime) + ' --> ' + toHmsMs(endTime) + '\n'
            out += body + '\n'
            out += '\n'
        }
        return out
    }
    function getTime(input) {
        let sec = 0
        if (input) {
            if (input.indexOf('/') === -1) {
                sec = Number(input.match(/(\d+)s/)[1])
            } else {
                let match = input.match(/(\d+)\/(\d+)s/)
                sec = match[1] / match[2]
            }
        }
        return sec
    }
    function toHmsMs(t) {
        let decimal = 0
        if (t > 0) {
            decimal = t - Math.floor(t)
        }
        t = Math.floor(t)
        let h = t / 3600 | 0
        let m = t % 3600 / 60 | 0
        let s = t % 60 | 0

        return `${zp(h)}:${zp(m)}:${zp(s)},${zp(Math.round(decimal * 1000), 3)}`

        function zp(num, length = 2) {
            return ('0000000000' + num).slice(-length);
        }
    }
    function handleDragOver(evt) {
        evt.stopPropagation();
        evt.preventDefault();
    }
    function save(filename, text) {
        let blob = new Blob([text], { type: 'text/plain' })
        let a = document.getElementById('down')
        a.href = window.URL.createObjectURL(blob)
        a.textContent = 'Download'
        a.download = filename
        //a.click()
    }
}