From aae352c998d52fe7b6d1d52fcd2899dc29baff4c Mon Sep 17 00:00:00 2001 From: Moe Alam Date: Fri, 9 Oct 2020 12:34:15 -0700 Subject: [PATCH] humanize timelapse mp4 builder --- languages/en_CA.json | 5 ++ libs/fileBin.js | 11 ++-- libs/timelapse.js | 62 +++++++++++++++++++- libs/videos.js | 5 +- web/libs/js/dash2.timelapse.jpeg.js | 89 ++++++++++++++++++++++------- 5 files changed, 144 insertions(+), 28 deletions(-) diff --git a/languages/en_CA.json b/languages/en_CA.json index a0ae5ff8..9d21e868 100644 --- a/languages/en_CA.json +++ b/languages/en_CA.json @@ -135,6 +135,8 @@ "Size (mb)": "Size (mb)", "Watch": "Watch", "Download": "Download", + "videoBuildingText1": "Video is currently building. Check again later.", + "Build Video": "Build Video", "Delete": "Delete", "Fix": "Fix", "Use HTML5 Play Method": "Use HTML5 Play Method", @@ -834,9 +836,12 @@ "Monitor mode is already": "Monitor mode is already", "Monitor or Key does not exist.": "Monitor or Key does not exist.", "No Group with this key exists": "No Group with this key exists", + "Downloading...": "Downloading...", "Downloading Videos": "Downloading Videos", "Zipping Videos": "Zipping Videos", + "Automatic Checking Cancelled": "Automatic Checking Cancelled", "Success": "Success", + "Please Wait or Click to Stop Checking": "Please Wait or Click to Stop Checking", "Search Settings": "Search Settings", "Trigger Successful": "Trigger Successful", "Trigger Blocked": "Trigger Blocked", diff --git a/libs/fileBin.js b/libs/fileBin.js index 22de6f8c..e29a5422 100644 --- a/libs/fileBin.js +++ b/libs/fileBin.js @@ -203,7 +203,10 @@ module.exports = function(s,config,lang,app,io){ /** * API : Get fileBin file */ - app.get(config.webPaths.apiPrefix+':auth/fileBin/:ke/:id/:year/:month/:day/:file', async (req,res) => { + app.get([ + config.webPaths.apiPrefix+':auth/fileBin/:ke/:id/:file', + config.webPaths.apiPrefix+':auth/fileBin/:ke/:id/:year/:month/:day/:file', + ], async (req,res) => { s.auth(req.params,function(user){ var failed = function(){ res.end(user.lang['File Not Found']) @@ -233,11 +236,11 @@ module.exports = function(s,config,lang,app,io){ if(r && r[0]){ r = r[0] r.details = JSON.parse(r.details) - req.dir = s.dir.fileBin + req.params.ke + '/' + req.params.id + '/' + r.details.year + '/' + r.details.month + '/' + r.details.day + '/' + req.params.file; - fs.stat(req.dir,function(err,stats){ + const filePath = s.dir.fileBin + req.params.ke + '/' + req.params.id + (r.details.year && r.details.month && r.details.day ? '/' + r.details.year + '/' + r.details.month + '/' + r.details.day : '') + '/' + req.params.file; + fs.stat(filePath,function(err,stats){ if(!err){ res.on('finish',function(){res.end()}) - fs.createReadStream(req.dir).pipe(res) + fs.createReadStream(filePath).pipe(res) }else{ failed() } diff --git a/libs/timelapse.js b/libs/timelapse.js index ccdb926c..ff6c932b 100644 --- a/libs/timelapse.js +++ b/libs/timelapse.js @@ -203,6 +203,66 @@ module.exports = function(s,config,lang,app,io){ },res,req); }); /** + * API : Build MP4 File + */ + app.post([ + config.webPaths.apiPrefix+':auth/timelapseBuildVideo/:ke', + config.webPaths.apiPrefix+':auth/timelapseBuildVideo/:ke/:id', + ], function (req,res){ + res.setHeader('Content-Type', 'application/json'); + s.auth(req.params,function(user){ + var hasRestrictions = user.details.sub && user.details.allmonitors !== '1' + if( + user.permissions.watch_videos==="0" || + hasRestrictions && + ( + !user.details.video_view || + user.details.video_view.indexOf(req.params.id) === -1 + ) + ){ + s.closeJsonResponse(res,[]) + return + } + const monitorRestrictions = s.getMonitorRestrictions(user.details,req.params.id) + if(monitorRestrictions.length === 0){ + s.closeJsonResponse(res,{ + ok: false + }) + return + } + const framesPosted = s.getPostData(req, 'frames', true) || [] + const frames = [] + var n = 0 + framesPosted.forEach((frame) => { + var firstParam = ['ke','=',req.params.ke] + if(n !== 0)firstParam = (['or']).concat(firstParam) + frames.push(firstParam,['mid','=',req.params.id],['filename','=',frame.filename]) + ++n + }) + s.knexQuery({ + action: "select", + columns: "*", + table: "Timelapse Frames", + where: frames + },(err,r) => { + if(r.length === 0){ + s.closeJsonResponse(res,{ + ok: false + }) + return + } + s.createVideoFromTimelapse(r,s.getPostData(req, 'fps'),function(response){ + s.closeJsonResponse(res,{ + ok : response.ok, + filename : response.filename, + fileExists : response.fileExists, + msg : response.msg, + }) + }) + }) + },res,req); + }); + /** * API : Get Timelapse images */ app.get([ @@ -291,7 +351,6 @@ module.exports = function(s,config,lang,app,io){ ['time','=<',dateNowMoment], ] },function(err,frames) { - console.log(frames.length) var groups = {} frames.forEach(function(frame){ if(groups[frame.ke])groups[frame.ke] = {} @@ -305,7 +364,6 @@ module.exports = function(s,config,lang,app,io){ if(response.ok){ } - console.log(response.fileLocation) }) }) }) diff --git a/libs/videos.js b/libs/videos.js index f5affe27..53697fa2 100644 --- a/libs/videos.js +++ b/libs/videos.js @@ -543,7 +543,9 @@ module.exports = function(s,config,lang){ fs.unlink(commandTempLocation,function(){ }) - delete(s.group[ke].activeMonitors[mid].buildingTimelapseVideo) + setTimeout(() => { + delete(s.group[ke].activeMonitors[mid].buildingTimelapseVideo) + },3000) }) var readFile = function(){ var filePath = concatFiles[currentFile] @@ -573,6 +575,7 @@ module.exports = function(s,config,lang){ callback({ ok: false, fileExists: true, + filename: finalFileName + '.mp4', fileLocation: finalMp4OutputLocation, msg: lang['Already exists'] }) diff --git a/web/libs/js/dash2.timelapse.jpeg.js b/web/libs/js/dash2.timelapse.jpeg.js index 49b886df..d2676c22 100644 --- a/web/libs/js/dash2.timelapse.jpeg.js +++ b/web/libs/js/dash2.timelapse.jpeg.js @@ -12,6 +12,7 @@ $(document).ready(function(e){ var playBackViewImage = timelapseWindow.find('.playBackView img') var liveStreamView = timelapseWindow.find('.liveStreamView') var monitorsList = timelapseWindow.find('.monitors_list') + var downloadButton = timelapseWindow.find('.download_mp4') var apiBaseUrl = $.ccio.init('location',$user) + $user.auth_token var downloadRecheckTimers = {} var currentPlaylist = {} @@ -20,6 +21,8 @@ $(document).ready(function(e){ var playInterval = 1000 / 30 var fieldHolderCssHeightModifier = 0 var canPlay = false; + var downloaderIsChecking = false + var allowKeepChecking = true var openTimelapseWindow = function(monitorId,startDate,endDate){ timelapseWindow.modal('show') @@ -62,6 +65,7 @@ $(document).ready(function(e){ } var drawTimelapseWindowElements = function(selectedMonitor,startDate,endDate){ + setDownloadButtonLabel(lang['Build Video'], 'database') var dateRange = getSelectedTime(true) if(!startDate)startDate = dateRange.startDate if(!endDate)endDate = dateRange.endDate @@ -153,6 +157,13 @@ $(document).ready(function(e){ playPauseText.text(lang.Pause) } } + var iconHtml = function(iconClasses,withSpace){ + if(withSpace === undefined)withSpace = true + return `` + (withSpace ? ' ' : '') + } + var setDownloadButtonLabel = function(text,icon){ + downloadButton.html(icon ? iconHtml(icon) + text : text) + } timelapseWindow.on('click','.frame',function(){ pauseTimelapse() var selectedFrame = $(this).attr('data-filename') @@ -169,33 +180,68 @@ $(document).ready(function(e){ togglePlayPause() }) timelapseWindow.on('click','.download_mp4',function(){ - var _this = $(this) - var runDownloader = function(){ + if(downloaderIsChecking){ + allowKeepChecking = false + setDownloadButtonLabel(lang['Build Video'], 'database') + }else{ + allowKeepChecking = true + var _this = $(this) + var fps = fpsSelector.val() var dateRange = getSelectedTime(true) var startDate = dateRange.startDate var endDate = dateRange.endDate var selectedMonitor = monitorsList.val() - var queryString = ['fps=' + fpsSelector.val(),'start=' + startDate,'end=' + endDate,'mp4=1'] - var timerId = queryString.join('&') - var generatorUrl = apiBaseUrl + '/timelapse/' + $user.ke + '/' + selectedMonitor - $.getJSON(generatorUrl + '?' + queryString.join('&'),function(response){ - _this.text(lang['Download']) - if(response.fileExists){ - var downloadName = startDate + '_' + endDate + '_' + selectedMonitor + '.mp4' - var a = document.createElement('a') - a.href = generatorUrl + '?' + queryString.concat(['download="1"']).join('&') - a.download = downloadName - a.click() - }else{ - _this.html(lang['Please Wait...']) - clearTimeout(downloadRecheckTimers[timerId]) - downloadRecheckTimers[timerId] = setTimeout(function(){ - _this.html(lang['Download']) - },5000) + var parsedFrames = JSON.stringify(currentPlaylistArray.map(function(frame){ + return { + mid: frame.mid, + ke: frame.ke, + filename: frame.filename, } - }) + })) + var postBody = { + fps: fps, + frames: parsedFrames, + } + var timerId = JSON.stringify(parsedFrames) + var generatorUrl = apiBaseUrl + '/timelapseBuildVideo/' + $user.ke + '/' + selectedMonitor + var runDownloader = function(){ + if(!allowKeepChecking){ + setDownloadButtonLabel(lang['Automatic Checking Cancelled']) + downloadRecheckTimers[timerId] = setTimeout(function(){ + setDownloadButtonLabel(lang['Build Video'], 'database') + },5000) + downloaderIsChecking = false + allowKeepChecking = true + return + } + downloaderIsChecking = true + setDownloadButtonLabel(lang['Please Wait or Click to Stop Checking'], 'spinner fa-pulse') + $.post(generatorUrl,postBody,function(response){ + if(response.fileExists){ + setDownloadButtonLabel(lang['Downloading...'], 'spinner fa-pulse') + var downloadUrl = apiBaseUrl + '/fileBin/' + $user.ke + '/' + selectedMonitor + '/' + response.filename + var downloadName = startDate + '_' + endDate + '_' + selectedMonitor + '.mp4' + var a = document.createElement('a') + a.href = downloadUrl + a.download = downloadName + a.click() + setTimeout(function(){ + setDownloadButtonLabel(lang['Download'], 'download') + },2000) + downloaderIsChecking = false + allowKeepChecking = true + }else{ + setDownloadButtonLabel(lang['Please Wait or Click to Stop Checking'], 'spinner fa-pulse') + clearTimeout(downloadRecheckTimers[timerId]) + downloadRecheckTimers[timerId] = setTimeout(function(){ + setDownloadButtonLabel(lang['Please Wait or Click to Stop Checking'], 'spinner fa-pulse') + runDownloader() + },5000) + } + }) + } + runDownloader() } - runDownloader() }) timelapseWindow.on('shown.bs.modal', function (e) { resetFilmStripPositions() @@ -216,4 +262,5 @@ $(document).ready(function(e){ openWindow: openTimelapseWindow, monitorsList: monitorsList } + setDownloadButtonLabel(lang['Build Video'], 'database') })