@@ -6,6 +6,7 @@ import { BehaviorSubject, Observable } from 'rxjs';
66import { FfpathsConfig } from './ffpaths' ;
77import * as moment from 'moment' ;
88import * as os from 'os' ;
9+ import { cursorTo } from 'readline' ;
910
1011type Statuses =
1112 | 'NOT_STARTED'
@@ -19,6 +20,7 @@ export interface ExtractionStatus {
1920 uri : string ;
2021 phase : Statuses ;
2122 percentage : number ;
23+ debug ?: unknown ;
2224}
2325
2426export interface Interval {
@@ -42,7 +44,7 @@ export class Video {
4244
4345 getInfo ( ) : Promise < ffmpeg . FfprobeData > {
4446 return new Promise ( ( resolve , reject ) => {
45- ffmpeg . ffprobe ( this . videoPath , ( err , data ) => {
47+ ffmpeg . ffprobe ( this . videoPath , [ '-show_chapters' ] , ( err , data ) => {
4648 if ( data ) resolve ( data ) ;
4749 if ( err ) reject ( err ) ;
4850 } ) ;
@@ -56,7 +58,9 @@ export class Video {
5658 fs . mkdirSync ( this . scratchPath , { recursive : true } ) ;
5759 // TODO: Is there a better way to find the "desktop" folder?
5860 const outputFolder = path . join ( os . homedir ( ) , 'Desktop' , 'Dialog' ) ;
59- fs . mkdirSync ( outputFolder ) ;
61+ if ( ! fs . existsSync ( outputFolder ) ) {
62+ fs . mkdirSync ( outputFolder ) ;
63+ }
6064 // TODO: This somehow throws a user-visible error but does not stop execution.
6165 // Figure out how to catch this and prevent moving forward.
6266 this . stream = fs . createWriteStream (
@@ -68,6 +72,43 @@ export class Video {
6872 // Note: This doesn't throw an error when it fails (for example, with recursive: false)...
6973 fs . rmdirSync ( this . scratchPath , { recursive : true } ) ;
7074
75+ this . extractionProgress . next ( {
76+ uri : this . videoPath , phase : 'DONE' ,
77+ percentage : 100 ,
78+ } ) ;
79+ console . log ( 'Extraction complete.' ) ;
80+ } catch ( e ) {
81+ this . extractionProgress . next ( {
82+ uri : this . videoPath , phase : 'ERROR' ,
83+ percentage : 100 ,
84+ debug : e ,
85+ } ) ;
86+ throw e ;
87+ }
88+ }
89+
90+ async extractDialogNew ( config : any ) : Promise < void > {
91+ try {
92+ this . scratchPath = fs . mkdtempSync ( path . join ( os . tmpdir ( ) , `${ path . basename ( this . videoPath , path . extname ( this . videoPath ) ) } -` ) ) ;
93+ console . log ( 'Scratch path' , this . scratchPath ) ;
94+ fs . mkdirSync ( this . scratchPath , { recursive : true } ) ;
95+ // TODO: Is there a better way to find the "desktop" folder?
96+ const outputFolder = path . join ( os . homedir ( ) , 'Desktop' , 'Dialog' ) ;
97+ if ( ! fs . existsSync ( outputFolder ) ) {
98+ fs . mkdirSync ( outputFolder ) ;
99+ }
100+ // TODO: This somehow throws a user-visible error but does not stop execution.
101+ // Figure out how to catch this and prevent moving forward.
102+ this . stream = fs . createWriteStream (
103+ `${ path . join ( outputFolder , path . basename ( this . videoPath , path . extname ( this . videoPath ) ) ) } .mp3` ) ;
104+ await this . extractSubtitles ( config . subtitleStream ) ;
105+ const intervals = await this . getSubtitleIntervals ( ) ;
106+ let combined = this . combineIntervals ( intervals ) ;
107+ combined = await this . subtractChapters ( combined , config . ignoredChapters ) ;
108+ await this . extractAudio ( combined , config . audioStream ) ;
109+ // Note: This doesn't throw an error when it fails (for example, with recursive: false)...
110+ fs . rmdirSync ( this . scratchPath , { recursive : true } ) ;
111+
71112 this . extractionProgress . next ( {
72113 uri : this . videoPath , phase : 'DONE' ,
73114 percentage : 100 ,
@@ -82,6 +123,43 @@ export class Video {
82123 }
83124 }
84125
126+ private async subtractChapters ( combined : Interval [ ] , chapters : string [ ] ) : Promise < Interval [ ] > {
127+ const info = await this . getInfo ( ) ;
128+
129+ const chapterIntervals : Interval [ ] = [ ] ;
130+ chapters . forEach ( chap =>
131+ info . chapters . filter ( c => c [ 'TAG:title' ] === chap )
132+ . forEach ( c => chapterIntervals . push ( {
133+ start : this . formalize ( moment . duration ( c . start_time , 'seconds' ) ) ,
134+ end : this . formalize ( moment . duration ( c . end_time , 'seconds' ) ) ,
135+ } ) ) ) ;
136+
137+ let out : Interval [ ] = [ ...combined ] ;
138+
139+ for ( const chapter of chapterIntervals ) {
140+ const revision = [ ] ;
141+ for ( const ivl of out ) {
142+ const cur : Interval = { start : ivl . start , end : ivl . end } ;
143+ if ( cur . start > chapter . start && cur . start < chapter . end ) {
144+ cur . start = chapter . end ;
145+ }
146+ if ( cur . end > chapter . start && cur . end < chapter . end ) {
147+ cur . end = chapter . start ;
148+ }
149+ if ( cur . start < cur . end ) {
150+ revision . push ( cur ) ;
151+ }
152+ }
153+ out = revision ;
154+ }
155+
156+ return out ;
157+ }
158+
159+ private formalize ( duration : moment . Duration ) : string {
160+ return `${ `${ duration . hours ( ) } ` . padStart ( 2 , '0' ) } :${ `${ duration . minutes ( ) } ` . padStart ( 2 , '0' ) } :${ `${ duration . seconds ( ) } ` . padStart ( 2 , '0' ) } .${ `${ duration . milliseconds ( ) } ` . padStart ( 3 , '0' ) } `
161+ }
162+
85163 private async toPromise ( command : ffmpeg . FfmpegCommand , finish : ( command : ffmpeg . FfmpegCommand ) => void ) : Promise < any > {
86164 return new Promise ( ( resolve , reject ) => {
87165 finish (
@@ -97,12 +175,14 @@ export class Video {
97175 }
98176
99177 /** Synchronously extracts segments. */
100- private async extractAudio ( intervals : Interval [ ] ) {
101- for ( let i = 0 , max = intervals . length ; i < max ; i ++ ) { //intervals.length; i++) {
178+ private async extractAudio ( intervals : Interval [ ] , stream ?: ffmpeg . FfprobeStream ) {
179+ const track = stream ? stream . index : 2 ; // TODO: get rid of 2
180+
181+ for ( let i = 0 , max = intervals . length ; i < max ; i ++ ) {
102182 const interval = intervals [ i ] ;
103183 const command = ffmpeg ( this . videoPath )
104184 . noVideo ( )
105- . outputOption ( `-ss` , `${ interval . start } ` , `-to` , `${ interval . end } ` ) //, "-q:a", "0", "-map", "a")
185+ . outputOption ( `-ss` , `${ interval . start } ` , `-to` , `${ interval . end } ` , '-map' , `0: ${ track } ` ) //, "-q:a", "0", "-map", "a")
106186 . audioBitrate ( '128k' )
107187 . audioCodec ( 'libmp3lame' )
108188 . format ( 'mp3' )
@@ -116,9 +196,10 @@ export class Video {
116196 }
117197 }
118198
119- private async extractSubtitles ( ) {
199+ private async extractSubtitles ( stream ?: ffmpeg . FfprobeStream ) {
200+ const track = stream ? stream . index : 2 ; // TODO: get rid of 2
120201 const command = ffmpeg ( this . videoPath )
121- . outputOption ( ' -map 0:2' )
202+ . outputOption ( ` -map 0:${ track } ` )
122203 . saveToFile ( path . join ( this . scratchPath , 'subs.srt' ) )
123204 . on ( 'progress' , progress => {
124205 this . extractionProgress . next ( { uri : this . videoPath , phase : 'EXTRACTING_SUBTITLES' , percentage : progress . percent } ) ;
0 commit comments