-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #7 from Mtillmann/shutter-edl
Shutter edl
- Loading branch information
Showing
10 changed files
with
188 additions
and
32 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
import { secondsToTimestamp, timestampToSeconds } from "../util.js"; | ||
import { FormatBase } from "./FormatBase.js"; | ||
|
||
export class ShutterEDL extends FormatBase { | ||
|
||
// this format is based on the shutter encoder edl format | ||
// https://github.com/paulpacifico/shutter-encoder/blob/f3d6bb6dfcd629861a0b0a50113bf4b062e1ba17/src/application/SceneDetection.java | ||
|
||
detect(inputString) { | ||
return /^TITLE:\s.*\r?\n/.test(inputString.trim()); | ||
} | ||
|
||
decodeTime(timeString) { | ||
return timeString.replace(/:(\d+)$/,'.$10'); | ||
} | ||
|
||
encodeTime(time) { | ||
// since this format apparently expects the end time of the next item and the previous start time | ||
// to be the same, | ||
// I'll round them to look like they looked in my sample file when converting | ||
// from shutter edl to shutter edl... | ||
|
||
const string = secondsToTimestamp(time, {milliseconds: true}); | ||
const ms = String(Math.ceil(parseInt(string.split('.').pop()) * 0.1)); | ||
return string.replace(/\.(\d+)$/,`:${ms.padStart(2, '0')}`); | ||
} | ||
|
||
parse(input) { | ||
if (!this.detect(input)) { | ||
throw new Error('input must start with TITLE:') | ||
} | ||
|
||
const titleMatch = input.match(/^TITLE:\s(.*)\r?\n/); | ||
this.meta.title = titleMatch?.[1] ?? 'Chapters'; | ||
|
||
this.chapters = Array.from(input.matchAll(/(?<index>\d{6})\s+(?<title>[^\s]+)\s+\w+\s+\w+\s+(?<startTime>\d\d:\d\d:\d\d:\d\d)\s+(?<endTime>\d\d:\d\d:\d\d:\d\d)/g)) | ||
.reduce((acc, match) => { | ||
const startTime = timestampToSeconds(this.decodeTime(match.groups.startTime)); | ||
const endTime = timestampToSeconds(this.decodeTime(match.groups.endTime)); | ||
const title = match.groups.title; | ||
|
||
if (acc.at(-1)?.startTime === startTime) { | ||
return acc; | ||
} | ||
|
||
console.log(startTime, endTime, title); | ||
|
||
acc.push({ | ||
startTime, | ||
endTime, | ||
title | ||
}); | ||
return acc; | ||
}, []); | ||
} | ||
|
||
toString() { | ||
// this format is weird, it expects 3 tracks per chapter, i suspect it's | ||
// V = video, A, A2 = stereo audio | ||
const tracks = ['V', 'A', 'A2']; | ||
const output = this.chapters.reduce((acc, chapter,i) => { | ||
|
||
const index = i * 3 + 1; | ||
const startTime = this.encodeTime(chapter.startTime); | ||
const endTime = this.encodeTime(chapter.endTime); | ||
for(let j = 0; j < 3; j++){ | ||
acc.push(`${(j + index).toString().padStart(6, '0')} ${chapter.title} ${tracks[j]}${" ".repeat(6 - tracks[j].length)}C ${startTime} ${endTime} ${startTime} ${endTime}`); | ||
} | ||
|
||
return acc; | ||
}, []); | ||
|
||
output.unshift('TITLE: ' + this.meta.title); | ||
return output.join("\n"); | ||
} | ||
} |
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,60 @@ | ||
|
||
import { readFileSync } from "fs"; | ||
import { sep } from "path"; | ||
import { ShutterEDL } from "../src/Formats/ShutterEDL.js"; | ||
import { Youtube } from "../src/Formats/Youtube.js"; | ||
|
||
|
||
describe('ShutterEDL Format Handler', () => { | ||
it('accepts no arguments', () => { | ||
expect(() => { | ||
new ShutterEDL(); | ||
}).not.toThrowError(TypeError); | ||
}); | ||
|
||
|
||
it('fails on malformed input', () => { | ||
expect(() => { | ||
new ShutterEDL('asdf'); | ||
}).toThrowError(Error); | ||
}); | ||
|
||
const content = readFileSync(module.path + sep + 'samples' + sep + 'shutter.edl', 'utf-8'); | ||
|
||
it('parses well-formed input', () => { | ||
expect(() => { | ||
new ShutterEDL(content); | ||
}).not.toThrow(Error); | ||
}); | ||
|
||
const instance = new ShutterEDL(content); | ||
|
||
it('has the correct number of chapters from content', () => { | ||
expect(instance.chapters.length).toEqual(5); | ||
}); | ||
|
||
it('has parsed the timestamps correctly', () => { | ||
expect(instance.chapters[0].startTime).toBe(0) | ||
}); | ||
|
||
it('has parsed the chapter titles correctly', () => { | ||
expect(instance.chapters[1].title).toBe('BigBuckBunny_320x180_cut.mp4') | ||
}); | ||
|
||
it('exports to correct format', () => { | ||
expect(instance.toString().slice(0, 6)).toEqual('TITLE:'); | ||
}); | ||
|
||
it('export includes correct timestamp', () => { | ||
expect(instance.toString()).toContain('00:00:47:17'); | ||
}); | ||
|
||
it('can import previously generated export', () => { | ||
expect(new ShutterEDL(instance.toString()).chapters[3].startTime).toEqual(23.01); | ||
}); | ||
|
||
it('can convert into other format', () => { | ||
expect(instance.to(Youtube)).toBeInstanceOf(Youtube) | ||
}); | ||
|
||
}); |
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,16 @@ | ||
TITLE: bunny-dings | ||
000001 BigBuckBunny_320x180.mp4 V C 00:00:00:00 00:00:11:21 00:00:00:00 00:00:11:21 | ||
000002 BigBuckBunny_320x180.mp4 A C 00:00:00:00 00:00:11:21 00:00:00:00 00:00:11:21 | ||
000003 BigBuckBunny_320x180.mp4 A2 C 00:00:00:00 00:00:11:21 00:00:00:00 00:00:11:21 | ||
000004 BigBuckBunny_320x180_cut.mp4 V C 00:00:11:21 00:00:15:18 00:00:11:21 00:00:15:18 | ||
000005 BigBuckBunny_320x180_cut.mp4 A C 00:00:11:21 00:00:15:18 00:00:11:21 00:00:15:18 | ||
000006 BigBuckBunny_320x180_cut.mp4 A2 C 00:00:11:21 00:00:15:18 00:00:11:21 00:00:15:18 | ||
000007 BigBuckBunny_320x180.mp4 V C 00:00:15:18 00:00:23:01 00:00:15:18 00:00:23:01 | ||
000008 BigBuckBunny_320x180.mp4 A C 00:00:15:18 00:00:23:01 00:00:15:18 00:00:23:01 | ||
000009 BigBuckBunny_320x180.mp4 A2 C 00:00:15:18 00:00:23:01 00:00:15:18 00:00:23:01 | ||
000010 BigBuckBunny_320x180_cut.mp4 V C 00:00:23:01 00:00:47:17 00:00:23:01 00:00:47:17 | ||
000011 BigBuckBunny_320x180_cut.mp4 A C 00:00:23:01 00:00:47:17 00:00:23:01 00:00:47:17 | ||
000012 BigBuckBunny_320x180_cut.mp4 A2 C 00:00:23:01 00:00:47:17 00:00:23:01 00:00:47:17 | ||
000013 BigBuckBunny_320x180.mp4 V C 00:00:47:17 00:00:56:02 00:00:47:17 00:00:56:02 | ||
000014 BigBuckBunny_320x180.mp4 A C 00:00:47:17 00:00:56:02 00:00:47:17 00:00:56:02 | ||
000015 BigBuckBunny_320x180.mp4 A2 C 00:00:47:17 00:00:56:02 00:00:47:17 00:00:56:02 |