1
+ import { secondsToTimestamp , timestampToSeconds } from "../util.js" ;
2
+ import { FormatBase } from "./FormatBase.js" ;
3
+
4
+ export class ShutterEDL extends FormatBase {
5
+
6
+ // this format is based on the shutter encoder edl format
7
+ // https://github.com/paulpacifico/shutter-encoder/blob/f3d6bb6dfcd629861a0b0a50113bf4b062e1ba17/src/application/SceneDetection.java
8
+
9
+ detect ( inputString ) {
10
+ return / ^ T I T L E : \s .* \r ? \n / . test ( inputString . trim ( ) ) ;
11
+ }
12
+
13
+ decodeTime ( timeString ) {
14
+ return timeString . replace ( / : ( \d + ) $ / , '.$10' ) ;
15
+ }
16
+
17
+ encodeTime ( time ) {
18
+ // since this format apparently expects the end time of the next item and the previous start time
19
+ // to be the same,
20
+ // I'll round them to look like they looked in my sample file when converting
21
+ // from shutter edl to shutter edl...
22
+
23
+ const string = secondsToTimestamp ( time , { milliseconds : true } ) ;
24
+ const ms = String ( Math . ceil ( parseInt ( string . split ( '.' ) . pop ( ) ) * 0.1 ) ) ;
25
+ return string . replace ( / \. ( \d + ) $ / , `:${ ms . padStart ( 2 , '0' ) } ` ) ;
26
+ }
27
+
28
+ parse ( input ) {
29
+ if ( ! this . detect ( input ) ) {
30
+ throw new Error ( 'input must start with TITLE:' )
31
+ }
32
+
33
+ const titleMatch = input . match ( / ^ T I T L E : \s ( .* ) \r ? \n / ) ;
34
+ this . meta . title = titleMatch ?. [ 1 ] ?? 'Chapters' ;
35
+
36
+ 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) )
37
+ . reduce ( ( acc , match ) => {
38
+ const startTime = timestampToSeconds ( this . decodeTime ( match . groups . startTime ) ) ;
39
+ const endTime = timestampToSeconds ( this . decodeTime ( match . groups . endTime ) ) ;
40
+ const title = match . groups . title ;
41
+
42
+ if ( acc . at ( - 1 ) ?. startTime === startTime ) {
43
+ return acc ;
44
+ }
45
+
46
+ console . log ( startTime , endTime , title ) ;
47
+
48
+ acc . push ( {
49
+ startTime,
50
+ endTime,
51
+ title
52
+ } ) ;
53
+ return acc ;
54
+ } , [ ] ) ;
55
+ }
56
+
57
+ toString ( ) {
58
+ // this format is weird, it expects 3 tracks per chapter, i suspect it's
59
+ // V = video, A, A2 = stereo audio
60
+ const tracks = [ 'V' , 'A' , 'A2' ] ;
61
+ const output = this . chapters . reduce ( ( acc , chapter , i ) => {
62
+
63
+ const index = i * 3 + 1 ;
64
+ const startTime = this . encodeTime ( chapter . startTime ) ;
65
+ const endTime = this . encodeTime ( chapter . endTime ) ;
66
+ for ( let j = 0 ; j < 3 ; j ++ ) {
67
+ acc . push ( `${ ( j + index ) . toString ( ) . padStart ( 6 , '0' ) } ${ chapter . title } ${ tracks [ j ] } ${ " " . repeat ( 6 - tracks [ j ] . length ) } C ${ startTime } ${ endTime } ${ startTime } ${ endTime } ` ) ;
68
+ }
69
+
70
+ return acc ;
71
+ } , [ ] ) ;
72
+
73
+ output . unshift ( 'TITLE: ' + this . meta . title ) ;
74
+ return output . join ( "\n" ) ;
75
+ }
76
+ }
0 commit comments