Return of the coProcessor

- now is optional and can be enabled in the Input section.
+ add humanized log for "error while decoding", mention network performance issues
merge-requests/49/head
Moe 2019-01-22 20:09:20 -08:00
parent 4857ce846e
commit 79e3632b88
6 changed files with 295 additions and 10 deletions

View File

@ -27,6 +27,8 @@ loadLib('codeTester')(s,config,lang,io)
loadLib('basic')(s,config)
//video processing engine
loadLib('ffmpeg')(s,config,function(){
//ffmpeg coProcessor
loadLib('ffmpegCoProcessor')(s,config,lang)
//database connection : mysql, sqlite3..
loadLib('sql')(s,config)
//working directories : videos, streams, fileBin..

View File

@ -422,6 +422,7 @@
"deleteMonitorStateText2": "Do you want to delete this Monitor's Preset?",
"Preset": "Preset",
"Presets": "Presets",
"Use coProcessor": "Use coProcessor",
"Audio Codec": "Audio Codec",
"Video Record Rate": "Video Record Rate <small>(FPS)</small>",
"Record Width": "Record Width",
@ -448,6 +449,8 @@
"Save Events to SQL": "Save Events to SQL",
"Email on Trigger": "Email on Trigger <small>Emails go to the main account holder's login address.</small>",
"Attach Video Clip": "Attach Video Clip",
"Error While Decoding": "Error While Decoding",
"ErrorWhileDecodingText": "Your hardware may have an unstable connection to the network. Check your network connections.",
"Discord": "Discord",
"Discord Alert on Trigger": "Discord Alert on Trigger",
"Allow Next Email": "Allow Next Email <small>in Minutes</small>",

View File

@ -382,7 +382,16 @@ module.exports = function(s,config,onFinish){
//x = temporary values
//check if CUDA is enabled
e.isStreamer = (e.type === 'dashcam'|| e.type === 'socket')
if(e.details.accelerator === '1' && e.details.hwaccel === 'cuvid' && e.details.hwaccel_vcodec === ('h264_cuvid' || 'hevc_cuvid' || 'mjpeg_cuvid' || 'mpeg4_cuvid')){
e.coProcessor = false
if(
e.details.use_coprocessor === '1' &&
e.details.accelerator === '1' &&
e.isStreamer === false &&
(!e.details.input_maps || e.details.input_maps.length === 0) &&
(e.details.snap === '1' || e.details.stream_type === 'mjpeg' || e.details.stream_type === 'b64' || e.details.detector === '1')
){
e.coProcessor = true
}else if(e.details.accelerator === '1' && e.details.hwaccel === 'cuvid' && e.details.hwaccel_vcodec === ('h264_cuvid' || 'hevc_cuvid' || 'mjpeg_cuvid' || 'mpeg4_cuvid')){
e.cudaEnabled = true
}
//
@ -563,9 +572,11 @@ module.exports = function(s,config,onFinish){
x.pipe+=x.preset_stream+x.stream_acodec+x.stream_vcodec+' -f hls'+x.cust_stream+' -hls_time '+x.hls_time+' -hls_list_size '+x.hls_list_size+' -start_number 0 -hls_allow_cache 0 -hls_flags +delete_segments+omit_endlist "'+e.sdir+'s.m3u8"';
break;
case'mjpeg':
if(e.coProcessor === false){
if(e.details.stream_quality && e.details.stream_quality !== '')x.cust_stream+=' -q:v '+e.details.stream_quality;
if(x.dimensions && x.cust_stream.indexOf('-s ')===-1){x.cust_stream+=' -s '+x.dimensions}
x.pipe+=' -an -c:v mjpeg -f mpjpeg -boundary_tag shinobi'+x.cust_stream+x.stream_video_filters+' pipe:1';
}
break;
case'h265':
x.cust_stream+=' -movflags +frag_keyframe+empty_moov+default_base_moof -metadata title="Shinobi H.265 Stream" -reset_timestamps 1'
@ -578,9 +589,11 @@ module.exports = function(s,config,onFinish){
x.pipe+=' -f hevc'+x.stream_acodec+x.stream_vcodec+x.cust_stream+' pipe:1';
break;
case'b64':case'':case undefined:case null://base64
if(e.details.stream_quality && e.details.stream_quality !== '')x.cust_stream+=' -q:v '+e.details.stream_quality;
if(x.dimensions && x.cust_stream.indexOf('-s ')===-1){x.cust_stream+=' -s '+x.dimensions}
x.pipe+=' -an -c:v mjpeg -f image2pipe'+x.cust_stream+x.stream_video_filters+' pipe:1';
if(e.coProcessor === false){
if(e.details.stream_quality && e.details.stream_quality !== '')x.cust_stream+=' -q:v '+e.details.stream_quality;
if(x.dimensions && x.cust_stream.indexOf('-s ')===-1){x.cust_stream+=' -s '+x.dimensions}
x.pipe+=' -an -c:v mjpeg -f image2pipe'+x.cust_stream+x.stream_video_filters+' pipe:1';
}
break;
default:
x.pipe=''
@ -588,11 +601,12 @@ module.exports = function(s,config,onFinish){
}
if(e.details.stream_channels){
e.details.stream_channels.forEach(function(v,n){
// if(v.stream_type === 'mjpeg')e.coProcessor = true;
x.pipe += s.createStreamChannel(e,n+config.pipeAddition,v)
})
}
//api - snapshot bin/ cgi.bin (JPEG Mode)
if(e.details.snap === '1'){
if(e.details.snap === '1' && e.coProcessor === false){
if(e.details.input_map_choices&&e.details.input_map_choices.snap){
//add input feed map
x.pipe += s.createFFmpegMap(e,e.details.input_map_choices.snap)
@ -751,7 +765,7 @@ module.exports = function(s,config,onFinish){
//x = temporary values
x.cust_detect = ' '
//detector - plugins, motion
if(e.details.detector === '1' && e.details.detector_send_frames === '1'){
if(e.details.detector === '1' && e.details.detector_send_frames === '1' && e.coProcessor === false){
if(e.details.input_map_choices&&e.details.input_map_choices.detector){
//add input feed map
x.pipe += s.createFFmpegMap(e,e.details.input_map_choices.detector)
@ -848,6 +862,12 @@ module.exports = function(s,config,onFinish){
x.pipe+=x.detector_buffer_fps+x.detector_buffer_acodec+' -c:v '+e.details.detector_buffer_vcodec+' -f hls -tune '+e.details.detector_buffer_tune+' -g '+e.details.detector_buffer_g+' -hls_time '+e.details.detector_buffer_hls_time+' -hls_list_size '+e.details.detector_buffer_hls_list_size+' -start_number '+e.details.detector_buffer_start_number+' -live_start_index '+e.details.detector_buffer_live_start_index+' -hls_allow_cache 0 -hls_flags +delete_segments+omit_endlist "'+e.sdir+'detectorStream.m3u8"'
}
}
ffmpeg.buildCoProcessorFeed = function(e,x){
if(e.coProcessor === true){
// the coProcessor ffmpeg consumes this HLS stream (no audio, frames only)
x.pipe += ' -q:v 1 -an -c:v copy -f hls -tune zerolatency -g 1 -hls_time 2 -hls_list_size 3 -start_number 0 -live_start_index 3 -hls_allow_cache 0 -hls_flags +delete_segments+omit_endlist "'+e.sdir+'coProcessor.m3u8"'
}
}
ffmpeg.assembleMainPieces = function(e,x){
//create executeable FFMPEG command
x.ffmpegCommandString = x.loglevel+x.input_fps;
@ -903,6 +923,7 @@ module.exports = function(s,config,onFinish){
ffmpeg.buildMainRecording(e,x)
ffmpeg.buildAudioDetector(e,x)
ffmpeg.buildMainDetector(e,x)
ffmpeg.buildCoProcessorFeed(e,x)
s.onFfmpegCameraStringCreationExtensions.forEach(function(extender){
extender(e,x)
})

234
libs/ffmpegCoProcessor.js Normal file
View File

@ -0,0 +1,234 @@
var spawn = require('child_process').spawn;
module.exports = function(s,config,lang,ffmpeg){
ffmpeg.buildCoProcessorInput = function(e,x){
if(e.details.userLoglevel&&e.details.userLoglevel!==''){x.loglevel='-loglevel '+e.details.userLoglevel;}else{x.loglevel='-loglevel error'}
x.input = x.loglevel+' -re -i '+e.sdir+'coProcessor.m3u8'
}
ffmpeg.buildCoProcessorStream = function(e,x){
x.stream_video_filters = []
//stream - timestamp
if(e.details.stream_timestamp&&e.details.stream_timestamp=="1"&&e.details.vcodec!=='copy'){
//font
if(e.details.stream_timestamp_font&&e.details.stream_timestamp_font!==''){x.stream_timestamp_font=e.details.stream_timestamp_font}else{x.stream_timestamp_font='/usr/share/fonts/truetype/freefont/FreeSans.ttf'}
//position x
if(e.details.stream_timestamp_x&&e.details.stream_timestamp_x!==''){x.stream_timestamp_x=e.details.stream_timestamp_x}else{x.stream_timestamp_x='(w-tw)/2'}
//position y
if(e.details.stream_timestamp_y&&e.details.stream_timestamp_y!==''){x.stream_timestamp_y=e.details.stream_timestamp_y}else{x.stream_timestamp_y='0'}
//text color
if(e.details.stream_timestamp_color&&e.details.stream_timestamp_color!==''){x.stream_timestamp_color=e.details.stream_timestamp_color}else{x.stream_timestamp_color='white'}
//box color
if(e.details.stream_timestamp_box_color&&e.details.stream_timestamp_box_color!==''){x.stream_timestamp_box_color=e.details.stream_timestamp_box_color}else{x.stream_timestamp_box_color='0x00000000@1'}
//text size
if(e.details.stream_timestamp_font_size&&e.details.stream_timestamp_font_size!==''){x.stream_timestamp_font_size=e.details.stream_timestamp_font_size}else{x.stream_timestamp_font_size='10'}
x.stream_video_filters.push('drawtext=fontfile='+x.stream_timestamp_font+':text=\'%{localtime}\':x='+x.stream_timestamp_x+':y='+x.stream_timestamp_y+':fontcolor='+x.stream_timestamp_color+':box=1:boxcolor='+x.stream_timestamp_box_color+':fontsize='+x.stream_timestamp_font_size);
}
//stream - watermark for -vf
if(e.details.stream_watermark&&e.details.stream_watermark=="1"&&e.details.stream_watermark_location&&e.details.stream_watermark_location!==''){
switch(e.details.stream_watermark_position){
case'tl'://top left
x.stream_watermark_position='10:10'
break;
case'tr'://top right
x.stream_watermark_position='main_w-overlay_w-10:10'
break;
case'bl'://bottom left
x.stream_watermark_position='10:main_h-overlay_h-10'
break;
default://bottom right
x.stream_watermark_position='(main_w-overlay_w-10)/2:(main_h-overlay_h-10)/2'
break;
}
x.stream_video_filters.push('movie='+e.details.stream_watermark_location+'[watermark],[in][watermark]overlay='+x.stream_watermark_position+'[out]');
}
//stream - rotation
if(e.details.rotate_stream&&e.details.rotate_stream!==""&&e.details.rotate_stream!=="no"&&e.details.stream_vcodec!=='copy'){
x.stream_video_filters.push('transpose='+e.details.rotate_stream);
}
if(e.details.svf&&e.details.svf!==''){
x.stream_video_filters.push(e.details.svf)
}
if(x.stream_video_filters.length>0){
x.stream_video_filters=' -vf '+x.stream_video_filters.join(',')
}else{
x.stream_video_filters=''
}
if(e.details.cust_stream&&e.details.cust_stream!==''){x.cust_stream=' '+e.details.cust_stream}else{x.cust_stream=''}
if(e.details.stream_fps&&e.details.stream_fps!==''){x.stream_fps=' -r '+e.details.stream_fps}else{x.stream_fps=''}
if(e.details.stream_vcodec !== 'copy' || e.details.stream_type === 'mjpeg' || e.details.stream_type === 'b64'){
x.cust_stream += x.stream_fps
}
switch(e.details.stream_type){
case'mjpeg':
if(e.details.stream_quality && e.details.stream_quality !== '')x.cust_stream+=' -q:v '+e.details.stream_quality;
if(x.dimensions && x.cust_stream.indexOf('-s ')===-1){x.cust_stream+=' -s '+x.dimensions}
x.pipe += ' -an -c:v mjpeg -f mpjpeg -boundary_tag shinobi'+x.cust_stream+x.stream_video_filters+' pipe:1';
break;
case'b64':case'':case undefined:case null://base64
if(e.details.stream_quality && e.details.stream_quality !== '')x.cust_stream+=' -q:v '+e.details.stream_quality;
if(x.dimensions && x.cust_stream.indexOf('-s ')===-1){x.cust_stream+=' -s '+x.dimensions}
x.pipe += ' -an -c:v mjpeg -f image2pipe'+x.cust_stream+x.stream_video_filters+' pipe:1';
break;
}
}
ffmpeg.buildCoProcessorDetector = function(e,x){
//detector frames
x.cust_detect=' '
if(e.details.detector === '1'){
if(e.details.detector_fps && e.details.detector_fps !== ''){
x.detector_fps = e.details.detector_fps
}else{
x.detector_fps = '2'
}
if(e.details.detector_scale_x && e.details.detector_scale_x !== '' && e.details.detector_scale_y && e.details.detector_scale_y !== ''){
x.dratio=' -s '+e.details.detector_scale_x+'x'+e.details.detector_scale_y
}else{
x.dratio=' -s 320x240'
}
if(e.details.cust_detect&&e.details.cust_detect!==''){x.cust_detect+=e.details.cust_detect;}
if(e.details.detector_pam==='1'){
x.pipe += ' -an -c:v pam -pix_fmt gray -f image2pipe -r '+x.detector_fps+x.cust_detect+x.dratio+' pipe:3'
if(e.details.detector_use_detect_object === '1'){
if(e.details.detector_use_motion === '1'){
if(e.details.detector_scale_x_object && e.details.detector_scale_x_object !== '' && e.details.detector_scale_y_object && e.details.detector_scale_y_object !== ''){
x.dratio=' -s '+e.details.detector_scale_x_object+'x'+e.details.detector_scale_y_object
}
if(e.details.detector_fps_object && e.details.detector_fps_object !== ''){
x.detector_fps = e.details.detector_fps_object
}
}
//for object detection
x.pipe += s.createFFmpegMap(e,e.details.input_map_choices.detector)
x.pipe += ' -f singlejpeg -vf fps='+x.detector_fps+x.cust_detect+x.dratio+' pipe:4';
}
}else{
x.pipe+=' -f singlejpeg -vf fps='+x.detector_fps+x.cust_detect+x.dratio+' pipe:3';
}
}
}
ffmpeg.buildCoProcessorJpegApi = function(e,x){
//snapshot frames
if(e.details.snap === '1'){
if(!e.details.snap_fps || e.details.snap_fps === ''){e.details.snap_fps = 1}
if(e.details.snap_vf && e.details.snap_vf !== ''){x.snap_vf=' -vf '+e.details.snap_vf}else{x.snap_vf=''}
if(e.details.snap_scale_x && e.details.snap_scale_x !== '' && e.details.snap_scale_y && e.details.snap_scale_y !== ''){x.snap_ratio = ' -s '+e.details.snap_scale_x+'x'+e.details.snap_scale_y}else{x.snap_ratio=''}
if(e.details.cust_snap && e.details.cust_snap !== ''){x.cust_snap = ' '+e.details.cust_snap}else{x.cust_snap=''}
x.pipe += ' -update 1 -r '+e.details.snap_fps+x.cust_snap+x.snap_ratio+x.snap_vf+' "'+e.sdir+'s.jpg" -y';
}
}
ffmpeg.buildCoProcessorPipeArray = function(e,x){
x.stdioPipes = [];
var times = config.pipeAddition;
if(e.details.stream_channels){
times+=e.details.stream_channels.length
}
for(var i=0; i < times; i++){
x.stdioPipes.push('pipe')
}
}
s.ffmpegCoProcessor = function(e){
if(e.coProcessor === false)return;
var x = {}
x.pipe = ''
ffmpeg.buildCoProcessorInput(e,x)
ffmpeg.buildCoProcessorStream(e,x)
ffmpeg.buildCoProcessorDetector(e,x)
ffmpeg.buildCoProcessorJpegApi(e,x)
ffmpeg.buildCoProcessorPipeArray(e,x)
var commandString = x.input + x.pipe
if(commandString === x.input){
return false
}
s.group[e.ke].mon[e.mid].coProcessorCmd = commandString
return spawn(config.ffmpegDir,s.splitForFFPMEG((commandString).replace(/\s+/g,' ').trim()),{detached: true,stdio:x.stdioPipes})
}
s.coSpawnLauncher = function(e){
if(s.group[e.ke].mon[e.id].isStarted === true && e.coProcessor === true){
s.coSpawnClose(e)
s.group[e.ke].mon[e.id].coSpawnProcessor = s.ffmpegCoProcessor(e)
if(s.group[e.ke].mon[e.id].coSpawnProcessor === false){
return
}
s.userLog(e,{type:lang['coProcessor Started'],msg:{msg:lang.coProcessorTextStarted,cmd:s.group[e.ke].mon[e.id].coProcessorCmd}});
s.group[e.ke].mon[e.id].coSpawnProcessorExit = function(){
s.userLog(e,{type:lang['coProcess Unexpected Exit'],msg:{msg:lang['coProcess Crashed for Monitor']+' : '+e.id,cmd:s.group[e.ke].mon[e.id].coProcessorCmd}});
setTimeout(function(){
s.coSpawnLauncher(e)
},2000)
}
s.group[e.ke].mon[e.id].coSpawnProcessor.on('end',s.group[e.ke].mon[e.id].coSpawnProcessorExit)
s.group[e.ke].mon[e.id].coSpawnProcessor.on('exit',s.group[e.ke].mon[e.id].coSpawnProcessorExit)
var checkLog = function(d,x){return d.indexOf(x)>-1;}
s.group[e.ke].mon[e.id].coSpawnProcessor.stderr.on('data',function(d){
d=d.toString();
switch(true){
case checkLog(d,'deprecated pixel format used'):
case checkLog(d,'[hls @'):
case checkLog(d,'Past duration'):
case checkLog(d,'Last message repeated'):
case checkLog(d,'pkt->duration = 0'):
case checkLog(d,'Non-monotonous DTS'):
case checkLog(d,'NULL @'):
return
break;
}
s.userLog(e,{type:lang.coProcessor,msg:d});
})
if(e.frame_to_stream){
s.group[e.ke].mon[e.id].coSpawnProcessor.stdout.on('data',e.frame_to_stream)
}
if(e.details.detector === '1'){
s.ocvTx({f:'init_monitor',id:e.id,ke:e.ke})
//frames from motion detect
if(e.details.detector_pam === '1'){
s.createPamDiffEngine(e)
s.group[e.ke].mon[e.id].coSpawnProcessor.stdio[3].pipe(s.group[e.ke].mon[e.id].p2p).pipe(s.group[e.ke].mon[e.id].pamDiff)
if(e.details.detector_use_detect_object === '1'){
s.group[e.ke].mon[e.id].coSpawnProcessor.stdio[4].on('data',function(d){
s.group[e.ke].mon[e.id].lastJpegDetectorFrame = d
})
}
}else if(s.ocv){
if(s.ocv.connectionType !== 'ram'){
s.group[e.ke].mon[e.id].coSpawnProcessor.stdio[3].on('data',function(d){
s.ocvTx({f:'frame',mon:s.group[e.ke].mon_conf[e.id].details,ke:e.ke,id:e.id,time:s.formattedTime(),frame:d});
})
}else{
s.group[e.ke].mon[e.id].coSpawnProcessor.stdio[3].on('data',function(d){
if(!s.group[e.ke].mon[e.id].detectorFrameSaveBuffer){
s.group[e.ke].mon[e.id].detectorFrameSaveBuffer=[d]
}else{
s.group[e.ke].mon[e.id].detectorFrameSaveBuffer.push(d)
}
if(d[d.length-2] === 0xFF && d[d.length-1] === 0xD9){
var buffer = Buffer.concat(s.group[e.ke].mon[e.id].detectorFrameSaveBuffer);
var frameLocation = s.dir.streams + e.ke + '/' + e.id + '/' + s.gid(5) + '.jpg'
if(s.ocv){
fs.writeFile(frameLocation,buffer,function(err){
if(err){
s.debugLog(err)
}else{
s.ocvTx({f:'frameFromRam',mon:s.group[e.ke].mon_conf[e.id].details,ke:e.ke,id:e.id,time:s.formattedTime(),frameLocation:frameLocation})
}
})
}
s.group[e.ke].mon[e.id].detectorFrameSaveBuffer = null;
}
})
}
}
}
}
}
s.coSpawnClose = function(e){
if(s.group[e.ke].mon[e.id].coSpawnProcessor){
s.group[e.ke].mon[e.id].coSpawnProcessor.removeListener('end',s.group[e.ke].mon[e.id].coSpawnProcessorExit);
s.group[e.ke].mon[e.id].coSpawnProcessor.removeListener('exit',s.group[e.ke].mon[e.id].coSpawnProcessorExit);
s.group[e.ke].mon[e.id].coSpawnProcessor.stdin.pause()
s.group[e.ke].mon[e.id].coSpawnProcessor.kill()
delete(s.group[e.ke].mon[e.id].coSpawnProcessor)
s.userLog(e,{type:lang['coProcessor Stopped'],msg:{msg:lang.coProcessorTextStopped+' : '+e.id}});
}
}
}

View File

@ -268,6 +268,7 @@ module.exports = function(s,config,lang){
if(s.group[e.ke].mon[e.id].childNode){
s.cx({f:'kill',d:s.cleanMonitorObject(e)},s.group[e.ke].mon[e.id].childNodeId)
}else{
s.coSpawnClose(e)
if(!x||x===1){return};
p=x.pid;
if(s.group[e.ke].mon_conf[e.id].type===('dashcam'||'socket'||'jpeg'||'pipe')){
@ -826,7 +827,7 @@ module.exports = function(s,config,lang){
audioDetector.start()
s.group[e.ke].mon[e.id].spawn.stdio[6].pipe(audioDetector.streamDecoder)
}
if(e.details.detector === '1'){
if(e.details.detector === '1' && e.coProcessor === false){
s.ocvTx({f:'init_monitor',id:e.id,ke:e.ke})
//frames from motion detect
if(e.details.detector_pam === '1'){
@ -915,7 +916,11 @@ module.exports = function(s,config,lang){
break;
}
if(e.frameToStream){
s.group[e.ke].mon[e.id].spawn.stdout.on('data',e.frameToStream)
if(e.coProcessor === true && e.details.stream_type === ('b64'||'mjpeg')){
}else{
s.group[e.ke].mon[e.id].spawn.stdout.on('data',e.frameToStream)
}
}
if(e.details.stream_channels && e.details.stream_channels !== ''){
var createStreamEmitter = function(channel,number){
@ -964,6 +969,9 @@ module.exports = function(s,config,lang){
// case checkLog(d,'No space left on device'):
//
// break;
case checkLog(d,'error while decoding'):
s.userLog(e,{type:lang['Error While Decoding'],msg:lang.ErrorWhileDecodingText});
break;
case checkLog(d,'[hls @'):
case checkLog(d,'Past duration'):
case checkLog(d,'Last message repeated'):
@ -1066,7 +1074,11 @@ module.exports = function(s,config,lang){
if(s.group[e.ke].mon[e.id].isStarted === true){
fs.stat(e.sdir+'s.jpg',function(err,snap){
var notStreaming = function(){
s.launchMonitorProcesses(e)
if(e.coProcessor === true){
s.coSpawnLauncher(e)
}else{
s.launchMonitorProcesses(e)
}
s.userLog(e,{type:lang['Camera is not streaming'],msg:{msg:lang['Restarting Process']}})
s.orphanedVideoCheck(e,2,null,true)
}
@ -1151,6 +1163,11 @@ module.exports = function(s,config,lang){
){
s.cameraFilterFfmpegLog(e)
}
if(e.coProcessor === true){
setTimeout(function(){
s.coSpawnLauncher(e)
},6000)
}
s.onMonitorStartExtensions.forEach(function(extender){
extender(Object.assign(s.group[e.ke].mon_conf[e.id],{}),e)
})

View File

@ -284,6 +284,14 @@
<div><input class="form-control" detail="hwaccel_device"></div>
</label>
</div>
<div class="form-group">
<label><div><span><%-lang['Use coProcessor']%></span></div>
<div><select class="form-control" detail="use_coprocessor">
<option value="0" selected><%-lang.No%></option>
<option value="1"><%-lang.Yes%></option>
</select></div>
</label>
</div>
</div>
</div>
<!-- END of Input -->
@ -842,7 +850,7 @@
<div><input class="form-control" detail="cust_record"></div>
</label>
</div>
<div class="h_rec_mtd_input h_rec_mtd_sip">
<div class="form-group h_rec_mtd_input h_rec_mtd_sip" style="display:none">
<label><div><span><%-lang['Traditional Recording Flags']%></span></div>
<div><input class="form-control" detail="cust_sip_record"></div>
</label>