318 lines
14 KiB
JavaScript
318 lines
14 KiB
JavaScript
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}
|
|
return new Promise((resolve,reject) => {
|
|
fs.stat(videosDirectory + filename,(err,stats) => {
|
|
if(!err && stats.size > 10){
|
|
s.knexQuery({
|
|
action: "select",
|
|
columns: "*",
|
|
table: "Videos",
|
|
where: [
|
|
['ke','=',monitor.ke],
|
|
['mid','=',monitor.mid],
|
|
['time','=',s.nameToTime(filename)],
|
|
],
|
|
limit: 1
|
|
},(err,rows) => {
|
|
if(!err && (!rows || !rows[0])){
|
|
//row does not exist, create one for video
|
|
var video = rows[0]
|
|
s.insertCompletedVideo(monitor,{
|
|
file : filename
|
|
},() => {
|
|
response.status = 2
|
|
resolve(response)
|
|
})
|
|
}else{
|
|
//row exists, no errors
|
|
response.status = 1
|
|
resolve(response)
|
|
}
|
|
})
|
|
}else{
|
|
response.status = 0
|
|
resolve(response)
|
|
}
|
|
})
|
|
})
|
|
}
|
|
const scanForOrphanedVideos = (monitor,options) => {
|
|
// const options = {
|
|
// checkMax: 2
|
|
// }
|
|
options = options ? options : {}
|
|
return new Promise((resolve,reject) => {
|
|
const response = {ok: false}
|
|
if(options.forceCheck === true || config.insertOrphans === true){
|
|
if(!options.checkMax){
|
|
options.checkMax = config.orphanedVideoCheckMax || 2
|
|
}
|
|
let finished = false
|
|
let orphanedFilesCount = 0;
|
|
let videosFound = 0;
|
|
const videosDirectory = s.getVideoDirectory(monitor)
|
|
const tempDirectory = s.getStreamsDirectory(monitor)
|
|
// const findCmd = [videosDirectory].concat(options.flags || ['-maxdepth','1'])
|
|
fs.writeFileSync(
|
|
tempDirectory + 'orphanCheck.sh',
|
|
`find "${videosDirectory}" -maxdepth 1 -type f -exec stat -c "%n" {} + | sort -r | head -n ${options.checkMax}`
|
|
);
|
|
let listing = spawn('sh',[tempDirectory + 'orphanCheck.sh'])
|
|
// const onData = options.onData ? options.onData : () => {}
|
|
const onError = options.onError ? options.onError : s.systemLog
|
|
const onExit = async () => {
|
|
try{
|
|
listing.kill('SIGTERM')
|
|
await fs.promises.rm(tempDirectory + 'orphanCheck.sh')
|
|
}catch(err){
|
|
s.debugLog(err)
|
|
}
|
|
delete(listing)
|
|
}
|
|
const onFinish = () => {
|
|
if(!finished){
|
|
finished = true
|
|
response.ok = true
|
|
response.orphanedFilesCount = orphanedFilesCount
|
|
resolve(response)
|
|
onExit()
|
|
}
|
|
}
|
|
const processLine = async (filePath) => {
|
|
let filename = filePath.split('/')
|
|
filename = `${filename[filename.length - 1]}`.trim()
|
|
if(filename && filename.indexOf('-') > -1 && filename.indexOf('.') > -1){
|
|
const { status } = await checkIfVideoIsOrphaned(monitor,videosDirectory,filename)
|
|
if(status === 2){
|
|
++orphanedFilesCount
|
|
}
|
|
++videosFound
|
|
if(videosFound === options.checkMax){
|
|
onFinish()
|
|
}
|
|
}
|
|
}
|
|
listing.stdout.on('data', async (d) => {
|
|
const filePathLines = d.toString().split('\n')
|
|
var i;
|
|
for (i = 0; i < filePathLines.length; i++) {
|
|
await processLine(filePathLines[i])
|
|
}
|
|
})
|
|
listing.stderr.on('data', d=>onError(d.toString()))
|
|
listing.on('close', (code) => {
|
|
// s.debugLog(`findOrphanedVideos ${monitor.ke} : ${monitor.mid} process exited with code ${code}`);
|
|
setTimeout(() => {
|
|
onFinish()
|
|
},1000)
|
|
});
|
|
}else{
|
|
resolve(response)
|
|
}
|
|
})
|
|
}
|
|
// orphanedVideoCheck : old function
|
|
const orphanedVideoCheck = (monitor,checkMax,callback,forceCheck) => {
|
|
var finish = function(orphanedFilesCount){
|
|
if(callback)callback(orphanedFilesCount)
|
|
}
|
|
if(forceCheck === true || config.insertOrphans === true){
|
|
if(!checkMax){
|
|
checkMax = config.orphanedVideoCheckMax || 2
|
|
}
|
|
|
|
var videosDirectory = s.getVideoDirectory(monitor)// + s.formattedTime(video.time) + '.' + video.ext
|
|
fs.readdir(videosDirectory,function(err,files){
|
|
if(files && files.length > 0){
|
|
var fiveRecentFiles = files.slice(files.length - checkMax,files.length)
|
|
var completedFile = 0
|
|
var orphanedFilesCount = 0
|
|
var fileComplete = function(){
|
|
++completedFile
|
|
if(fiveRecentFiles.length === completedFile){
|
|
finish(orphanedFilesCount)
|
|
}
|
|
}
|
|
fiveRecentFiles.forEach(function(filename){
|
|
if(/T[0-9][0-9]-[0-9][0-9]-[0-9][0-9]./.test(filename)){
|
|
fs.stat(videosDirectory + filename,(err,stats) => {
|
|
if(!err && stats.size > 10){
|
|
s.knexQuery({
|
|
action: "select",
|
|
columns: "*",
|
|
table: "Videos",
|
|
where: [
|
|
['ke','=',monitor.ke],
|
|
['mid','=',monitor.mid],
|
|
['time','=',s.nameToTime(filename)],
|
|
],
|
|
limit: 1
|
|
},(err,rows) => {
|
|
if(!err && (!rows || !rows[0])){
|
|
++orphanedFilesCount
|
|
var video = rows[0]
|
|
s.insertCompletedVideo(monitor,{
|
|
file : filename
|
|
},() => {
|
|
fileComplete()
|
|
})
|
|
}else{
|
|
fileComplete()
|
|
}
|
|
})
|
|
}
|
|
})
|
|
}
|
|
})
|
|
}else{
|
|
finish()
|
|
}
|
|
})
|
|
}else{
|
|
finish()
|
|
}
|
|
}
|
|
function cutVideoLength(options){
|
|
return new Promise((resolve,reject) => {
|
|
const response = {ok: false}
|
|
const inputFilePath = options.filePath
|
|
const monitorId = options.mid
|
|
const groupKey = options.ke
|
|
const cutLength = options.cutLength || 10
|
|
const tempDirectory = s.getStreamsDirectory(options)
|
|
let fileExt = inputFilePath.split('.')
|
|
fileExt = fileExt[fileExt.length -1]
|
|
const filename = `${s.gid(10)}.${fileExt}`
|
|
const videoOutPath = `${tempDirectory}${filename}`
|
|
const cuttingProcess = spawn(config.ffmpegDir,['-loglevel','warning','-i', inputFilePath, '-c','copy','-t',`${cutLength}`,videoOutPath])
|
|
cuttingProcess.stderr.on('data',(data) => {
|
|
const err = data.toString()
|
|
s.debugLog('cutVideoLength',options,err)
|
|
})
|
|
cuttingProcess.on('close',(data) => {
|
|
fs.stat(videoOutPath,(err) => {
|
|
if(!err){
|
|
response.ok = true
|
|
response.filename = filename
|
|
response.filePath = videoOutPath
|
|
setTimeout(() => {
|
|
s.file('delete',videoOutPath)
|
|
},1000 * 60 * 3)
|
|
}else{
|
|
s.debugLog('cutVideoLength:readFile',options,err)
|
|
}
|
|
resolve(response)
|
|
})
|
|
})
|
|
})
|
|
}
|
|
async function getVideosBasedOnTagFoundInMatrixOfAssociatedEvent({
|
|
groupKey,
|
|
monitorId,
|
|
startTime,
|
|
endTime,
|
|
searchQuery,
|
|
monitorRestrictions
|
|
}){
|
|
const initialEventQuery = [
|
|
['ke','=',groupKey],
|
|
['objects','LIKE',`%${searchQuery}%`],
|
|
]
|
|
if(monitorId)initialEventQuery.push(['mid','=',monitorId]);
|
|
if(startTime)initialEventQuery.push(['time','>',startTime]);
|
|
if(endTime)initialEventQuery.push(['end','<',endTime]);
|
|
if(monitorRestrictions)initialEventQuery.push(monitorRestrictions);
|
|
const videoSelectResponse = await s.knexQueryPromise({
|
|
action: "select",
|
|
columns: "*",
|
|
table: "Videos",
|
|
orderBy: ['time','desc'],
|
|
where: initialEventQuery
|
|
});
|
|
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,
|
|
getVideosBasedOnTagFoundInMatrixOfAssociatedEvent: getVideosBasedOnTagFoundInMatrixOfAssociatedEvent,
|
|
}
|
|
}
|