From 8733f89b297da43414e62ec3c52d3f0b76b76198 Mon Sep 17 00:00:00 2001 From: Andrew Bauer Date: Thu, 2 May 2019 20:09:15 -0500 Subject: [PATCH 01/12] eol f27 support --- .travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 6c41f1123..80541eaab 100644 --- a/.travis.yml +++ b/.travis.yml @@ -33,7 +33,6 @@ install: env: - SMPFLAGS=-j4 OS=el DIST=7 - - SMPFLAGS=-j4 OS=fedora DIST=27 DOCKER_REPO=knnniggett/packpack - SMPFLAGS=-j4 OS=fedora DIST=28 DOCKER_REPO=knnniggett/packpack - SMPFLAGS=-j4 OS=fedora DIST=29 DOCKER_REPO=knnniggett/packpack - SMPFLAGS=-j4 OS=ubuntu DIST=trusty From 74bd8126321d8f985d21fc4f8c5049a46145f26b Mon Sep 17 00:00:00 2001 From: Andrew Bauer Date: Fri, 3 May 2019 09:07:19 -0500 Subject: [PATCH 02/12] rpm packaging - buildrequire zlib-devel --- distros/redhat/zoneminder.spec | 1 + 1 file changed, 1 insertion(+) diff --git a/distros/redhat/zoneminder.spec b/distros/redhat/zoneminder.spec index 8e542f658..f22b518f9 100644 --- a/distros/redhat/zoneminder.spec +++ b/distros/redhat/zoneminder.spec @@ -73,6 +73,7 @@ BuildRequires: libcurl-devel BuildRequires: libv4l-devel BuildRequires: desktop-file-utils BuildRequires: gzip +BuildRequires: zlib-devel # ZoneMinder looks for and records the location of the ffmpeg binary during build BuildRequires: ffmpeg From faaec9e1d6742f747a5e327c7e79ab29b47cc2ac Mon Sep 17 00:00:00 2001 From: Chris Date: Mon, 6 May 2019 12:14:03 -0400 Subject: [PATCH 03/12] Another attempt to fix SQL Control values (#2600) --- db/zm_create.sql.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/db/zm_create.sql.in b/db/zm_create.sql.in index 787854091..a5f5cb70c 100644 --- a/db/zm_create.sql.in +++ b/db/zm_create.sql.in @@ -789,7 +789,7 @@ INSERT INTO `Controls` VALUES (NULL,'IOS Camera','Ffmpeg','IPCAMIOS',0,0,0,0,0,0 INSERT INTO `Controls` VALUES (NULL,'Dericam P2','Ffmpeg','DericamP2',0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,10,0,1,1,1,0,0,0,1,1,0,0,0,0,1,1,45,0,0,1,0,0,0,0,1,1,45,0,0,0,0); INSERT INTO `Controls` VALUES (NULL,'Trendnet','Remote','Trendnet',1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,0,1,0,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0); INSERT INTO `Controls` VALUES (NULL,'PSIA','Remote','PSIA',0,0,0,0,1,0,0,1,0,0,0,0,0,0,0,100,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,20,0,1,1,1,0,0,1,0,1,0,0,0,0,1,-100,100,0,0,1,0,0,0,0,1,-100,100,0,0,0,0); -INSERT INTO `Controls` VALUES (NULL,'Dahua','Ffmpeg','Dahua',0,0,1,1,1,1,0,0,1,0,0,0,0,0,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,20,1,1,1,1,0,0,1,0,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0); +INSERT INTO `Controls` VALUES (NULL,'Dahua','Ffmpeg','Dahua',0,0,1,1,1,0,0,1,0,0,0,0,0,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,20,1,1,1,1,0,0,1,0,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0); INSERT INTO `Controls` VALUES (NULL,'FOSCAMR2C','Libvlc','FOSCAMR2C',1,1,1,0,0,0,0,0,0,NULL,NULL,NULL,NULL,0,NULL,NULL,0,0,0,0,0,NULL,NULL,NULL,NULL,0,NULL,NULL,0,0,0,0,0,NULL,NULL,NULL,NULL,0,NULL,NULL,0,0,0,0,0,NULL,NULL,NULL,NULL,0,NULL,NULL,0,0,0,0,0,NULL,NULL,NULL,NULL,0,NULL,NULL,1,12,0,1,1,1,0,0,0,1,1,NULL,NULL,NULL,NULL,1,0,4,0,NULL,1,NULL,NULL,NULL,NULL,1,0,4,0,NULL,0,0); INSERT INTO `Controls` VALUES (NULL,'Amcrest HTTP API','Ffmpeg','Amcrest_HTTP',0,0,1,0,1,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,5,0,0,1,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,5); From d3a680aaa36e30e3970b0a3fc0c8c32933319854 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Fri, 10 May 2019 12:31:10 -0400 Subject: [PATCH 04/12] Set out_frame duration when resampling. Better error message if failed to write to fifo --- src/zm_videostore.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/zm_videostore.cpp b/src/zm_videostore.cpp index 0d3e22b4e..f8a04ff1c 100644 --- a/src/zm_videostore.cpp +++ b/src/zm_videostore.cpp @@ -1181,6 +1181,9 @@ int VideoStore::resample_audio() { ret = swr_convert_frame(resample_ctx, out_frame, in_frame); zm_dump_frame(out_frame, "Out frame after convert"); + // resampling doesn't change the duration, or set it. + out_frame->duration = in_frame->duration; + if ( ret < 0 ) { Error("Could not resample frame (error '%s')", av_make_error_string(ret).c_str()); @@ -1193,7 +1196,8 @@ int VideoStore::resample_audio() { /** Store the new samples in the FIFO buffer. */ ret = av_audio_fifo_write(fifo, (void **)out_frame->data, out_frame->nb_samples); if ( ret < out_frame->nb_samples ) { - Error("Could not write data to FIFO on %d written, expecting %d", ret, out_frame->nb_samples); + Error("Could not write data to FIFO. %d written, expecting %d. Reason %s", + ret, out_frame->nb_samples, av_make_error_string(ret).c_str()); return 0; } From 67c20aa976e529d5f00ca8ac7903e04af064983a Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Fri, 10 May 2019 12:58:54 -0400 Subject: [PATCH 05/12] fix frame->duration to frame->pkt_duration --- src/zm_videostore.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/zm_videostore.cpp b/src/zm_videostore.cpp index f8a04ff1c..978dfd328 100644 --- a/src/zm_videostore.cpp +++ b/src/zm_videostore.cpp @@ -1182,7 +1182,7 @@ int VideoStore::resample_audio() { zm_dump_frame(out_frame, "Out frame after convert"); // resampling doesn't change the duration, or set it. - out_frame->duration = in_frame->duration; + out_frame->pkt_duration = in_frame->pkt_duration; if ( ret < 0 ) { Error("Could not resample frame (error '%s')", From d9f7e93df3a17842f351a39a005e4c52efc356e4 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Fri, 10 May 2019 14:27:51 -0400 Subject: [PATCH 06/12] Fix typo gegress to degrees. Fixes #2601 --- scripts/ZoneMinder/lib/ZoneMinder/Control/Amcrest_HTTP.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Control/Amcrest_HTTP.pm b/scripts/ZoneMinder/lib/ZoneMinder/Control/Amcrest_HTTP.pm index 7a89f353e..e95f86cba 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Control/Amcrest_HTTP.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Control/Amcrest_HTTP.pm @@ -189,7 +189,7 @@ sub moveAbs ## Up, Down, Left, Right, etc. ??? Doesn't make sense here... my $tilt_degrees = shift || 0; my $speed = shift || 1; Debug( "Move ABS" ); - $self->sendCmd( 'cgi-bin/ptz.cgi?action=start&code=PositionABS&channel=0&arg1='.$pan_degress.'&arg2='.$tilt_degrees.'&arg3=0&arg4='.$speed ); + $self->sendCmd( 'cgi-bin/ptz.cgi?action=start&code=PositionABS&channel=0&arg1='.$pan_degrees.'&arg2='.$tilt_degrees.'&arg3=0&arg4='.$speed ); } sub moveConUp From 22c5d46c65b2b94bf25f977f1e0319cfe0462ad0 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Sun, 12 May 2019 12:14:03 -0400 Subject: [PATCH 07/12] rescale audio packet duration and pts before feeding to codec after resample --- src/zm_videostore.cpp | 41 +++++++++++++++++++++++++++++++++++------ 1 file changed, 35 insertions(+), 6 deletions(-) diff --git a/src/zm_videostore.cpp b/src/zm_videostore.cpp index 978dfd328..2130db03b 100644 --- a/src/zm_videostore.cpp +++ b/src/zm_videostore.cpp @@ -1011,7 +1011,6 @@ int VideoStore::writeAudioFramePacket(AVPacket *ipkt) { } zm_dump_frame(out_frame, "Out frame after resample"); - out_frame->pts = in_frame->pts; // out_frame pts is in the input pkt pts... needs to be adjusted before sending to the encoder if ( out_frame->pts != AV_NOPTS_VALUE ) { if ( !audio_first_pts ) { @@ -1065,6 +1064,8 @@ int VideoStore::writeAudioFramePacket(AVPacket *ipkt) { return 0; } #endif +#if 0 + // These should be set by encoder. They may not directly relate to ipkt due to buffering in codec. opkt.duration = av_rescale_q(opkt.duration, audio_in_stream->time_base, audio_out_stream->time_base); @@ -1074,6 +1075,7 @@ int VideoStore::writeAudioFramePacket(AVPacket *ipkt) { opkt.dts = av_rescale_q(opkt.dts, audio_in_stream->time_base, audio_out_stream->time_base); +#endif dumpPacket(audio_out_stream, &opkt, "raw opkt"); } else { @@ -1179,16 +1181,32 @@ int VideoStore::resample_audio() { Debug(2, "Converting %d to %d samples using swresample", in_frame->nb_samples, out_frame->nb_samples); ret = swr_convert_frame(resample_ctx, out_frame, in_frame); - zm_dump_frame(out_frame, "Out frame after convert"); - - // resampling doesn't change the duration, or set it. - out_frame->pkt_duration = in_frame->pkt_duration; - if ( ret < 0 ) { Error("Could not resample frame (error '%s')", av_make_error_string(ret).c_str()); return 0; } + zm_dump_frame(out_frame, "Out frame after convert"); + + +#if 0 + // out_frame pts is in the input pkt pts... needs to be adjusted before sending to the encoder + if ( out_frame->pts != AV_NOPTS_VALUE ) { + if ( !audio_first_pts ) { + audio_first_pts = out_frame->pts; + Debug(1, "No audio_first_pts setting to %" PRId64, audio_first_pts); + out_frame->pts = 0; + } else { + out_frame->pts = out_frame->pts - audio_first_pts; + } + // + } else { + // sending AV_NOPTS_VALUE doesn't really work but we seem to get it in ffmpeg 2.8 + out_frame->pts = audio_next_pts; + } + audio_next_pts = out_frame->pts + out_frame->nb_samples; +#endif + if ((ret = av_audio_fifo_realloc(fifo, av_audio_fifo_size(fifo) + out_frame->nb_samples)) < 0) { Error("Could not reallocate FIFO"); return 0; @@ -1214,6 +1232,17 @@ int VideoStore::resample_audio() { return 0; } out_frame->nb_samples = frame_size; + // resampling changes the duration because the timebase is 1/samples + if ( in_frame->pts != AV_NOPTS_VALUE ) { + out_frame->pkt_duration = av_rescale_q( + in_frame->pkt_duration, + audio_in_stream->time_base, + audio_out_stream->time_base); + out_frame->pts = av_rescale_q( + in_frame->pts, + audio_in_stream->time_base, + audio_out_stream->time_base); + } #else #if defined(HAVE_LIBAVRESAMPLE) ret = avresample_convert(resample_ctx, NULL, 0, 0, in_frame->data, From 74d9f4f1aaae736132296083effedcb734996309 Mon Sep 17 00:00:00 2001 From: Jonathan Meredith <35303639+jimender2@users.noreply.github.com> Date: Mon, 13 May 2019 07:58:18 -0400 Subject: [PATCH 08/12] Spelling and grammar fixes in help (#2603) * Edit Help array to make it match others below. This should not affect the results * Misc. grammer and spelling fixes along with removing some duplicated words. This should not affect compilation. * More grammer and spelling errors * Replace Javascript with ZoneMinder because it did not make sense there. * More spelling and grammar edits --- .../lib/ZoneMinder/ConfigData.pm.in | 83 ++++++++++--------- web/lang/en_gb.php | 42 +++++----- web/skins/classic/views/monitor.php | 24 +++--- 3 files changed, 75 insertions(+), 74 deletions(-) diff --git a/scripts/ZoneMinder/lib/ZoneMinder/ConfigData.pm.in b/scripts/ZoneMinder/lib/ZoneMinder/ConfigData.pm.in index c6ec697f1..4d96f11db 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/ConfigData.pm.in +++ b/scripts/ZoneMinder/lib/ZoneMinder/ConfigData.pm.in @@ -197,8 +197,8 @@ our @options = ( name => 'ZM_BANDWIDTH_DEFAULT', default => 'high', description => 'Default setting for bandwidth profile used by web interface', - help => q`The classic skin for ZoneMinder has different - profiles to use for low medium or high bandwidth connections. + help => q`The classic skin for ZoneMinder has different + profiles to use for low, medium, or high bandwidth connections. `, type => $types{string}, category => 'system', @@ -547,7 +547,7 @@ our @options = ( higher quality setting than the ordinary file setting. If set to a lower value then it is ignored. Thus leaving it at the default of 0 effectively means to use the regular file quality - setting for all saved images. This is to prevent acccidentally + setting for all saved images. This is to prevent accidentally saving important images at a worse quality setting. `, type => $types{integer}, @@ -671,7 +671,7 @@ our @options = ( Internet Explorer that don't natively support this format. If you use this browser it is highly recommended to install this from the [cambozola project site](http://www.charliemouse.com/code/cambozola/). - However, if it is not installed still images at a lower refresh rate can + However, if it is not installed still images at a lower refresh rate can still be viewed. `, type => $types{boolean}, @@ -926,10 +926,10 @@ our @options = ( help => q` Due to browsers only wanting to open 6 connections, if you have more than 6 monitors, you can have trouble viewing more than 6. This setting - specified the beginning of a port range that will be used to contact ZM + specified the beginning of a port range that will be used to contact ZM on. Each monitor will use this value plus the Monitor Id to stream - content. So a value of 2000 here will cause a stream for Monitor 1 to - hit port 2001. Please ensure that you configure apache appropriately + content. So a value of 2000 here will cause a stream for Monitor 1 to + hit port 2001. Please ensure that you configure apache appropriately to respond on these ports.`, type => $types{integer}, category => 'network', @@ -1065,12 +1065,12 @@ our @options = ( default => '0', description => 'Save logging output to the system log', help => q` - ZoneMinder logging is now more more integrated between + ZoneMinder logging is now more integrated between components and allows you to specify the destination for logging output and the individual levels for each. This option lets you control the level of logging output that goes to the system log. ZoneMinder binaries have always logged to the - system log but now scripts and web logging is also included. To + system log but script and web logging is now included. To preserve the previous behaviour you should ensure this value is set to Info or Warning. This option controls the maximum level of logging that will be written, so Info includes Warnings and @@ -1092,7 +1092,7 @@ our @options = ( default => '-5', description => 'Save logging output to component files', help => q` - ZoneMinder logging is now more more integrated between + ZoneMinder logging is now more integrated between components and allows you to specify the destination for logging output and the individual levels for each. This option lets you control the level of logging output that goes to @@ -1122,7 +1122,7 @@ our @options = ( default => '-5', description => 'Save logging output to the weblog', help => q` - ZoneMinder logging is now more more integrated between + ZoneMinder logging is now more integrated between components and allows you to specify the destination for logging output and the individual levels for each. This option lets you control the level of logging output from the web @@ -1149,7 +1149,7 @@ our @options = ( default => '0', description => 'Save logging output to the database', help => q` - ZoneMinder logging is now more more integrated between + ZoneMinder logging is now more integrated between components and allows you to specify the destination for logging output and the individual levels for each. This option lets you control the level of logging output that is written to @@ -1204,7 +1204,7 @@ our @options = ( help => q` When enabled (default is on), this option will log FFMPEG messages. FFMPEG messages can be useful when debugging streaming issues. However, - depending on your distro and FFMPEG version, this may also result in + depending on your distro and FFMPEG version, this may also result in more logs than you'd typically like to see. If all your streams are working well, you may choose to turn this off. `, @@ -1219,7 +1219,7 @@ our @options = ( ZoneMinder components usually support debug logging available to help with diagnosing problems. Binary components have several levels of debug whereas more other components have only - one. Normally this is disabled to minimise performance + one. Normally this is disabled to minimize performance penalties and avoid filling logs too quickly. This option lets you switch on other options that allow you to configure additional debug information to be output. Components will pick @@ -1480,8 +1480,8 @@ our @options = ( default => 'ZoneMinder', description => 'The title displayed wherever the site references itself.', help => q` - If you want the site to identify as something other than ZoneMinder, change this here. - It can be used to more accurately identify this installation from others. + If you want the site to identify as something other than ZoneMinder, change this here. + It can be used to more accurately identify this installation from others. `, type => $types{string}, category => 'web', @@ -1504,8 +1504,8 @@ our @options = ( default => 'http://zoneminder.com', description => 'The url used in the home/logo area of the navigation bar.', help => q` - By default this takes you to the zoneminder.com website, - but perhaps you would prefer it to take you somewhere else. + By default this takes you to the zoneminder.com website, + but perhaps you would prefer it to take you somewhere else. `, type => $types{string}, category => 'web', @@ -1515,7 +1515,7 @@ our @options = ( default => 'ZoneMinder', description => 'The content of the home button.', help => q` - You may wish to set this to empty if you are using css to put a background image on it. + You may wish to set this to empty if you are using css to put a background image on it. `, type => $types{string}, category => 'web', @@ -1538,7 +1538,8 @@ our @options = ( name => 'ZM_WEB_EVENT_DISK_SPACE', default => 'no', description => 'Whether to show disk space used by each event.', - help => q`Adds another column to the listing of events + help => q` + Adds another column to the listing of events showing the disk space used by the event. This will impart a small overhead as it will call du on the event directory. In practice this overhead is fairly small but may be noticeable on IO-constrained @@ -1555,7 +1556,7 @@ our @options = ( Traditionally the main ZoneMinder web console window has resized itself to shrink to a size small enough to list only the monitors that are actually present. This is intended to - make the window more unobtrusize but may not be to everyones + make the window more unobtrusize but may not be to everyone's tastes, especially if opened in a tab in browsers which support this kind if layout. Switch this option off to have the console window size left to the users preference @@ -2105,7 +2106,7 @@ our @options = ( a remote ftp server. This option indicates that ftp transfers should be done in passive mode. This uses a single connection for all ftp activity and, whilst slower than active transfers, - is more robust and likely to work from behind filewalls. This + is more robust and likely to work from behind firewalls. This option is ignored for SFTP transfers. `, requires => [ { name => 'ZM_OPT_UPLOAD', value => 'yes' } ], @@ -2631,7 +2632,7 @@ our @options = ( help => q` As event images are captured they are stored to the filesystem with a numerical index. By default this index has three digits - so the numbers start 001, 002 etc. This works works for most + so the numbers start 001, 002 etc. This works for most scenarios as events with more than 999 frames are rarely captured. However if you have extremely long events and use external applications then you may wish to increase this to @@ -2728,7 +2729,7 @@ our @options = ( it to the ZoneMinder development team. This data will be used to determine things like who and where our customers are, how big their systems are, the underlying hardware and operating system, etc. - This is being done for the sole purpoase of creating a better + This is being done for the sole purpose of creating a better product for our target audience. This script is intended to be completely transparent to the end user, and can be disabled from the web console under Options. For more details on what information @@ -2752,7 +2753,7 @@ our @options = ( { name => 'ZM_TELEMETRY_LAST_UPLOAD', default => '', - description => 'When the last ZoneMinder telemetry upload ocurred', + description => 'When the last ZoneMinder telemetry upload occurred', help => '', type => $types{integer}, readonly => 1, @@ -2810,7 +2811,7 @@ our @options = ( default => 'javascript', description => 'What method windows should use to refresh themselves', help => q` - Many windows in Javascript need to refresh themselves to keep + Many windows in ZoneMinder need to refresh themselves to keep their information current. This option determines what method they should use to do this. Choosing 'javascript' means that each window will have a short JavaScript statement in with a @@ -2944,7 +2945,7 @@ our @options = ( some indication of the type of content. However this is not a standard part of HTML. The official method is to use OBJECT tags which are able to give more information allowing the - correct media viewers etc to be loaded. However these are less + correct media viewers etc. to be loaded. However these are less widely supported and content may be specifically tailored to a particular platform or player. This option controls whether media content is enclosed in EMBED tags only or whether, where @@ -2968,7 +2969,7 @@ our @options = ( browsers. When this condition occurs, ZoneMinder will write a warning to the log file. To get around this, one can install a browser plugin or extension to ignore X-Frame headers, and then the page will - display properly. Once the plugin or extenstion has ben installed, + display properly. Once the plugin or extension has ben installed, the end user may choose to turn this warning off. `, type => $types{boolean}, @@ -3082,7 +3083,7 @@ our @options = ( description => 'How often (in seconds) the event listing is refreshed in the watch window', help => q` The monitor window is actually made from several frames. The - lower framme contains a listing of the last few events for easy + lower frame contains a listing of the last few events for easy access. This option determines how often this is refreshed. `, type => $types{integer}, @@ -3212,12 +3213,12 @@ our @options = ( { name => 'ZM_WEB_H_SCALE_THUMBS', default => 'no', - description => 'Scale thumbnails in events, bandwidth versus cpu in rescaling', + description => 'Scale thumbnails in events, bandwidth versus CPU in rescaling', help => q` If unset, this option sends the whole image to the browser which resizes it in the window. If set the image is scaled down on the server before sending a reduced size image to the - browser to conserve bandwidth at the cost of cpu on the server. + browser to conserve bandwidth at the cost of CPU on the server. Note that ZM can only perform the resizing if the appropriate PHP graphics functionality is installed. This is usually available in the php-gd package. @@ -3251,7 +3252,7 @@ our @options = ( help => q` When viewing events an event navigation panel and progress bar is shown below the event itself. This allows you to jump to - specific points in the event, but can can also dynamically + specific points in the event, but can also dynamically update to display the current progress of the event replay itself. This progress is calculated from the actual event duration and is not directly linked to the replay itself, so on @@ -3355,7 +3356,7 @@ our @options = ( description => 'How often (in seconds) the event listing is refreshed in the watch window', help => q` The monitor window is actually made from several frames. The - lower framme contains a listing of the last few events for easy + lower frame contains a listing of the last few events for easy access. This option determines how often this is refreshed. `, type => $types{integer}, @@ -3485,12 +3486,12 @@ our @options = ( { name => 'ZM_WEB_M_SCALE_THUMBS', default => 'yes', - description => 'Scale thumbnails in events, bandwidth versus cpu in rescaling', + description => 'Scale thumbnails in events, bandwidth versus CPU in rescaling', help => q` If unset, this option sends the whole image to the browser which resizes it in the window. If set the image is scaled down on the server before sending a reduced size image to the - browser to conserve bandwidth at the cost of cpu on the server. + browser to conserve bandwidth at the cost of CPU on the server. Note that ZM can only perform the resizing if the appropriate PHP graphics functionality is installed. This is usually available in the php-gd package. @@ -3524,7 +3525,7 @@ our @options = ( help => q` When viewing events an event navigation panel and progress bar is shown below the event itself. This allows you to jump to - specific points in the event, but can can also dynamically + specific points in the event, but can also dynamically update to display the current progress of the event replay itself. This progress is calculated from the actual event duration and is not directly linked to the replay itself, so on @@ -3628,7 +3629,7 @@ our @options = ( description => 'How often (in seconds) the event listing is refreshed in the watch window', help => q` The monitor window is actually made from several frames. The - lower framme contains a listing of the last few events for easy + lower frame contains a listing of the last few events for easy access. This option determines how often this is refreshed. `, type => $types{integer}, @@ -3757,12 +3758,12 @@ our @options = ( { name => 'ZM_WEB_L_SCALE_THUMBS', default => 'yes', - description => 'Scale thumbnails in events, bandwidth versus cpu in rescaling', + description => 'Scale thumbnails in events, bandwidth versus CPU in rescaling', help => q` If unset, this option sends the whole image to the browser which resizes it in the window. If set the image is scaled down on the server before sending a reduced size image to the - browser to conserve bandwidth at the cost of cpu on the server. + browser to conserve bandwidth at the cost of CPU on the server. Note that ZM can only perform the resizing if the appropriate PHP graphics functionality is installed. This is usually available in the php-gd package. @@ -3796,7 +3797,7 @@ our @options = ( help => q` When viewing events an event navigation panel and progress bar is shown below the event itself. This allows you to jump to - specific points in the event, but can can also dynamically + specific points in the event, but can also dynamically update to display the current progress of the event replay itself. This progress is calculated from the actual event duration and is not directly linked to the replay itself, so on @@ -3988,7 +3989,7 @@ saveConfigToDB(); The ZoneMinder:ConfigData module contains the master definition of the ZoneMinder configuration options as well as helper methods. This module is -intended for specialist confguration management and would not normally be +intended for specialist configuration management and would not normally be used by end users. The configuration held in this module, which was previously in zmconfig.pl, diff --git a/web/lang/en_gb.php b/web/lang/en_gb.php index 945d26bea..72d6de8ab 100644 --- a/web/lang/en_gb.php +++ b/web/lang/en_gb.php @@ -32,7 +32,7 @@ // a formatting string. If the dynamic element is a number you will usually need to use a variable // replacement also as described below. // c) Variable replacements are used in conjunction with complex replacements and involve the generation -// of a singular or plural noun depending on the number passed into the zmVlang function. See the +// of a singular or plural noun depending on the number passed into the zmVlang function. See the // the zmVlang section below for a further description of this. // d) Optional strings which can be used to replace the prompts and/or help text for the Options section // of the web interface. These are not listed below as they are quite large and held in the database @@ -40,7 +40,7 @@ // quite easily from the Config table in the database if necessary. // 3. The tokens listed below are not used to build up phrases or sentences from single words. Therefore // you can safely assume that a single word token will only be used in that context. -// 4. In new language files, or if you are changing only a few words or phrases it makes sense from a +// 4. In new language files, or if you are changing only a few words or phrases it makes sense from a // maintenance point of view to include the original language file and override the old definitions rather // than copy all the language tokens across. To do this change the line below to whatever your base language // is and uncomment it. @@ -57,10 +57,10 @@ // If you do need to change your locale, be aware that the format of this function // is subtlely different in versions of PHP before and after 4.3.0, see // http://uk2.php.net/manual/en/function.setlocale.php for details. -// Also be aware that changing the whole locale may affect some floating point or decimal +// Also be aware that changing the whole locale may affect some floating point or decimal // arithmetic in the database, if this is the case change only the individual locale areas // that don't affect this rather than all at once. See the examples below. -// Finally, depending on your setup, PHP may not enjoy have multiple locales in a shared +// Finally, depending on your setup, PHP may not enjoy have multiple locales in a shared // threaded environment, if you get funny errors it may be this. // // Examples @@ -768,7 +768,7 @@ $SLANG = array( 'Update' => 'Update', 'Upload' => 'Upload', 'Updated' => 'Updated', - 'UsedPlugins' => 'Used Plugins', + 'UsedPlugins' => 'Used Plugins', 'UseFilterExprsPost' => ' filter expressions', // This is used at the end of the phrase 'use N filter expressions' 'UseFilterExprsPre' => 'Use ', // This is used at the beginning of the phrase 'use N filter expressions' 'UseFilter' => 'Use Filter', @@ -847,7 +847,7 @@ $CLANG = array( 'VersionMismatch' => 'Version mismatch, system is version %1$s, database is %2$s.', ); -// The next section allows you to describe a series of word ending and counts used to +// The next section allows you to describe a series of word ending and counts used to // generate the correctly conjugated forms of words depending on a count that is associated // with that word. // This intended to allow phrases such a '0 potatoes', '1 potato', '2 potatoes' etc to @@ -888,7 +888,7 @@ $VLANG = array( // with variable counts. This is used to conjugate the Vlang arrays above with a number passed // in to generate the correct noun form. // -// In languages such as English this is fairly simple +// In languages such as English this is fairly simple // Note this still has to be used with printf etc to get the right formatting function zmVlang( $langVarArray, $count ) { @@ -906,9 +906,9 @@ function zmVlang( $langVarArray, $count ) // This is an version that could be used in the Russian example above // The rules are that the first word form is used if the count ends in // 0, 5-9 or 11-19. The second form is used then the count ends in 1 -// (not including 11 as above) and the third form is used when the +// (not including 11 as above) and the third form is used when the // count ends in 2-4, again excluding any values ending in 12-14. -// +// // function zmVlang( $langVarArray, $count ) // { // $secondlastdigit = substr( $count, -2, 1 ); @@ -916,7 +916,7 @@ function zmVlang( $langVarArray, $count ) // // or // // $secondlastdigit = ($count/10)%10; // // $lastdigit = $count%10; -// +// // // Get rid of the special cases first, the teens // if ( $secondlastdigit == 1 && $lastdigit != 0 ) // { @@ -950,7 +950,7 @@ function zmVlang( $langVarArray, $count ) // die( 'Error, unable to correlate variable language string' ); // } -// This is an example of how the function is used in the code which you can uncomment and +// This is an example of how the function is used in the code which you can uncomment and // use to test your custom function. //$monitors = array(); //$monitors[] = 1; // Choose any number @@ -967,17 +967,17 @@ $OLANG = array( "\"reorder_queue_size=nnn\" Set number of packets to buffer for handling of reordered packets~~~~". "\"loglevel=debug\" Set verbosity of FFmpeg (quiet, panic, fatal, error, warning, info, verbose, debug)" ), - 'OPTIONS_RTSPTrans' => array( + 'OPTIONS_RTSPTrans' => array( 'Help' => "This sets the RTSP Transport Protocol for FFmpeg.~~ ". - "TCP - Use TCP (interleaving within the RTSP control channel) as transport protocol.~~". - "UDP - Use UDP as transport protocol. Higher resolution cameras have experienced some 'smearing' while using UDP, if so try TCP~~". - "UDP Multicast - Use UDP Multicast as transport protocol~~". - "HTTP - Use HTTP tunneling as transport protocol, which is useful for passing proxies.~~" + "TCP - Use TCP (interleaving within the RTSP control channel) as transport protocol.~~". + "UDP - Use UDP as transport protocol. Higher resolution cameras have experienced some 'smearing' while using UDP, if so try TCP~~". + "UDP Multicast - Use UDP Multicast as transport protocol~~". + "HTTP - Use HTTP tunneling as transport protocol, which is useful for passing proxies.~~" ), 'OPTIONS_LIBVLC' => array( 'Help' => "Parameters in this field are passed on to libVLC. Multiple parameters can be separated by ,~~ ". "Examples (do not enter quotes)~~~~". - "\"--rtp-client-port=nnn\" Set local port to use for rtp data~~~~". + "\"--rtp-client-port=nnn\" Set local port to use for rtp data~~~~". "\"--verbose=2\" Set verbosity of libVLC" ), 'OPTIONS_EXIF' => array( @@ -986,7 +986,7 @@ $OLANG = array( 'OPTIONS_RTSPDESCRIBE' => array( 'Help' => "Sometimes, during the initial RTSP handshake, the camera will send an updated media URL. ". "Enable this option to tell ZoneMinder to use this URL. Disable this option to ignore the ". - "value from the camera and use the value as entered in the monitor configuration~~~~". + "value from the camera and use the value as entered in the monitor configuration~~~~". "Generally this should be enabled. However, there are cases where the camera can get its". "own URL incorrect, such as when the camera is streaming through a firewall"), 'OPTIONS_MAXFPS' => array( @@ -994,12 +994,12 @@ $OLANG = array( "Failure to adhere to these limitations will cause a delay in live video, irregular frame skipping, ". "and missed events~~". "For streaming IP cameras, do not use this field to reduce the frame rate. Set the frame rate in the". - " camera, instead. You can, however, use a value that is slightly higher than the frame rate in the camera. ". - "In this case, this helps keep the cpu from being overtaxed in the event of a network problem.~~". + " camera, instead. You can, however, use a value that is slightly higher than the frame rate in the camera. ". + "In this case, this helps keep the cpu from being overtaxed in the event of a network problem.~~". "Some, mostly older, IP cameras support snapshot mode. In this case ZoneMinder is actively polling the camera ". "for new images. In this case, it is safe to use the field." ), - + // 'LANG_DEFAULT' => array( // 'Prompt' => "This is a new prompt for this option", // 'Help' => "This is some new help for this option which will be displayed in the popup window when the ? is clicked" diff --git a/web/skins/classic/views/monitor.php b/web/skins/classic/views/monitor.php index a0d6c7e91..d6d7780b7 100644 --- a/web/skins/classic/views/monitor.php +++ b/web/skins/classic/views/monitor.php @@ -39,7 +39,7 @@ if ( ! empty($_REQUEST['mid']) ) { $monitor = new ZM\Monitor( $_REQUEST['mid'] ); if ( $monitor and ZM_OPT_X10 ) $x10Monitor = dbFetchOne('SELECT * FROM TriggersX10 WHERE MonitorId = ?', NULL, array($_REQUEST['mid'])); -} +} if ( ! $monitor ) { $nextId = getTableAutoInc('Monitors'); @@ -132,9 +132,9 @@ if ( ! $monitor ) { if ( ZM_OPT_X10 && empty($x10Monitor) ) { $x10Monitor = array( - 'Activation' => '', - 'AlarmInput' => '', - 'AlarmOutput' => '', + 'Activation' => '', + 'AlarmInput' => '', + 'AlarmOutput' => '', ); } @@ -166,7 +166,7 @@ if ( $monitor->AlarmMaxFPS() == '0.00' ) if ( !empty($_REQUEST['preset']) ) { $preset = dbFetchOne( 'SELECT Type, Device, Channel, Format, Protocol, Method, Host, Port, Path, Width, Height, Palette, MaxFPS, Controllable, ControlId, ControlDevice, ControlAddress, DefaultRate, DefaultScale FROM MonitorPresets WHERE Id = ?', NULL, array($_REQUEST['preset']) ); foreach ( $preset as $name=>$value ) { - # Does isset handle NULL's? I don't think this code is correct. + # Does isset handle NULL's? I don't think this code is correct. if ( isset($value) ) { $monitor->$name = $value; } @@ -176,7 +176,7 @@ if ( !empty($_REQUEST['probe']) ) { $probe = json_decode(base64_decode($_REQUEST['probe'])); foreach ( $probe as $name=>$value ) { if ( isset($value) ) { - # Does isset handle NULL's? I don't think this code is correct. + # Does isset handle NULL's? I don't think this code is correct. $monitor->$name = urldecode($value); } } @@ -683,7 +683,7 @@ switch ( $tab ) { ?> -'None','auto'=>'Auto'); foreach ( ZM\Server::find(NULL, array('order'=>'lower(Name)')) as $Server ) { $servers[$Server->Id()] = $Server->Name(); @@ -763,7 +763,7 @@ echo htmlOptions(ZM\Group::get_dropdown_options( ), $monitor->GroupIds() ); - + Type() == 'NVSocket' ) { include('_monitor_source_nvsocket.php'); } else if ( $monitor->Type() == 'Remote' ) { @@ -894,10 +894,10 @@ if ( $monitor->Type() != 'NVSocket' && $monitor->Type() != 'WebSite' ) { () () - + Orientation() );?> Type() == 'Local' ) { ?> @@ -920,7 +920,7 @@ if ( $monitor->Type() == 'Local' ) { ?> - 'Disabled', ); From 1855ee441a9e32a8fd2b843cc34a3f0c9f13534f Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 15 May 2019 19:35:19 -0400 Subject: [PATCH 09/12] Fix #2609 invalid value horz instead of hori in orientations enumeration --- web/skins/classic/views/monitor.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/skins/classic/views/monitor.php b/web/skins/classic/views/monitor.php index d6d7780b7..301851629 100644 --- a/web/skins/classic/views/monitor.php +++ b/web/skins/classic/views/monitor.php @@ -389,7 +389,7 @@ $orientations = array( '90' => translate('RotateRight'), '180' => translate('Inverted'), '270' => translate('RotateLeft'), - 'horz' => translate('FlippedHori'), + 'hori' => translate('FlippedHori'), 'vert' => translate('FlippedVert') ); From 4f44db8cbf4d6d292c9c55088fee6ec4455e8cc5 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Thu, 16 May 2019 15:35:19 -0400 Subject: [PATCH 10/12] ifdef HAVE_ZLIB_H around code that uses Image->Zip (#2597) --- src/zm_monitorstream.cpp | 8 ++++++++ src/zm_monitorstream.h | 2 ++ 2 files changed, 10 insertions(+) diff --git a/src/zm_monitorstream.cpp b/src/zm_monitorstream.cpp index 0b2811656..124994a48 100644 --- a/src/zm_monitorstream.cpp +++ b/src/zm_monitorstream.cpp @@ -394,10 +394,15 @@ bool MonitorStream::sendFrame(Image *image, struct timeval *timestamp) { img_buffer_size = send_image->Size(); break; case STREAM_ZIP : +#if HAVE_ZLIB_H fputs("Content-Type: image/x-rgbz\r\n",stdout); unsigned long zip_buffer_size; send_image->Zip(img_buffer, &zip_buffer_size); img_buffer_size = zip_buffer_size; +#else + Error("zlib is required for zipped images. Falling back to raw image"); + type = STREAM_RAW; +#endif // HAVE_ZLIB_H break; default : Error("Unexpected frame type %d", type); @@ -794,6 +799,8 @@ void MonitorStream::SingleImageRaw( int scale ) { fwrite( snap_image->Buffer(), snap_image->Size(), 1, stdout ); } + +#ifdef HAVE_ZLIB_H void MonitorStream::SingleImageZip( int scale ) { unsigned long img_buffer_size = 0; static Bytef img_buffer[ZM_MAX_IMAGE_SIZE]; @@ -816,3 +823,4 @@ void MonitorStream::SingleImageZip( int scale ) { fprintf( stdout, "Content-Type: image/x-rgbz\r\n\r\n" ); fwrite( img_buffer, img_buffer_size, 1, stdout ); } +#endif // HAVE_ZLIB_H diff --git a/src/zm_monitorstream.h b/src/zm_monitorstream.h index f3351d3b7..e120331af 100644 --- a/src/zm_monitorstream.h +++ b/src/zm_monitorstream.h @@ -55,7 +55,9 @@ class MonitorStream : public StreamBase { void processCommand( const CmdMsg *msg ); void SingleImage( int scale=100 ); void SingleImageRaw( int scale=100 ); +#ifdef HAVE_ZLIB_H void SingleImageZip( int scale=100 ); +#endif public: MonitorStream() : From eb005e8b9c33e6bdba8b1796393eebf3ea3fdaad Mon Sep 17 00:00:00 2001 From: Mitch Capper Date: Thu, 16 May 2019 12:37:03 -0700 Subject: [PATCH 11/12] FIFO support for zoneminder zone debugging (#2594) Adds fifo options for diagnostic images for much lower impact diagnostics mode. Diagnostic images are only written when there is a client listening for them (otherwise they are skipped). Also added a json stream for the detection data so you can see in real time the pixels or blobs detected for the motion. This allows for easy real time stream of both delta and reference images (as video streams) along with the detection numbers. --- .../lib/ZoneMinder/ConfigData.pm.in | 10 +- src/CMakeLists.txt | 2 +- src/zm_fifo.cpp | 247 ++++++++++++++++++ src/zm_fifo.h | 61 +++++ src/zm_image.cpp | 57 +++- src/zm_image.h | 3 + src/zm_jpeg.cpp | 7 + src/zm_jpeg.h | 2 + src/zm_monitor.cpp | 13 +- src/zm_zone.cpp | 27 +- src/zma.cpp | 2 + src/zms.cpp | 10 +- 12 files changed, 419 insertions(+), 22 deletions(-) create mode 100644 src/zm_fifo.cpp create mode 100644 src/zm_fifo.h diff --git a/scripts/ZoneMinder/lib/ZoneMinder/ConfigData.pm.in b/scripts/ZoneMinder/lib/ZoneMinder/ConfigData.pm.in index 4d96f11db..495e53d29 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/ConfigData.pm.in +++ b/scripts/ZoneMinder/lib/ZoneMinder/ConfigData.pm.in @@ -3945,7 +3945,15 @@ our @options = ( help => q`This will affect how long a session will be valid for since the last request. Keeping this short helps prevent session hijacking. Keeping it long allows you to stay logged in longer without refreshing the view.`, type => $types{integer}, category => 'system', - } + }, + { + name => 'ZM_RECORD_DIAG_IMAGES_FIFO', + default => 'no', + description => ' Recording intermediate alarm diagnostic use fifo instead of files (faster)', + help => 'This tries to lessen the load of recording diag images by sending them to a memory FIFO pipe instead of creating each file.', + type => $types{boolean}, + category => 'logging', + }, ); our %options_hash = map { ( $_->{name}, $_ ) } @options; diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index e7e3157eb..e27c36d6a 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -4,7 +4,7 @@ configure_file(zm_config.h.in "${CMAKE_CURRENT_BINARY_DIR}/zm_config.h" @ONLY) # Group together all the source files that are used by all the binaries (zmc, zma, zmu, zms etc) -set(ZM_BIN_SRC_FILES zm_box.cpp zm_buffer.cpp zm_camera.cpp zm_comms.cpp zm_config.cpp zm_coord.cpp zm_curl_camera.cpp zm.cpp zm_db.cpp zm_logger.cpp zm_event.cpp zm_frame.cpp zm_eventstream.cpp zm_exception.cpp zm_file_camera.cpp zm_ffmpeg_input.cpp zm_ffmpeg_camera.cpp zm_group.cpp zm_image.cpp zm_jpeg.cpp zm_libvlc_camera.cpp zm_local_camera.cpp zm_monitor.cpp zm_monitorstream.cpp zm_ffmpeg.cpp zm_mpeg.cpp zm_packet.cpp zm_packetqueue.cpp zm_poly.cpp zm_regexp.cpp zm_remote_camera.cpp zm_remote_camera_http.cpp zm_remote_camera_nvsocket.cpp zm_remote_camera_rtsp.cpp zm_rtp.cpp zm_rtp_ctrl.cpp zm_rtp_data.cpp zm_rtp_source.cpp zm_rtsp.cpp zm_rtsp_auth.cpp zm_sdp.cpp zm_signal.cpp zm_stream.cpp zm_swscale.cpp zm_thread.cpp zm_time.cpp zm_timer.cpp zm_user.cpp zm_utils.cpp zm_video.cpp zm_videostore.cpp zm_zone.cpp zm_storage.cpp) +set(ZM_BIN_SRC_FILES zm_box.cpp zm_buffer.cpp zm_camera.cpp zm_comms.cpp zm_config.cpp zm_coord.cpp zm_curl_camera.cpp zm.cpp zm_db.cpp zm_logger.cpp zm_event.cpp zm_frame.cpp zm_eventstream.cpp zm_exception.cpp zm_file_camera.cpp zm_ffmpeg_input.cpp zm_ffmpeg_camera.cpp zm_group.cpp zm_image.cpp zm_jpeg.cpp zm_libvlc_camera.cpp zm_local_camera.cpp zm_monitor.cpp zm_monitorstream.cpp zm_ffmpeg.cpp zm_mpeg.cpp zm_packet.cpp zm_packetqueue.cpp zm_poly.cpp zm_regexp.cpp zm_remote_camera.cpp zm_remote_camera_http.cpp zm_remote_camera_nvsocket.cpp zm_remote_camera_rtsp.cpp zm_rtp.cpp zm_rtp_ctrl.cpp zm_rtp_data.cpp zm_rtp_source.cpp zm_rtsp.cpp zm_rtsp_auth.cpp zm_sdp.cpp zm_signal.cpp zm_stream.cpp zm_swscale.cpp zm_thread.cpp zm_time.cpp zm_timer.cpp zm_user.cpp zm_utils.cpp zm_video.cpp zm_videostore.cpp zm_zone.cpp zm_fifo.cpp zm_storage.cpp) # A fix for cmake recompiling the source files for every target. add_library(zm STATIC ${ZM_BIN_SRC_FILES}) diff --git a/src/zm_fifo.cpp b/src/zm_fifo.cpp new file mode 100644 index 000000000..538b696fa --- /dev/null +++ b/src/zm_fifo.cpp @@ -0,0 +1,247 @@ +#include +#include +#include +#include +#include +#include + +#include "zm.h" +#include "zm_db.h" +#include "zm_time.h" +#include "zm_mpeg.h" +#include "zm_signal.h" +#include "zm_monitor.h" +#include "zm_fifo.h" +#define RAW_BUFFER 512 +static bool zm_fifodbg_inited = false; +FILE *zm_fifodbg_log_fd = 0; +char zm_fifodbg_log[PATH_MAX] = ""; + +static bool zmFifoDbgOpen(){ + if (zm_fifodbg_log_fd) + fclose(zm_fifodbg_log_fd); + zm_fifodbg_log_fd = NULL; + signal(SIGPIPE, SIG_IGN); + FifoStream::fifo_create_if_missing(zm_fifodbg_log); + int fd = open(zm_fifodbg_log,O_WRONLY|O_NONBLOCK|O_TRUNC); + if (fd < 0) + return ( false ); + int res = flock(fd,LOCK_EX | LOCK_NB); + if (res < 0) + { + close(fd); + return ( false ); + } + zm_fifodbg_log_fd = fdopen(fd,"wb"); + if (zm_fifodbg_log_fd == NULL) + { + close(fd); + return ( false ); + } + return ( true ); +} +int zmFifoDbgInit(Monitor *monitor){ + zm_fifodbg_inited = true; + snprintf( zm_fifodbg_log, sizeof(zm_fifodbg_log), "%s/%d/dbgpipe.log", monitor->getStorage()->Path(), monitor->Id() ); + zmFifoDbgOpen(); + return 1; +} +void zmFifoDbgOutput( int hex, const char * const file, const int line, const int level, const char *fstring, ... ){ + char dbg_string[8192]; + va_list arg_ptr; + if (! zm_fifodbg_inited) + return; + if (! zm_fifodbg_log_fd && ! zmFifoDbgOpen()) + return; + + char *dbg_ptr = dbg_string; + va_start( arg_ptr, fstring ); + if ( hex ) + { + unsigned char *data = va_arg( arg_ptr, unsigned char * ); + int len = va_arg( arg_ptr, int ); + int i; + dbg_ptr += snprintf( dbg_ptr, sizeof(dbg_string)-(dbg_ptr-dbg_string), "%d:", len ); + for ( i = 0; i < len; i++ ) + { + dbg_ptr += snprintf( dbg_ptr, sizeof(dbg_string)-(dbg_ptr-dbg_string), " %02x", data[i] ); + } + } + else + { + dbg_ptr += vsnprintf( dbg_ptr, sizeof(dbg_string)-(dbg_ptr-dbg_string), fstring, arg_ptr ); + } + va_end(arg_ptr); + strncpy( dbg_ptr++, "\n", 1); + int res = fwrite( dbg_string, dbg_ptr-dbg_string, 1, zm_fifodbg_log_fd ); + if (res != 1){ + fclose(zm_fifodbg_log_fd); + zm_fifodbg_log_fd = NULL; + } + else + { + fflush(zm_fifodbg_log_fd); + } +} +bool FifoStream::sendRAWFrames(){ + static unsigned char buffer[RAW_BUFFER]; + int fd = open(stream_path,O_RDONLY); + if (fd < 0) + { + Error( "Can't open %s: %s", stream_path, strerror(errno)); + return( false ); + } + while( (bytes_read = read(fd,buffer,RAW_BUFFER)) ) + { + if (bytes_read == 0) + continue; + if (bytes_read < 0) + { + Error( "Problem during reading: %s", strerror(errno)); + close(fd); + return( false ); + } + if ( fwrite( buffer, bytes_read, 1, stdout ) != 1){ + Error( "Problem during writing: %s", strerror(errno)); + close(fd); + return( false ); + } + fflush( stdout ); + } + close(fd); + return ( true ); +} + +void FifoStream::file_create_if_missing(const char * path, bool is_fifo,bool delete_fake_fifo){ + static struct stat st; + if(stat(path,&st) == 0){ + + if (! is_fifo || S_ISFIFO(st.st_mode) || ! delete_fake_fifo) + return; + Debug(5, "Supposed to be a fifo pipe but isn't, unlinking: %s", path); + unlink(path); + } + int fd; + if (! is_fifo){ + Debug(5, "Creating non fifo file as requested: %s", path); + fd = open(path,O_CREAT|O_WRONLY,S_IRUSR|S_IWUSR); + close(fd); + return; + } + Debug(5, "Making fifo file of: %s", path); + mkfifo(path,S_IRUSR|S_IWUSR); +} +void FifoStream::fifo_create_if_missing(const char * path, bool delete_fake_fifo){ + file_create_if_missing(path,true,delete_fake_fifo); +} +bool FifoStream::sendMJEGFrames(){ + static unsigned char buffer[ZM_MAX_IMAGE_SIZE]; + int fd = open(stream_path,O_RDONLY); + if (fd < 0) + { + Error( "Can't open %s: %s", stream_path, strerror(errno)); + return( false ); + } + total_read = 0; + while( (bytes_read = read(fd,buffer+total_read,ZM_MAX_IMAGE_SIZE-total_read)) ) + { + if (bytes_read < 0) + { + Error( "Problem during reading: %s", strerror(errno)); + close(fd); + return( false ); + } + total_read+=bytes_read; + } + close(fd); + if (total_read == 0) + return( true ); + if (frame_count%frame_mod != 0) + return (true ); + if (fprintf( stdout, "--ZoneMinderFrame\r\n" ) < 0 ) + { + Error( "Problem during writing: %s", strerror(errno)); + return( false ); + } + + fprintf( stdout, "Content-Type: image/jpeg\r\n" ); + fprintf( stdout, "Content-Length: %d\r\n\r\n", total_read ); + if ( fwrite( buffer, total_read, 1, stdout ) != 1){ + Error( "Problem during reading: %s", strerror(errno)); + return( false ); + } + fprintf( stdout, "\r\n\r\n" ); + fflush( stdout); + last_frame_sent = TV_2_FLOAT( now ); + frame_count++; + return( true ); +} + +void FifoStream::setStreamStart( const char * path ){ + stream_path = strdup(path); +} +void FifoStream::setStreamStart( int monitor_id, const char * format ){ + char diag_path[PATH_MAX]; + const char * filename; + Monitor * monitor = Monitor::Load(monitor_id, false, Monitor::QUERY); + + if (! strcmp(format,"reference") ) + { + stream_type = MJPEG; + filename = "diagpipe-r.jpg"; + } + else if (! strcmp(format,"delta")) + { + filename = "diagpipe-d.jpg"; + stream_type = MJPEG; + } + else + { + stream_type = RAW; + filename = "dbgpipe.log"; + } + + snprintf( diag_path, sizeof(diag_path), "%s/%d/%s", monitor->getStorage()->Path(), monitor->Id(), filename ); + setStreamStart(diag_path); +} +void FifoStream::runStream(){ + if (stream_type == MJPEG) + fprintf( stdout, "Content-Type: multipart/x-mixed-replace;boundary=ZoneMinderFrame\r\n\r\n" ); + else + fprintf( stdout, "Content-Type: text/html\r\n\r\n" ); + + char lock_file[PATH_MAX]; + strcpy(lock_file,stream_path); + strcat(lock_file,".rlock"); + file_create_if_missing(lock_file,false); + int fd_lock = open(lock_file,O_RDONLY); + if (fd_lock < 0) + { + Error( "Can't open %s: %s", lock_file, strerror(errno)); + return; + } + int res = flock(fd_lock,LOCK_EX | LOCK_NB); + if (res < 0) + { + Error( "Flocking problem on %s: - %s", lock_file, strerror(errno) ); + close(fd_lock); + return; + } + + while( !zm_terminate ) + { + gettimeofday( &now, NULL ); + checkCommandQueue(); + if (stream_type == MJPEG) + { + if (! sendMJEGFrames()) + zm_terminate = true; + } + else + { + if (! sendRAWFrames()) + zm_terminate = true; + } + } + close(fd_lock); +} diff --git a/src/zm_fifo.h b/src/zm_fifo.h new file mode 100644 index 000000000..8d1b1ae8c --- /dev/null +++ b/src/zm_fifo.h @@ -0,0 +1,61 @@ +#ifndef ZM_FIFO_H +#define ZM_FIFO_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "zm.h" +#include "zm_image.h" +#include "zm_monitor.h" +#include "zm_stream.h" + + +#define zmFifoDbgPrintf(level,params...) {\ + zmFifoDbgOutput( 0, __FILE__, __LINE__, level, ##params );\ + } + +#ifndef ZM_DBG_OFF +#define FifoDebug(level,params...) zmFifoDbgPrintf(level,##params) +#else +#define FifoDebug(level,params...) +#endif +void zmFifoDbgOutput( int hex, const char * const file, const int line, const int level, const char *fstring, ... ) __attribute__ ((format(printf, 5, 6))); +int zmFifoDbgInit(Monitor * monitor); + +class FifoStream : public StreamBase +{ + +private: + char * stream_path; + int fd; + int total_read; + int bytes_read; + unsigned int frame_count; + static void file_create_if_missing(const char * path, bool is_fifo, bool delete_fake_fifo=true); + +protected: + typedef enum { MJPEG, RAW } StreamType; + StreamType stream_type; + bool sendMJEGFrames( ); + bool sendRAWFrames( ); + void processCommand( const CmdMsg *msg ) {}; + +public: + FifoStream(){ + + + } + static void fifo_create_if_missing(const char * path,bool delete_fake_fifo=true); + void setStreamStart( const char * path ); + void setStreamStart( int monitor_id, const char * format ); + + void runStream(); +}; +#endif diff --git a/src/zm_image.cpp b/src/zm_image.cpp index e58e40342..12c3da86e 100644 --- a/src/zm_image.cpp +++ b/src/zm_image.cpp @@ -24,6 +24,7 @@ #include "zm_rgb.h" #include "zm_ffmpeg.h" +#include #include #include @@ -957,7 +958,7 @@ cinfo->out_color_space = JCS_RGB; return( true ); } -// Multiple calling formats to permit inclusion (or not) of both quality_override and timestamp (exif), with suitable defaults. +// Multiple calling formats to permit inclusion (or not) of non blocking, quality_override and timestamp (exif), with suitable defaults. // Note quality=zero means default bool Image::WriteJpeg(const char *filename, int quality_override) const { @@ -966,33 +967,71 @@ bool Image::WriteJpeg(const char *filename, int quality_override) const { bool Image::WriteJpeg(const char *filename) const { return Image::WriteJpeg(filename, 0, (timeval){0,0}); } +bool Image::WriteJpeg(const char *filename, bool on_blocking_abort) const { + return Image::WriteJpeg(filename, 0, (timeval){0,0}, on_blocking_abort); +} bool Image::WriteJpeg(const char *filename, struct timeval timestamp) const { return Image::WriteJpeg(filename, 0, timestamp); } bool Image::WriteJpeg(const char *filename, int quality_override, struct timeval timestamp) const { + return Image::WriteJpeg(filename, quality_override, timestamp, false); +} +bool Image::WriteJpeg(const char *filename, int quality_override, struct timeval timestamp, bool on_blocking_abort) const { if ( config.colour_jpeg_files && colours == ZM_COLOUR_GRAY8 ) { Image temp_image(*this); temp_image.Colourise(ZM_COLOUR_RGB24, ZM_SUBPIX_ORDER_RGB); - return temp_image.WriteJpeg(filename, quality_override, timestamp); + return temp_image.WriteJpeg(filename, quality_override, timestamp, on_blocking_abort); } int quality = quality_override?quality_override:config.jpeg_file_quality; struct jpeg_compress_struct *cinfo = writejpg_ccinfo[quality]; + FILE *outfile =NULL; + static int raw_fd = 0; + bool need_create_comp = false; + raw_fd = 0; if ( !cinfo ) { cinfo = writejpg_ccinfo[quality] = new jpeg_compress_struct; cinfo->err = jpeg_std_error( &jpg_err.pub ); - jpg_err.pub.error_exit = zm_jpeg_error_exit; - jpg_err.pub.emit_message = zm_jpeg_emit_message; jpeg_create_compress( cinfo ); + need_create_comp=true; + } + if (! on_blocking_abort) { + jpg_err.pub.error_exit = zm_jpeg_error_exit; + jpg_err.pub.emit_message = zm_jpeg_emit_message; + } else { + jpg_err.pub.error_exit = zm_jpeg_error_silent; + jpg_err.pub.emit_message = zm_jpeg_emit_silence; + if (setjmp( jpg_err.setjmp_buffer ) ) { + jpeg_abort_compress( cinfo ); + Debug( 5, "Aborted a write mid-stream and %s and %d", (outfile == NULL) ? "closing file" : "file not opened", raw_fd ); + if (raw_fd) + close(raw_fd); + if (outfile) + fclose( outfile ); + return ( false ); + } + } + if (need_create_comp) + jpeg_create_compress( cinfo ); + + if (! on_blocking_abort) { + if ( (outfile = fopen(filename, "wb")) == NULL ) { + Error( "Can't open %s for writing: %s", filename, strerror(errno) ); + return false; + } + } else { + raw_fd = open(filename,O_WRONLY|O_NONBLOCK|O_CREAT|O_TRUNC,S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH); + if (raw_fd < 0) + return ( false ); + outfile = fdopen(raw_fd,"wb"); + if (outfile == NULL) { + close(raw_fd); + return( false ); + } } - FILE *outfile; - if ( (outfile = fopen(filename, "wb")) == NULL ) { - Error("Can't open %s: %s", filename, strerror(errno)); - return false; - } jpeg_stdio_dest( cinfo, outfile ); cinfo->image_width = width; /* image width and height, in pixels */ diff --git a/src/zm_image.h b/src/zm_image.h index 7133d0e35..d90d7c358 100644 --- a/src/zm_image.h +++ b/src/zm_image.h @@ -218,9 +218,12 @@ public: bool ReadJpeg( const char *filename, unsigned int p_colours, unsigned int p_subpixelorder); bool WriteJpeg ( const char *filename) const; + bool WriteJpeg ( const char *filename, bool on_blocking_abort) const; bool WriteJpeg ( const char *filename, int quality_override ) const; bool WriteJpeg ( const char *filename, struct timeval timestamp ) const; bool WriteJpeg ( const char *filename, int quality_override, struct timeval timestamp ) const; + bool WriteJpeg ( const char *filename, int quality_override, struct timeval timestamp, bool on_blocking_abort ) const; + bool DecodeJpeg( const JOCTET *inbuffer, int inbuffer_size, unsigned int p_colours, unsigned int p_subpixelorder); bool EncodeJpeg( JOCTET *outbuffer, int *outbuffer_size, int quality_override=0 ) const; diff --git a/src/zm_jpeg.cpp b/src/zm_jpeg.cpp index cddf086c5..8e8899227 100644 --- a/src/zm_jpeg.cpp +++ b/src/zm_jpeg.cpp @@ -30,6 +30,13 @@ extern "C" static int jpeg_err_count = 0; +void zm_jpeg_error_silent( j_common_ptr cinfo ){ + zm_error_ptr zmerr = (zm_error_ptr)cinfo->err; + longjmp( zmerr->setjmp_buffer, 1 ); +} +void zm_jpeg_emit_silence( j_common_ptr cinfo, int msg_level ){ +} + void zm_jpeg_error_exit( j_common_ptr cinfo ) { static char buffer[JMSG_LENGTH_MAX]; diff --git a/src/zm_jpeg.h b/src/zm_jpeg.h index d0348dd3e..f0b4a355c 100644 --- a/src/zm_jpeg.h +++ b/src/zm_jpeg.h @@ -38,6 +38,8 @@ struct zm_error_mgr typedef struct zm_error_mgr *zm_error_ptr; +void zm_jpeg_error_silent( j_common_ptr cinfo ); +void zm_jpeg_emit_silence( j_common_ptr cinfo, int msg_level ); void zm_jpeg_error_exit( j_common_ptr cinfo ); void zm_jpeg_emit_message( j_common_ptr cinfo, int msg_level ); diff --git a/src/zm_monitor.cpp b/src/zm_monitor.cpp index 2987d08f2..99416b658 100644 --- a/src/zm_monitor.cpp +++ b/src/zm_monitor.cpp @@ -44,6 +44,7 @@ #if HAVE_LIBAVFORMAT #include "zm_ffmpeg_camera.h" #endif // HAVE_LIBAVFORMAT +#include "zm_fifo.h" #if HAVE_LIBVLC #include "zm_libvlc_camera.h" #endif // HAVE_LIBVLC @@ -517,8 +518,12 @@ Monitor::Monitor( ReloadLinkedMonitors(p_linked_monitors); if ( config.record_diag_images ) { - diag_path_r = stringtf("%s/%d/diag-r.jpg", storage->Path(), id); - diag_path_d = stringtf("%s/%d/diag-d.jpg", storage->Path(), id); + diag_path_r = stringtf(config.record_diag_images_fifo ? "%s/%d/diagpipe-r.jpg" : "%s/%d/diag-r.jpg", storage->Path(), id); + diag_path_d = stringtf(config.record_diag_images_fifo ? "%s/%d/diagpipe-d.jpg" : "%s/%d/diag-d.jpg", storage->Path(), id); + if (config.record_diag_images_fifo){ + FifoStream::fifo_create_if_missing(diag_path_r.c_str()); + FifoStream::fifo_create_if_missing(diag_path_d.c_str()); + } } } // end if purpose == ANALYSIS @@ -2614,8 +2619,8 @@ unsigned int Monitor::DetectMotion(const Image &comp_image, Event::StringSet &zo ref_image.Delta(comp_image, &delta_image); if ( config.record_diag_images ) { - ref_image.WriteJpeg(diag_path_r.c_str()); - delta_image.WriteJpeg(diag_path_d.c_str()); + ref_image.WriteJpeg(diag_path_r.c_str(), config.record_diag_images_fifo); + delta_image.WriteJpeg(diag_path_d.c_str(), config.record_diag_images_fifo); } // Blank out all exclusion zones diff --git a/src/zm_zone.cpp b/src/zm_zone.cpp index 6d4ae15e3..de2f8003d 100644 --- a/src/zm_zone.cpp +++ b/src/zm_zone.cpp @@ -24,6 +24,7 @@ #include "zm_zone.h" #include "zm_image.h" #include "zm_monitor.h" +#include "zm_fifo.h" void Zone::Setup( @@ -112,8 +113,10 @@ void Zone::Setup( } if ( config.record_diag_images ) { - snprintf(diag_path, sizeof(diag_path), "%s/diag-%d-poly.jpg", monitor->getStorage()->Path(), id); - pg_image->WriteJpeg(diag_path); + snprintf(diag_path, sizeof(diag_path), config.record_diag_images_fifo ? "%s/diagpipe-%d-poly.jpg" : "%s/diag-%d-poly.jpg", monitor->getStorage()->Path(), id); + if (config.record_diag_images_fifo) + FifoStream::fifo_create_if_missing(diag_path); + pg_image->WriteJpeg(diag_path, config.record_diag_images_fifo); } else { diag_path[0] = 0; } @@ -232,7 +235,7 @@ bool Zone::CheckAlarms(const Image *delta_image) { std_alarmedpixels(diff_image, pg_image, &alarm_pixels, &pixel_diff_count); if ( config.record_diag_images ) - diff_image->WriteJpeg(diag_path); + diff_image->WriteJpeg(diag_path, config.record_diag_images_fifo); if ( pixel_diff_count && alarm_pixels ) pixel_diff = pixel_diff_count/alarm_pixels; @@ -240,6 +243,10 @@ bool Zone::CheckAlarms(const Image *delta_image) { Debug(5, "Got %d alarmed pixels, need %d -> %d, avg pixel diff %d", alarm_pixels, min_alarm_pixels, max_alarm_pixels, pixel_diff); + if (config.record_diag_images_fifo) + FifoDebug( 5, "{\"zone\":%d,\"type\":\"ALRM\",\"pixels\":%d,\"avg_diff\":%d}", id,alarm_pixels, pixel_diff ); + + if ( alarm_pixels ) { if ( min_alarm_pixels && (alarm_pixels < (unsigned int)min_alarm_pixels) ) { /* Not enough pixels alarmed */ @@ -316,11 +323,14 @@ bool Zone::CheckAlarms(const Image *delta_image) { } if ( config.record_diag_images ) - diff_image->WriteJpeg(diag_path); + diff_image->WriteJpeg(diag_path, config.record_diag_images_fifo); Debug(5, "Got %d filtered pixels, need %d -> %d", alarm_filter_pixels, min_filter_pixels, max_filter_pixels); + if (config.record_diag_images_fifo) + FifoDebug( 5, "{\"zone\":%d,\"type\":\"FILT\",\"pixels\":%d}", id, alarm_filter_pixels ); + if ( alarm_filter_pixels ) { if ( min_filter_pixels && (alarm_filter_pixels < min_filter_pixels) ) { /* Not enough pixels alarmed */ @@ -542,7 +552,7 @@ bool Zone::CheckAlarms(const Image *delta_image) { } } if ( config.record_diag_images ) - diff_image->WriteJpeg(diag_path); + diff_image->WriteJpeg(diag_path, config.record_diag_images_fifo); if ( !alarm_blobs ) { return false; @@ -551,6 +561,9 @@ bool Zone::CheckAlarms(const Image *delta_image) { Debug(5, "Got %d raw blob pixels, %d raw blobs, need %d -> %d, %d -> %d", alarm_blob_pixels, alarm_blobs, min_blob_pixels, max_blob_pixels, min_blobs, max_blobs); + if (config.record_diag_images_fifo) + FifoDebug( 5, "{\"zone\":%d,\"type\":\"RBLB\",\"pixels\":%d,\"blobs\":%d}", id, alarm_blob_pixels, alarm_blobs ); + // Now eliminate blobs under the threshold for ( int i = 1; i < WHITE; i++ ) { BlobStats *bs = &blob_stats[i]; @@ -587,10 +600,12 @@ bool Zone::CheckAlarms(const Image *delta_image) { } // end if bs_count } // end for i < WHITE if ( config.record_diag_images ) - diff_image->WriteJpeg(diag_path); + diff_image->WriteJpeg(diag_path, config.record_diag_images_fifo); Debug(5, "Got %d blob pixels, %d blobs, need %d -> %d, %d -> %d", alarm_blob_pixels, alarm_blobs, min_blob_pixels, max_blob_pixels, min_blobs, max_blobs); + if (config.record_diag_images_fifo) + FifoDebug( 5, "{\"zone\":%d,\"type\":\"FBLB\",\"pixels\":%d,\"blobs\":%d}", id, alarm_blob_pixels, alarm_blobs ); if ( alarm_blobs ) { if ( min_blobs && (alarm_blobs < min_blobs) ) { diff --git a/src/zma.cpp b/src/zma.cpp index 33dedbe54..a0cb048bf 100644 --- a/src/zma.cpp +++ b/src/zma.cpp @@ -56,6 +56,7 @@ behind. #include "zm_db.h" #include "zm_signal.h" #include "zm_monitor.h" +#include "zm_fifo.h" void Usage() { fprintf(stderr, "zma -m \n"); @@ -129,6 +130,7 @@ int main( int argc, char *argv[] ) { hwcaps_detect(); Monitor *monitor = Monitor::Load(id, true, Monitor::ANALYSIS); + zmFifoDbgInit( monitor ); if ( monitor ) { Info("In mode %d/%d, warming up", monitor->GetFunction(), monitor->Enabled()); diff --git a/src/zms.cpp b/src/zms.cpp index 0a3712938..6042dbef3 100644 --- a/src/zms.cpp +++ b/src/zms.cpp @@ -28,6 +28,7 @@ #include "zm_monitor.h" #include "zm_monitorstream.h" #include "zm_eventstream.h" +#include "zm_fifo.h" bool ValidateAccess( User *user, int mon_id ) { bool allowed = true; @@ -54,7 +55,7 @@ int main( int argc, const char *argv[] ) { srand( getpid() * time( 0 ) ); - enum { ZMS_UNKNOWN, ZMS_MONITOR, ZMS_EVENT } source = ZMS_UNKNOWN; + enum { ZMS_UNKNOWN, ZMS_MONITOR, ZMS_EVENT, ZMS_FIFO } source = ZMS_UNKNOWN; enum { ZMS_JPEG, ZMS_MPEG, ZMS_RAW, ZMS_ZIP, ZMS_SINGLE } mode = ZMS_JPEG; char format[32] = ""; int monitor_id = 0; @@ -110,6 +111,8 @@ int main( int argc, const char *argv[] ) { value = (char *)""; if ( !strcmp(name, "source") ) { source = !strcmp(value, "event")?ZMS_EVENT:ZMS_MONITOR; + if (! strcmp( value,"fifo") ) + source = ZMS_FIFO; } else if ( !strcmp(name, "mode") ) { mode = !strcmp(value, "jpeg")?ZMS_JPEG:ZMS_MPEG; mode = !strcmp(value, "raw")?ZMS_RAW:mode; @@ -274,6 +277,11 @@ int main( int argc, const char *argv[] ) { #endif // HAVE_LIBAVCODEC } stream.runStream(); + } else if (source == ZMS_FIFO ) { + FifoStream stream; + stream.setStreamMaxFPS( maxfps ); + stream.setStreamStart( monitor_id, format ); + stream.runStream(); } else if ( source == ZMS_EVENT ) { if ( ! event_id ) { Fatal( "Can't view an event without specifying an event_id." ); From 96f578f1bde6330e15dc8b5a497477956945452c Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Thu, 16 May 2019 15:37:37 -0400 Subject: [PATCH 12/12] If running a custom run state, show the state instead of Running. Also select the running state in the state change popup. (#2604) --- web/skins/classic/includes/functions.php | 13 ++++++++++--- web/skins/classic/views/state.php | 4 +++- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/web/skins/classic/includes/functions.php b/web/skins/classic/includes/functions.php index 2d71b2397..40bf18b30 100644 --- a/web/skins/classic/includes/functions.php +++ b/web/skins/classic/includes/functions.php @@ -233,7 +233,12 @@ function getNavBarHTML($reload = null) { ob_start(); if ( $running == null ) $running = daemonCheck(); - $status = $running?translate('Running'):translate('Stopped'); + if ( $running ) { + $state = dbFetchOne('SELECT Name FROM States WHERE isActive=1', 'Name'); + if ( $state == 'default' ) + $state = ''; + } + $status = $running ? ($state ? $state : translate('Running')) : translate('Stopped'); ?>