diff --git a/languages/en_CA.json b/languages/en_CA.json
index de8ed5a0..139ee804 100644
--- a/languages/en_CA.json
+++ b/languages/en_CA.json
@@ -485,7 +485,7 @@
"Delete Filter": "Delete Filter",
"confirmDeleteFilter": "Do you want to delete this filter? You cannot recover it.",
"Fix Video": "Fix Video",
- "FixVideoMsg": "Do you want to fix this video? You cannot undo this action.",
+ "FixVideoMsg": "Do you want to fix this video? This will create a new file and overwrite the old one. You cannot undo this action.",
"DeleteVideoMsg": "Do you want to delete this video? You cannot recover it.",
"DeleteThisMsg": "Do you want to delete this? You cannot recover it.",
"DeleteTheseMsg": "Do you want to delete these? You cannot recover them.",
diff --git a/libs/timelapse.js b/libs/timelapse.js
index 0cfaeb71..3f8c3869 100644
--- a/libs/timelapse.js
+++ b/libs/timelapse.js
@@ -17,6 +17,9 @@ module.exports = function(s,config,lang,app,io){
const {
processKill,
} = require('./monitor/utils.js')(s,config,lang)
+ const {
+ stitchMp4Files,
+ } = require('./video/utils.js')(s,config,lang)
const timelapseFramesCache = {}
const timelapseFramesCacheTimeouts = {}
s.getTimelapseFrameDirectory = function(e){
@@ -248,24 +251,6 @@ module.exports = function(s,config,lang,app,io){
})
})
}
- async function stitchMp4Files(options){
- return new Promise((resolve,reject) => {
- const concatListFile = options.listFile
- const finalMp4OutputLocation = options.output
- const commandString = `-y -threads 1 -f concat -safe 0 -i "${concatListFile}" -c:v copy -an -preset ultrafast "${finalMp4OutputLocation}"`
- s.debugLog("stitchMp4Files",commandString)
- const videoBuildProcess = spawn(config.ffmpegDir,splitForFFPMEG(commandString))
- videoBuildProcess.stdout.on('data',function(data){
- s.debugLog('stdout',finalMp4OutputLocation,data.toString())
- })
- videoBuildProcess.stderr.on('data',function(data){
- s.debugLog('stderr',finalMp4OutputLocation,data.toString())
- })
- videoBuildProcess.on('exit',async function(data){
- resolve()
- })
- })
- }
async function chunkFramesAndBuildMultipleVideosThenSticth(options){
// a single video with too many frames makes the video unplayable, this is the fix.
const frames = options.frames
@@ -311,10 +296,10 @@ module.exports = function(s,config,lang,app,io){
listFile: concatListFile,
output: finalMp4OutputLocation,
})
- await fs.promises.unlink(concatListFile)
- for (let i = 0; i < filePathsList; i++) {
+ await fs.promises.rm(concatListFile)
+ for (let i = 0; i < filePathsList.length; i++) {
var segmentFileOutput = filePathsList[i]
- await fs.promises.unlink(segmentFileOutput)
+ await fs.promises.rm(segmentFileOutput)
}
s.debugLog('videoBuildProcess Stitching Complete!',finalMp4OutputLocation)
}
diff --git a/libs/video/utils.js b/libs/video/utils.js
index 2332c04c..ce6e58c8 100644
--- a/libs/video/utils.js
+++ b/libs/video/utils.js
@@ -1,6 +1,9 @@
const fs = require('fs')
const { spawn } = require('child_process')
module.exports = (s,config,lang) => {
+ const {
+ splitForFFPMEG,
+ } = require('../ffmpeg/utils.js')(s,config,lang)
// orphanedVideoCheck : new function
const checkIfVideoIsOrphaned = (monitor,videosDirectory,filename) => {
const response = {ok: true}
@@ -64,10 +67,10 @@ module.exports = (s,config,lang) => {
let listing = spawn('sh',[tempDirectory + 'orphanCheck.sh'])
// const onData = options.onData ? options.onData : () => {}
const onError = options.onError ? options.onError : s.systemLog
- const onExit = () => {
+ const onExit = async () => {
try{
listing.kill('SIGTERM')
- fs.unlink(tempDirectory + 'orphanCheck.sh',() => {})
+ await fs.promises.rm(tempDirectory + 'orphanCheck.sh')
}catch(err){
s.debugLog(err)
}
@@ -235,7 +238,77 @@ module.exports = (s,config,lang) => {
});
return videoSelectResponse
}
+ async function stitchMp4Files(options){
+ return new Promise((resolve,reject) => {
+ const concatListFile = options.listFile
+ const finalMp4OutputLocation = options.output
+ const commandString = `-y -threads 1 -f concat -safe 0 -i "${concatListFile}" -c:v copy -an -preset ultrafast "${finalMp4OutputLocation}"`
+ s.debugLog("stitchMp4Files",commandString)
+ const videoBuildProcess = spawn(config.ffmpegDir,splitForFFPMEG(commandString))
+ videoBuildProcess.stdout.on('data',function(data){
+ s.debugLog('stdout',finalMp4OutputLocation,data.toString())
+ })
+ videoBuildProcess.stderr.on('data',function(data){
+ s.debugLog('stderr',finalMp4OutputLocation,data.toString())
+ })
+ videoBuildProcess.on('exit',async function(data){
+ resolve()
+ })
+ })
+ }
+ const fixingAlready = {}
+ async function reEncodeVideoAndReplace(videoRow){
+ return new Promise((resolve,reject) => {
+ const response = {ok: true}
+ const fixingId = `${videoRow.ke}${videoRow.mid}${videoRow.time}`
+ if(fixingAlready[fixingId]){
+ response.ok = false
+ response.msg = lang['Already Processing']
+ resolve(response)
+ }else{
+ const filename = s.formattedTime(videoRow.time)+'.'+videoRow.ext
+ const tempFilename = s.formattedTime(videoRow.time)+'.reencoding.'+videoRow.ext
+ const videoFolder = s.getVideoDirectory(videoRow)
+ const inputFilePath = `${videoFolder}${filename}`
+ const outputFilePath = `${videoFolder}${tempFilename}`
+ const commandString = `-y -threads 1 -re -i "${inputFilePath}" -c:v copy -c:a copy -preset ultrafast "${outputFilePath}"`
+ fixingAlready[fixingId] = true
+ const videoBuildProcess = spawn(config.ffmpegDir,splitForFFPMEG(commandString))
+ videoBuildProcess.stdout.on('data',function(data){
+ s.debugLog('stdout',outputFilePath,data.toString())
+ })
+ videoBuildProcess.stderr.on('data',function(data){
+ s.debugLog('stderr',outputFilePath,data.toString())
+ })
+ videoBuildProcess.on('exit',async function(data){
+ fixingAlready[fixingId] = false
+ try{
+ function failed(err){
+ response.ok = false
+ response.err = err
+ resolve(response)
+ }
+ const newFileStats = await fs.promises.stat(outputFilePath)
+ await fs.promises.rm(inputFilePath)
+ let readStream = fs.createReadStream(outputFilePath);
+ let writeStream = fs.createWriteStream(inputFilePath);
+ readStream.pipe(writeStream);
+ writeStream.on('finish', async () => {
+ resolve(response)
+ await fs.promises.rm(outputFilePath)
+ });
+ writeStream.on('error', failed);
+ readStream.on('error', failed);
+ }catch(err){
+ failed()
+ }
+ })
+ }
+ })
+ }
return {
+ reEncodeVideoAndReplace,
+ stitchMp4Files,
orphanedVideoCheck: orphanedVideoCheck,
scanForOrphanedVideos: scanForOrphanedVideos,
cutVideoLength: cutVideoLength,
diff --git a/libs/webServerPaths.js b/libs/webServerPaths.js
index 8d26c141..6830ac78 100644
--- a/libs/webServerPaths.js
+++ b/libs/webServerPaths.js
@@ -31,6 +31,7 @@ module.exports = function(s,config,lang,app,io){
destroySubstreamProcess,
} = require('./monitor/utils.js')(s,config,lang)
const {
+ reEncodeVideoAndReplace,
getVideosBasedOnTagFoundInMatrixOfAssociatedEvent,
} = require('./video/utils.js')(s,config,lang)
s.renderPage = function(req,res,paths,passables,callback){
@@ -1651,7 +1652,7 @@ module.exports = function(s,config,lang,app,io){
config.webPaths.apiPrefix+':auth/cloudVideos/:ke/:id/:file/:mode',
config.webPaths.apiPrefix+':auth/cloudVideos/:ke/:id/:file/:mode/:f'
], function (req,res){
- var response = {ok:false};
+ let response = { ok: false };
res.setHeader('Content-Type', 'application/json');
s.auth(req.params,function(user){
if(user.permissions.watch_videos==="0"||user.details.sub&&user.details.allmonitors!=='1'&&user.details.video_delete.indexOf(req.params.id)===-1){
@@ -1683,15 +1684,14 @@ module.exports = function(s,config,lang,app,io){
['time','=',time]
],
limit: 1
- },(err,r) => {
+ },async (err,r) => {
if(r && r[0]){
r=r[0];
r.filename=s.formattedTime(r.time)+'.'+r.ext;
var details = s.parseJSON(r.details) || {}
switch(req.params.mode){
case'fix':
- response.ok = true;
- s.video('fix',r)
+ response = await reEncodeVideoAndReplace(r)
break;
case'status':
r.f = 'video_edit'
diff --git a/web/assets/js/bs5.videos.js b/web/assets/js/bs5.videos.js
index b2b9e288..250fc5d4 100644
--- a/web/assets/js/bs5.videos.js
+++ b/web/assets/js/bs5.videos.js
@@ -583,7 +583,35 @@ $(document).ready(function(){
if(data.ok){
console.log('Video Deleted')
}else{
- console.log('Video Not Deleted',data,deleteEndpoint)
+ console.log('Video Not Deleted',data,videoEndpoint)
+ }
+ })
+ }
+ });
+ return false;
+ })
+ .on('click','.fix-video',function(e){
+ e.preventDefault()
+ var el = $(this).parents('[data-mid]')
+ var monitorId = el.attr('data-mid')
+ var videoTime = el.attr('data-time')
+ var video = loadedVideosInMemory[`${monitorId}${videoTime}`]
+ var ext = video.filename.split('.')
+ ext = ext[ext.length - 1]
+ var videoEndpoint = getApiPrefix(`videos`) + '/' + video.mid + '/' + video.filename
+ $.confirm.create({
+ title: lang["Fix Video"] + ' : ' + video.filename,
+ body: `${lang.FixVideoMsg}