Add Fix button to reprocess Videos from Videos Table

- This is for when a video has become corrupt and you want to attempt making it playable with the content it has.
+ move stitchMp4Files function
merge-requests/367/head
Moe 2022-07-07 15:53:46 -07:00
parent 87867373a5
commit 85ab76178f
6 changed files with 116 additions and 29 deletions

View File

@ -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.",

View File

@ -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)
}

View File

@ -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,

View File

@ -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'

View File

@ -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}<br><br><div class="row"><video class="video_video" autoplay loop controls><source src="${videoEndpoint}" type="video/${ext}"></video></div>`,
clickOptions: {
title: '<i class="fa fa-wrench"></i> ' + lang.Fix,
class: 'btn-danger btn-sm'
},
clickCallback: function(){
$.getJSON(videoEndpoint + '/fix',function(data){
if(data.ok){
console.log('Video Fixed')
}else{
console.log('Video Not Fixed',data,videoEndpoint)
}
})
}

View File

@ -115,6 +115,7 @@ $(document).ready(function(e){
<a class="btn btn-sm btn-primary" href="${href}" download title="${lang.Download}"><i class="fa fa-download"></i></a>
<a class="btn btn-sm btn-primary preview-video" href="${href}" title="${lang.Play}"><i class="fa fa-play"></i></a>
<a class="btn btn-sm btn-default open-video" href="${href}" title="${lang.Play}"><i class="fa fa-play"></i></a>
<a class="btn btn-sm btn-warning fix-video" href="${href}" title="${lang.Fix}"><i class="fa fa-wrench"></i></a>
<a class="btn btn-sm btn-danger delete-video" href="${href}" title="${lang.Delete}"><i class="fa fa-trash-o"></i></a>
</div>
`,