-
-
Notifications
You must be signed in to change notification settings - Fork 1.4k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Add config option for asking about file open #467 - Implement text/youtube segments import #458 - Implement CUE sheet import #458 - Implement XMEML import (Final Cut Pro / Davinci Resolve) - Allow import embedded chapters as segments #300
- Loading branch information
Showing
11 changed files
with
336 additions
and
74 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
import fastXmlParser from 'fast-xml-parser'; | ||
import i18n from 'i18next'; | ||
|
||
import csvParse from 'csv-parse'; | ||
import pify from 'pify'; | ||
import sortBy from 'lodash/sortBy'; | ||
|
||
const csvParseAsync = pify(csvParse); | ||
|
||
export async function parseCsv(str) { | ||
const rows = await csvParseAsync(str, {}); | ||
if (rows.length === 0) throw new Error(i18n.t('No rows found')); | ||
if (!rows.every(row => row.length === 3)) throw new Error(i18n.t('One or more rows does not have 3 columns')); | ||
|
||
const mapped = rows | ||
.map(([start, end, name]) => ({ | ||
start: start === '' ? undefined : parseFloat(start, 10), | ||
end: end === '' ? undefined : parseFloat(end, 10), | ||
name, | ||
})); | ||
|
||
if (!mapped.every(({ start, end }) => ( | ||
(start === undefined || !Number.isNaN(start)) | ||
&& (end === undefined || !Number.isNaN(end)) | ||
))) { | ||
console.log(mapped); | ||
throw new Error(i18n.t('Invalid start or end value. Must contain a number of seconds')); | ||
} | ||
|
||
return mapped; | ||
} | ||
|
||
export function parseCuesheet(cuesheet) { | ||
// There are 75 such frames per second of audio. | ||
// https://en.wikipedia.org/wiki/Cue_sheet_(computing) | ||
const fps = 75; | ||
|
||
const { tracks } = cuesheet.files[0]; | ||
|
||
function parseTime(track) { | ||
const index = track.indexes[0]; | ||
if (!index) return undefined; | ||
const { time } = index; | ||
if (!time) return undefined; | ||
|
||
return (time.min * 60) + time.sec + time.frame / fps; | ||
} | ||
|
||
return tracks.map((track, i) => { | ||
const nextTrack = tracks[i + 1]; | ||
const end = nextTrack && parseTime(nextTrack); | ||
|
||
return { name: track.title, start: parseTime(track), end }; | ||
}); | ||
} | ||
|
||
// https://developer.apple.com/library/archive/documentation/AppleApplications/Reference/FinalCutPro_XML/VersionsoftheInterchangeFormat/VersionsoftheInterchangeFormat.html | ||
export function parseXmeml(xmlStr) { | ||
const xml = fastXmlParser.parse(xmlStr); | ||
// TODO maybe support media.audio also? | ||
return xml.xmeml.project.children.sequence.media.video.track.clipitem.map((item) => ({ start: item.start / item.rate.timebase, end: item.end / item.rate.timebase })); | ||
} | ||
|
||
export function parseYouTube(str) { | ||
const regex = /(?:([0-9]{2,}):)?([0-9]{2}):([0-9]{2})(?:\.([0-9]{3}))?[^\S\n]+([^\n]*)\n/g; | ||
|
||
const lines = []; | ||
|
||
function parseLine(match) { | ||
if (!match) return undefined; | ||
const [, hourStr, minStr, secStr, msStr, name] = match; | ||
const hour = hourStr != null ? parseInt(hourStr, 10) : 0; | ||
const min = parseInt(minStr, 10); | ||
const sec = parseInt(secStr, 10); | ||
const ms = msStr != null ? parseInt(msStr, 10) : 0; | ||
|
||
const time = (((hour * 60) + min) * 60 + sec) + ms / 1000; | ||
|
||
return { time, name }; | ||
} | ||
|
||
let m; | ||
// eslint-disable-next-line no-cond-assign | ||
while ((m = regex.exec(`${str}\n`))) { | ||
lines.push(parseLine(m)); | ||
} | ||
|
||
const linesSorted = sortBy(lines, (l) => l.time); | ||
|
||
const edl = linesSorted.map((line, i) => { | ||
const nextLine = linesSorted[i + 1]; | ||
return { start: line.time, end: nextLine && nextLine.time, name: line.name }; | ||
}); | ||
|
||
return edl.filter((ed) => ed.start !== ed.end); | ||
} |
Oops, something went wrong.