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 functionmerge-requests/367/head
parent
87867373a5
commit
85ab76178f
|
|
@ -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.",
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
`,
|
||||
|
|
|
|||
Loading…
Reference in New Issue