1
+ 'use strict' ;
2
+
3
+ const { parentPort, workerData } = require ( 'worker_threads' ) ;
4
+ const sharp = require ( 'sharp' ) ;
5
+ const { WritableStream } = require ( 'node:stream/web' ) ;
6
+ const path = require ( 'path' ) ;
7
+ const fs = require ( 'fs' ) ;
8
+
9
+ const { removeMany, removeFolder } = require ( './utils' ) ;
10
+ const VideoConverter = require ( './VideoConverter' ) ;
11
+ const Colour = require ( './Colour' ) ;
12
+ const Utils = require ( './utils' ) ;
13
+
14
+ const today = new Date ( ) ;
15
+ const titleConfig = {
16
+ width : 1920 ,
17
+ height : 1080 ,
18
+ title : `I&E ${ today . getFullYear ( ) } `
19
+ } ;
20
+
21
+ const dlPath = workerData . dlPath ;
22
+ const config = workerData . config ;
23
+
24
+ let isWorking = false ;
25
+
26
+ async function processFiles ( directory , files ) {
27
+ if ( ! files || ! files . value || files . value . length === 0 ) {
28
+ return done ( ) ;
29
+ }
30
+
31
+ const videos = ( await Promise . all ( [ ...files . value . map ( f => processFile ( dlPath , directory . name , f ) ) , generateTitle ( {
32
+ category : '' ,
33
+ band : directory . name ,
34
+ name : titleConfig . title ,
35
+ width : titleConfig . width ,
36
+ height : titleConfig . height ,
37
+ path : path . join ( 'tmp' , `${ directory . name } .jpg` )
38
+ } ) ] ) ) . filter ( x => x ) ;
39
+
40
+ const title = videos . pop ( ) ;
41
+ const withoutTitle = videos [ 0 ] ;
42
+ videos [ 0 ] = await VideoConverter . addTitleToVideo ( title , videos [ 0 ] , 5 , true ) ;
43
+
44
+ if ( videos [ 0 ] . indexOf ( ":\\" ) < 0 ) {
45
+ videos [ 0 ] = path . join ( __dirname , videos [ 0 ] ) ;
46
+ }
47
+
48
+ //4. Combine files
49
+ const master = path . join ( 'tmp' , `${ directory . name } .mp4` ) ;
50
+ if ( videos . length === 0 ) {
51
+ console . log ( ) ;
52
+ return '' ;
53
+ }
54
+
55
+ if ( videos . length === 1 ) {
56
+ //rename only video to final video
57
+ fs . renameSync ( videos [ 0 ] , master ) ;
58
+ } else {
59
+ const tempJoin = await VideoConverter . combineCommonFormatVideos ( ...videos ) ;
60
+ fs . renameSync ( tempJoin , master ) ;
61
+ }
62
+
63
+ removeMany ( [ ...videos , title , withoutTitle ] ) ;
64
+ console . log ( ) ;
65
+
66
+ return done ( master ) ;
67
+ }
68
+
69
+ function logProcessStart ( category , message ) {
70
+ Colour . writeColouredText ( `${ category . trim ( ) . substring ( 0 , 2 ) } : ${ message } ` , Colour . OPTIONS . FG_MAGENTA ) ;
71
+ }
72
+
73
+ function logProcessEnd ( category , message ) {
74
+ Colour . writeColouredText ( `${ category . trim ( ) . substring ( 0 , 2 ) } : ${ message } ` , Colour . OPTIONS . FG_CYAN ) ;
75
+ }
76
+
77
+ async function processFile ( dlPath , category , fileData ) {
78
+ if ( ! ( 'file' in fileData ) ) {
79
+ return '' ;
80
+ }
81
+
82
+ const parts = path . parse ( fileData . name ) . name . split ( ' - ' ) ;
83
+ const name = parts [ 0 ] . trim ( ) ;
84
+ const toRemove = [ ] ;
85
+ let band = '' ;
86
+ let trusted = false ;
87
+
88
+ if ( parts . length >= 2 ) {
89
+ band = parts [ 1 ] . trim ( ) ;
90
+ }
91
+
92
+ logProcessStart ( category , `Downloading ${ name } of ${ band } , who has entered category ${ category } ...` ) ;
93
+
94
+ //1. Download file
95
+ dlPath = await downloadFile ( dlPath , fileData , category . substring ( 0 , 2 ) ) ;
96
+
97
+ logProcessEnd ( category , `Downloaded ${ name } of ${ band } to ${ dlPath } ` ) ;
98
+
99
+ if ( await VideoConverter . isPortrait ( dlPath ) ) {
100
+ logProcessStart ( category , `${ name } of ${ band } filmed in portrait - adding background to make landscape!` ) ;
101
+ toRemove . push ( dlPath ) ;
102
+ dlPath = await VideoConverter . addLandscapeBackgroundToPortraitVideo ( dlPath , "bg.png" , titleConfig . height ) ;
103
+ logProcessEnd ( category , `${ name } of ${ band } is now landscape` ) ;
104
+ trusted = true ;
105
+ }
106
+
107
+ //2. Generate title card
108
+ logProcessStart ( category , `Generating title card for ${ name } of ${ band } ` ) ;
109
+ const title = await generateTitle ( {
110
+ category : category ?. trim ( ) ?? '' ,
111
+ band : band ?. trim ( ) ?? '' ,
112
+ name : name ?. trim ( ) ?? '' ,
113
+ width : titleConfig . width ,
114
+ height : titleConfig . height ,
115
+ path : dlPath + '.jpg'
116
+ } ) ;
117
+ logProcessEnd ( category , `Generated title card for ${ name } of ${ band } at ${ title } ` ) ;
118
+
119
+ //3. Add title card to video
120
+ //yeahhh so this is where it gets horrible - we use ffmpeg here to do things!
121
+ logProcessStart ( category , `Adding title card to video entry of ${ name } of ${ band } ` ) ;
122
+ const all = await VideoConverter . addTitleToVideo ( title , dlPath , 5 , trusted ) ;
123
+ toRemove . push ( title , dlPath ) ;
124
+ logProcessEnd ( category , `${ name } of ${ band } now has a title card!` ) ;
125
+
126
+ logProcessStart ( category , `Removing ${ toRemove . length } temp files` ) ;
127
+ removeMany ( toRemove ) ;
128
+ logProcessEnd ( category , "Temporary files removed" ) ;
129
+
130
+ Colour . writeColouredText ( `Finished processing ${ name } of ${ band } - final video is available at ${ all } ` , Colour . OPTIONS . FG_GREEN ) ;
131
+ return all ;
132
+ }
133
+
134
+ async function downloadFile ( dlPath , fileData , prefix = '' ) {
135
+ dlPath = path . join ( dlPath , prefix + fileData . name ) ;
136
+ const res = await fetch ( fileData [ '@microsoft.graph.downloadUrl' ] ) ;
137
+ const fileStream = fs . createWriteStream ( dlPath ) ;
138
+ const stream = new WritableStream ( {
139
+ write ( chunk ) {
140
+ fileStream . write ( chunk ) ;
141
+ }
142
+ } ) ;
143
+
144
+ await res . body . pipeTo ( stream ) ;
145
+
146
+ return dlPath ;
147
+ }
148
+
149
+ async function generateTitle ( opts ) {
150
+ const svgImage = `
151
+ <svg width="${ opts . width } " height="${ opts . height } ">
152
+ <style>
153
+ .name {
154
+ fill: #000;
155
+ font-size: 70px;
156
+ font-weight: bold;
157
+ font-family: 'Asket';
158
+ }
159
+
160
+ .band, .category{
161
+ fill: #000;
162
+ font-family: 'Open Sans';
163
+ }
164
+
165
+ .band {
166
+ font-size: 70px;
167
+ font-weight: lighter;
168
+ }
169
+
170
+ .category{
171
+ font-size: 50px;
172
+ }
173
+ </style>
174
+ <text x="50%" y="35%" text-anchor="middle" class="name">${ opts . name . toUpperCase ( ) . replaceAll ( '&' , '&' ) } </text>
175
+ <text x="50%" y="50%" text-anchor="middle" font-style="italic" class="band">${ opts . band . replaceAll ( '&' , '&' ) } </text>
176
+ <text x="50%" y="65%" text-anchor="middle" class="category">${ opts . category . replaceAll ( '&' , '&' ) } </text>
177
+ </svg>
178
+ ` ;
179
+
180
+ const buffer = Buffer . from ( svgImage ) ;
181
+
182
+ const byba = await sharp ( 'byba.png' ) . flatten ( { background : '#FFF' } ) . resize ( { height : opts . height / 2 } ) . ensureAlpha ( 0.4 ) . toBuffer ( ) ;
183
+ const tymba = await sharp ( 'TYMBA.png' ) . flatten ( { background : '#FFF' } ) . resize ( { height : opts . height / 2 } ) . ensureAlpha ( 0.4 ) . toBuffer ( ) ;
184
+ const sponsor = await sharp ( 'marching arts.png' ) . flatten ( { background : '#FFF' } ) . resize ( { height : opts . height * 0.1 } ) . toBuffer ( ) ;
185
+
186
+ await sharp ( {
187
+ create : {
188
+ width : opts . width ,
189
+ height : opts . height ,
190
+ channels : 4 ,
191
+ background : {
192
+ r : 255 ,
193
+ g : 255 ,
194
+ b : 255 ,
195
+ alpha : 1
196
+ }
197
+ }
198
+ } ) . composite ( [
199
+ {
200
+ input : byba ,
201
+ top : opts . height / 4 ,
202
+ left : - 300
203
+ } ,
204
+ {
205
+ input : tymba ,
206
+ top : opts . height / 4 ,
207
+ left : opts . width - 270
208
+ } ,
209
+ {
210
+ input : sponsor ,
211
+ left : opts . width * 0.4 ,
212
+ top : opts . height * 0.85
213
+ } ,
214
+ {
215
+ input : buffer ,
216
+ top : 0 ,
217
+ left : 0
218
+ }
219
+ ] ) . toFile ( opts . path ) ;
220
+
221
+ return opts . path ;
222
+ }
223
+
224
+ function done ( mergedFile ) {
225
+ isWorking = false ;
226
+ const msg = {
227
+ type : "DONE"
228
+ } ;
229
+
230
+ if ( mergedFile != null ) {
231
+ msg . file = mergedFile ;
232
+ }
233
+
234
+ parentPort . postMessage ( msg ) ;
235
+ console . timeEnd ( timerName ) ;
236
+ workStart = 0 ;
237
+ }
238
+
239
+ let timerName = "" ;
240
+ let workStart = 0 ;
241
+ parentPort . on ( 'message' , async ( msg ) => {
242
+ if ( msg ?. type && msg . type == "FILE" ) {
243
+ timerName = msg . name ;
244
+ workStart = performance . now ( ) ;
245
+ console . time ( timerName ) ;
246
+
247
+ isWorking = true ;
248
+ const files = await Utils . listFiles ( config , path . join ( workerData . fileBase , msg . name ) )
249
+ await processFiles ( msg , files ) ;
250
+
251
+ } else if ( msg . type == "STATUS" ) {
252
+ const data = {
253
+ type : 'STATUS' ,
254
+ isWorking
255
+ } ;
256
+
257
+ if ( isWorking ) {
258
+ data . job = timerName ,
259
+ data . start = workStart
260
+ } ;
261
+
262
+ parentPort . postMessage ( data ) ;
263
+ }
264
+ } ) ;
0 commit comments