@@ -13,6 +13,7 @@ local tables = require('jls.util.tables')
13
13
local strings = require (' jls.util.strings' )
14
14
local Map = require (' jls.util.Map' )
15
15
local List = require (' jls.util.List' )
16
+ local HttpContext = require (' jls.net.http.HttpContext' )
16
17
local HttpExchange = require (' jls.net.http.HttpExchange' )
17
18
local FileHttpHandler = require (' jls.net.http.handler.FileHttpHandler' )
18
19
local RestHttpHandler = require (' jls.net.http.handler.RestHttpHandler' )
@@ -62,6 +63,9 @@ local function startProcess(command, outputFile, callback)
62
63
fd = FileDescriptor .openSync (outputFile , ' w' )
63
64
pb :redirectOutput (fd )
64
65
end
66
+ if logger :isLoggable (logger .FINE ) then
67
+ logger :fine (' Start process: ' .. table.concat (command , ' ' ))
68
+ end
65
69
local ph = pb :start ()
66
70
ph :ended ():next (function (exitCode )
67
71
if fd then
96
100
97
101
local scriptFile = File :new (arg [0 ]):getAbsoluteFile ()
98
102
local scriptDir = scriptFile :getParentFile ()
99
-
100
- local assetsHandler
101
103
local assetsDir = File :new (scriptDir , ' assets' )
102
104
local assetsZip = File :new (scriptDir , ' assets.zip' )
103
- if assetsDir :isDirectory () then
104
- assetsHandler = FileHttpHandler :new (assetsDir )
105
- elseif assetsZip :isFile () then
106
- assetsHandler = ZipFileHttpHandler :new (assetsZip )
107
- end
105
+ local extensionsDir = File :new (scriptDir , config .extensions )
108
106
109
107
local commandQueue = {}
110
108
local commandCount = 0
@@ -133,6 +131,28 @@ ffmpeg:configure(config)
133
131
134
132
-- Application local functions
135
133
134
+ local function loadExtensions (...)
135
+ if not extensionsDir :isDirectory () then
136
+ return
137
+ end
138
+ logger :info (' Loading extensions from directory "' .. extensionsDir :getPath ().. ' "' )
139
+ for _ , extensionDir in ipairs (extensionsDir :listFiles ()) do
140
+ local extensionLuaFile = File :new (extensionDir , ' init.lua' )
141
+ if extensionDir :isDirectory () and extensionLuaFile :isFile () then
142
+ local scriptFn , err = loadfile (extensionLuaFile :getPath ())
143
+ if not scriptFn or err then
144
+ logger :warn (' Cannot load extension from script "' .. extensionLuaFile :getPath ().. ' " due to ' .. tostring (err ))
145
+ else
146
+ local status
147
+ status , err = pcall (scriptFn , ... )
148
+ if not status then
149
+ logger :warn (' Cannot load extension from script "' .. extensionLuaFile :getPath ().. ' " due to ' .. tostring (err ))
150
+ end
151
+ end
152
+ end
153
+ end
154
+ end
155
+
136
156
local function terminate ()
137
157
for _ , ph in ipairs (processList ) do
138
158
ph :destroy ()
@@ -188,18 +208,50 @@ local function enqueueCommand(args, id, outputFile)
188
208
return promise
189
209
end
190
210
211
+ local function endExport (exportContext , webSocket , exitCode )
212
+ exportContext .exitCode = exitCode
213
+ webSocket :close ()
214
+ Map .deleteValues (exportContexts , exportContext )
215
+ end
216
+
217
+ local function startExportCommand (exportContext , webSocket , index )
218
+ local command = exportContext .commands [index ]
219
+ if not command then
220
+ endExport (exportContext , webSocket , 0 )
221
+ return
222
+ end
223
+ webSocket :sendTextMessage (' \n -- starting command ' .. tostring (index ).. ' /' .. tostring (# exportContext .commands ).. ' ------\n\n ' )
224
+ local pb = createProcessBuilder (command )
225
+ local p = Pipe :new ()
226
+ pb :redirectError (p )
227
+ local ph = pb :start ()
228
+ ph :ended ():next (function (exitCode )
229
+ if exitCode == 0 then
230
+ startExportCommand (exportContext , webSocket , index + 1 )
231
+ else
232
+ endExport (exportContext , webSocket , exitCode )
233
+ end
234
+ end )
235
+ exportContext .process = ph
236
+ p :readStart (function (err , data )
237
+ if not err and data then
238
+ webSocket :sendTextMessage (data )
239
+ else
240
+ p :close ()
241
+ end
242
+ end )
243
+ end
244
+
191
245
-- HTTP contexts used by the web application
192
246
local httpContexts = {
247
+ -- HTTP resources
193
248
[' /(.*)' ] = FileHttpHandler :new (File :new (scriptDir , ' htdocs' ), nil , ' fcut.html' ),
249
+ -- Context to retrieve the configuration
194
250
[' /config/(.*)' ] = TableHttpHandler :new (config , nil , true ),
195
- [' /assets/(.*)' ] = assetsHandler ,
196
- }
197
-
198
- -- Create the HTTP contexts used by the web application
199
- local function createHttpContexts (httpServer )
200
- logger :info (' HTTP Server bound on port ' .. tostring (select (2 , httpServer :getAddress ())))
251
+ -- Assets HTTP resources directory or ZIP file
252
+ [' /assets/(.*)' ] = assetsZip :isFile () and not assetsDir :isDirectory () and ZipFileHttpHandler :new (assetsZip ) or FileHttpHandler :new (assetsDir ),
201
253
-- Context to retrieve and cache a movie image at a specific time
202
- httpServer : createContext ( ' /source/([^/]+)/(%d+)%.jpg' , Map .assign (FileHttpHandler :new (cacheDir ), {
254
+ [ ' /source/([^/]+)/(%d+%.?%d* )%.jpg' ] = Map .assign (FileHttpHandler :new (cacheDir ), {
203
255
getPath = function (_ , exchange )
204
256
return string.sub (exchange :getRequest ():getTargetPath (), 9 )
205
257
end ,
@@ -210,9 +262,9 @@ local function createHttpContexts(httpServer)
210
262
return enqueueCommand (command , ' preview' )
211
263
end )
212
264
end
213
- }))
265
+ }),
214
266
-- Context to retrieve and cache a movie information
215
- httpServer : createContext ( ' /source/([^/]+)/info%.json' , Map .assign (FileHttpHandler :new (cacheDir ), {
267
+ [ ' /source/([^/]+)/info%.json' ] = Map .assign (FileHttpHandler :new (cacheDir ), {
216
268
getPath = function (_ , exchange )
217
269
return string.sub (exchange :getRequest ():getTargetPath (), 9 )
218
270
end ,
@@ -223,9 +275,9 @@ local function createHttpContexts(httpServer)
223
275
return enqueueCommand (command , nil , tmpFile )
224
276
end )
225
277
end
226
- }))
278
+ }),
227
279
-- Context for the application REST API
228
- httpServer : createContext ( ' /rest/(.*)' , RestHttpHandler :new ({
280
+ [ ' /rest/(.*)' ] = RestHttpHandler :new ({
229
281
[' getSourceId?method=POST' ] = function (exchange )
230
282
local filename = exchange :getRequest ():getBody ()
231
283
return ffmpeg :openSource (filename )
@@ -288,70 +340,33 @@ local function createHttpContexts(httpServer)
288
340
}
289
341
return exportId
290
342
end ,
291
- }))
343
+ }),
292
344
-- Context that handle the export commands and output to a WebSocket
293
- httpServer : createContext ( ' /console/(.*)' , Map .assign (WebSocketUpgradeHandler :new (), {
345
+ [ ' /console/(.*)' ] = Map .assign (WebSocketUpgradeHandler :new (), {
294
346
onOpen = function (_ , webSocket , exchange )
295
347
local exportId = exchange :getRequestArguments ()
296
348
local exportContext = exportContexts [exportId ]
297
349
if not exportContext then
298
350
webSocket :close ()
299
351
return
300
352
end
301
- local commands = exportContext .commands
302
353
local header = {' -- export commands ------' };
303
- for index , command in ipairs (commands ) do
354
+ for index , command in ipairs (exportContext . commands ) do
304
355
table.insert (header , ' ' .. tostring (index ).. ' : ' .. table.concat (command , ' ' ))
305
356
end
306
357
table.insert (header , ' ' )
307
358
webSocket :sendTextMessage (table.concat (header , ' \n ' ))
308
- local function endExport (exitCode )
309
- exportContext .exitCode = exitCode
310
- webSocket :close ()
311
- exportContexts [exportId ] = nil
312
- end
313
- local index = 0
314
- local function startNextCommand ()
315
- index = index + 1
316
- if index > # commands then
317
- endExport (0 )
318
- return
319
- end
320
- local command = commands [index ]
321
- webSocket :sendTextMessage (' \n -- starting command ' .. tostring (index ).. ' /' .. tostring (# commands ).. ' ------\n\n ' )
322
- local pb = createProcessBuilder (command )
323
- local p = Pipe :new ()
324
- pb :redirectError (p )
325
- local ph = pb :start ()
326
- ph :ended ():next (function (exitCode )
327
- if exitCode == 0 then
328
- startNextCommand ()
329
- else
330
- endExport (exitCode )
331
- end
332
- end )
333
- exportContext .process = ph
334
- p :readStart (function (err , data )
335
- if not err and data then
336
- webSocket :sendTextMessage (data )
337
- else
338
- p :close ()
339
- end
340
- end )
341
- end
342
- startNextCommand ()
359
+ startExportCommand (exportContext , webSocket , 1 )
343
360
end
344
- }))
345
- end
361
+ }),
362
+ HttpContext :new (' /extensions/([a-zA-Z0-9%-_]+)/(.*)' , FileHttpHandler :new (extensionsDir , ' r' , ' index.html' )):setPathReplacement (' %1/htdocs/%2' ),
363
+ }
346
364
347
365
-- Start the application as an HTTP server or a WebView
348
366
if config .webview .disable then
349
367
local httpServer = require (' jls.net.http.HttpServer' ):new ()
350
368
httpServer :bind (config .webview .address , config .webview .port ):next (function ()
351
- for path , handler in pairs (httpContexts ) do
352
- httpServer :createContext (path , handler )
353
- end
354
- createHttpContexts (httpServer )
369
+ httpServer :createContexts (httpContexts )
355
370
if config .webview .port == 0 then
356
371
print (' FCut HTTP Server available at http://localhost:' .. tostring (select (2 , httpServer :getAddress ())))
357
372
end
@@ -363,20 +378,24 @@ if config.webview.disable then
363
378
-- HttpExchange.ok(exchange, 'Closing')
364
379
end ,
365
380
}))
381
+ loadExtensions (httpServer )
366
382
end , function (err )
367
383
logger :warn (' Cannot bind HTTP server due to ' .. tostring (err ))
368
384
os.exit (1 )
369
385
end )
370
386
else
371
- require (' jls.util.WebView' ).open (' http://localhost:' .. tostring (config .webview .port ).. ' /' , {
387
+ local url = ' http://localhost:' .. tostring (config .webview .port ).. ' /'
388
+ if config .extension then
389
+ url = url .. ' extensions/' .. config .extension .. ' /'
390
+ end
391
+ require (' jls.util.WebView' ).open (url , {
372
392
title = ' Fast Cut (Preview)' ,
373
393
resizable = true ,
374
394
bind = true ,
375
395
debug = config .webview .debug ,
376
396
contexts = httpContexts ,
377
397
}):next (function (webview )
378
398
local httpServer = webview :getHttpServer ()
379
- createHttpContexts (httpServer )
380
399
httpServer :createContext (' /webview/(.*)' , RestHttpHandler :new ({
381
400
[' fullscreen(requestJson)?method=POST&Content-Type=application/json' ] = function (exchange , fullscreen )
382
401
webview :fullscreen (fullscreen == true );
417
436
return self :call (getFileName , order )
418
437
end
419
438
end
439
+ loadExtensions (httpServer )
420
440
return webview :getThread ():ended ()
421
441
end ):next (function ()
422
442
logger :info (' WebView closed' )
0 commit comments