Add Video Merge Feature in Videos Table
parent
7508bd79fb
commit
0b02762ad8
languages
libs
web
pages/blocks/home
|
@ -468,6 +468,8 @@
|
||||||
"Max Storage Amount": "Max Storage Amount",
|
"Max Storage Amount": "Max Storage Amount",
|
||||||
"Video Share": "Video Share",
|
"Video Share": "Video Share",
|
||||||
"FileBin": "FileBin",
|
"FileBin": "FileBin",
|
||||||
|
"File Saved": "File Saved",
|
||||||
|
"checkFileBinForNewFile": "Check the FileBin for the newly created file.",
|
||||||
"File Download Ready": "File Download Ready",
|
"File Download Ready": "File Download Ready",
|
||||||
"Timelapse Video Build Complete": "Timelapse Video Build Complete",
|
"Timelapse Video Build Complete": "Timelapse Video Build Complete",
|
||||||
"yourFileDownloadedShortly": "Please wait. Your file will be downloaded shortly.",
|
"yourFileDownloadedShortly": "Please wait. Your file will be downloaded shortly.",
|
||||||
|
@ -570,6 +572,10 @@
|
||||||
"clientStreamFailedattemptingReconnect": "Client side stream check failed, attempting reconnect.",
|
"clientStreamFailedattemptingReconnect": "Client side stream check failed, attempting reconnect.",
|
||||||
"Export Video": "Export Video",
|
"Export Video": "Export Video",
|
||||||
"Merge Video": "Merge Video",
|
"Merge Video": "Merge Video",
|
||||||
|
"Merge Videos": "Merge Videos",
|
||||||
|
"Merge": "Merge",
|
||||||
|
"MergeAllSelected": "Merge all selected Videos?",
|
||||||
|
"MergeAllInRange": "Merge all Videos in date range selected?",
|
||||||
"Delete Filter": "Delete Filter",
|
"Delete Filter": "Delete Filter",
|
||||||
"Delete Files": "Delete Files",
|
"Delete Files": "Delete Files",
|
||||||
"confirmDeleteFilter": "Do you want to delete this filter? You cannot recover it.",
|
"confirmDeleteFilter": "Do you want to delete this filter? You cannot recover it.",
|
||||||
|
@ -1093,6 +1099,7 @@
|
||||||
"Can't Connect": "Can't Connect",
|
"Can't Connect": "Can't Connect",
|
||||||
"Video Finished": "Video Finished",
|
"Video Finished": "Video Finished",
|
||||||
"No Monitors Selected": "No Monitors Selected",
|
"No Monitors Selected": "No Monitors Selected",
|
||||||
|
"No Monitor Selected": "No Monitor Selected",
|
||||||
"Nothing Selected": "Nothing Selected",
|
"Nothing Selected": "Nothing Selected",
|
||||||
"makeASelection": "Make a selection and try again.",
|
"makeASelection": "Make a selection and try again.",
|
||||||
"monSavedButNotCopied": "Your monitor was saved but not copied to any other monitor.",
|
"monSavedButNotCopied": "Your monitor was saved but not copied to any other monitor.",
|
||||||
|
|
|
@ -192,6 +192,11 @@ module.exports = function(s,config,lang,app,io){
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
s.notifyFileBinUploaded = function(fileBinInsertQuery){
|
||||||
|
s.tx(Object.assign({
|
||||||
|
f: 'fileBin_item_added',
|
||||||
|
},fileBinInsertQuery),'GRP_'+fileBinInsertQuery.ke);
|
||||||
|
}
|
||||||
s.getFileBinDirectory = getFileBinDirectory
|
s.getFileBinDirectory = getFileBinDirectory
|
||||||
s.getFileBinEntry = getFileBinEntry
|
s.getFileBinEntry = getFileBinEntry
|
||||||
s.getFileBinBuffer = getFileBinBuffer
|
s.getFileBinBuffer = getFileBinBuffer
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
const fs = require('fs')
|
const fs = require('fs')
|
||||||
const { spawn } = require('child_process')
|
const { spawn } = require('child_process')
|
||||||
const async = require('async');
|
const async = require('async');
|
||||||
|
const path = require('path');
|
||||||
|
const fsP = require('fs').promises;
|
||||||
module.exports = (s,config,lang) => {
|
module.exports = (s,config,lang) => {
|
||||||
const {
|
const {
|
||||||
ffprobe,
|
ffprobe,
|
||||||
|
@ -593,6 +595,7 @@ module.exports = (s,config,lang) => {
|
||||||
time: video.time,
|
time: video.time,
|
||||||
}
|
}
|
||||||
await s.insertFileBinEntry(fileBinInsertQuery)
|
await s.insertFileBinEntry(fileBinInsertQuery)
|
||||||
|
s.notifyFileBinUploaded(fileBinInsertQuery)
|
||||||
s.tx(Object.assign({
|
s.tx(Object.assign({
|
||||||
f: 'fileBin_item_added',
|
f: 'fileBin_item_added',
|
||||||
slicedVideo: true,
|
slicedVideo: true,
|
||||||
|
@ -604,6 +607,149 @@ module.exports = (s,config,lang) => {
|
||||||
}
|
}
|
||||||
return response
|
return response
|
||||||
}
|
}
|
||||||
|
const mergingVideos = {};
|
||||||
|
const mergeVideos = async function({
|
||||||
|
groupKey,
|
||||||
|
monitorId,
|
||||||
|
filePaths,
|
||||||
|
outputFilePath,
|
||||||
|
videoCodec = 'libx265',
|
||||||
|
audioCodec = 'aac',
|
||||||
|
onStdout = (data) => {s.systemLog(`${data}`)},
|
||||||
|
onStderr = (data) => {s.systemLog(`${data}`)},
|
||||||
|
}) {
|
||||||
|
if (!Array.isArray(filePaths) || filePaths.length === 0) {
|
||||||
|
throw new Error('First parameter must be a non-empty array of absolute file paths.');
|
||||||
|
}
|
||||||
|
if(mergingVideos[outputFilePath])return;
|
||||||
|
const currentDate = new Date();
|
||||||
|
const fileExtensions = filePaths.map(file => path.extname(file).toLowerCase());
|
||||||
|
const allSameExtension = fileExtensions.every(ext => ext === fileExtensions[0]);
|
||||||
|
const fileList = filePaths.map(file => `file '${file}'`).join('\n');
|
||||||
|
const tempFileListPath = path.join(s.dir.streams, groupKey, monitorId, `video_merge_${currentDate}.txt`);
|
||||||
|
mergingVideos[outputFilePath] = currentDate;
|
||||||
|
try {
|
||||||
|
await fsP.writeFile(tempFileListPath, fileList);
|
||||||
|
let ffmpegArgs;
|
||||||
|
// if (allSameExtension) {
|
||||||
|
// ffmpegArgs = [
|
||||||
|
// '-f', 'concat',
|
||||||
|
// '-safe', '0',
|
||||||
|
// '-i', tempFileListPath,
|
||||||
|
// '-c', 'copy',
|
||||||
|
// '-y',
|
||||||
|
// outputFilePath
|
||||||
|
// ];
|
||||||
|
// } else {
|
||||||
|
ffmpegArgs = [
|
||||||
|
'-loglevel', 'warning',
|
||||||
|
'-f', 'concat',
|
||||||
|
'-safe', '0',
|
||||||
|
'-i', tempFileListPath,
|
||||||
|
'-c:v', videoCodec,
|
||||||
|
'-c:a', audioCodec,
|
||||||
|
'-strict', '-2',
|
||||||
|
'-crf', '1',
|
||||||
|
'-y',
|
||||||
|
outputFilePath
|
||||||
|
];
|
||||||
|
// }
|
||||||
|
s.debugLog(fileList)
|
||||||
|
s.debugLog(ffmpegArgs)
|
||||||
|
|
||||||
|
await new Promise((resolve, reject) => {
|
||||||
|
const ffmpegProcess = spawn(config.ffmpegDir, ffmpegArgs);
|
||||||
|
ffmpegProcess.stdout.on('data', onStdout);
|
||||||
|
ffmpegProcess.stderr.on('data', onStderr);
|
||||||
|
ffmpegProcess.on('close', (code) => {
|
||||||
|
delete(mergingVideos[outputFilePath]);
|
||||||
|
if (code === 0) {
|
||||||
|
console.log(`FFmpeg process exited with code ${code}`);
|
||||||
|
resolve();
|
||||||
|
} else {
|
||||||
|
reject(new Error(`FFmpeg process exited with code ${code}`));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
ffmpegProcess.on('error', (err) => {
|
||||||
|
reject(new Error(`FFmpeg error: ${err}`));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
await fsP.unlink(tempFileListPath);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
async function mergeVideosAndBin(videos){
|
||||||
|
const currentTime = new Date();
|
||||||
|
const firstVideo = videos[0];
|
||||||
|
const lastVideo = videos[videos.length - 1];
|
||||||
|
const groupKey = firstVideo.ke;
|
||||||
|
const monitorId = firstVideo.mid;
|
||||||
|
const logTarget = { ke: groupKey, mid: '$USER' };
|
||||||
|
try{
|
||||||
|
try{
|
||||||
|
await fsP.stat(outputFilePath)
|
||||||
|
return outputFilePath
|
||||||
|
}catch(err){
|
||||||
|
|
||||||
|
}
|
||||||
|
const filePaths = videos.map(video => {
|
||||||
|
const monitorConfig = s.group[video.ke].rawMonitorConfigurations[video.mid];
|
||||||
|
const filePath = path.join(s.getVideoDirectory(video), `${s.formattedTime(video.time)}.mp4`);
|
||||||
|
return filePath
|
||||||
|
});
|
||||||
|
const filename = `${s.formattedTime(firstVideo.time)}-${s.formattedTime(lastVideo.time)}-${filePaths.length}.mp4`
|
||||||
|
const fileBinFolder = s.getFileBinDirectory(firstVideo);
|
||||||
|
const outputFilePath = path.join(fileBinFolder, filename);
|
||||||
|
|
||||||
|
s.userLog(logTarget,{
|
||||||
|
type: 'mergeVideos ffmpeg START',
|
||||||
|
msg: {
|
||||||
|
monitorId,
|
||||||
|
numberOfVideos: filePaths.length,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
await mergeVideos({
|
||||||
|
groupKey,
|
||||||
|
monitorId,
|
||||||
|
filePaths,
|
||||||
|
outputFilePath,
|
||||||
|
onStdout: (data) => {
|
||||||
|
s.debugLog(data.toString())
|
||||||
|
s.userLog(logTarget,{
|
||||||
|
type: 'mergeVideos ffmpeg LOG',
|
||||||
|
msg: `${data}`
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onStderr: (data) => {
|
||||||
|
s.debugLog(data.toString())
|
||||||
|
s.userLog(logTarget,{
|
||||||
|
type: 'mergeVideos ffmpeg ERROR',
|
||||||
|
msg: `${data}`
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const fileSize = (await fsP.stat(outputFilePath)).size;
|
||||||
|
const fileBinInsertQuery = {
|
||||||
|
ke: groupKey,
|
||||||
|
mid: monitorId,
|
||||||
|
name: filename,
|
||||||
|
size: fileSize,
|
||||||
|
details: {},
|
||||||
|
status: 1,
|
||||||
|
time: currentTime,
|
||||||
|
}
|
||||||
|
await s.insertFileBinEntry(fileBinInsertQuery);
|
||||||
|
s.notifyFileBinUploaded(fileBinInsertQuery);
|
||||||
|
return outputFilePath
|
||||||
|
}catch(err){
|
||||||
|
console.log('mergeVideos process ERROR', err)
|
||||||
|
s.userLog(logTarget,{
|
||||||
|
type: 'mergeVideos process ERROR',
|
||||||
|
msg: `${err.toString()}`
|
||||||
|
});
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
reEncodeVideoAndReplace,
|
reEncodeVideoAndReplace,
|
||||||
stitchMp4Files,
|
stitchMp4Files,
|
||||||
|
@ -615,5 +761,7 @@ module.exports = (s,config,lang) => {
|
||||||
reEncodeVideoAndBinOriginalAddToQueue,
|
reEncodeVideoAndBinOriginalAddToQueue,
|
||||||
archiveVideo,
|
archiveVideo,
|
||||||
sliceVideo,
|
sliceVideo,
|
||||||
|
mergeVideos,
|
||||||
|
mergeVideosAndBin,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,6 +39,7 @@ module.exports = function(s,config,lang,app,io){
|
||||||
reEncodeVideoAndReplace,
|
reEncodeVideoAndReplace,
|
||||||
reEncodeVideoAndBinOriginalAddToQueue,
|
reEncodeVideoAndBinOriginalAddToQueue,
|
||||||
getVideosBasedOnTagFoundInMatrixOfAssociatedEvent,
|
getVideosBasedOnTagFoundInMatrixOfAssociatedEvent,
|
||||||
|
mergeVideosAndBin,
|
||||||
} = require('./video/utils.js')(s,config,lang)
|
} = require('./video/utils.js')(s,config,lang)
|
||||||
s.renderPage = function(req,res,paths,passables,callback){
|
s.renderPage = function(req,res,paths,passables,callback){
|
||||||
passables.window = {}
|
passables.window = {}
|
||||||
|
@ -1970,7 +1971,69 @@ module.exports = function(s,config,lang,app,io){
|
||||||
res.end(s.prettyPrint(response));
|
res.end(s.prettyPrint(response));
|
||||||
})
|
})
|
||||||
},res,req);
|
},res,req);
|
||||||
})
|
});
|
||||||
|
/**
|
||||||
|
* API : Merge Videos and Bin it
|
||||||
|
*/
|
||||||
|
app.post(config.webPaths.apiPrefix+':auth/mergeVideos/:ke/:id', function (req,res){
|
||||||
|
s.auth(req.params, async function(user){
|
||||||
|
const monitorId = req.params.id
|
||||||
|
const groupKey = req.params.ke
|
||||||
|
const {
|
||||||
|
monitorPermissions,
|
||||||
|
monitorRestrictions,
|
||||||
|
} = s.getMonitorsPermitted(user.details,monitorId,'video_view')
|
||||||
|
const {
|
||||||
|
isRestricted,
|
||||||
|
isRestrictedApiKey,
|
||||||
|
apiKeyPermissions,
|
||||||
|
} = s.checkPermission(user);
|
||||||
|
if(
|
||||||
|
isRestrictedApiKey && apiKeyPermissions.watch_videos_disallowed ||
|
||||||
|
isRestricted && (
|
||||||
|
monitorId && !monitorPermissions[`${monitorId}_video_view`] ||
|
||||||
|
monitorRestrictions.length === 0
|
||||||
|
)
|
||||||
|
){
|
||||||
|
s.closeJsonResponse(res,{ok: false, msg: lang['Not Authorized'], videos: []});
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const response = { ok: false }
|
||||||
|
const selectedVideos = s.getPostData(req,'videos');
|
||||||
|
console.log('selected',selectedVideos)
|
||||||
|
if(selectedVideos && selectedVideos.length > 1){
|
||||||
|
const mergedFilePath = await mergeVideosAndBin(selectedVideos);
|
||||||
|
response.ok = !!mergedFilePath;
|
||||||
|
s.closeJsonResponse(res, response);
|
||||||
|
}else{
|
||||||
|
s.sqlQueryBetweenTimesWithPermissions({
|
||||||
|
table: 'Videos',
|
||||||
|
user: user,
|
||||||
|
noCount: true,
|
||||||
|
groupKey,
|
||||||
|
monitorId,
|
||||||
|
startTime: s.getPostData(req,'start'),
|
||||||
|
endTime: s.getPostData(req,'end'),
|
||||||
|
startTimeOperator: s.getPostData(req,'startOperator'),
|
||||||
|
endTimeOperator: s.getPostData(req,'endOperator'),
|
||||||
|
noLimit: s.getPostData(req,'noLimit'),
|
||||||
|
limit: s.getPostData(req,'limit'),
|
||||||
|
archived: s.getPostData(req,'archived'),
|
||||||
|
endIsStartTo: !!s.getPostData(req,'endIsStartTo'),
|
||||||
|
parseRowDetails: false,
|
||||||
|
rowName: 'videos',
|
||||||
|
monitorRestrictions: monitorRestrictions,
|
||||||
|
preliminaryValidationFailed: false
|
||||||
|
}, async ({ videos }) => {
|
||||||
|
if(videos){
|
||||||
|
const mergedFilePath = await mergeVideosAndBin(videos);
|
||||||
|
response.ok = !!mergedFilePath;
|
||||||
|
}
|
||||||
|
s.closeJsonResponse(res, response);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},res,req);
|
||||||
|
});
|
||||||
/**
|
/**
|
||||||
* API : Get Login Tokens
|
* API : Get Login Tokens
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -1168,6 +1168,9 @@ function getRunningMonitors(asArray){
|
||||||
})
|
})
|
||||||
return asArray ? Object.values(foundMonitors) : foundMonitors
|
return asArray ? Object.values(foundMonitors) : foundMonitors
|
||||||
}
|
}
|
||||||
|
function buildFileBinUrl(data){
|
||||||
|
return apiBaseUrl + '/fileBin/' + data.ke + '/' + data.mid + '/' + data.name
|
||||||
|
}
|
||||||
$(document).ready(function(){
|
$(document).ready(function(){
|
||||||
$('body')
|
$('body')
|
||||||
.on('click','[system]',function(){
|
.on('click','[system]',function(){
|
||||||
|
|
|
@ -311,9 +311,6 @@ $(document).ready(function(e){
|
||||||
function downloadTimelapseFrame(frame){
|
function downloadTimelapseFrame(frame){
|
||||||
downloadFile(frame.href,frame.filename)
|
downloadFile(frame.href,frame.filename)
|
||||||
}
|
}
|
||||||
function buildFileBinUrl(data){
|
|
||||||
return apiBaseUrl + '/fileBin/' + data.ke + '/' + data.mid + '/' + data.name
|
|
||||||
}
|
|
||||||
function downloadTimelapseVideo(data){
|
function downloadTimelapseVideo(data){
|
||||||
var downloadUrl = buildFileBinUrl(data)
|
var downloadUrl = buildFileBinUrl(data)
|
||||||
downloadFile(downloadUrl,data.name)
|
downloadFile(downloadUrl,data.name)
|
||||||
|
|
|
@ -442,35 +442,93 @@ function loadEventsData(videoEvents){
|
||||||
loadedEventsInMemory[`${anEvent.mid}${anEvent.time}`] = anEvent
|
loadedEventsInMemory[`${anEvent.mid}${anEvent.time}`] = anEvent
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
function getVideoSearchRequestQueries(options){
|
||||||
|
var searchQuery = options.searchQuery
|
||||||
|
var requestQueries = []
|
||||||
|
var monitorId = options.monitorId
|
||||||
|
var archived = options.archived
|
||||||
|
var customVideoSet = options.customVideoSet
|
||||||
|
var limit = options.limit
|
||||||
|
var eventLimit = options.eventLimit || 300
|
||||||
|
var doLimitOnFames = options.doLimitOnFames || false
|
||||||
|
var eventStartTime
|
||||||
|
var eventEndTime
|
||||||
|
if(options.startDate){
|
||||||
|
eventStartTime = formattedTimeForFilename(options.startDate,false)
|
||||||
|
requestQueries.push(`start=${eventStartTime}`)
|
||||||
|
}
|
||||||
|
if(options.endDate){
|
||||||
|
eventEndTime = formattedTimeForFilename(options.endDate,false)
|
||||||
|
requestQueries.push(`end=${eventEndTime}`)
|
||||||
|
}
|
||||||
|
if(searchQuery){
|
||||||
|
requestQueries.push(`search=${searchQuery}`)
|
||||||
|
}
|
||||||
|
if(archived){
|
||||||
|
requestQueries.push(`archived=1`)
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
searchQuery,
|
||||||
|
monitorId,
|
||||||
|
archived,
|
||||||
|
customVideoSet,
|
||||||
|
limit,
|
||||||
|
eventLimit,
|
||||||
|
doLimitOnFames,
|
||||||
|
eventStartTime,
|
||||||
|
eventEndTime,
|
||||||
|
requestQueries,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function mergeVideosAndBin(options,callback){
|
||||||
|
const {
|
||||||
|
searchQuery,
|
||||||
|
monitorId,
|
||||||
|
archived,
|
||||||
|
customVideoSet,
|
||||||
|
limit,
|
||||||
|
eventLimit,
|
||||||
|
doLimitOnFames,
|
||||||
|
eventStartTime,
|
||||||
|
eventEndTime,
|
||||||
|
requestQueries,
|
||||||
|
} = getVideoSearchRequestQueries(options);
|
||||||
|
const videos = options.videos.map(video => {
|
||||||
|
const newVideo = {
|
||||||
|
ke: video.ke,
|
||||||
|
mid: video.mid,
|
||||||
|
time: video.time,
|
||||||
|
end: video.end,
|
||||||
|
saveDir: video.saveDir,
|
||||||
|
details: video.details,
|
||||||
|
};
|
||||||
|
delete(newVideo.timelapseFrames)
|
||||||
|
return newVideo
|
||||||
|
});
|
||||||
|
console.log(videos)
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
$.post(`${getApiPrefix(`mergeVideos`)}${monitorId ? `/${monitorId}` : ''}?${requestQueries.concat([limit ? `limit=${limit}` : `noLimit=1`]).join('&')}`, {
|
||||||
|
videos,
|
||||||
|
},function(data){
|
||||||
|
resolve(data)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
function getVideos(options,callback,noEvents){
|
function getVideos(options,callback,noEvents){
|
||||||
return new Promise((resolve,reject) => {
|
return new Promise((resolve,reject) => {
|
||||||
options = options ? options : {}
|
options = options ? options : {}
|
||||||
var searchQuery = options.searchQuery
|
const {
|
||||||
var requestQueries = []
|
searchQuery,
|
||||||
var monitorId = options.monitorId
|
monitorId,
|
||||||
var archived = options.archived
|
archived,
|
||||||
var customVideoSet = options.customVideoSet
|
customVideoSet,
|
||||||
var limit = options.limit
|
limit,
|
||||||
var eventLimit = options.eventLimit || 300
|
eventLimit,
|
||||||
var doLimitOnFames = options.doLimitOnFames || false
|
doLimitOnFames,
|
||||||
var eventStartTime
|
eventStartTime,
|
||||||
var eventEndTime
|
eventEndTime,
|
||||||
// var startDate = options.startDate
|
requestQueries,
|
||||||
// var endDate = options.endDate
|
} = getVideoSearchRequestQueries(options);
|
||||||
if(options.startDate){
|
|
||||||
eventStartTime = formattedTimeForFilename(options.startDate,false)
|
|
||||||
requestQueries.push(`start=${eventStartTime}`)
|
|
||||||
}
|
|
||||||
if(options.endDate){
|
|
||||||
eventEndTime = formattedTimeForFilename(options.endDate,false)
|
|
||||||
requestQueries.push(`end=${eventEndTime}`)
|
|
||||||
}
|
|
||||||
if(searchQuery){
|
|
||||||
requestQueries.push(`search=${searchQuery}`)
|
|
||||||
}
|
|
||||||
if(archived){
|
|
||||||
requestQueries.push(`archived=1`)
|
|
||||||
}
|
|
||||||
$.getJSON(`${getApiPrefix(customVideoSet ? customVideoSet : searchQuery ? `videosByEventTag` : `videos`)}${monitorId ? `/${monitorId}` : ''}?${requestQueries.concat([limit ? `limit=${limit}` : `noLimit=1`]).join('&')}`,function(data){
|
$.getJSON(`${getApiPrefix(customVideoSet ? customVideoSet : searchQuery ? `videosByEventTag` : `videos`)}${monitorId ? `/${monitorId}` : ''}?${requestQueries.concat([limit ? `limit=${limit}` : `noLimit=1`]).join('&')}`,function(data){
|
||||||
var videos = data.videos.map((video) => {
|
var videos = data.videos.map((video) => {
|
||||||
return Object.assign({},video,{
|
return Object.assign({},video,{
|
||||||
|
|
|
@ -274,6 +274,43 @@ $(document).ready(function(e){
|
||||||
var downloadUrl = buildNewFileLink(data)
|
var downloadUrl = buildNewFileLink(data)
|
||||||
downloadFile(downloadUrl,data.name)
|
downloadFile(downloadUrl,data.name)
|
||||||
}
|
}
|
||||||
|
function mergeSelectedVideos(){
|
||||||
|
var videos = getSelectedRows(true)
|
||||||
|
var dateRange = getSelectedTime(dateSelector);
|
||||||
|
var searchQuery = objectTagSearchField.val() || null;
|
||||||
|
var startDate = dateRange.startDate;
|
||||||
|
var endDate = dateRange.endDate;
|
||||||
|
var monitorId = monitorsList.val();
|
||||||
|
var wantsArchivedVideo = getVideoSetSelected() === 'archive';
|
||||||
|
if(!monitorId){
|
||||||
|
new PNotify({
|
||||||
|
title: lang['No Monitor Selected'],
|
||||||
|
text: lang['No Monitor Found, Ignoring Request'],
|
||||||
|
type: 'danger',
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
$.confirm.create({
|
||||||
|
title: lang["Merge Videos"],
|
||||||
|
body: `${videos.length > 0 ? lang.MergeAllSelected : lang.MergeAllInRange}`,
|
||||||
|
clickOptions: {
|
||||||
|
title: '<i class="fa fa-check"></i> ' + lang.Save,
|
||||||
|
class: 'btn-success btn-sm'
|
||||||
|
},
|
||||||
|
clickCallback: async function(){
|
||||||
|
console.log('Merging Video...')
|
||||||
|
var result = await mergeVideosAndBin({
|
||||||
|
monitorId,
|
||||||
|
startDate,
|
||||||
|
endDate,
|
||||||
|
videos,
|
||||||
|
searchQuery,
|
||||||
|
archived: wantsArchivedVideo,
|
||||||
|
});
|
||||||
|
console.log('Merged Video! Check Filebin.')
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
$('body')
|
$('body')
|
||||||
.on('click','.open-videosTable',function(e){
|
.on('click','.open-videosTable',function(e){
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
|
@ -307,6 +344,11 @@ $(document).ready(function(e){
|
||||||
zipVideosAndDownloadWithConfirm(videos)
|
zipVideosAndDownloadWithConfirm(videos)
|
||||||
return false;
|
return false;
|
||||||
})
|
})
|
||||||
|
.on('click','.merge-selected-videos',function(e){
|
||||||
|
e.preventDefault()
|
||||||
|
mergeSelectedVideos();
|
||||||
|
return false;
|
||||||
|
})
|
||||||
.on('click','.refresh-data',function(e){
|
.on('click','.refresh-data',function(e){
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
drawVideosTableViewElements()
|
drawVideosTableViewElements()
|
||||||
|
@ -410,6 +452,14 @@ $(document).ready(function(e){
|
||||||
})
|
})
|
||||||
onWebSocketEvent((data) => {
|
onWebSocketEvent((data) => {
|
||||||
switch(data.f){
|
switch(data.f){
|
||||||
|
case'fileBin_item_added':
|
||||||
|
new PNotify({
|
||||||
|
title: lang['File Saved'],
|
||||||
|
text: `${lang.checkFileBinForNewFile}<br><br><a class="btn btn-sm btn-success" href="${buildFileBinUrl(data)}" download>${lang.Download}</a>`,
|
||||||
|
type: 'success',
|
||||||
|
sticky: true,
|
||||||
|
})
|
||||||
|
break;
|
||||||
case'video_delete':
|
case'video_delete':
|
||||||
case'video_delete_cloud':
|
case'video_delete_cloud':
|
||||||
if(tabTree.name === 'videosTableView'){
|
if(tabTree.name === 'videosTableView'){
|
||||||
|
|
|
@ -21,6 +21,7 @@
|
||||||
<li><a class="dropdown-item unarchive-selected-videos cursor-pointer"><%- lang.Unarchive %></a></li>
|
<li><a class="dropdown-item unarchive-selected-videos cursor-pointer"><%- lang.Unarchive %></a></li>
|
||||||
<li><a class="dropdown-item compress-selected-videos cursor-pointer"><%- lang.Compress %></a></li>
|
<li><a class="dropdown-item compress-selected-videos cursor-pointer"><%- lang.Compress %></a></li>
|
||||||
<li><a class="dropdown-item zip-selected-videos cursor-pointer"><%- lang['Zip and Download'] %></a></li>
|
<li><a class="dropdown-item zip-selected-videos cursor-pointer"><%- lang['Zip and Download'] %></a></li>
|
||||||
|
<li><a class="dropdown-item merge-selected-videos cursor-pointer"><%- lang['Merge'] %></a></li>
|
||||||
<!-- <li><a class="dropdown-item merge-selected-videos cursor-pointer"><%- lang.Merge %></a></li> -->
|
<!-- <li><a class="dropdown-item merge-selected-videos cursor-pointer"><%- lang.Merge %></a></li> -->
|
||||||
<li><hr class="dropdown-divider"></li>
|
<li><hr class="dropdown-divider"></li>
|
||||||
<li><a class="dropdown-item delete-selected-videos cursor-pointer"><%- lang.Delete %></a></li>
|
<li><a class="dropdown-item delete-selected-videos cursor-pointer"><%- lang.Delete %></a></li>
|
||||||
|
|
Loading…
Reference in New Issue