Method to merge videos from Videos List
- button will appear as "Merge and Download" next to "Zip and Download"merge-requests/63/head
parent
bbb6ef108a
commit
75269f7d9f
|
|
@ -261,13 +261,17 @@
|
|||
"Set to Watch Only": "Set to Watch Only",
|
||||
"Save as": "Save as",
|
||||
"Add New": "Add New",
|
||||
"Merge and Download": "Merge and Download",
|
||||
"Zip and Download": "Zip and Download",
|
||||
"Merge Selected Videos": "Merge Selected Videos",
|
||||
"Export Selected Videos": "Export Selected Videos",
|
||||
"Delete Selected Videos": "Delete Selected Videos",
|
||||
"DeleteSelectedVideosMsg": "Do you want to delete these videos? You cannot recover them.",
|
||||
"ExportSelectedVideosMsg": "Do you want to export these videos? It may take some time to zip and download.",
|
||||
"MergeSelectedVideosMsg": "Do you want to merge these videos? It may take some time to merge and download. The moment the connection is closed the file will be deleted. Ensure you keep the browser open until it is complete.",
|
||||
"clientStreamFailedattemptingReconnect": "Client side ctream check failed, attempting reconnect.",
|
||||
"Export Video": "Export Video",
|
||||
"Merge Video": "Merge Video",
|
||||
"Delete Filter": "Delete Filter",
|
||||
"confirmDeleteFilter": "Do you want to delete this filter? You cannot recover it.",
|
||||
"Fix Video": "Fix Video",
|
||||
|
|
@ -716,6 +720,7 @@
|
|||
"Preview":"Preview",
|
||||
"Websocket Connected":"Websocket Connected",
|
||||
"Websocket Disconnected":"Websocket Disconnected",
|
||||
"Videos Merge":"Videos Merge",
|
||||
"Token":"Token",
|
||||
"Channel ID":"Channel ID",
|
||||
"New Authentication Token":"New Authentication Token",
|
||||
|
|
|
|||
|
|
@ -223,6 +223,64 @@ module.exports = function(s,config,lang){
|
|||
})
|
||||
return items
|
||||
}
|
||||
s.mergeRecordedVideos = function(videoRows,groupKey,callback){
|
||||
var tempDir = s.dir.streams + groupKey + '/'
|
||||
var pathDir = s.dir.fileBin + groupKey + '/'
|
||||
var streamDirItems = fs.readdirSync(pathDir)
|
||||
var items = []
|
||||
var mergedFile = []
|
||||
videoRows.forEach(function(video){
|
||||
var filepath = s.getVideoDirectory(video) + s.formattedTime(video.time) + '.' + video.ext
|
||||
if(
|
||||
filepath.indexOf('.mp4') > -1
|
||||
// || filename.indexOf('.webm') > -1
|
||||
){
|
||||
mergedFile.push(s.formattedTime(video.time))
|
||||
items.push(filepath)
|
||||
}
|
||||
})
|
||||
mergedFile.sort()
|
||||
mergedFile = mergedFile.join('_') + '.mp4'
|
||||
var mergedFilepath = pathDir + mergedFile
|
||||
var mergedRawFilepath = pathDir + 'raw_' + mergedFile
|
||||
items.sort()
|
||||
fs.stat(mergedFilepath,function(err,stats){
|
||||
if(err){
|
||||
//not exist
|
||||
var tempScriptPath = tempDir + s.gid(5) + '.sh'
|
||||
var cat = 'cat '+items.join(' ')+' > '+mergedRawFilepath
|
||||
fs.writeFileSync(tempScriptPath,cat,'utf8')
|
||||
exec('sh ' + tempScriptPath,function(){
|
||||
s.userLog({
|
||||
ke: groupKey,
|
||||
mid: '$USER'
|
||||
},{type:lang['Videos Merge'],msg:mergedFile})
|
||||
var merger = spawn(config.ffmpegDir,s.splitForFFPMEG(('-re -loglevel warning -i ' + mergedRawFilepath + ' -acodec copy -vcodec copy ' + mergedFilepath)))
|
||||
merger.stderr.on('data',function(data){
|
||||
s.userLog({
|
||||
ke: groupKey,
|
||||
mid: '$USER'
|
||||
},{type:lang['Videos Merge'],msg:data.toString()})
|
||||
})
|
||||
merger.on('close',function(){
|
||||
s.file('delete',mergedRawFilepath)
|
||||
s.file('delete',tempScriptPath)
|
||||
setTimeout(function(){
|
||||
fs.stat(mergedFilepath,function(err,stats){
|
||||
if(!err)s.file('delete',mergedFilepath)
|
||||
})
|
||||
},1000 * 60 * 60 * 24)
|
||||
delete(merger)
|
||||
callback(mergedFilepath,mergedFile)
|
||||
})
|
||||
})
|
||||
}else{
|
||||
//file exist
|
||||
callback(mergedFilepath,mergedFile)
|
||||
}
|
||||
})
|
||||
return items
|
||||
}
|
||||
|
||||
s.cameraDestroy = function(x,e,p){
|
||||
if(s.group[e.ke]&&s.group[e.ke].mon[e.id]&&s.group[e.ke].mon[e.id].spawn !== undefined){
|
||||
|
|
@ -609,6 +667,14 @@ module.exports = function(s,config,lang){
|
|||
// exec('chmod -R 777 '+e.sdir,function(err){
|
||||
//
|
||||
// })
|
||||
var binDir = s.dir.fileBin + e.ke + '/'
|
||||
if (!fs.existsSync(binDir)){
|
||||
fs.mkdirSync(binDir)
|
||||
}
|
||||
binDir = s.dir.fileBin + e.ke + '/' + e.id + '/'
|
||||
if (!fs.existsSync(binDir)){
|
||||
fs.mkdirSync(binDir)
|
||||
}
|
||||
return setStreamDir
|
||||
}
|
||||
s.stripAuthFromHost = function(e){
|
||||
|
|
|
|||
|
|
@ -373,4 +373,40 @@ module.exports = function(s,config,lang){
|
|||
finish()
|
||||
}
|
||||
}
|
||||
s.streamMp4FileOverHttp = function(filePath,req,res){
|
||||
var ext = filePath.split('.')
|
||||
ext = filePath[filePath.length - 1]
|
||||
var total = fs.statSync(filePath).size;
|
||||
if (req.headers['range']) {
|
||||
try{
|
||||
var range = req.headers.range;
|
||||
var parts = range.replace(/bytes=/, "").split("-");
|
||||
var partialstart = parts[0];
|
||||
var partialend = parts[1];
|
||||
var start = parseInt(partialstart, 10);
|
||||
var end = partialend ? parseInt(partialend, 10) : total-1;
|
||||
var chunksize = (end-start)+1;
|
||||
var file = fs.createReadStream(filePath, {start: start, end: end});
|
||||
req.headerWrite={ 'Content-Range': 'bytes ' + start + '-' + end + '/' + total, 'Accept-Ranges': 'bytes', 'Content-Length': chunksize, 'Content-Type': 'video/'+req.ext }
|
||||
req.writeCode=206
|
||||
}catch(err){
|
||||
req.headerWrite={ 'Content-Length': total, 'Content-Type': 'video/'+req.ext};
|
||||
var file = fs.createReadStream(filePath)
|
||||
req.writeCode=200
|
||||
}
|
||||
} else {
|
||||
req.headerWrite={ 'Content-Length': total, 'Content-Type': 'video/'+req.ext};
|
||||
var file = fs.createReadStream(filePath)
|
||||
req.writeCode=200
|
||||
}
|
||||
if(req.query.downloadName){
|
||||
req.headerWrite['content-disposition']='attachment; filename="'+req.query.downloadName+'"';
|
||||
}
|
||||
res.writeHead(req.writeCode,req.headerWrite);
|
||||
file.on('close',function(){
|
||||
res.end()
|
||||
})
|
||||
file.pipe(res)
|
||||
return file
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -926,6 +926,53 @@ module.exports = function(s,config,lang,app,io){
|
|||
s.auth(req.params,req.fn,res,req);
|
||||
});
|
||||
/**
|
||||
* API : Merge Recorded Videos into one file
|
||||
*/
|
||||
app.get(config.webPaths.apiPrefix+':auth/videosMerge/:ke', function (req,res){
|
||||
res.header("Access-Control-Allow-Origin",req.headers.origin);
|
||||
var failed = function(resp){
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
res.end(s.prettyPrint(resp))
|
||||
}
|
||||
if(req.query.videos && req.query.videos !== ''){
|
||||
s.auth(req.params,function(user){
|
||||
var videosSelected = JSON.parse(req.query.videos)
|
||||
var where = []
|
||||
var values = []
|
||||
videosSelected.forEach(function(video){
|
||||
where.push("(ke=? AND mid=? AND `time`=?)")
|
||||
if(!video.ke)video.ke = req.params.ke
|
||||
values.push(video.ke)
|
||||
values.push(video.mid)
|
||||
var time = s.nameToTime(video.filename)
|
||||
if(req.query.isUTC === 'true'){
|
||||
time = s.utcToLocal(time)
|
||||
}
|
||||
time = new Date(time)
|
||||
values.push(time)
|
||||
})
|
||||
s.sqlQuery('SELECT * FROM Videos WHERE '+where.join(' OR '),values,function(err,r){
|
||||
var resp = {ok: false}
|
||||
if(r && r[0]){
|
||||
s.mergeRecordedVideos(r,req.params.ke,function(fullPath,filename){
|
||||
res.setHeader('Content-Disposition', 'attachment; filename="'+filename+'"')
|
||||
var file = fs.createReadStream(fullPath)
|
||||
file.on('close',function(){
|
||||
s.file('delete',fullPath)
|
||||
res.end()
|
||||
})
|
||||
file.pipe(res)
|
||||
})
|
||||
}else{
|
||||
failed({ok:false,msg:'No Videos Found'})
|
||||
}
|
||||
})
|
||||
},res,req);
|
||||
}else{
|
||||
failed({ok:false,msg:'"videos" query variable is missing from request.'})
|
||||
}
|
||||
})
|
||||
/**
|
||||
* API : Get Videos
|
||||
*/
|
||||
app.get([
|
||||
|
|
@ -1628,38 +1675,7 @@ module.exports = function(s,config,lang,app,io){
|
|||
if(r&&r[0]){
|
||||
req.dir=s.getVideoDirectory(r[0])+req.params.file
|
||||
if (fs.existsSync(req.dir)){
|
||||
req.ext=req.params.file.split('.')[1];
|
||||
var total = fs.statSync(req.dir).size;
|
||||
if (req.headers['range']) {
|
||||
try{
|
||||
var range = req.headers.range;
|
||||
var parts = range.replace(/bytes=/, "").split("-");
|
||||
var partialstart = parts[0];
|
||||
var partialend = parts[1];
|
||||
var start = parseInt(partialstart, 10);
|
||||
var end = partialend ? parseInt(partialend, 10) : total-1;
|
||||
var chunksize = (end-start)+1;
|
||||
var file = fs.createReadStream(req.dir, {start: start, end: end});
|
||||
req.headerWrite={ 'Content-Range': 'bytes ' + start + '-' + end + '/' + total, 'Accept-Ranges': 'bytes', 'Content-Length': chunksize, 'Content-Type': 'video/'+req.ext }
|
||||
req.writeCode=206
|
||||
}catch(err){
|
||||
req.headerWrite={ 'Content-Length': total, 'Content-Type': 'video/'+req.ext};
|
||||
var file = fs.createReadStream(req.dir)
|
||||
req.writeCode=200
|
||||
}
|
||||
} else {
|
||||
req.headerWrite={ 'Content-Length': total, 'Content-Type': 'video/'+req.ext};
|
||||
var file=fs.createReadStream(req.dir)
|
||||
req.writeCode=200
|
||||
}
|
||||
if(req.query.downloadName){
|
||||
req.headerWrite['content-disposition']='attachment; filename="'+req.query.downloadName+'"';
|
||||
}
|
||||
res.writeHead(req.writeCode,req.headerWrite);
|
||||
file.on('close',function(){
|
||||
res.end();
|
||||
})
|
||||
file.pipe(res);
|
||||
s.streamMp4FileOverHttp(req.dir,req,res)
|
||||
}else{
|
||||
res.end(user.lang['File Not Found in Filesystem'])
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ $.vidview={
|
|||
$.vidview.set.change(function(){
|
||||
var el = $(this)
|
||||
var isCloud = (el.val() === 'cloud')
|
||||
var zipDlButton = $.vidview.e.find('.export_selected')
|
||||
var zipDlButton = $.vidview.e.find('.export_selected,.merge_selected')
|
||||
if(isCloud){
|
||||
zipDlButton.hide()
|
||||
}else{
|
||||
|
|
@ -119,11 +119,38 @@ $.vidview.e.find('.export_selected').click(function(){
|
|||
if($.ccio.useUTC === true){
|
||||
queryVariables.push('isUTC=true')
|
||||
}
|
||||
console.log(queryVariables)
|
||||
var downloadZip = $.ccio.init('location',$user)+$user.auth_token+'/zipVideos/'+$user.ke+'?'+queryVariables.join('&')
|
||||
$('#temp').html('<iframe>a</iframe>').find('iframe').attr('src',downloadZip);
|
||||
});
|
||||
})
|
||||
$.vidview.e.find('.merge_selected').click(function(){
|
||||
e = {}
|
||||
var videos = $.vidview.getSelected(true)
|
||||
if(videos.length === 0){
|
||||
$.ccio.init('note',{
|
||||
title:'No Videos Selected',
|
||||
text:'You must choose at least one video.',
|
||||
type:'error'
|
||||
},$user);
|
||||
return
|
||||
}
|
||||
$.confirm.e.modal('show');
|
||||
$.confirm.title.text(lang['Merge Selected Videos'])
|
||||
var html = lang.MergeSelectedVideosMsg+'<div style="margin-bottom:15px"></div>'
|
||||
$.each(videos,function(n,v){
|
||||
html+=v.filename+'<br>';
|
||||
})
|
||||
$.confirm.body.html(html)
|
||||
$.confirm.click({title:'Merge Video',class:'btn-danger'},function(){
|
||||
var queryVariables = []
|
||||
queryVariables.push('videos='+JSON.stringify(videos))
|
||||
if($.ccio.useUTC === true){
|
||||
queryVariables.push('isUTC=true')
|
||||
}
|
||||
var downloadZip = $.ccio.init('location',$user)+$user.auth_token+'/videosMerge/'+$user.ke+'?'+queryVariables.join('&')
|
||||
$('#temp').html('<iframe>a</iframe>').find('iframe').attr('src',downloadZip)
|
||||
});
|
||||
})
|
||||
$.vidview.pages.on('click','[page]',function(e){
|
||||
e.limit=$.vidview.limit.val();
|
||||
e.page=$(this).attr('page');
|
||||
|
|
|
|||
|
|
@ -43,6 +43,7 @@
|
|||
<div class="col-md-4 text-left">
|
||||
<a class="btn btn-danger delete_selected"><i class="fa fa-trash-o"></i> <%-lang['Delete']%></a>
|
||||
<a class="btn btn-default export_selected"><i class="fa fa-folder-o"></i> <%-lang['Zip and Download']%></a>
|
||||
<a class="btn btn-default merge_selected"><i class="fa fa-copy"></i> <%-lang['Merge and Download']%></a>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="text-center" id="videos_viewer_pages"></div>
|
||||
|
|
|
|||
Loading…
Reference in New Issue