├── .gitignore ├── AgentDVRPlugins.sln ├── Barcode ├── Barcode.csproj ├── Main.cs ├── README.md ├── config.cs └── json │ └── config_en.json ├── Demo ├── Demo.csproj ├── Main.cs ├── README.md ├── config.cs ├── json │ └── config_en.json └── readme.txt ├── Gain ├── Gain.csproj ├── Main.cs ├── README.md ├── config.cs └── json │ └── config_en.json ├── LICENSE ├── Listen ├── AudioFeatureBuffer.cs ├── Listen.csproj ├── Main.cs ├── MelSpectrogram.cs ├── README.md ├── config.cs ├── json │ └── config_en.json └── onnx │ ├── yamnet.onnx │ └── yamnet_class_map.csv ├── LiveDelay ├── DelayBuffer.cs ├── LiveDelay.csproj ├── Main.cs ├── README.md ├── config.cs ├── json │ └── config_en.json └── readme.txt ├── PluginUtils ├── Areas.cs ├── Extensions.cs ├── ICamera.cs ├── IMicrophone.cs ├── IPlugin.cs ├── Line2D.cs ├── PluginBase.cs ├── PluginUtils.projitems ├── PluginUtils.shproj ├── Points.cs ├── PrecomputedIndicesCache.cs ├── ResourceLoader.cs ├── ResultInfo.cs └── Utils.cs ├── README.md ├── TensorFlow ├── EventHandlers.cs ├── ITensorProcessor.cs ├── MainClass.cs ├── Processors │ ├── InceptionProcessor.cs │ ├── MaskRCNNProcessor.cs │ ├── MultiBoxGraphProcessor.cs │ ├── ProcessorBase.cs │ └── ResnetProcessor.cs ├── README.md ├── TensorFlow.csproj ├── config.cs ├── json │ └── config_en.json └── readme.txt ├── Test ├── Program.cs └── Test.csproj └── Weather ├── Main.cs ├── Models.cs ├── README.md ├── Weather.csproj ├── config.cs ├── icons ├── 01d.png ├── 01n.png ├── 02d.png ├── 02n.png ├── 03d.png ├── 03n.png ├── 04d.png ├── 04n.png ├── 09d.png ├── 09n.png ├── 10d.png ├── 10n.png ├── 11d.png ├── 11n.png ├── 13d.png ├── 13n.png ├── 50d.png └── 50n.png ├── json └── config_en.json └── readme.txt /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.rsuser 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Mono auto generated files 17 | mono_crash.* 18 | 19 | # Build results 20 | [Dd]ebug/ 21 | [Dd]ebugPublic/ 22 | [Rr]elease/ 23 | [Rr]eleases/ 24 | x64/ 25 | x86/ 26 | [Aa][Rr][Mm]/ 27 | [Aa][Rr][Mm]64/ 28 | bld/ 29 | [Bb]in/ 30 | [Oo]bj/ 31 | [Ll]og/ 32 | [Ll]ogs/ 33 | 34 | # Visual Studio 2015/2017 cache/options directory 35 | .vs/ 36 | # Uncomment if you have tasks that create the project's static files in wwwroot 37 | #wwwroot/ 38 | 39 | # Visual Studio 2017 auto generated files 40 | Generated\ Files/ 41 | 42 | # MSTest test Results 43 | [Tt]est[Rr]esult*/ 44 | [Bb]uild[Ll]og.* 45 | 46 | # NUnit 47 | *.VisualState.xml 48 | TestResult.xml 49 | nunit-*.xml 50 | 51 | # Build Results of an ATL Project 52 | [Dd]ebugPS/ 53 | [Rr]eleasePS/ 54 | dlldata.c 55 | 56 | # Benchmark Results 57 | BenchmarkDotNet.Artifacts/ 58 | 59 | # .NET Core 60 | project.lock.json 61 | project.fragment.lock.json 62 | artifacts/ 63 | 64 | # StyleCop 65 | StyleCopReport.xml 66 | 67 | # Files built by Visual Studio 68 | *_i.c 69 | *_p.c 70 | *_h.h 71 | *.ilk 72 | *.meta 73 | *.obj 74 | *.iobj 75 | *.pch 76 | *.pdb 77 | *.ipdb 78 | *.pgc 79 | *.pgd 80 | *.rsp 81 | *.sbr 82 | *.tlb 83 | *.tli 84 | *.tlh 85 | *.tmp 86 | *.tmp_proj 87 | *_wpftmp.csproj 88 | *.log 89 | *.vspscc 90 | *.vssscc 91 | .builds 92 | *.pidb 93 | *.svclog 94 | *.scc 95 | 96 | # Chutzpah Test files 97 | _Chutzpah* 98 | 99 | # Visual C++ cache files 100 | ipch/ 101 | *.aps 102 | *.ncb 103 | *.opendb 104 | *.opensdf 105 | *.sdf 106 | *.cachefile 107 | *.VC.db 108 | *.VC.VC.opendb 109 | 110 | # Visual Studio profiler 111 | *.psess 112 | *.vsp 113 | *.vspx 114 | *.sap 115 | 116 | # Visual Studio Trace Files 117 | *.e2e 118 | 119 | # TFS 2012 Local Workspace 120 | $tf/ 121 | 122 | # Guidance Automation Toolkit 123 | *.gpState 124 | 125 | # ReSharper is a .NET coding add-in 126 | _ReSharper*/ 127 | *.[Rr]e[Ss]harper 128 | *.DotSettings.user 129 | 130 | # TeamCity is a build add-in 131 | _TeamCity* 132 | 133 | # DotCover is a Code Coverage Tool 134 | *.dotCover 135 | 136 | # AxoCover is a Code Coverage Tool 137 | .axoCover/* 138 | !.axoCover/settings.json 139 | 140 | # Visual Studio code coverage results 141 | *.coverage 142 | *.coveragexml 143 | 144 | # NCrunch 145 | _NCrunch_* 146 | .*crunch*.local.xml 147 | nCrunchTemp_* 148 | 149 | # MightyMoose 150 | *.mm.* 151 | AutoTest.Net/ 152 | 153 | # Web workbench (sass) 154 | .sass-cache/ 155 | 156 | # Installshield output folder 157 | [Ee]xpress/ 158 | 159 | # DocProject is a documentation generator add-in 160 | DocProject/buildhelp/ 161 | DocProject/Help/*.HxT 162 | DocProject/Help/*.HxC 163 | DocProject/Help/*.hhc 164 | DocProject/Help/*.hhk 165 | DocProject/Help/*.hhp 166 | DocProject/Help/Html2 167 | DocProject/Help/html 168 | 169 | # Click-Once directory 170 | publish/ 171 | 172 | # Publish Web Output 173 | *.[Pp]ublish.xml 174 | *.azurePubxml 175 | # Note: Comment the next line if you want to checkin your web deploy settings, 176 | # but database connection strings (with potential passwords) will be unencrypted 177 | *.pubxml 178 | *.publishproj 179 | 180 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 181 | # checkin your Azure Web App publish settings, but sensitive information contained 182 | # in these scripts will be unencrypted 183 | PublishScripts/ 184 | 185 | # NuGet Packages 186 | *.nupkg 187 | # NuGet Symbol Packages 188 | *.snupkg 189 | # The packages folder can be ignored because of Package Restore 190 | **/[Pp]ackages/* 191 | # except build/, which is used as an MSBuild target. 192 | !**/[Pp]ackages/build/ 193 | # Uncomment if necessary however generally it will be regenerated when needed 194 | #!**/[Pp]ackages/repositories.config 195 | # NuGet v3's project.json files produces more ignorable files 196 | *.nuget.props 197 | *.nuget.targets 198 | 199 | # Microsoft Azure Build Output 200 | csx/ 201 | *.build.csdef 202 | 203 | # Microsoft Azure Emulator 204 | ecf/ 205 | rcf/ 206 | 207 | # Windows Store app package directories and files 208 | AppPackages/ 209 | BundleArtifacts/ 210 | Package.StoreAssociation.xml 211 | _pkginfo.txt 212 | *.appx 213 | *.appxbundle 214 | *.appxupload 215 | 216 | # Visual Studio cache files 217 | # files ending in .cache can be ignored 218 | *.[Cc]ache 219 | # but keep track of directories ending in .cache 220 | !?*.[Cc]ache/ 221 | 222 | # Others 223 | ClientBin/ 224 | ~$* 225 | *~ 226 | *.dbmdl 227 | *.dbproj.schemaview 228 | *.jfm 229 | *.pfx 230 | *.publishsettings 231 | orleans.codegen.cs 232 | 233 | # Including strong name files can present a security risk 234 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 235 | #*.snk 236 | 237 | # Since there are multiple workflows, uncomment next line to ignore bower_components 238 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 239 | #bower_components/ 240 | 241 | # RIA/Silverlight projects 242 | Generated_Code/ 243 | 244 | # Backup & report files from converting an old project file 245 | # to a newer Visual Studio version. Backup files are not needed, 246 | # because we have git ;-) 247 | _UpgradeReport_Files/ 248 | Backup*/ 249 | UpgradeLog*.XML 250 | UpgradeLog*.htm 251 | ServiceFabricBackup/ 252 | *.rptproj.bak 253 | 254 | # SQL Server files 255 | *.mdf 256 | *.ldf 257 | *.ndf 258 | 259 | # Business Intelligence projects 260 | *.rdl.data 261 | *.bim.layout 262 | *.bim_*.settings 263 | *.rptproj.rsuser 264 | *- [Bb]ackup.rdl 265 | *- [Bb]ackup ([0-9]).rdl 266 | *- [Bb]ackup ([0-9][0-9]).rdl 267 | 268 | # Microsoft Fakes 269 | FakesAssemblies/ 270 | 271 | # GhostDoc plugin setting file 272 | *.GhostDoc.xml 273 | 274 | # Node.js Tools for Visual Studio 275 | .ntvs_analysis.dat 276 | node_modules/ 277 | 278 | # Visual Studio 6 build log 279 | *.plg 280 | 281 | # Visual Studio 6 workspace options file 282 | *.opt 283 | 284 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 285 | *.vbw 286 | 287 | # Visual Studio LightSwitch build output 288 | **/*.HTMLClient/GeneratedArtifacts 289 | **/*.DesktopClient/GeneratedArtifacts 290 | **/*.DesktopClient/ModelManifest.xml 291 | **/*.Server/GeneratedArtifacts 292 | **/*.Server/ModelManifest.xml 293 | _Pvt_Extensions 294 | 295 | # Paket dependency manager 296 | .paket/paket.exe 297 | paket-files/ 298 | 299 | # FAKE - F# Make 300 | .fake/ 301 | 302 | # CodeRush personal settings 303 | .cr/personal 304 | 305 | # Python Tools for Visual Studio (PTVS) 306 | __pycache__/ 307 | *.pyc 308 | 309 | # Cake - Uncomment if you are using it 310 | # tools/** 311 | # !tools/packages.config 312 | 313 | # Tabs Studio 314 | *.tss 315 | 316 | # Telerik's JustMock configuration file 317 | *.jmconfig 318 | 319 | # BizTalk build output 320 | *.btp.cs 321 | *.btm.cs 322 | *.odx.cs 323 | *.xsd.cs 324 | 325 | # OpenCover UI analysis results 326 | OpenCover/ 327 | 328 | # Azure Stream Analytics local run output 329 | ASALocalRun/ 330 | 331 | # MSBuild Binary and Structured Log 332 | *.binlog 333 | 334 | # NVidia Nsight GPU debugger configuration file 335 | *.nvuser 336 | 337 | # MFractors (Xamarin productivity tool) working folder 338 | .mfractor/ 339 | 340 | # Local History for Visual Studio 341 | .localhistory/ 342 | 343 | # BeatPulse healthcheck temp database 344 | healthchecksdb 345 | 346 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 347 | MigrationBackup/ 348 | 349 | # Ionide (cross platform F# VS Code tools) working folder 350 | .ionide/ 351 | -------------------------------------------------------------------------------- /AgentDVRPlugins.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.0.31903.59 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Barcode", "Barcode\Barcode.csproj", "{901697A9-455E-40AF-AEC2-A80EF90631BF}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Demo", "Demo\Demo.csproj", "{2FF8F0BE-304F-456E-9891-924D10553C57}" 9 | EndProject 10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Gain", "Gain\Gain.csproj", "{78A4154B-7DFA-43B1-A949-A3C3A89C4F35}" 11 | EndProject 12 | Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "PluginUtils", "PluginUtils\PluginUtils.shproj", "{C44A7120-65D6-4C1C-8DB6-CCF543AC496D}" 13 | EndProject 14 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Test", "Test\Test.csproj", "{5680B49A-19DF-41F3-A994-8EA8828457AF}" 15 | ProjectSection(ProjectDependencies) = postProject 16 | {78A4154B-7DFA-43B1-A949-A3C3A89C4F35} = {78A4154B-7DFA-43B1-A949-A3C3A89C4F35} 17 | {901697A9-455E-40AF-AEC2-A80EF90631BF} = {901697A9-455E-40AF-AEC2-A80EF90631BF} 18 | {2FF8F0BE-304F-456E-9891-924D10553C57} = {2FF8F0BE-304F-456E-9891-924D10553C57} 19 | EndProjectSection 20 | EndProject 21 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TensorFlow", "TensorFlow\TensorFlow.csproj", "{F6B46FBC-8AA5-45A1-B422-712720FC7168}" 22 | EndProject 23 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Listen", "Listen\Listen.csproj", "{4E9181F6-E8A0-4714-970A-FC3629600C44}" 24 | EndProject 25 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LiveDelay", "LiveDelay\LiveDelay.csproj", "{1A501145-8EE5-4D47-A996-C72973070593}" 26 | EndProject 27 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Weather", "Weather\Weather.csproj", "{E6859106-3404-4929-9C83-628822D9E158}" 28 | EndProject 29 | Global 30 | GlobalSection(SharedMSBuildProjectFiles) = preSolution 31 | PluginUtils\PluginUtils.projitems*{1a501145-8ee5-4d47-a996-c72973070593}*SharedItemsImports = 5 32 | PluginUtils\PluginUtils.projitems*{2ff8f0be-304f-456e-9891-924d10553c57}*SharedItemsImports = 5 33 | PluginUtils\PluginUtils.projitems*{4e9181f6-e8a0-4714-970a-fc3629600c44}*SharedItemsImports = 5 34 | PluginUtils\PluginUtils.projitems*{78a4154b-7dfa-43b1-a949-a3c3a89c4f35}*SharedItemsImports = 5 35 | PluginUtils\PluginUtils.projitems*{901697a9-455e-40af-aec2-a80ef90631bf}*SharedItemsImports = 5 36 | PluginUtils\PluginUtils.projitems*{c44a7120-65d6-4c1c-8db6-ccf543ac496d}*SharedItemsImports = 13 37 | PluginUtils\PluginUtils.projitems*{e6859106-3404-4929-9c83-628822d9e158}*SharedItemsImports = 5 38 | PluginUtils\PluginUtils.projitems*{f6b46fbc-8aa5-45a1-b422-712720fc7168}*SharedItemsImports = 5 39 | EndGlobalSection 40 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 41 | Debug|Any CPU = Debug|Any CPU 42 | Release|Any CPU = Release|Any CPU 43 | EndGlobalSection 44 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 45 | {901697A9-455E-40AF-AEC2-A80EF90631BF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 46 | {901697A9-455E-40AF-AEC2-A80EF90631BF}.Debug|Any CPU.Build.0 = Debug|Any CPU 47 | {901697A9-455E-40AF-AEC2-A80EF90631BF}.Release|Any CPU.ActiveCfg = Release|Any CPU 48 | {901697A9-455E-40AF-AEC2-A80EF90631BF}.Release|Any CPU.Build.0 = Release|Any CPU 49 | {2FF8F0BE-304F-456E-9891-924D10553C57}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 50 | {2FF8F0BE-304F-456E-9891-924D10553C57}.Debug|Any CPU.Build.0 = Debug|Any CPU 51 | {2FF8F0BE-304F-456E-9891-924D10553C57}.Release|Any CPU.ActiveCfg = Release|Any CPU 52 | {2FF8F0BE-304F-456E-9891-924D10553C57}.Release|Any CPU.Build.0 = Release|Any CPU 53 | {78A4154B-7DFA-43B1-A949-A3C3A89C4F35}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 54 | {78A4154B-7DFA-43B1-A949-A3C3A89C4F35}.Debug|Any CPU.Build.0 = Debug|Any CPU 55 | {78A4154B-7DFA-43B1-A949-A3C3A89C4F35}.Release|Any CPU.ActiveCfg = Release|Any CPU 56 | {78A4154B-7DFA-43B1-A949-A3C3A89C4F35}.Release|Any CPU.Build.0 = Release|Any CPU 57 | {5680B49A-19DF-41F3-A994-8EA8828457AF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 58 | {5680B49A-19DF-41F3-A994-8EA8828457AF}.Debug|Any CPU.Build.0 = Debug|Any CPU 59 | {5680B49A-19DF-41F3-A994-8EA8828457AF}.Release|Any CPU.ActiveCfg = Release|Any CPU 60 | {5680B49A-19DF-41F3-A994-8EA8828457AF}.Release|Any CPU.Build.0 = Release|Any CPU 61 | {F6B46FBC-8AA5-45A1-B422-712720FC7168}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 62 | {F6B46FBC-8AA5-45A1-B422-712720FC7168}.Debug|Any CPU.Build.0 = Debug|Any CPU 63 | {F6B46FBC-8AA5-45A1-B422-712720FC7168}.Release|Any CPU.ActiveCfg = Release|Any CPU 64 | {F6B46FBC-8AA5-45A1-B422-712720FC7168}.Release|Any CPU.Build.0 = Release|Any CPU 65 | {4E9181F6-E8A0-4714-970A-FC3629600C44}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 66 | {4E9181F6-E8A0-4714-970A-FC3629600C44}.Debug|Any CPU.Build.0 = Debug|Any CPU 67 | {4E9181F6-E8A0-4714-970A-FC3629600C44}.Release|Any CPU.ActiveCfg = Release|Any CPU 68 | {4E9181F6-E8A0-4714-970A-FC3629600C44}.Release|Any CPU.Build.0 = Release|Any CPU 69 | {1A501145-8EE5-4D47-A996-C72973070593}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 70 | {1A501145-8EE5-4D47-A996-C72973070593}.Debug|Any CPU.Build.0 = Debug|Any CPU 71 | {1A501145-8EE5-4D47-A996-C72973070593}.Release|Any CPU.ActiveCfg = Release|Any CPU 72 | {1A501145-8EE5-4D47-A996-C72973070593}.Release|Any CPU.Build.0 = Release|Any CPU 73 | {E6859106-3404-4929-9C83-628822D9E158}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 74 | {E6859106-3404-4929-9C83-628822D9E158}.Debug|Any CPU.Build.0 = Debug|Any CPU 75 | {E6859106-3404-4929-9C83-628822D9E158}.Release|Any CPU.ActiveCfg = Release|Any CPU 76 | {E6859106-3404-4929-9C83-628822D9E158}.Release|Any CPU.Build.0 = Release|Any CPU 77 | EndGlobalSection 78 | GlobalSection(SolutionProperties) = preSolution 79 | HideSolutionNode = FALSE 80 | EndGlobalSection 81 | GlobalSection(ExtensibilityGlobals) = postSolution 82 | SolutionGuid = {E840E422-2BD2-4F86-AC0C-02714034DF44} 83 | EndGlobalSection 84 | EndGlobal 85 | -------------------------------------------------------------------------------- /Barcode/Barcode.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | Plugins 4 | Barcode 5 | net9.0 6 | DeveloperInABox 7 | Video Surveillance Software 8 | 2025 DeveloperInABox 9 | false 10 | 1.2.1.0 11 | 1.2.1.0 12 | true 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | true 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /Barcode/Main.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Drawing; 4 | using System.Runtime.InteropServices; 5 | using System.Threading.Tasks; 6 | using PluginUtils; 7 | using ZXing; 8 | using ZXing.Common; 9 | 10 | namespace Plugins 11 | { 12 | public class Main : PluginBase, ICamera 13 | { 14 | private DateTime _lastScan = DateTime.UtcNow; 15 | private Task _processor; 16 | 17 | public Main() : base() 18 | { 19 | 20 | } 21 | 22 | public override List GetCustomEvents() 23 | { 24 | return new List { "Barcode Recognized" }; 25 | } 26 | 27 | public string Supports 28 | { 29 | get 30 | { 31 | return "video"; 32 | } 33 | } 34 | 35 | public void ProcessVideoFrame(IntPtr frame, Size sz, int channels, int stride) 36 | { 37 | if (channels != 3) 38 | { 39 | return; 40 | } 41 | 42 | if (Utils.TaskRunning(_processor)) 43 | return; 44 | 45 | if (_lastScan > DateTime.UtcNow.AddMilliseconds(0 - ConfigObject.Interval)) 46 | return; 47 | 48 | _lastScan = DateTime.UtcNow; 49 | byte[] rgbRaw = new byte[stride * sz.Height]; 50 | Marshal.Copy(frame, rgbRaw, 0, rgbRaw.Length); 51 | _processor = Task.Run(() => RunScanner(rgbRaw, sz.Width, sz.Height)); 52 | 53 | } 54 | 55 | #region ImageProcessing 56 | private void RunScanner(byte[] img, int width, int height) 57 | { 58 | var mBarcodeReader = new MultiFormatReader(); 59 | 60 | 61 | var hints = new Dictionary(); 62 | var fmts = new List(); 63 | 64 | if (ConfigObject.AZTEC) 65 | fmts.Add(BarcodeFormat.AZTEC); 66 | 67 | if (ConfigObject.CODABAR) 68 | fmts.Add(BarcodeFormat.CODABAR); 69 | 70 | 71 | if (ConfigObject.CODE_128) 72 | fmts.Add(BarcodeFormat.CODE_128); 73 | 74 | if (ConfigObject.CODE_39) 75 | fmts.Add(BarcodeFormat.CODE_39); 76 | 77 | if (ConfigObject.CODE_93) 78 | fmts.Add(BarcodeFormat.CODE_93); 79 | 80 | if (ConfigObject.DATA_MATRIX) 81 | fmts.Add(BarcodeFormat.DATA_MATRIX); 82 | 83 | if (ConfigObject.EAN_13) 84 | fmts.Add(BarcodeFormat.EAN_13); 85 | 86 | if (ConfigObject.EAN_8) 87 | fmts.Add(BarcodeFormat.EAN_8); 88 | 89 | if (ConfigObject.IMB) 90 | fmts.Add(BarcodeFormat.IMB); 91 | 92 | if (ConfigObject.MAXICODE) 93 | fmts.Add(BarcodeFormat.MAXICODE); 94 | 95 | if (ConfigObject.MSI) 96 | fmts.Add(BarcodeFormat.MSI); 97 | 98 | if (ConfigObject.PDF_417) 99 | fmts.Add(BarcodeFormat.PDF_417); 100 | 101 | if (ConfigObject.PHARMACODE) 102 | fmts.Add(BarcodeFormat.PHARMA_CODE); 103 | 104 | if (ConfigObject.PLESSEY) 105 | fmts.Add(BarcodeFormat.PLESSEY); 106 | 107 | if (ConfigObject.QR_CODE) 108 | fmts.Add(BarcodeFormat.QR_CODE); 109 | 110 | if (ConfigObject.RSS_14) 111 | fmts.Add(BarcodeFormat.RSS_14); 112 | 113 | if (ConfigObject.RSS_Expanded) 114 | fmts.Add(BarcodeFormat.RSS_EXPANDED); 115 | 116 | if (ConfigObject.UPC_A) 117 | fmts.Add(BarcodeFormat.UPC_A); 118 | 119 | if (ConfigObject.UPC_E) 120 | fmts.Add(BarcodeFormat.UPC_E); 121 | 122 | if (ConfigObject.UPC_EAN_Extension) 123 | fmts.Add(BarcodeFormat.UPC_EAN_EXTENSION); 124 | 125 | if (ConfigObject.Intensive) 126 | hints.Add(DecodeHintType.TRY_HARDER, true); 127 | 128 | hints.Add(DecodeHintType.POSSIBLE_FORMATS, fmts); 129 | 130 | Result rawResult = null; 131 | var r = new RGBLuminanceSource(img, width, height); 132 | var x = new HybridBinarizer(r); 133 | var bitmap = new BinaryBitmap(x); 134 | 135 | try 136 | { 137 | rawResult = mBarcodeReader.decode(bitmap, hints); 138 | } 139 | catch (ReaderException e) 140 | { 141 | Utils.LastException = e; 142 | } 143 | catch (Exception ex) 144 | { 145 | Utils.LastException = ex; 146 | } 147 | 148 | if (rawResult != null) 149 | { 150 | if (!String.IsNullOrEmpty(rawResult.Text)) 151 | { 152 | Results.Add(new ResultInfo("Barcode Recognized", rawResult.Text)); 153 | } 154 | } 155 | } 156 | 157 | #endregion 158 | 159 | ~Main() 160 | { 161 | Dispose(false); 162 | } 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /Barcode/README.md: -------------------------------------------------------------------------------- 1 | # AgentDVR-Plugins: Barcode Recognition 2 | 3 | 4 | Download Agent DVR here: 5 | https://www.ispyconnect.com/download.aspx 6 | 7 | See General information on plugins here: 8 | https://github.com/ispysoftware/AgentDVR-Plugins 9 | 10 | The barcode plugin scans a camera feed once a second for barcodes and raises events in Agent DVR you can use to perform actions. 11 | 12 | This plugin is Windows Only. 13 | 14 | ![barcode_config](https://user-images.githubusercontent.com/800093/163331670-08364a2c-0ca7-47cf-a6b1-4d03f0c81517.png) 15 | 16 | Choose from the list of available barcode types. Be sure to only select the ones you are using to maximise performance. 17 | 18 | ![barcode_types](https://user-images.githubusercontent.com/800093/163331676-98f1ed6a-083d-464b-a7c1-00323d55ac07.png) 19 | 20 | This plugin raises an event called "Barcode Recognized". To use this, edit the camera and select the Actions tab. Under **If** choose "Barcode: Barcode Recognized". Under **Then** select "Show Message". Under **Message** enter {MSG}. Click OK. When the plugin recognises a barcode it will display the barcode in the Agent UI at the top left. 21 | 22 | General notes on creating plugins: 23 | 24 | https://www.ispyconnect.com/userguide-agent-plugins.aspx 25 | 26 | -------------------------------------------------------------------------------- /Barcode/config.cs: -------------------------------------------------------------------------------- 1 | public partial class configuration { 2 | 3 | private int intervalField; 4 | 5 | private bool intensiveField; 6 | 7 | private bool aZTECField; 8 | 9 | private bool cODABARField; 10 | 11 | private bool cODE_128Field; 12 | 13 | private bool cODE_39Field; 14 | 15 | private bool cODE_93Field; 16 | 17 | private bool dATA_MATRIXField; 18 | 19 | private bool eAN_13Field; 20 | 21 | private bool eAN_8Field; 22 | 23 | private bool iMBField; 24 | 25 | private bool iTFField; 26 | 27 | private bool mAXICODEField; 28 | 29 | private bool mSIField; 30 | 31 | private bool pDF_417Field; 32 | 33 | private bool pHARMACODEField; 34 | 35 | private bool pLESSEYField; 36 | 37 | private bool qR_CODEField; 38 | 39 | private bool rSS_14Field; 40 | 41 | private bool rSS_ExpandedField; 42 | 43 | private bool uPC_AField; 44 | 45 | private bool uPC_EField; 46 | 47 | private bool uPC_EAN_ExtensionField; 48 | 49 | public configuration() { 50 | this.intervalField = 1000; 51 | this.intensiveField = true; 52 | this.aZTECField = false; 53 | this.cODABARField = false; 54 | this.cODE_128Field = false; 55 | this.cODE_39Field = false; 56 | this.cODE_93Field = false; 57 | this.dATA_MATRIXField = false; 58 | this.eAN_13Field = true; 59 | this.eAN_8Field = true; 60 | this.iMBField = false; 61 | this.iTFField = false; 62 | this.mAXICODEField = false; 63 | this.mSIField = false; 64 | this.pDF_417Field = false; 65 | this.pHARMACODEField = false; 66 | this.pLESSEYField = false; 67 | this.qR_CODEField = true; 68 | this.rSS_14Field = false; 69 | this.rSS_ExpandedField = false; 70 | this.uPC_AField = true; 71 | this.uPC_EField = true; 72 | this.uPC_EAN_ExtensionField = false; 73 | } 74 | 75 | /// 76 | public int Interval { 77 | get { 78 | return this.intervalField; 79 | } 80 | set { 81 | this.intervalField = value; 82 | } 83 | } 84 | 85 | /// 86 | public bool Intensive { 87 | get { 88 | return this.intensiveField; 89 | } 90 | set { 91 | this.intensiveField = value; 92 | } 93 | } 94 | 95 | /// 96 | public bool AZTEC { 97 | get { 98 | return this.aZTECField; 99 | } 100 | set { 101 | this.aZTECField = value; 102 | } 103 | } 104 | 105 | /// 106 | public bool CODABAR { 107 | get { 108 | return this.cODABARField; 109 | } 110 | set { 111 | this.cODABARField = value; 112 | } 113 | } 114 | 115 | /// 116 | public bool CODE_128 { 117 | get { 118 | return this.cODE_128Field; 119 | } 120 | set { 121 | this.cODE_128Field = value; 122 | } 123 | } 124 | 125 | /// 126 | public bool CODE_39 { 127 | get { 128 | return this.cODE_39Field; 129 | } 130 | set { 131 | this.cODE_39Field = value; 132 | } 133 | } 134 | 135 | /// 136 | public bool CODE_93 { 137 | get { 138 | return this.cODE_93Field; 139 | } 140 | set { 141 | this.cODE_93Field = value; 142 | } 143 | } 144 | 145 | /// 146 | public bool DATA_MATRIX { 147 | get { 148 | return this.dATA_MATRIXField; 149 | } 150 | set { 151 | this.dATA_MATRIXField = value; 152 | } 153 | } 154 | 155 | /// 156 | public bool EAN_13 { 157 | get { 158 | return this.eAN_13Field; 159 | } 160 | set { 161 | this.eAN_13Field = value; 162 | } 163 | } 164 | 165 | /// 166 | public bool EAN_8 { 167 | get { 168 | return this.eAN_8Field; 169 | } 170 | set { 171 | this.eAN_8Field = value; 172 | } 173 | } 174 | 175 | /// 176 | public bool IMB { 177 | get { 178 | return this.iMBField; 179 | } 180 | set { 181 | this.iMBField = value; 182 | } 183 | } 184 | 185 | /// 186 | public bool ITF { 187 | get { 188 | return this.iTFField; 189 | } 190 | set { 191 | this.iTFField = value; 192 | } 193 | } 194 | 195 | /// 196 | public bool MAXICODE { 197 | get { 198 | return this.mAXICODEField; 199 | } 200 | set { 201 | this.mAXICODEField = value; 202 | } 203 | } 204 | 205 | /// 206 | public bool MSI { 207 | get { 208 | return this.mSIField; 209 | } 210 | set { 211 | this.mSIField = value; 212 | } 213 | } 214 | 215 | /// 216 | public bool PDF_417 { 217 | get { 218 | return this.pDF_417Field; 219 | } 220 | set { 221 | this.pDF_417Field = value; 222 | } 223 | } 224 | 225 | /// 226 | public bool PHARMACODE { 227 | get { 228 | return this.pHARMACODEField; 229 | } 230 | set { 231 | this.pHARMACODEField = value; 232 | } 233 | } 234 | 235 | /// 236 | public bool PLESSEY { 237 | get { 238 | return this.pLESSEYField; 239 | } 240 | set { 241 | this.pLESSEYField = value; 242 | } 243 | } 244 | 245 | /// 246 | public bool QR_CODE { 247 | get { 248 | return this.qR_CODEField; 249 | } 250 | set { 251 | this.qR_CODEField = value; 252 | } 253 | } 254 | 255 | /// 256 | public bool RSS_14 { 257 | get { 258 | return this.rSS_14Field; 259 | } 260 | set { 261 | this.rSS_14Field = value; 262 | } 263 | } 264 | 265 | /// 266 | public bool RSS_Expanded { 267 | get { 268 | return this.rSS_ExpandedField; 269 | } 270 | set { 271 | this.rSS_ExpandedField = value; 272 | } 273 | } 274 | 275 | /// 276 | public bool UPC_A { 277 | get { 278 | return this.uPC_AField; 279 | } 280 | set { 281 | this.uPC_AField = value; 282 | } 283 | } 284 | 285 | /// 286 | public bool UPC_E { 287 | get { 288 | return this.uPC_EField; 289 | } 290 | set { 291 | this.uPC_EField = value; 292 | } 293 | } 294 | 295 | /// 296 | public bool UPC_EAN_Extension { 297 | get { 298 | return this.uPC_EAN_ExtensionField; 299 | } 300 | set { 301 | this.uPC_EAN_ExtensionField = value; 302 | } 303 | } 304 | } 305 | -------------------------------------------------------------------------------- /Barcode/json/config_en.json: -------------------------------------------------------------------------------- 1 | { 2 | "header": "Gain", 3 | "okResult": "UploadJSON", 4 | "name": "EditPluginConfiguration", 5 | "origin": "Plugin", 6 | "version": "VERSION", 7 | "sections": [ 8 | { 9 | "header": "General", 10 | "displayType": "Form", 11 | "items": [ 12 | { 13 | "text": "Settings", 14 | "type": "Header", 15 | "help": "Add an action on the camera for Barcode Recognized and use the tag {MSG} to retrieve the detected barcode. Use the menu above to choose the barcode types to look for." 16 | }, 17 | { 18 | "text": "Interval", 19 | "value": false, 20 | "type": "Int32", 21 | "min": 100, 22 | "max": 20000, 23 | "bindto": "Interval", 24 | "help": "How often to scan - milliseconds (1000 = 1 second)" 25 | }, 26 | { 27 | "text": "Intensive", 28 | "value": false, 29 | "type": "Boolean", 30 | "bindto": "Intensive", 31 | "help": "Put extra effort into finding barcodes (uses more CPU)" 32 | } 33 | ] 34 | }, 35 | { 36 | "header": "Barcode Types", 37 | "displayType": "Form", 38 | "items": [ 39 | { 40 | "text": "Barcode Types", 41 | "type": "Header", 42 | "help": "Select only the types you need to maximise performance" 43 | }, 44 | { 45 | "text": "AZTEC", 46 | "value": false, 47 | "type": "Boolean", 48 | "bindto": "AZTEC" 49 | }, 50 | { 51 | "text": "CODABAR", 52 | "value": false, 53 | "type": "Boolean", 54 | "bindto": "CODABAR" 55 | }, 56 | { 57 | "text": "CODE_128", 58 | "value": false, 59 | "type": "Boolean", 60 | "bindto": "CODE_128" 61 | }, 62 | { 63 | "text": "CODE_39", 64 | "value": false, 65 | "type": "Boolean", 66 | "bindto": "CODE_39" 67 | }, 68 | { 69 | "text": "CODE_93", 70 | "value": false, 71 | "type": "Boolean", 72 | "bindto": "CODE_93" 73 | }, 74 | { 75 | "text": "DATA_MATRIX", 76 | "value": false, 77 | "type": "Boolean", 78 | "bindto": "DATA_MATRIX" 79 | }, 80 | { 81 | "text": "EAN_13", 82 | "value": false, 83 | "type": "Boolean", 84 | "bindto": "EAN_13" 85 | }, 86 | { 87 | "text": "IMB", 88 | "value": false, 89 | "type": "Boolean", 90 | "bindto": "IMB" 91 | }, 92 | { 93 | "text": "ITF", 94 | "value": false, 95 | "type": "Boolean", 96 | "bindto": "ITF" 97 | }, 98 | { 99 | "text": "MAXICODE", 100 | "value": false, 101 | "type": "Boolean", 102 | "bindto": "MAXICODE" 103 | }, 104 | { 105 | "text": "MSI", 106 | "value": false, 107 | "type": "Boolean", 108 | "bindto": "MSI" 109 | }, 110 | { 111 | "text": "PDF_417", 112 | "value": false, 113 | "type": "Boolean", 114 | "bindto": "PDF_417" 115 | }, 116 | { 117 | "text": "PHARMACODE", 118 | "value": false, 119 | "type": "Boolean", 120 | "bindto": "PHARMACODE" 121 | }, 122 | { 123 | "text": "PLESSEY", 124 | "value": false, 125 | "type": "Boolean", 126 | "bindto": "PLESSEY" 127 | }, 128 | { 129 | "text": "QR_CODE", 130 | "value": false, 131 | "type": "Boolean", 132 | "bindto": "QR_CODE" 133 | }, 134 | { 135 | "text": "RSS_14", 136 | "value": false, 137 | "type": "Boolean", 138 | "bindto": "RSS_14" 139 | }, 140 | { 141 | "text": "RSS_Expanded", 142 | "value": false, 143 | "type": "Boolean", 144 | "bindto": "RSS_Expanded" 145 | }, 146 | { 147 | "text": "UPC_A", 148 | "value": false, 149 | "type": "Boolean", 150 | "bindto": "UPC_A" 151 | }, 152 | { 153 | "text": "UPC_E", 154 | "value": false, 155 | "type": "Boolean", 156 | "bindto": "UPC_E" 157 | }, 158 | { 159 | "text": "UPC_EAN_Extension", 160 | "value": false, 161 | "type": "Boolean", 162 | "bindto": "UPC_EAN_Extension" 163 | } 164 | ] 165 | } 166 | ] 167 | } -------------------------------------------------------------------------------- /Demo/Demo.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net9.0 5 | Plugins 6 | Demo 7 | DeveloperInABox 8 | Video Surveillance Software 9 | 2025 DeveloperInABox 10 | false 11 | true 12 | 1.3.2.0 13 | 1.3.2.0 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | F:\Projects\emgucv\libs\Build\Emgu.CV.Platform.NetStandard\AnyCPU\Release\Emgu.CV.Platform.NetStandard.dll 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /Demo/Main.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using SixLabors.ImageSharp; 4 | using SixLabors.ImageSharp.Processing; 5 | using SixLabors.ImageSharp.Drawing.Processing; 6 | using SixLabors.ImageSharp.PixelFormats; 7 | using PluginUtils; 8 | using SixLabors.Fonts; 9 | using System.Linq; 10 | using Emgu.CV; 11 | using Emgu.CV.CvEnum; 12 | 13 | namespace Plugins 14 | { 15 | public class Main : PluginBase, ICamera, IMicrophone 16 | { 17 | private DateTime _lastAlert = DateTime.UtcNow; 18 | private Font _drawFont; 19 | private bool _needUpdate = false; 20 | private Pen _wirepen, _trippedpen; 21 | private List _tripwires = new List(); 22 | 23 | public Main():base() 24 | { 25 | 26 | //get cross platform font family 27 | string[] fontfams = new[] { "Verdana", "Arial", "Helvetica", "Geneva", "FreeMono", "DejaVu Sans"}; 28 | var ff = false; 29 | FontFamily fam; 30 | foreach (var fontfam in fontfams) 31 | { 32 | if (SystemFonts.Collection.TryGet(fontfam, out fam)) 33 | { 34 | ff = true; 35 | break; 36 | } 37 | } 38 | if (!ff) 39 | fam = SystemFonts.Collection.Families.First(); 40 | 41 | _drawFont = SystemFonts.CreateFont(fam.Name, 20, FontStyle.Regular); 42 | _wirepen = Pens.Solid(Color.Green, 3); 43 | _trippedpen = Pens.Solid(Color.Red, 3); 44 | 45 | } 46 | 47 | public string Supports 48 | { 49 | get 50 | { 51 | return "video,audio"; 52 | } 53 | } 54 | 55 | public override List GetCustomEvents() 56 | { 57 | return new List() { "Box Bounce", "Box Crossed Tripwire" }; 58 | } 59 | 60 | public override void SetConfiguration(string json) 61 | { 62 | base.SetConfiguration(json); 63 | _needUpdate = true; 64 | 65 | } 66 | 67 | public override void ProcessAgentEvent(string ev) 68 | { 69 | switch(ev) 70 | { 71 | case "MotionAlert": 72 | break; 73 | case "MotionDetect": 74 | break; 75 | case "ManualAlert": 76 | break; 77 | case "RecordingStart": 78 | //this will tag new recordings with "Demo plugin attached" 79 | Results.Add(new ResultInfo("tag", "", "Demo plugin attached")); 80 | break; 81 | case "RecordingStop": 82 | break; 83 | case "AudioAlert": 84 | break; 85 | case "AudioDetect": 86 | break; 87 | } 88 | } 89 | 90 | public byte[] ProcessAudioFrame(byte[] rawData, int bytesRecorded, int samplerate, int channels) 91 | { 92 | //22050, one channel 93 | CheckAlert(); 94 | if (!ConfigObject.VolumeEnabled) 95 | return rawData; 96 | 97 | //demo audio effect 98 | return adjustVolume(rawData, Convert.ToDouble(ConfigObject.Volume) / 100d); 99 | } 100 | 101 | public void ProcessVideoFrame(IntPtr frame, System.Drawing.Size sz, int channels, int stride) 102 | { 103 | // Create the source Mat from the frame 104 | using (var srcMat = new Mat(sz, DepthType.Cv8U, 3, frame, stride)) 105 | { 106 | // Create a new Mat to hold the grayscale image 107 | using (var grayMat = new Mat()) 108 | { 109 | // Convert from BGR to Gray, storing the result in grayMat 110 | CvInvoke.CvtColor(srcMat, grayMat, ColorConversion.Bgr2Gray); 111 | 112 | // Apply threshold to the grayscale image 113 | CvInvoke.Threshold(grayMat, grayMat, 128, 255, ThresholdType.Binary); 114 | 115 | // If you need the image back in 3 channels (for drawing, etc.), convert it back: 116 | CvInvoke.CvtColor(grayMat, srcMat, ColorConversion.Gray2Bgr); 117 | } 118 | } 119 | 120 | if (_needUpdate) 121 | { 122 | _tripwires = Utils.ParseTripWires(sz, ConfigObject.Example_Trip_Wires); 123 | _needUpdate = false; 124 | } 125 | //fire off an alert every 10 seconds 126 | CheckAlert(); 127 | 128 | if (ConfigObject.MirrorEnabled) 129 | { 130 | 131 | //demo mirror effect 132 | var bWidth = sz.Width / ConfigObject.Size; 133 | unsafe 134 | { 135 | byte* ptr = (byte*)frame; 136 | 137 | for (var y = 0; y < sz.Height; y++) 138 | { 139 | for (var b = 0; b < ConfigObject.Size; b++) 140 | { 141 | int xStart = b * bWidth, xEnd = Math.Min(sz.Width, (b + 1) * bWidth); 142 | int j = 0; 143 | for (var x = xStart; x < xEnd; x++) 144 | { 145 | for (int c = 0; c < channels; c++) 146 | ptr[y * stride + (x * channels) + c] = ptr[y * stride + (xEnd - x) * channels + c]; 147 | 148 | } 149 | } 150 | } 151 | } 152 | } 153 | 154 | if (ConfigObject.GraphicsEnabled) 155 | { 156 | //Use SixLabors drawing for cross platform support. 157 | unsafe 158 | { 159 | using (var image = Image.WrapMemory(frame.ToPointer(), stride * sz.Height, sz.Width, sz.Height)) 160 | { 161 | var box = new Rectangle(recLoc, new Size(recSize, recSize)); 162 | image.Mutate(x => x.Fill(Color.Red, box)); 163 | 164 | image.Mutate(x => x.DrawText("DVD", _drawFont, Color.White, new PointF(recLoc.X + 10, recLoc.Y + 20))); 165 | 166 | //draw trip wires if defined 167 | if (_tripwires.Count>0) 168 | { 169 | foreach (var wire in _tripwires) 170 | { 171 | var points = new PointF[] { new PointF(wire.InitialPoint.X, wire.InitialPoint.Y), new PointF(wire.TerminalPoint.X, wire.TerminalPoint.Y) }; 172 | 173 | var pen = _wirepen; 174 | 175 | if (Utils.LineIntersectsRect(wire.InitialPoint, wire.TerminalPoint, new System.Drawing.Rectangle(box.X, box.Y, box.Width, box.Height))) { 176 | pen = _trippedpen; 177 | if (wire.LastTripped < DateTime.UtcNow.AddSeconds(-4)) 178 | { 179 | //crossed tripwire, suspend new event from this tripwire for at least 4 seconds 180 | wire.LastTripped = DateTime.UtcNow; 181 | Results.Add(new ResultInfo("Box Crossed Tripwire", "box crossed the tripwire")); 182 | } 183 | } 184 | 185 | image.Mutate(x => x.DrawLine(pen, points)); 186 | 187 | } 188 | } 189 | //draw rectangles if defined 190 | if (!string.IsNullOrEmpty(ConfigObject.Example_Area)) 191 | { 192 | var areas = Utils.ParseAreas(sz, ConfigObject.Example_Area); 193 | foreach (var area in areas) 194 | { 195 | image.Mutate(x => x.Fill(Color.WhiteSmoke, new Rectangle(area.X, area.Y, area.Width, area.Height))); 196 | } 197 | } 198 | } 199 | } 200 | 201 | //bounce rectangle about 202 | MoveRec(sz.Width,sz.Height); 203 | } 204 | } 205 | 206 | #region adjust volume 207 | private byte[] adjustVolume(byte[] audioSamples, double volume) 208 | { 209 | byte[] array = new byte[audioSamples.Length]; 210 | for (int i = 0; i < array.Length; i += 2) 211 | { 212 | // convert byte pair to int 213 | short buf1 = audioSamples[i + 1]; 214 | short buf2 = audioSamples[i]; 215 | 216 | buf1 = (short)((buf1 & 0xff) << 8); 217 | buf2 = (short)(buf2 & 0xff); 218 | 219 | short res = (short)(buf1 | buf2); 220 | res = (short)(res * volume); 221 | 222 | // convert back 223 | array[i] = (byte)res; 224 | array[i + 1] = (byte)(res >> 8); 225 | 226 | } 227 | return array; 228 | } 229 | #endregion 230 | 231 | #region bouncing rectangle 232 | private Point recLoc = new Point(100, 100); 233 | private int recSize = 80; 234 | private int speed = 5; 235 | private int XBounce = 1; 236 | private int YBounce = -1; 237 | 238 | private void MoveRec(int width, int height) 239 | { 240 | if ((recLoc.X >= 0) && (recLoc.X + recSize <= width)) //Within X Bounds 241 | { 242 | recLoc.X -= XBounce * speed; 243 | } 244 | else 245 | { 246 | Results.Add(new ResultInfo("Box Bounce", "bounce detected")); 247 | XBounce = -XBounce; 248 | recLoc.X -= XBounce * speed; 249 | } 250 | 251 | if ((recLoc.Y >= 0) && (recLoc.Y + recSize <= height)) //Within Y Bounds 252 | { 253 | recLoc.Y -= YBounce * speed; 254 | } 255 | else 256 | { 257 | Results.Add(new ResultInfo("Box Bounce", "bounce detected")); 258 | YBounce = -YBounce; 259 | recLoc.Y -= YBounce * speed; 260 | } 261 | } 262 | #endregion 263 | 264 | private void CheckAlert() 265 | { 266 | if (ConfigObject.AlertsEnabled) 267 | { 268 | if (_lastAlert < DateTime.UtcNow.AddSeconds(-10)) 269 | { 270 | _lastAlert = DateTime.UtcNow; 271 | Results.Add(new ResultInfo("alert")); 272 | } 273 | } 274 | } 275 | 276 | ~Main() 277 | { 278 | Dispose(false); 279 | } 280 | } 281 | } 282 | -------------------------------------------------------------------------------- /Demo/README.md: -------------------------------------------------------------------------------- 1 | # AgentDVR-Plugins: Demo 2 | 3 | 4 | Download Agent DVR here: 5 | https://www.ispyconnect.com/download.aspx 6 | 7 | See General information on plugins here: 8 | https://github.com/ispysoftware/AgentDVR-Plugins 9 | 10 | This plugin is for developers to show the various integration points available when building plugins. 11 | 12 | General notes on creating plugins: 13 | 14 | https://www.ispyconnect.com/userguide-agent-plugins.aspx 15 | -------------------------------------------------------------------------------- /Demo/config.cs: -------------------------------------------------------------------------------- 1 | public class configuration { 2 | 3 | private bool supportsAudioField; 4 | 5 | private bool supportsVideoField; 6 | 7 | private bool volumeEnabledField; 8 | 9 | private bool mirrorEnabledField; 10 | 11 | private bool alertsEnabledField; 12 | 13 | private bool graphicsEnabledField; 14 | 15 | private int volumeField; 16 | 17 | private int sizeField; 18 | 19 | private string example_StringField; 20 | 21 | private string example_InfoField; 22 | 23 | private bool example_CheckboxField; 24 | 25 | private string example_SelectField; 26 | 27 | private string example_CommandField; 28 | 29 | private string example_SoundField; 30 | 31 | private string example_OverlayField; 32 | 33 | private int example_SliderField; 34 | 35 | private int example_Range_MinField; 36 | 37 | private int example_Range_MaxField; 38 | 39 | private double example_DecimalField; 40 | 41 | private int example_Int32Field; 42 | 43 | private string example_ColorField; 44 | 45 | private int example_TimeField; 46 | 47 | private string example_DateField; 48 | 49 | private string example_TextField; 50 | 51 | private string example_AreaField; 52 | 53 | private string example_ZoneField; 54 | 55 | private string example_Trip_WiresField; 56 | 57 | public configuration() { 58 | this.supportsAudioField = true; 59 | this.supportsVideoField = true; 60 | this.volumeEnabledField = true; 61 | this.mirrorEnabledField = true; 62 | this.alertsEnabledField = true; 63 | this.graphicsEnabledField = true; 64 | this.volumeField = 100; 65 | this.sizeField = 1; 66 | this.example_StringField = "abc123"; 67 | this.example_InfoField = "Information"; 68 | this.example_CheckboxField = true; 69 | this.example_SelectField = "steak"; 70 | this.example_CommandField = ""; 71 | this.example_SoundField = ""; 72 | this.example_OverlayField = ""; 73 | this.example_SliderField = 50; 74 | this.example_Range_MinField = 40; 75 | this.example_Range_MaxField = 70; 76 | this.example_DecimalField = 40.5D; 77 | this.example_Int32Field = 20; 78 | this.example_ColorField = "#ff0000"; 79 | this.example_TimeField = 1026; 80 | this.example_DateField = ""; 81 | this.example_TextField = "Any text content"; 82 | this.example_AreaField = "[]"; 83 | this.example_ZoneField = "111111111111111111111111111111111111111111111111111111111111111111111111111111111" + 84 | "11111111111111111111111111000000000000000001111111111111111111111111000000000000" + 85 | "00000000000000000000000000111111111100000000000000000000000000000000000000111111" + 86 | "11110000000000000000000000000000000000000011111111110000000000111111100000000000" + 87 | "00000000001111111111111111111111111110000011111111111111111111111111111111111111" + 88 | "11111000011111111111111111111111111111111111111111111000001111111111111111111111" + 89 | "11111111111111111111100000111111111111111111111111111111111111111111100000011111" + 90 | "11111111111111111111111111111111111110000000111111111111111111111111111111111111" + 91 | "11111100000001111110000001111111111111111111111111111100000000000000000001111111" + 92 | "11111111111111111111111000000000000000000111111111111111111111111111111100000000" + 93 | "00000000011111111111111111111111111111111000000000000001111111111111111111111111" + 94 | "11111110000000000111111111111111111111111111111111111100000000000001111111111111" + 95 | "11111111111111111111110000000000000011111111111111111111111111111111100000000000" + 96 | "00000011111111111111111111111111111100000001100000000011111111111111111111111111" + 97 | "11110000001110000000000111111111111111111111111111110000001110000000000111111111" + 98 | "11111111111111111111000001111000010000001111111111111111111111111111000001110000" + 99 | "01000000111111111111111111111111111100000011000001100000011111111111111111111111" + 100 | "11110000001100000110000001111111111111111111111111110000000100000111000000111111" + 101 | "11111111111111111111100000000000111100000011111111111111111111111111110000000000" + 102 | "11111000001111111111111111111111111111000000000011111000000111111111111111111111" + 103 | "11111110000000001111110000011111111111111111111111111111000000011111110000001111" + 104 | "11111111111111111111111111111111111111000000111111111111000000000000000000000111" + 105 | "11111110000011111111111100000000000000000000000000011110000011111111111100000000" + 106 | "00000000000000000000000111111111111111110000000000000000000000000000000011111111" + 107 | "11111111111111111111111110000000000000001111111111111111111111111111111111111111" + 108 | "00000000111111111111110000000000000000000000111111100000111111111111110000000000" + 109 | "00000000000000001111111111111111111111000000000000000000000000000000111111111111" + 110 | "11111100000000000000000000000000000000011111111111111111111111111111111100000000" + 111 | "00000000001111111111111111111111111111111111100000000000001111111111111111111111" + 112 | "11111111111111110000000000111111111111111111111111111111111111111110000000111111" + 113 | "11111111111111111111111111111111111111111111111111111111111111111111111111111111" + 114 | "11111111111111111111111111111111111110000000000111111111111111111111111111111111" + 115 | "11100000000000001111111111111111111111111111111111000000000000000111111111111111" + 116 | "11111111111111111100000000000000000111111111111111111111111111111100000001100000" + 117 | "00001111111111111111111111111111100000011111000000000111111111111111111111111111" + 118 | "10000001111111000000001111111111111111111111111110000000111111100000000111111111" + 119 | "11111111111111111000000000000110000000011111111111111111111111111000000000000000" + 120 | "00000001111111111111111111111111100000000000000000000001111111111111111111111111" + 121 | "1000000000000000000000011111111"; 122 | this.example_Trip_WiresField = ""; 123 | } 124 | 125 | /// 126 | public bool SupportsAudio { 127 | get { 128 | return this.supportsAudioField; 129 | } 130 | set { 131 | this.supportsAudioField = value; 132 | } 133 | } 134 | 135 | /// 136 | public bool SupportsVideo { 137 | get { 138 | return this.supportsVideoField; 139 | } 140 | set { 141 | this.supportsVideoField = value; 142 | } 143 | } 144 | 145 | /// 146 | public bool VolumeEnabled { 147 | get { 148 | return this.volumeEnabledField; 149 | } 150 | set { 151 | this.volumeEnabledField = value; 152 | } 153 | } 154 | 155 | /// 156 | public bool MirrorEnabled { 157 | get { 158 | return this.mirrorEnabledField; 159 | } 160 | set { 161 | this.mirrorEnabledField = value; 162 | } 163 | } 164 | 165 | /// 166 | public bool AlertsEnabled { 167 | get { 168 | return this.alertsEnabledField; 169 | } 170 | set { 171 | this.alertsEnabledField = value; 172 | } 173 | } 174 | 175 | /// 176 | public bool GraphicsEnabled { 177 | get { 178 | return this.graphicsEnabledField; 179 | } 180 | set { 181 | this.graphicsEnabledField = value; 182 | } 183 | } 184 | 185 | /// 186 | public int Volume { 187 | get { 188 | return this.volumeField; 189 | } 190 | set { 191 | this.volumeField = value; 192 | } 193 | } 194 | 195 | /// 196 | public int Size { 197 | get { 198 | return this.sizeField; 199 | } 200 | set { 201 | this.sizeField = value; 202 | } 203 | } 204 | 205 | /// 206 | public string Example_String { 207 | get { 208 | return this.example_StringField; 209 | } 210 | set { 211 | this.example_StringField = value; 212 | } 213 | } 214 | 215 | /// 216 | public string Example_Info { 217 | get { 218 | return this.example_InfoField; 219 | } 220 | set { 221 | this.example_InfoField = value; 222 | } 223 | } 224 | 225 | /// 226 | public bool Example_Checkbox { 227 | get { 228 | return this.example_CheckboxField; 229 | } 230 | set { 231 | this.example_CheckboxField = value; 232 | } 233 | } 234 | 235 | /// 236 | public string Example_Select { 237 | get { 238 | return this.example_SelectField; 239 | } 240 | set { 241 | this.example_SelectField = value; 242 | } 243 | } 244 | 245 | /// 246 | public string Example_Command { 247 | get { 248 | return this.example_CommandField; 249 | } 250 | set { 251 | this.example_CommandField = value; 252 | } 253 | } 254 | 255 | /// 256 | public string Example_Sound { 257 | get { 258 | return this.example_SoundField; 259 | } 260 | set { 261 | this.example_SoundField = value; 262 | } 263 | } 264 | 265 | /// 266 | public string Example_Overlay { 267 | get { 268 | return this.example_OverlayField; 269 | } 270 | set { 271 | this.example_OverlayField = value; 272 | } 273 | } 274 | 275 | /// 276 | public int Example_Slider { 277 | get { 278 | return this.example_SliderField; 279 | } 280 | set { 281 | this.example_SliderField = value; 282 | } 283 | } 284 | 285 | /// 286 | public int Example_Range_Min { 287 | get { 288 | return this.example_Range_MinField; 289 | } 290 | set { 291 | this.example_Range_MinField = value; 292 | } 293 | } 294 | 295 | /// 296 | public int Example_Range_Max { 297 | get { 298 | return this.example_Range_MaxField; 299 | } 300 | set { 301 | this.example_Range_MaxField = value; 302 | } 303 | } 304 | 305 | /// 306 | public double Example_Decimal { 307 | get { 308 | return this.example_DecimalField; 309 | } 310 | set { 311 | this.example_DecimalField = value; 312 | } 313 | } 314 | 315 | /// 316 | public int Example_Int32 { 317 | get { 318 | return this.example_Int32Field; 319 | } 320 | set { 321 | this.example_Int32Field = value; 322 | } 323 | } 324 | 325 | /// 326 | public string Example_Color { 327 | get { 328 | return this.example_ColorField; 329 | } 330 | set { 331 | this.example_ColorField = value; 332 | } 333 | } 334 | 335 | /// 336 | public int Example_Time { 337 | get { 338 | return this.example_TimeField; 339 | } 340 | set { 341 | this.example_TimeField = value; 342 | } 343 | } 344 | 345 | /// 346 | public string Example_Date { 347 | get { 348 | return this.example_DateField; 349 | } 350 | set { 351 | this.example_DateField = value; 352 | } 353 | } 354 | 355 | /// 356 | public string Example_Text { 357 | get { 358 | return this.example_TextField; 359 | } 360 | set { 361 | this.example_TextField = value; 362 | } 363 | } 364 | 365 | /// 366 | public string Example_Area { 367 | get { 368 | return this.example_AreaField; 369 | } 370 | set { 371 | this.example_AreaField = value; 372 | } 373 | } 374 | 375 | /// 376 | public string Example_Zone { 377 | get { 378 | return this.example_ZoneField; 379 | } 380 | set { 381 | this.example_ZoneField = value; 382 | } 383 | } 384 | 385 | /// 386 | public string Example_Trip_Wires { 387 | get { 388 | return this.example_Trip_WiresField; 389 | } 390 | set { 391 | this.example_Trip_WiresField = value; 392 | } 393 | } 394 | } 395 | -------------------------------------------------------------------------------- /Demo/json/config_en.json: -------------------------------------------------------------------------------- 1 | { 2 | "header": "Gain", 3 | "okResult": "UploadJSON", 4 | "name": "EditPluginConfiguration", 5 | "origin": "Plugin", 6 | "version": "VERSION", 7 | "sections": [ 8 | { 9 | "header": "General", 10 | "displayType": "Form", 11 | "items": [ 12 | { 13 | "text": "Settings", 14 | "type": "Header" 15 | }, 16 | { 17 | "text": "Alert", 18 | "value": false, 19 | "type": "Boolean", 20 | "bindto": "AlertsEnabled", 21 | "live": true, 22 | "help": "This demo plugin generates an alert every 10 seconds." 23 | }, 24 | { 25 | "text": "Mirror Effect", 26 | "type": "Header" 27 | }, 28 | { 29 | "text": "Mirror Enabled", 30 | "value": false, 31 | "type": "Boolean", 32 | "bindto": "MirrorEnabled", 33 | "live": true, 34 | "help": "Applies a mirror effect to the video" 35 | }, 36 | { 37 | "text": "Size", 38 | "value": "", 39 | "type": "Slider", 40 | "range": false, 41 | "min": 1, 42 | "max": 10, 43 | "bindto": "Size", 44 | "live": true 45 | }, 46 | { 47 | "text": "Graphics Effect", 48 | "type": "Header" 49 | }, 50 | { 51 | "text": "Graphics Enabled", 52 | "value": false, 53 | "type": "Boolean", 54 | "bindto": "GraphicsEnabled", 55 | "live": true, 56 | "help": "Draws a bouncing red square on top of the video. Generates a 'Rectangle Bounce' event you can add actions to under Actions in main camera config" 57 | }, 58 | { 59 | "text": "Volume Effect", 60 | "type": "Header" 61 | }, 62 | { 63 | "text": "Enabled", 64 | "value": false, 65 | "type": "Boolean", 66 | "bindto": "VolumeEnabled", 67 | "live": true 68 | }, 69 | { 70 | "text": "Volume", 71 | "value": "", 72 | "type": "Slider", 73 | "range": false, 74 | "min": 0, 75 | "max": 100, 76 | "bindto": "Volume", 77 | "live": true 78 | } 79 | ] 80 | }, 81 | { 82 | "header": "Example Controls", 83 | "displayType": "Form", 84 | "items": [ 85 | { 86 | "text": "String", 87 | "value": "", 88 | "type": "String", 89 | "bindto": "Example_String" 90 | }, 91 | { 92 | "text": "Info", 93 | "value": "Some HTML Content", 94 | "type": "Info", 95 | "bindto": "Example_Info" 96 | }, 97 | { 98 | "text": "Link", 99 | "url": "https://www.ispyconnect.com", 100 | "value": "Click here", 101 | "type": "Link" 102 | }, 103 | { 104 | "text": "Checkbox", 105 | "value": true, 106 | "type": "Boolean", 107 | "bindto": "Example_Checkbox" 108 | }, 109 | { 110 | "text": "Select", 111 | "value": "", 112 | "type": "Select", 113 | "help": "Select your favorite food", 114 | "bindto": "Example_Select", 115 | "options": [ 116 | { 117 | "text": "Cake", 118 | "value": "cake" 119 | }, 120 | { 121 | "text": "Steak", 122 | "value": "steak" 123 | }, 124 | { 125 | "text": "Pizza", 126 | "value": "pizza" 127 | } 128 | ] 129 | }, 130 | { 131 | "text": "Command", 132 | "value": "", 133 | "type": "Select", 134 | "help": "Select a command", 135 | "bindto": "Example_SelectCommand", 136 | "options": [ 137 | "COMMANDS" 138 | ] 139 | }, 140 | { 141 | "text": "Sound", 142 | "value": "", 143 | "type": "Select", 144 | "help": "Select a sound", 145 | "bindto": "Example_SelectSound", 146 | "options": [ 147 | "SOUNDS" 148 | ] 149 | }, 150 | { 151 | "text": "Overlay", 152 | "value": "", 153 | "type": "Select", 154 | "help": "Select an overlay", 155 | "bindto": "Example_SelectOverlay", 156 | "options": [ 157 | "OVERLAYS" 158 | ] 159 | }, 160 | { 161 | "text": "Slider", 162 | "value": 50, 163 | "type": "Slider", 164 | "range": false, 165 | "min": 30, 166 | "max": 100, 167 | "help": "Set the quality", 168 | "bindto": "Example_Slider" 169 | }, 170 | { 171 | "text": "Range", 172 | "value": [], 173 | "type": "Slider", 174 | "range": true, 175 | "min": 30, 176 | "max": 100, 177 | "help": "Set the limits", 178 | "bindto": "Example_Range_Min,Example_Range_Max" 179 | }, 180 | { 181 | "text": "Decimal", 182 | "value": 0.5, 183 | "type": "Decimal", 184 | "min": 0, 185 | "max": 9999, 186 | "bindto": "Example_Decimal" 187 | }, 188 | { 189 | "text": "Int32", 190 | "value": 100, 191 | "type": "Int32", 192 | "min": 0, 193 | "max": 99999999, 194 | "bindto": "Example_Int32" 195 | }, 196 | { 197 | "text": "Color", 198 | "value": "#ff0000", 199 | "type": "Color", 200 | "converter": "rgbtohex", 201 | "bindto": "Example_Color" 202 | }, 203 | { 204 | "text": "Time", 205 | "value": "", 206 | "type": "Time", 207 | "unit": "min", 208 | "step": "60", 209 | "bindto": "Example_Time" 210 | }, 211 | { 212 | "text": "Date", 213 | "value": "", 214 | "type": "Date", 215 | "bindto": "Example_Date" 216 | }, 217 | { 218 | "text": "TextArea", 219 | "value": "", 220 | "type": "TextArea", 221 | "bindto": "Example_Text" 222 | } 223 | ] 224 | }, 225 | { 226 | "header": "Freeform Area", 227 | "displayType": "Form", 228 | "items": [ 229 | { 230 | "text": "Area", 231 | "value": "", 232 | "type": "ZoneManager", 233 | "bindto": "Example_Zone", 234 | "hasZones": true, 235 | "help": "Draw an area. Left click to draw, right click to erase" 236 | } 237 | ] 238 | }, 239 | { 240 | "header": "Trip Wires", 241 | "displayType": "Form", 242 | "items": [ 243 | { 244 | "text": "Trip Wires", 245 | "value": "", 246 | "type": "LineManager", 247 | "max": 10, 248 | "bindto": "Example_Trip_Wires", 249 | "help": "Click and drag to draw lines, drag a point out to remove", 250 | "overlay": true 251 | } 252 | ] 253 | }, 254 | { 255 | "header": "Spectrum Analyser", 256 | "displayType": "Form", 257 | "items": [ 258 | { 259 | "text": "Spectrum Analyser", 260 | "value": "", 261 | "type": "SpectrumAnalyser", 262 | "width": 320, 263 | "height": 240 264 | } 265 | ] 266 | } 267 | ] 268 | } -------------------------------------------------------------------------------- /Demo/readme.txt: -------------------------------------------------------------------------------- 1 | Please see https://www.ispyconnect.com/userguide-agent-plugins.aspx for instructions 2 | -------------------------------------------------------------------------------- /Gain/Gain.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | Plugins 4 | Gain 5 | net9.0 6 | DeveloperInABox 7 | Video Surveillance Software 8 | 2025 DeveloperInABox 9 | false 10 | 1.1.6.0 11 | 1.1.6.0 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /Gain/Main.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using NAudio.Dsp; 4 | using PluginUtils; 5 | 6 | namespace Plugins 7 | { 8 | public class Main : PluginBase, IMicrophone 9 | { 10 | 11 | private EqualizerBand[] _bands; 12 | private BiQuadFilter[,] _filters = null; 13 | private bool _update = true; 14 | 15 | public Main(): base() 16 | { 17 | 18 | } 19 | 20 | public string Supports 21 | { 22 | get 23 | { 24 | return "audio"; 25 | } 26 | } 27 | 28 | public override void SetConfiguration(string json) 29 | { 30 | base.SetConfiguration(json); 31 | _update = true; 32 | } 33 | 34 | public byte[] ProcessAudioFrame(byte[] rawData, int bytesRecorded, int samplerate, int channels) 35 | { 36 | byte[] truncArray = new byte[bytesRecorded]; 37 | 38 | Array.Copy(rawData, truncArray, truncArray.Length); 39 | if (ConfigObject.enabled) 40 | { 41 | if (_update) 42 | { 43 | CreateFilters(); 44 | } 45 | List samples = new List(); 46 | for (int n = 0; n < bytesRecorded; n += 2) 47 | { 48 | float sampleValue = BitConverter.ToInt16(truncArray, n) / 32768f; 49 | for (int band = 0; band < _bands.Length; band++) 50 | { 51 | sampleValue = _filters[0, band].Transform(sampleValue); 52 | } 53 | 54 | samples.Add(sampleValue); 55 | } 56 | 57 | truncArray = GetSamplesWaveData(samples.ToArray(), samples.Count); 58 | 59 | } 60 | return truncArray; 61 | } 62 | 63 | #region audio processing 64 | private static byte[] GetSamplesWaveData(float[] samples, int samplesCount) 65 | { 66 | var pcm = new byte[samplesCount * 2]; 67 | int sampleIndex = 0, 68 | pcmIndex = 0; 69 | 70 | while (sampleIndex < samplesCount) 71 | { 72 | var outsample = (short)(samples[sampleIndex] * short.MaxValue); 73 | pcm[pcmIndex] = (byte)(outsample & 0xff); 74 | pcm[pcmIndex + 1] = (byte)((outsample >> 8) & 0xff); 75 | 76 | sampleIndex++; 77 | pcmIndex += 2; 78 | } 79 | 80 | return pcm; 81 | } 82 | 83 | private void CreateFilters() 84 | { 85 | _bands = new[] 86 | { 87 | new EqualizerBand {Bandwidth = 0.8f, Frequency = 100, Gain = ConfigObject.band1}, 88 | new EqualizerBand {Bandwidth = 0.8f, Frequency = 200, Gain = ConfigObject.band2}, 89 | new EqualizerBand {Bandwidth = 0.8f, Frequency = 400, Gain = ConfigObject.band3}, 90 | new EqualizerBand {Bandwidth = 0.8f, Frequency = 800, Gain = ConfigObject.band4}, 91 | new EqualizerBand {Bandwidth = 0.8f, Frequency = 1200, Gain = ConfigObject.band5}, 92 | new EqualizerBand {Bandwidth = 0.8f, Frequency = 2400, Gain = ConfigObject.band6}, 93 | new EqualizerBand {Bandwidth = 0.8f, Frequency = 4800, Gain = ConfigObject.band7} 94 | }; 95 | 96 | _filters = new BiQuadFilter[1, _bands.Length]; 97 | 98 | for (int bandIndex = 0; bandIndex < _bands.Length; bandIndex++) 99 | { 100 | var band = _bands[bandIndex]; 101 | for (int n = 0; n < 1; n++) 102 | { 103 | if (_filters[n, bandIndex] == null) 104 | _filters[n, bandIndex] = BiQuadFilter.PeakingEQ(22050, band.Frequency, band.Bandwidth, band.Gain); 105 | else 106 | _filters[n, bandIndex].SetPeakingEq(22050, band.Frequency, band.Bandwidth, band.Gain); 107 | } 108 | } 109 | _update = false; 110 | } 111 | 112 | class EqualizerBand 113 | { 114 | public float Frequency { get; set; } 115 | public float Gain { get; set; } 116 | public float Bandwidth { get; set; } 117 | } 118 | #endregion 119 | 120 | ~Main() 121 | { 122 | Dispose(false); 123 | } 124 | 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /Gain/README.md: -------------------------------------------------------------------------------- 1 | # AgentDVR-Plugins: Gain 2 | 3 | 4 | Download Agent DVR here: 5 | https://www.ispyconnect.com/download.aspx 6 | 7 | See General information on plugins here: 8 | https://github.com/ispysoftware/AgentDVR-Plugins 9 | 10 | The gain plugin provides controls for a band pass filter of live audio. 11 | 12 | ![gain](https://user-images.githubusercontent.com/800093/163334270-5ab7f07b-b4da-4d26-adfe-6cc9f6523e2e.png) 13 | 14 | General notes on creating plugins: 15 | 16 | https://www.ispyconnect.com/userguide-agent-plugins.aspx 17 | 18 | -------------------------------------------------------------------------------- /Gain/config.cs: -------------------------------------------------------------------------------- 1 | public partial class configuration { 2 | 3 | private int band1Field; 4 | 5 | private int band2Field; 6 | 7 | private int band3Field; 8 | 9 | private int band4Field; 10 | 11 | private int band5Field; 12 | 13 | private int band6Field; 14 | 15 | private int band7Field; 16 | 17 | private bool enabledField; 18 | 19 | public configuration() { 20 | this.band1Field = 0; 21 | this.band2Field = 0; 22 | this.band3Field = 0; 23 | this.band4Field = 0; 24 | this.band5Field = 0; 25 | this.band6Field = 0; 26 | this.band7Field = 0; 27 | this.enabledField = false; 28 | } 29 | 30 | /// 31 | public int band1 { 32 | get { 33 | return this.band1Field; 34 | } 35 | set { 36 | this.band1Field = value; 37 | } 38 | } 39 | 40 | /// 41 | public int band2 { 42 | get { 43 | return this.band2Field; 44 | } 45 | set { 46 | this.band2Field = value; 47 | } 48 | } 49 | 50 | /// 51 | public int band3 { 52 | get { 53 | return this.band3Field; 54 | } 55 | set { 56 | this.band3Field = value; 57 | } 58 | } 59 | 60 | /// 61 | public int band4 { 62 | get { 63 | return this.band4Field; 64 | } 65 | set { 66 | this.band4Field = value; 67 | } 68 | } 69 | 70 | /// 71 | public int band5 { 72 | get { 73 | return this.band5Field; 74 | } 75 | set { 76 | this.band5Field = value; 77 | } 78 | } 79 | 80 | /// 81 | public int band6 { 82 | get { 83 | return this.band6Field; 84 | } 85 | set { 86 | this.band6Field = value; 87 | } 88 | } 89 | 90 | /// 91 | public int band7 { 92 | get { 93 | return this.band7Field; 94 | } 95 | set { 96 | this.band7Field = value; 97 | } 98 | } 99 | 100 | /// 101 | public bool enabled { 102 | get { 103 | return this.enabledField; 104 | } 105 | set { 106 | this.enabledField = value; 107 | } 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /Gain/json/config_en.json: -------------------------------------------------------------------------------- 1 | { 2 | "header": "Gain", 3 | "okResult": "UploadJSON", 4 | "name": "EditPluginConfiguration", 5 | "origin": "Plugin", 6 | "version": "VERSION", 7 | "sections": [ 8 | { 9 | "header": "Audio Gain", 10 | "displayType": "Form", 11 | "items": [ 12 | { "text": "Spectrum Analyser", "value": "", "type": "SpectrumAnalyser", "width": 320, "height": 240}, 13 | { "text": "Band Pass Equalizer", "type": "Header"}, 14 | { "text": "Enabled", "value": false, "type": "Boolean", "bindto": "enabled", "live": true }, 15 | { "text": "100Hz", "value": [ ], "type": "Slider", "min": -30, "max": 30, "range": false, "bindto": "band1", "live": true}, 16 | { "text": "200Hz", "value": [ ], "type": "Slider", "min": -30, "max": 30, "range": false, "bindto": "band2", "live": true}, 17 | { "text": "400Hz", "value": [ ], "type": "Slider", "min": -30, "max": 30, "range": false, "bindto": "band3", "live": true}, 18 | { "text": "800Hz", "value": [ ], "type": "Slider", "min": -30, "max": 30, "range": false, "bindto": "band4", "live": true}, 19 | { "text": "1200Hz", "value": [ ], "type": "Slider", "min": -30, "max": 30, "range": false, "bindto": "band5", "live": true}, 20 | { "text": "2400Hz", "value": [ ], "type": "Slider", "min": -30, "max": 30, "range": false, "bindto": "band6", "live": true}, 21 | { "text": "4800Hz", "value": [ ], "type": "Slider", "min": -30, "max": 30, "range": false, "bindto": "band7", "live": true} 22 | ] 23 | } 24 | 25 | ] 26 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /Listen/AudioFeatureBuffer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace Plugins 6 | { 7 | class AudioFeatureBuffer 8 | { 9 | public const int InputSamplingRate = 16000; 10 | 11 | private readonly MelSpectrogram _processor; 12 | private readonly int _stftHopLength; 13 | private readonly int _stftWindowLength; 14 | private readonly int _nMelBands; 15 | 16 | private readonly float[] _waveformBuffer; 17 | private int _waveformCount; 18 | private readonly float[] _outputBuffer; 19 | private int _outputCount; 20 | 21 | public AudioFeatureBuffer(int stftHopLength = 160, int stftWindowLength = 400, int nMelBands = 64) 22 | { 23 | _processor = new MelSpectrogram(); 24 | _stftHopLength = stftHopLength; 25 | _stftWindowLength = stftWindowLength; 26 | _nMelBands = nMelBands; 27 | 28 | _waveformBuffer = new float[2 * _stftHopLength + _stftWindowLength]; 29 | _waveformCount = 0; 30 | _outputBuffer = new float[_nMelBands * (_stftWindowLength + _stftHopLength)]; 31 | _outputCount = 0; 32 | } 33 | 34 | public int OutputCount { get { return _outputCount; } } 35 | public float[] OutputBuffer { get { return _outputBuffer; } } 36 | 37 | public float[] Resample(float[] waveform, int sampleRate) 38 | { 39 | if (sampleRate == InputSamplingRate) 40 | { 41 | return waveform; 42 | } 43 | else 44 | { 45 | int toLen = (int)(waveform.Length * ((double)InputSamplingRate / sampleRate)); 46 | float stepRate = ((float)sampleRate) / InputSamplingRate; 47 | float[] toWaveform = new float[toLen]; 48 | for (int toIndex = 0; toIndex < toWaveform.Length; toIndex++) 49 | { 50 | int fromIndex = (int)(toIndex * stepRate); 51 | if (fromIndex < waveform.Length) 52 | { 53 | toWaveform[toIndex] = waveform[fromIndex]; 54 | } 55 | } 56 | return toWaveform; 57 | } 58 | } 59 | 60 | public int Write(float[] waveform, int offset, int count) 61 | { 62 | int written = 0; 63 | 64 | if (_waveformCount > 0) 65 | { 66 | int needed = ((_waveformCount - 1) / _stftHopLength) * _stftHopLength + _stftWindowLength - _waveformCount; 67 | written = Math.Min(needed, count); 68 | 69 | Array.Copy(waveform, offset, _waveformBuffer, _waveformCount, written); 70 | _waveformCount += written; 71 | 72 | int wavebufferOffset = 0; 73 | while (wavebufferOffset + _stftWindowLength < _waveformCount) 74 | { 75 | _processor.Transform(_waveformBuffer, wavebufferOffset, _outputBuffer, _outputCount); 76 | _outputCount += _nMelBands; 77 | wavebufferOffset += _stftHopLength; 78 | } 79 | 80 | if (written < needed) 81 | { 82 | Array.Copy(_waveformBuffer, wavebufferOffset, _waveformBuffer, 0, _waveformCount - wavebufferOffset); 83 | _waveformCount -= wavebufferOffset; 84 | return written; 85 | } 86 | 87 | _waveformCount = 0; 88 | written -= _stftWindowLength - _stftHopLength; 89 | } 90 | 91 | while (written + _stftWindowLength < count) 92 | { 93 | if (_outputCount + _nMelBands >= _outputBuffer.Length) 94 | { 95 | return written; 96 | } 97 | _processor.Transform(waveform, offset + written, _outputBuffer, _outputCount); 98 | _outputCount += _nMelBands; 99 | written += _stftHopLength; 100 | } 101 | 102 | Array.Copy(waveform, offset + written, _waveformBuffer, 0, count - written); 103 | _waveformCount = count - written; 104 | written = count; 105 | return written; 106 | } 107 | 108 | public void ConsumeOutput(int count) 109 | { 110 | Array.Copy(_outputBuffer, count, _outputBuffer, 0, _outputCount - count); 111 | _outputCount -= count; 112 | } 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /Listen/Listen.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | Plugins 4 | Listen 5 | net9.0 6 | DeveloperInABox 7 | Video Surveillance Software 8 | 2025 DeveloperInABox 9 | false 10 | true 11 | true 12 | true 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 1.3.0.0 35 | 1.3.0.0 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /Listen/Main.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using PluginUtils; 5 | using Microsoft.ML.OnnxRuntime; 6 | using Microsoft.ML.OnnxRuntime.Tensors; 7 | using NAudio.Wave; 8 | using System.Globalization; 9 | using System.Text.Json.Nodes; 10 | 11 | namespace Plugins 12 | { 13 | public class Main : PluginBase, IMicrophone 14 | { 15 | private AudioFeatureBuffer featureBuffer = new AudioFeatureBuffer(); 16 | 17 | 18 | public Main(): base() 19 | { 20 | 21 | } 22 | 23 | public override List GetCustomEvents() 24 | { 25 | return new List { "Sound Detected", "Sound Recognized" }; 26 | } 27 | 28 | public string Supports 29 | { 30 | get 31 | { 32 | return "audio"; 33 | } 34 | } 35 | 36 | public override void SetConfiguration(string json) 37 | { 38 | base.SetConfiguration(json); 39 | } 40 | private static List _classes = null; 41 | private static List Classes 42 | { 43 | get 44 | { 45 | if (_classes!=null) 46 | return _classes; 47 | 48 | _classes = new List(); 49 | var txt = ResourceLoader.GetResourceText("yamnet_class_map.csv"); 50 | var lines = txt.Split(Environment.NewLine.ToCharArray()); 51 | for(var i=1;i0) 60 | { 61 | name = line.Substring(line.IndexOf("\"")).Trim('"'); 62 | } 63 | name = name.Replace(",", "-").ToLowerInvariant(); 64 | _classes.Add(new YamClass() { id = id, name = name }); 65 | 66 | } 67 | _classes = _classes.OrderBy(p => p.name).ToList(); 68 | return _classes; 69 | } 70 | } 71 | 72 | 73 | private struct YamClass 74 | { 75 | public int id; 76 | public string name; 77 | } 78 | 79 | 80 | private static string ClassesJSON() 81 | { 82 | var js = "["; 83 | foreach (var c in Classes) 84 | { 85 | js += "{\"original\":\"" + c.name + "\",\"translated\":\"" + c.name + "\"},"; 86 | } 87 | js = js.Trim(',') + "]"; 88 | return js; 89 | } 90 | 91 | public override string GetConfiguration(string languageCode) 92 | { 93 | //populate json 94 | var json = ResourceLoader.LoadJson(languageCode); 95 | json = json.Replace("\"YAMOBJECTS\"", ClassesJSON()); 96 | json = json.Replace("OBJECTS_SELECTED", ConfigObject.listenfor); 97 | 98 | JsonNode? d = Utils.PopulateResponse(json, ConfigObject); 99 | return d?.ToJsonString() ?? string.Empty; 100 | 101 | } 102 | 103 | public byte[] ProcessAudioFrame(byte[] rawData, int bytesRecorded, int samplerate, int channels) 104 | { 105 | if (ConfigObject.enabled) 106 | { 107 | //convert to mono float array 108 | List _fsamples = new List(); 109 | var skip = channels * 2; 110 | for(var i = 0; i < rawData.Length; i+=skip) 111 | { 112 | Int16 s = BitConverter.ToInt16(rawData, i); 113 | float f = s / 32768f; 114 | _fsamples.Add(f); 115 | } 116 | 117 | var waveform = featureBuffer.Resample(_fsamples.ToArray(), samplerate); 118 | int offset = 0; 119 | while (offset < waveform.Length) 120 | { 121 | int written = featureBuffer.Write(waveform, offset, waveform.Length - offset); 122 | offset += written; 123 | while (featureBuffer.OutputCount >= 96 * 64) 124 | { 125 | try 126 | { 127 | var features = new float[96 * 64]; 128 | Array.Copy(featureBuffer.OutputBuffer, 0, features, 0, 96 * 64); 129 | OnPatchReceived(features); 130 | } 131 | finally 132 | { 133 | featureBuffer.ConsumeOutput(48 * 64); 134 | } 135 | } 136 | } 137 | } 138 | return rawData; 139 | } 140 | 141 | 142 | 143 | private InferenceSession modelSession = null; 144 | 145 | #region audio processing 146 | private void OnPatchReceived(float[] features) 147 | { 148 | if (modelSession == null) 149 | { 150 | modelSession = new InferenceSession(ResourceLoader.GetResourceBytes("yamnet.onnx")); 151 | //for gpu usage 152 | //modelSession = new InferenceSession(ResourceLoader.GetResourceBytes("yamnet.onnx"), SessionOptions.MakeSessionOptionWithCudaProvider(0)); 153 | } 154 | 155 | var inputMeta = modelSession.InputMetadata; 156 | var container = new List(); 157 | 158 | var name = inputMeta.Keys.First(); 159 | 160 | var tensor = new DenseTensor(features, inputMeta[name].Dimensions); 161 | container.Add(NamedOnnxValue.CreateFromTensor(name, tensor)); 162 | using (var results = modelSession.Run(container)) 163 | { 164 | var r = results.First().AsTensor(); 165 | int prediction = MaxProbability(r, out var max); 166 | var c = Classes.First(p => p.id == prediction).name; 167 | var aijson = "{\"sound\":\"" + c + "\",\"probability\": " + max.ToString(CultureInfo.InvariantCulture) + "}"; 168 | 169 | Results.Add(new ResultInfo("Sound Detected", c, c, aijson)); 170 | if (max * 100 >= ConfigObject.confidence) 171 | { 172 | if (("," + ConfigObject.listenfor + ",").Contains("," + c + ",")) 173 | { 174 | Results.Add(new ResultInfo("Sound Recognized", c, c, aijson)); 175 | if (ConfigObject.alerts) 176 | { 177 | Results.Add(new ResultInfo("alert", c, c, aijson)); 178 | } 179 | } 180 | } 181 | } 182 | } 183 | 184 | static int MaxProbability(Tensor probabilities, out float max) 185 | { 186 | max = -9999.9F; 187 | int maxIndex = -1; 188 | for (int i = 0; i < probabilities.Length; ++i) 189 | { 190 | float prob = probabilities.GetValue(i); 191 | if (prob > max) 192 | { 193 | max = prob; 194 | maxIndex = i; 195 | } 196 | } 197 | return maxIndex; 198 | 199 | } 200 | 201 | 202 | #endregion 203 | 204 | ~Main() 205 | { 206 | modelSession?.Dispose(); 207 | Dispose(false); 208 | } 209 | 210 | } 211 | } 212 | -------------------------------------------------------------------------------- /Listen/MelSpectrogram.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace Plugins 6 | { 7 | class MelSpectrogram 8 | { 9 | private readonly double[] window; 10 | private readonly double[] melBands; 11 | private readonly double[] temp1; 12 | private readonly double[] temp2; 13 | private readonly int _fftLength; 14 | private readonly int _nMelBands; 15 | private readonly double _sampleRate; 16 | private readonly double _logOffset; 17 | 18 | public MelSpectrogram( 19 | int sampleRate = 16000, 20 | int stftWindowLength = 400, int stftLength = 512, 21 | int nMelBands = 64, double melMinHz = 125.0, double melMaxHz = 7500.0, 22 | double logOffset = 0.001) 23 | { 24 | _sampleRate = sampleRate; 25 | window = MakeHannWindow(stftWindowLength); 26 | melBands = MakeMelBands(melMinHz, melMaxHz, nMelBands); 27 | temp1 = new double[stftLength]; 28 | temp2 = new double[stftLength]; 29 | _fftLength = stftLength; 30 | _nMelBands = nMelBands; 31 | _logOffset = logOffset; 32 | } 33 | 34 | static double[] MakeHannWindow(int windowLength) 35 | { 36 | double[] window = new double[windowLength]; 37 | for (int i = 0; i < windowLength; i++) 38 | { 39 | window[i] = 0.5 * (1 - Math.Cos(2 * Math.PI * i / windowLength)); 40 | } 41 | return window; 42 | } 43 | 44 | public void Transform(float[] waveform, int waveformOffset, float[] melspec, int melspecOffset) 45 | { 46 | GetFrame(waveform, waveformOffset, temp1); 47 | CFFT(temp1, temp2, _fftLength); 48 | ToMagnitude(temp2, temp1, _fftLength); 49 | ToMelSpec(temp2, melspec, melspecOffset); 50 | } 51 | 52 | private void ToMelSpec(double[] spec, float[] melspec, int melspecOffset) 53 | { 54 | for (int i = 0; i < _nMelBands; i++) 55 | { 56 | double startHz = melBands[i]; 57 | double peakHz = melBands[i + 1]; 58 | double endHz = melBands[i + 2]; 59 | double v = 0.0; 60 | int j = (int)(startHz * _fftLength / _sampleRate) + 1; 61 | while (true) 62 | { 63 | double hz = j * _sampleRate / _fftLength; 64 | if (hz > peakHz) 65 | break; 66 | double r = (hz - startHz) / (peakHz - startHz); 67 | v += spec[j] * r; 68 | j++; 69 | } 70 | while (true) 71 | { 72 | double hz = j * _sampleRate / _fftLength; 73 | if (hz > endHz) 74 | break; 75 | double r = (endHz - hz) / (endHz - peakHz); 76 | v += spec[j] * r; 77 | j++; 78 | } 79 | melspec[melspecOffset + i] = (float)Math.Log(v + _logOffset); 80 | } 81 | } 82 | 83 | void GetFrame(float[] waveform, int start, double[] frame) 84 | { 85 | for (int i = 0; i < window.Length; i++) 86 | { 87 | frame[i] = waveform[start + i] * window[i]; 88 | } 89 | for (int i = window.Length; i < frame.Length; i++) 90 | { 91 | frame[i] = 0.0; 92 | } 93 | } 94 | 95 | static void ToMagnitude(double[] xr, double[] xi, int N) 96 | { 97 | for (int n = 0; n < N; n++) 98 | { 99 | xr[n] = Math.Sqrt(xr[n] * xr[n] + xi[n] * xi[n]); 100 | } 101 | } 102 | 103 | static double HzToMel(double hz) 104 | { 105 | return 2595 * Math.Log10(1 + hz / 700); 106 | } 107 | 108 | static double MelToHz(double mel) 109 | { 110 | return (Math.Pow(10, mel / 2595) - 1) * 700; 111 | } 112 | 113 | static double[] MakeMelBands(double melMinHz, double melMaxHz, int nMelBanks) 114 | { 115 | double melMin = HzToMel(melMinHz); 116 | double melMax = HzToMel(melMaxHz); 117 | double[] melBanks = new double[nMelBanks + 2]; 118 | for (int i = 0; i < nMelBanks + 2; i++) 119 | { 120 | double mel = (melMax - melMin) * i / (nMelBanks + 1) + melMin; 121 | melBanks[i] = MelToHz(mel); 122 | } 123 | return melBanks; 124 | } 125 | 126 | static int SwapIndex(int i) 127 | { 128 | return (i >> 8) & 0x01 129 | | (i >> 6) & 0x02 130 | | (i >> 4) & 0x04 131 | | (i >> 2) & 0x08 132 | | (i) & 0x10 133 | | (i << 2) & 0x20 134 | | (i << 4) & 0x40 135 | | (i << 6) & 0x80 136 | | (i << 8) & 0x100; 137 | } 138 | 139 | public static void CFFT(double[] xr, double[] xi, int N) 140 | { 141 | double[] t = xi; 142 | xi = xr; 143 | xr = t; 144 | for (int i = 0; i < N; i++) 145 | { 146 | xr[i] = xi[SwapIndex(i)]; 147 | } 148 | for (int i = 0; i < N; i++) 149 | { 150 | xi[i] = 0.0; 151 | } 152 | for (int n = 1; n < N; n *= 2) 153 | { 154 | for (int j = 0; j < N; j += n * 2) 155 | { 156 | for (int k = 0; k < n; k++) 157 | { 158 | double ar = Math.Cos(-Math.PI * k / n); 159 | double ai = Math.Sin(-Math.PI * k / n); 160 | double er = xr[j + k]; 161 | double ei = xi[j + k]; 162 | double or = xr[j + k + n]; 163 | double oi = xi[j + k + n]; 164 | double aor = ar * or - ai * oi; 165 | double aoi = ai * or + ar * oi; 166 | xr[j + k] = er + aor; 167 | xi[j + k] = ei + aoi; 168 | xr[j + k + n] = er - aor; 169 | xi[j + k + n] = ei - aoi; 170 | } 171 | } 172 | } 173 | } 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /Listen/README.md: -------------------------------------------------------------------------------- 1 | # AgentDVR-Plugins: Listen 2 | 3 | 4 | Download Agent DVR here: 5 | https://www.ispyconnect.com/download.aspx 6 | 7 | See General information on plugins here: 8 | https://github.com/ispysoftware/AgentDVR-Plugins 9 | 10 | The listen plugin uses machine learning to process live audio and recognise sounds from a list of around 520 options. It raises events in Agent DVR you can use to perform actions. 11 | 12 | You could use this for notifications if your dog is barking or glass is smashed for example. 13 | 14 | ![listen](https://user-images.githubusercontent.com/800093/163334854-ff528a23-98b6-4faa-a253-7ecb0686d25e.png) 15 | 16 | Set the minimum confidence level (from 0 = no confidence to 100 = certain) and choose the types of sound you want to be notified of. If you want to raise alerts (you are recording on alert for example) - then check the "Raise Alerts" option. 17 | 18 | This plugin raises 2 events. One called "Sound Detected" and one called "Sound Recognized". To use this, edit the microphone and select the Actions tab. Under **If** choose "Listen: Sound Recognized". Under **Then** select "Show Message". Under **Message** enter {MSG}: 19 | 20 | ![listen_config](https://user-images.githubusercontent.com/800093/163338148-3044e4ec-1acf-444c-bfe0-f38c4e05a1fe.png) 21 | 22 | Click OK. When the plugin recognises a sound you selected in the configuration that is above the confidence level you chose it will display the sound type in the Agent UI at the top left. 23 | 24 | You can also use {AIJSON} in your actions and Agent will pass through json that looks like: 25 | 26 | {"sound":"whistling","probability": 0.55} 27 | 28 | The Sound Detected event is raised whenever the plugin recognizes a sound above your confidence threshold but it isn't in the **Listen For** list. 29 | 30 | General notes on creating plugins: 31 | 32 | https://www.ispyconnect.com/userguide-agent-plugins.aspx 33 | 34 | 35 | -------------------------------------------------------------------------------- /Listen/config.cs: -------------------------------------------------------------------------------- 1 | public partial class configuration { 2 | 3 | private string listenforField; 4 | 5 | private bool enabledField; 6 | 7 | private bool alertsField; 8 | 9 | private int confidenceField; 10 | 11 | public configuration() { 12 | this.listenforField = ""; 13 | this.enabledField = true; 14 | this.alertsField = true; 15 | this.confidenceField = 60; 16 | } 17 | 18 | /// 19 | public string listenfor { 20 | get { 21 | return this.listenforField; 22 | } 23 | set { 24 | this.listenforField = value; 25 | } 26 | } 27 | 28 | /// 29 | public bool enabled { 30 | get { 31 | return this.enabledField; 32 | } 33 | set { 34 | this.enabledField = value; 35 | } 36 | } 37 | 38 | /// 39 | public bool alerts { 40 | get { 41 | return this.alertsField; 42 | } 43 | set { 44 | this.alertsField = value; 45 | } 46 | } 47 | 48 | /// 49 | public int confidence { 50 | get { 51 | return this.confidenceField; 52 | } 53 | set { 54 | this.confidenceField = value; 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /Listen/json/config_en.json: -------------------------------------------------------------------------------- 1 | { 2 | "header": "Listen", 3 | "okResult": "UploadJSON", 4 | "name": "EditPluginConfiguration", 5 | "origin": "Plugin", 6 | "version": "VERSION", 7 | "sections": [ 8 | { 9 | "header": "Listen", 10 | "displayType": "Form", 11 | "items": [ 12 | { 13 | "text": "Settings", 14 | "type": "Header", 15 | "help": "Add an action on the microphone for Sound Recognized and use the tag {MSG} to retrieve the detected sound." 16 | }, 17 | { 18 | "text": "Enabled", 19 | "value": false, 20 | "type": "Boolean", 21 | "bindto": "enabled", 22 | "live": true 23 | }, 24 | { 25 | "text": "Raise Alerts", 26 | "value": false, 27 | "type": "Boolean", 28 | "bindto": "alerts", 29 | "live": true 30 | }, 31 | { 32 | "text": "Confidence", 33 | "value": 0, 34 | "type": "Slider", 35 | "min": 0, 36 | "max": 100, 37 | "bindto": "confidence", 38 | "help": "Minimum Confidence" 39 | }, 40 | { 41 | "text": "Listen For", 42 | "value": "", 43 | "type": "StringSelect", 44 | "bindto": "listenfor", 45 | "help": "Sounds to listen for", 46 | "id": "txtListenFor", 47 | "iddisp": "txtListenFor_disp", 48 | "available": "YAMOBJECTS", 49 | "translated": "OBJECTS_SELECTED" 50 | } 51 | ] 52 | } 53 | 54 | ] 55 | } -------------------------------------------------------------------------------- /Listen/onnx/yamnet.onnx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ispysoftware/AgentDVR-Plugins/c6f0a9e81afca19cf4bd535058976a29416570b2/Listen/onnx/yamnet.onnx -------------------------------------------------------------------------------- /LiveDelay/DelayBuffer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Runtime.InteropServices; 4 | using SixLabors.ImageSharp; 5 | using SixLabors.ImageSharp.Processing; 6 | using SixLabors.ImageSharp.Drawing.Processing; 7 | using SixLabors.ImageSharp.PixelFormats; 8 | using SixLabors.Fonts; 9 | 10 | namespace Plugins 11 | { 12 | internal class DelayBuffer 13 | { 14 | public int BufferSeconds; 15 | public bool BufferFull; 16 | public DateTime Created; 17 | private byte[] _data; 18 | 19 | public DelayBuffer(byte[] data) 20 | { 21 | Created = DateTime.UtcNow; 22 | _data = data; 23 | } 24 | 25 | public DelayBuffer() 26 | { 27 | Created = DateTime.UtcNow; 28 | } 29 | 30 | private Queue _audioQueue = new Queue(); 31 | private Queue _videoQueue = new Queue(); 32 | 33 | public byte[] GetBuffer(byte[] rawData, int bytesRecorded) 34 | { 35 | if (BufferSeconds == 0) 36 | return rawData; 37 | 38 | _audioQueue.Enqueue(new DelayBuffer(rawData)); 39 | if (!BufferFull) 40 | BufferFull = _audioQueue.Peek().Created < DateTime.UtcNow.AddSeconds(0 - BufferSeconds); 41 | if (BufferFull) 42 | { 43 | return _audioQueue.Dequeue()._data; 44 | } 45 | //return live audio until we have a buffer 46 | return rawData; 47 | } 48 | 49 | public void GetBuffer(IntPtr frame, System.Drawing.Size sz, int channels, int stride, Font font) 50 | { 51 | if (BufferSeconds == 0) 52 | return; 53 | byte[] data = new byte[stride * sz.Height]; 54 | if (frame == IntPtr.Zero || data.Length <= 0) 55 | return; 56 | Marshal.Copy(frame,data,0,data.Length); 57 | 58 | _videoQueue.Enqueue(new DelayBuffer(data)); 59 | 60 | if (!BufferFull) 61 | BufferFull = _videoQueue.Peek().Created < DateTime.UtcNow.AddSeconds(0 - BufferSeconds); 62 | 63 | if (BufferFull) 64 | { 65 | var d = _videoQueue.Dequeue()._data; 66 | if (stride * sz.Height == d.Length) 67 | { 68 | Marshal.Copy(d, 0, frame, d.Length); 69 | return; 70 | } 71 | //resolution changed - need to clear buffer out 72 | } 73 | 74 | 75 | //write buffering notice 76 | unsafe 77 | { 78 | using (var image = Image.WrapMemory(frame.ToPointer(), stride * sz.Height, sz.Width, sz.Height)) 79 | { 80 | const string txt = "DELAYING..."; 81 | FontRectangle size = TextMeasurer.MeasureAdvance(txt, new TextOptions(font)); 82 | var box = new Rectangle(sz.Width / 2 - (int)size.Width / 2 - 5, sz.Height / 2 - (int)size.Height / 2 - 5, (int)size.Width + 10, (int)size.Height + 10); 83 | image.Mutate(x => x.Fill(Color.Red, box)); 84 | image.Mutate(x => x.DrawText(txt, font, Color.White, new PointF(box.X + 5, box.Y + 5))); 85 | 86 | } 87 | } 88 | 89 | } 90 | 91 | public void Clear() 92 | { 93 | _audioQueue = new Queue(); 94 | _videoQueue = new Queue(); 95 | BufferFull = false; 96 | } 97 | 98 | public void Close() 99 | { 100 | _audioQueue = null; 101 | _videoQueue = null; 102 | } 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /LiveDelay/LiveDelay.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net9.0 5 | Plugins 6 | Live Delay 7 | DeveloperInABox 8 | Video Surveillance Software 9 | 2025 DeveloperInABox 10 | false 11 | true 12 | 1.3.1.0 13 | 1.3.1.0 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /LiveDelay/Main.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using PluginUtils; 4 | using SixLabors.Fonts; 5 | using System.Linq; 6 | 7 | namespace Plugins 8 | { 9 | public class Main : PluginBase, ICamera, IMicrophone 10 | { 11 | private DelayBuffer _delayBuffer; 12 | private Font _messageFont; 13 | 14 | public Main():base() 15 | { 16 | 17 | //get cross platform font family 18 | string[] fontfams = new[] { "Verdana", "Arial", "Helvetica", "Geneva", "FreeMono", "DejaVu Sans"}; 19 | var ff = false; 20 | FontFamily fam; 21 | foreach (var fontfam in fontfams) 22 | { 23 | if (SystemFonts.Collection.TryGet(fontfam, out fam)) 24 | { 25 | ff = true; 26 | break; 27 | } 28 | } 29 | if (!ff) 30 | fam = SystemFonts.Collection.Families.First(); 31 | 32 | _messageFont = SystemFonts.CreateFont(fam.Name, 20, FontStyle.Regular); 33 | _delayBuffer = new DelayBuffer(); 34 | } 35 | 36 | public string Supports 37 | { 38 | get 39 | { 40 | return "video,audio"; 41 | } 42 | } 43 | 44 | public override List GetCustomEvents() 45 | { 46 | return new List() { }; 47 | } 48 | 49 | public override void SetConfiguration(string json) 50 | { 51 | base.SetConfiguration(json); 52 | _delayBuffer.Clear(); 53 | _delayBuffer.BufferSeconds = ConfigObject.Delay; 54 | 55 | } 56 | 57 | public override void ProcessAgentEvent(string ev) 58 | { 59 | 60 | } 61 | 62 | public byte[] ProcessAudioFrame(byte[] rawData, int bytesRecorded, int samplerate, int channels) 63 | { 64 | return _delayBuffer.GetBuffer(rawData, bytesRecorded); 65 | } 66 | 67 | public void ProcessVideoFrame(IntPtr frame, System.Drawing.Size sz, int channels, int stride) 68 | { 69 | _delayBuffer.GetBuffer(frame, sz, channels, stride, _messageFont); 70 | } 71 | 72 | ~Main() 73 | { 74 | _delayBuffer?.Close(); 75 | Dispose(false); 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /LiveDelay/README.md: -------------------------------------------------------------------------------- 1 | # AgentDVR-Plugins: Live Delay 2 | 3 | 4 | Download Agent DVR here: 5 | https://www.ispyconnect.com/download.aspx 6 | 7 | See General information on plugins here: 8 | https://github.com/ispysoftware/AgentDVR-Plugins 9 | 10 | The live delay plugin delays live video by a configurable number of seconds. Useful for sports analysis for example. 11 | 12 | General notes on creating plugins: 13 | 14 | https://www.ispyconnect.com/userguide-agent-plugins.aspx 15 | -------------------------------------------------------------------------------- /LiveDelay/config.cs: -------------------------------------------------------------------------------- 1 | public class configuration { 2 | 3 | private bool supportsAudioField; 4 | 5 | private bool supportsVideoField; 6 | 7 | private int delayField; 8 | 9 | public configuration() { 10 | this.supportsAudioField = true; 11 | this.supportsVideoField = true; 12 | this.delayField = 0; 13 | } 14 | 15 | /// 16 | public bool SupportsAudio { 17 | get { 18 | return this.supportsAudioField; 19 | } 20 | set { 21 | this.supportsAudioField = value; 22 | } 23 | } 24 | 25 | /// 26 | public bool SupportsVideo { 27 | get { 28 | return this.supportsVideoField; 29 | } 30 | set { 31 | this.supportsVideoField = value; 32 | } 33 | } 34 | 35 | /// 36 | public int Delay { 37 | get { 38 | return this.delayField; 39 | } 40 | set { 41 | this.delayField = value; 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /LiveDelay/json/config_en.json: -------------------------------------------------------------------------------- 1 | { 2 | "header": "Live Delay", 3 | "okResult": "UploadJSON", 4 | "name": "EditPluginConfiguration", 5 | "origin": "Plugin", 6 | "version": "VERSION", 7 | "sections": [ 8 | { 9 | "header": "General", 10 | "displayType": "Form", 11 | "items": [ 12 | { 13 | "text": "Settings", 14 | "type": "Header" 15 | }, 16 | { 17 | "text": "Delay", 18 | "value": false, 19 | "type": "Int32", 20 | "bindto": "Delay", 21 | "live": true, 22 | "help": "Seconds to delay content" 23 | } 24 | ] 25 | } 26 | ] 27 | } -------------------------------------------------------------------------------- /LiveDelay/readme.txt: -------------------------------------------------------------------------------- 1 | Please see https://www.ispyconnect.com/userguide-agent-plugins.aspx for instructions 2 | -------------------------------------------------------------------------------- /PluginUtils/Areas.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace PluginUtils 6 | { 7 | public class Areas 8 | { 9 | public float x, y, w, h; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /PluginUtils/Extensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Drawing; 3 | 4 | namespace PluginUtils 5 | { 6 | public static class Extensions 7 | { 8 | public static string ToRGBString(this Color color) 9 | { 10 | return color.R + "," + color.G + "," + color.B; 11 | } 12 | 13 | public static Point Adjust(this Point point, int x, int y) 14 | { 15 | return new Point(point.X + x, point.Y + y); 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /PluginUtils/ICamera.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Drawing; 4 | using System.Text; 5 | 6 | namespace PluginUtils 7 | { 8 | public interface ICamera: IPlugin 9 | { 10 | /// 11 | /// Processes incoming video frames from Agent 12 | /// 13 | /// Pointer to raw frame data 14 | /// Size of the frame in pixels 15 | /// Number of channels (should be 3) 16 | /// Stride (or Step) of the image 17 | void ProcessVideoFrame(IntPtr frame, Size sz, int channels, int stride); 18 | 19 | /// 20 | /// Set the basic camera information for use in the plugin 21 | /// 22 | /// The Agent camera name 23 | /// The Agent object ID 24 | /// The local port of the Server for API calls 25 | void SetCameraInfo(string name, int objectID, int localPort); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /PluginUtils/IMicrophone.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace PluginUtils 6 | { 7 | public interface IMicrophone: IPlugin 8 | { 9 | /// 10 | /// Process incoming audio data from Agent. 11 | /// 12 | /// byte array of the raw data from the microphone 13 | /// The number of bytes in the rawData 14 | /// The audio samplerate 15 | /// The number of channels 16 | /// The modified audio data 17 | byte[] ProcessAudioFrame(byte[] rawData, int bytesRecorded, int samplerate, int channels); 18 | 19 | /// 20 | /// Set the basic microphone information for use in the plugin 21 | /// 22 | /// The Agent microphone name 23 | /// The Agent object ID 24 | /// The local port of the Server for API calls 25 | /// The number of samples in 1 second (Hz) 26 | /// The number of audio channels 27 | void SetMicrophoneInfo(string name, int objectID, int localPort, int sampleRate, int channels); 28 | 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /PluginUtils/IPlugin.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace PluginUtils 6 | { 7 | public interface IPlugin: IDisposable 8 | { 9 | /// 10 | /// Fixed string defining if this plugin supports "video", "audio" or both "video,audio" 11 | /// 12 | string Supports { get; } 13 | /// 14 | /// Update the plugin configuration from JSON 15 | /// 16 | /// 17 | void SetConfiguration(string json); 18 | /// 19 | /// The JSON representation of the plugin configuration 20 | /// 21 | /// language code - if not found will default to English 22 | /// 23 | string GetConfiguration(string languageCode); 24 | /// 25 | /// The last exception this plugin encountered if any 26 | /// 27 | Exception LastException { get; } 28 | /// 29 | /// The path to where the Agent executable is stored - for example: C:\Program Files\Agent\ 30 | /// 31 | string AppPath 32 | { 33 | get; 34 | set; 35 | } 36 | 37 | /// 38 | /// The path to where the Agent data is stored - for example: C:\Program Files\Agent\Media\ 39 | /// 40 | string AppDataPath 41 | { 42 | get; 43 | set; 44 | } 45 | /// 46 | /// Process an incoming event from Agent 47 | /// 48 | /// ev will be one of: MotionAlert,MotionDetect,ManualAlert,RecordingStart,RecordingStop,AudioAlert,AudioDetect 49 | /// A string 50 | void ProcessAgentEvent(string ev); 51 | 52 | /// 53 | /// return a list of events your plugin generates in human readable format, for example "Door opened","Light switched off","Camera tampered with". Do not include the plugin name. 54 | /// 55 | /// A list of the available custom events 56 | List GetCustomEvents(); 57 | 58 | /// 59 | /// 60 | /// 61 | /// 62 | string GetResultJSON(); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /PluginUtils/Line2D.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Drawing; 4 | using System.Text; 5 | 6 | namespace PluginUtils 7 | { 8 | public class Line2D 9 | { 10 | public Point InitialPoint; 11 | public Point TerminalPoint; 12 | public Point MidPoint; 13 | public DateTime LastTripped; 14 | public int LeftUpTripCount; 15 | public int RightDownTripCount; 16 | public int Angle; 17 | private Point _p1, _p2; 18 | 19 | public Line2D(Size frameSize, Point p1, Point p2) 20 | { 21 | _p1 = p1; 22 | _p2 = p2; 23 | Update(frameSize); 24 | } 25 | 26 | internal Point ScaleUpPercentToFrame(Size sz, Point p) 27 | { 28 | var x = (p.X / 100d) * sz.Width; 29 | var y = (p.Y / 100d) * sz.Height; 30 | 31 | return new Point(Convert.ToInt16(x), Convert.ToInt16(y)); 32 | } 33 | 34 | public void Update(Size frameSize) 35 | { 36 | InitialPoint = ScaleUpPercentToFrame(frameSize, _p1); 37 | TerminalPoint = ScaleUpPercentToFrame(frameSize, _p2); 38 | LastTripped = DateTime.MinValue; 39 | MidPoint = new Point(InitialPoint.X + (TerminalPoint.X - InitialPoint.X) / 2, InitialPoint.Y + (TerminalPoint.Y - InitialPoint.Y) / 2); 40 | 41 | Angle = 0; 42 | if (InitialPoint != TerminalPoint) 43 | { 44 | float xDiff = InitialPoint.X - TerminalPoint.X; 45 | float yDiff = InitialPoint.Y - TerminalPoint.Y; 46 | 47 | Angle = Convert.ToInt32(Math.Atan2(yDiff, xDiff) * 180.0 / Math.PI); 48 | Angle = (Angle + 360) % 360; 49 | } 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /PluginUtils/PluginBase.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics.CodeAnalysis; 4 | using System.Text.Json.Nodes; 5 | using System.Text.Json; 6 | 7 | namespace PluginUtils 8 | { 9 | public class PluginBase 10 | { 11 | private bool _disposed; 12 | 13 | 14 | public PluginBase() 15 | { 16 | 17 | } 18 | 19 | private configuration _configObject; 20 | public configuration ConfigObject 21 | { 22 | get 23 | { 24 | if (_configObject != null) 25 | return _configObject; 26 | 27 | _configObject = new configuration(); 28 | return _configObject; 29 | } 30 | } 31 | [DynamicDependency(DynamicallyAccessedMemberTypes.PublicMethods, typeof(PluginBase))] 32 | public virtual string GetConfiguration(string languageCode) 33 | { 34 | // Load the raw JSON from somewhere 35 | string rawJson = ResourceLoader.LoadJson(languageCode); 36 | 37 | // Call your new PopulateResponse method that returns a JsonNode 38 | JsonNode? d = Utils.PopulateResponse(rawJson, ConfigObject); 39 | return d?.ToJsonString() ?? string.Empty; 40 | } 41 | 42 | [DynamicDependency(DynamicallyAccessedMemberTypes.PublicMethods, typeof(PluginBase))] 43 | public virtual void SetConfiguration(string json) 44 | { 45 | //populate configObject with json values 46 | try 47 | { 48 | // 1. Parse the incoming JSON into a JsonNode 49 | JsonNode? node = JsonNode.Parse(json); 50 | Utils.PopulateObject(node, ConfigObject); 51 | } 52 | catch (Exception ex) 53 | { 54 | Utils.LastException = ex; 55 | //Console.WriteLine(ex.Message+" "+ex.StackTrace); 56 | } 57 | 58 | } 59 | 60 | public string CameraName, MicrophoneName; 61 | public int CameraID, MicrophoneID, LocalPort, SampleRate, Channels; 62 | 63 | [DynamicDependency(DynamicallyAccessedMemberTypes.PublicMethods, typeof(PluginBase))] 64 | public virtual string GetResultJSON() 65 | { 66 | lock (ResultsLock) 67 | { 68 | string json = JsonSerializer.Serialize(Results); 69 | Results.Clear(); 70 | return json; 71 | } 72 | } 73 | 74 | private List _results = new List(); 75 | private static object ResultsLock = new object(); 76 | public List Results 77 | { 78 | get 79 | { 80 | lock (ResultsLock) 81 | return _results; 82 | } 83 | set 84 | { 85 | lock(ResultsLock) 86 | _results = value; 87 | } 88 | } 89 | 90 | public string AppPath 91 | { 92 | get; 93 | set; 94 | } 95 | 96 | public string AppDataPath 97 | { 98 | get; 99 | set; 100 | } 101 | 102 | public virtual void ProcessAgentEvent(string ev) 103 | { 104 | } 105 | 106 | public Exception LastException 107 | { 108 | get 109 | { 110 | var ex = Utils.LastException; 111 | Utils.LastException = null; 112 | return ex; 113 | } 114 | } 115 | 116 | public virtual List GetCustomEvents() 117 | { 118 | return null; 119 | } 120 | 121 | 122 | public void SetCameraInfo(string name, int objectID, int localPort) 123 | { 124 | CameraName = name; 125 | CameraID = objectID; 126 | LocalPort = localPort; 127 | } 128 | 129 | public void SetMicrophoneInfo(string name, int objectID, int localPort, int sampleRate, int channels) 130 | { 131 | MicrophoneName = name; 132 | MicrophoneID = objectID; 133 | LocalPort = localPort; 134 | SampleRate = sampleRate; 135 | Channels = channels; 136 | } 137 | 138 | //Implement IDisposable. 139 | public void Dispose() 140 | { 141 | Dispose(true); 142 | GC.SuppressFinalize(this); 143 | } 144 | 145 | protected virtual void Dispose(bool disposing) 146 | { 147 | if (!_disposed) 148 | { 149 | if (disposing) 150 | { 151 | // Free other state (managed objects). 152 | } 153 | // Free your own state (unmanaged objects). 154 | // Set large fields to null. 155 | _disposed = true; 156 | } 157 | } 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /PluginUtils/PluginUtils.projitems: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | $(MSBuildAllProjects);$(MSBuildThisFileFullPath) 5 | true 6 | c44a7120-65d6-4c1c-8db6-ccf543ac496d 7 | 8 | 9 | PluginUtils 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /PluginUtils/PluginUtils.shproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | c44a7120-65d6-4c1c-8db6-ccf543ac496d 5 | 14.0 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /PluginUtils/Points.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace PluginUtils 6 | { 7 | public class Points 8 | { 9 | public float x, y, x2, y2; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /PluginUtils/PrecomputedIndicesCache.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.Extensions.Caching.Memory; 3 | namespace PluginUtils 4 | { 5 | public class PrecomputedIndicesCache 6 | { 7 | // Define a key structure for caching X and Y computations 8 | private struct CacheKey : IEquatable 9 | { 10 | public int RecWidth { get; } 11 | public float ScaleX { get; } 12 | public int RecHeight { get; } 13 | public float ScaleY { get; } 14 | 15 | public CacheKey(int recWidth, float scaleX, int recHeight, float scaleY) 16 | { 17 | RecWidth = recWidth; 18 | ScaleX = scaleX; 19 | RecHeight = recHeight; 20 | ScaleY = scaleY; 21 | } 22 | 23 | public bool Equals(CacheKey other) 24 | { 25 | return RecWidth == other.RecWidth && 26 | ScaleX.Equals(other.ScaleX) && 27 | RecHeight == other.RecHeight && 28 | ScaleY.Equals(other.ScaleY); 29 | } 30 | 31 | public override bool Equals(object obj) 32 | { 33 | return obj is CacheKey other && Equals(other); 34 | } 35 | 36 | public override int GetHashCode() 37 | { 38 | unchecked 39 | { 40 | int hash = 17; 41 | hash = hash * 23 + RecWidth.GetHashCode(); 42 | hash = hash * 23 + ScaleX.GetHashCode(); 43 | hash = hash * 23 + RecHeight.GetHashCode(); 44 | hash = hash * 23 + ScaleY.GetHashCode(); 45 | return hash; 46 | } 47 | } 48 | } 49 | 50 | // The MemoryCache to store precomputed indices and weights 51 | private readonly MemoryCache cache; 52 | 53 | // Maximum number of cache entries 54 | private const long MaxCacheSize = 1000; // Adjust based on your requirements 55 | 56 | // Structure to hold the cached arrays 57 | public class CachedData 58 | { 59 | public int[] XIndices { get; } 60 | public int[] XWeights { get; } 61 | public int[] YIndices { get; } 62 | public int[] YWeights { get; } 63 | 64 | public CachedData(int[] xIndices, int[] xWeights, int[] yIndices, int[] yWeights) 65 | { 66 | XIndices = xIndices; 67 | XWeights = xWeights; 68 | YIndices = yIndices; 69 | YWeights = yWeights; 70 | } 71 | } 72 | 73 | public PrecomputedIndicesCache() 74 | { 75 | var cacheOptions = new MemoryCacheOptions 76 | { 77 | SizeLimit = MaxCacheSize, // Set the size limit 78 | }; 79 | cache = new MemoryCache(cacheOptions); 80 | } 81 | 82 | /// 83 | /// Retrieves the precomputed indices and weights from the cache. 84 | /// If not present, computes them, stores them in the cache, and then returns. 85 | /// 86 | /// Width for X plane computation. 87 | /// Scale factor for X plane. 88 | /// Height for Y plane computation. 89 | /// Scale factor for Y plane. 90 | /// A CachedData object containing the indices and weights. 91 | public CachedData GetOrAddIndices(int recWidth, float scaleX, int recHeight, float scaleY) 92 | { 93 | var key = new CacheKey(recWidth, scaleX, recHeight, scaleY); 94 | 95 | // Try to get the cached data 96 | if (!cache.TryGetValue(key, out CachedData cachedData)) 97 | { 98 | // Compute the data if not present in cache 99 | cachedData = ComputeCachedData(key); 100 | 101 | // Define cache entry options with size and eviction policy 102 | var cacheEntryOptions = new MemoryCacheEntryOptions() 103 | .SetSize(1) // Each entry counts as '1' towards the size limit 104 | .SetSlidingExpiration(TimeSpan.FromMinutes(30)) // Optional: Adjust as needed 105 | .SetPriority(CacheItemPriority.High); // Optional: Adjust based on importance 106 | 107 | // Add the computed data to the cache 108 | cache.Set(key, cachedData, cacheEntryOptions); 109 | } 110 | 111 | return cachedData; 112 | } 113 | 114 | /// 115 | /// Computes the CachedData based on the provided CacheKey. 116 | /// 117 | /// The cache key containing parameters for computation. 118 | /// A new instance of CachedData. 119 | private CachedData ComputeCachedData(CacheKey key) 120 | { 121 | // Choose a scale factor of 256 for Q8.8 fixed-point format 122 | const float scale = 256.0f; 123 | 124 | // Compute X indices and fixed-point weights 125 | int[] xIndices = new int[key.RecWidth]; 126 | int[] xWeights = new int[key.RecWidth]; 127 | for (int x = 0; x < key.RecWidth; x++) 128 | { 129 | float sx = x * key.ScaleX; 130 | xIndices[x] = (int)sx; 131 | float frac = sx - xIndices[x]; // Fractional part 132 | int w = Math.Clamp((int)(frac * scale + 0.5f), 0, 256); 133 | xWeights[x] = w; 134 | } 135 | 136 | // Compute Y indices and fixed-point weights 137 | int[] yIndices = new int[key.RecHeight]; 138 | int[] yWeights = new int[key.RecHeight]; 139 | for (int y = 0; y < key.RecHeight; y++) 140 | { 141 | float sy = y * key.ScaleY; 142 | yIndices[y] = (int)sy; 143 | float frac = sy - yIndices[y]; 144 | int w = Math.Clamp((int)(frac * scale + 0.5f), 0, 256); 145 | yWeights[y] = w; 146 | } 147 | 148 | return new CachedData(xIndices, xWeights, yIndices, yWeights); 149 | } 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /PluginUtils/ResourceLoader.cs: -------------------------------------------------------------------------------- 1 | using PluginUtils; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Reflection; 7 | using System.Text; 8 | 9 | namespace PluginUtils 10 | { 11 | internal class ResourceLoader 12 | { 13 | public static string ResourcePath = "Plugins."; 14 | public static string LoadJson(string languageCode) 15 | { 16 | var assembly = Assembly.GetExecutingAssembly(); 17 | var json = LoadResource(assembly, "json.config_" + languageCode + ".json"); 18 | if (string.IsNullOrEmpty(json)) 19 | json = LoadResource(assembly, "json.config_en.json"); 20 | json = json.Replace("VERSION", "v" + typeof(ResourceLoader).Assembly.GetName().Version); 21 | return json; 22 | 23 | } 24 | 25 | public static string GetResourceText(string filename) 26 | { 27 | var assembly = Assembly.GetExecutingAssembly(); 28 | filename = filename.Replace("/", ".").Replace("\\", "."); 29 | 30 | var resourceName = assembly.GetManifestResourceNames().First(s => s.EndsWith(filename.Replace("/", "."), StringComparison.CurrentCultureIgnoreCase)); 31 | 32 | using (StreamReader reader = new StreamReader(assembly.GetManifestResourceStream(resourceName), 33 | detectEncodingFromByteOrderMarks: true)) 34 | { 35 | return reader.ReadToEnd(); 36 | } 37 | } 38 | 39 | public static byte[] GetResourceBytes(string filename) 40 | { 41 | var assembly = Assembly.GetExecutingAssembly(); 42 | filename = filename.Replace("/", ".").Replace("\\", "."); 43 | 44 | var resourceName = assembly.GetManifestResourceNames().FirstOrDefault(s => s.EndsWith(filename.Replace("/", "."), StringComparison.CurrentCultureIgnoreCase)); 45 | if (resourceName == null) 46 | return null; 47 | 48 | using (var stream = assembly.GetManifestResourceStream(resourceName)) 49 | { 50 | if (stream == null) 51 | { 52 | throw new InvalidOperationException("Could not load manifest resource stream."); 53 | } 54 | using (var memstream = new MemoryStream()) 55 | { 56 | stream.CopyTo(memstream); 57 | return memstream.ToArray(); 58 | } 59 | } 60 | } 61 | 62 | 63 | private static string LoadResource(Assembly assembly, string resourceName) 64 | { 65 | resourceName = ResourcePath + resourceName; 66 | if (!assembly.GetManifestResourceNames().Contains(resourceName)) 67 | return ""; 68 | 69 | using (Stream stream = assembly.GetManifestResourceStream(resourceName)) 70 | { 71 | if (stream != null) 72 | { 73 | using (StreamReader reader = new StreamReader(stream)) 74 | { 75 | return reader.ReadToEnd(); 76 | } 77 | } 78 | } 79 | return ""; 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /PluginUtils/ResultInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace PluginUtils 6 | { 7 | public partial class ResultInfo 8 | { 9 | public string EventName; 10 | public string MSG; 11 | public string AIJSON; 12 | public string Filename; 13 | public string Tags; 14 | public ResultInfo(string eventName, string msg = "", string tags = "", string aijson = "", string filename = "") 15 | { 16 | EventName = eventName; 17 | MSG = msg; 18 | AIJSON = aijson; 19 | Filename = filename; 20 | Tags = tags; 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AgentDVR-Plugins 2 | Plugins for Agent DVR 3 | 4 | Download Agent DVR here: 5 | https://www.ispyconnect.com/download.aspx 6 | 7 | The easiest way to install plugins is to use the remote web portal. Access to this requires a subscription but a one-week free trial is available. When connected to the web portal click on the **Server icon** at top left and **Plugins** under **System**. Select the plugin you want to use in the drop-down at top-right and click **Install**. 8 | 9 | To install without using the web portal you will need to build the plugins from source and copy the built output to [AgentDVR Directory]/Plugins/PLUGINNAME. You must create the `Plugins` directory if it does not exist. 10 | 11 | To use the plugin, add a device (camera and/or microphone) and edit it. See the Plugins tab in the drop-down at top right. Choose your plugin and click the "..." button to configure it. 12 | 13 | # IMPORTANT: 14 | 15 | If you are using an audio plugin like [Listen](https://github.com/ispysoftware/AgentDVR-Plugins/tree/main/Listen) on a camera you will need to edit the camera, select the **Audio** tab and click to configure the microphone. From there you can access the **Plugins** tab for audio devices. Alternatively you can click the **Server Icon**, **Edit Devices** and edit the microphone from that list. 16 | 17 | # General notes on creating plugins: 18 | 19 | Create/ Build a plugin and copy the built output to AgentDVR/Plugins/YourPluginName/ 20 | Restart Agent to use the plugin. To access the plugin settings edit the device, select the Plugin tab and select your plugin. 21 | 22 | https://www.ispyconnect.com/userguide-agent-plugins.aspx 23 | -------------------------------------------------------------------------------- /TensorFlow/EventHandlers.cs: -------------------------------------------------------------------------------- 1 | using SixLabors.ImageSharp; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Globalization; 5 | using System.Linq; 6 | using System.Text; 7 | 8 | namespace Plugins 9 | { 10 | internal static class EventHandlers 11 | { 12 | public delegate void ProcessorEventHandler(ITensorProcessor sender, EventArgs e); 13 | public delegate void ProcessorResultHandler(ITensorProcessor sender, ResultEventArgs e); 14 | 15 | public class ResultEntry 16 | { 17 | public string Label; 18 | public int Probability; 19 | public RectangleF Region = Rectangle.Empty; 20 | 21 | } 22 | public class ResultEventArgs : EventArgs 23 | { 24 | public List Results; 25 | public ResultEventArgs(List results) 26 | { 27 | Results = results.OrderByDescending(p => p.Probability).ToList(); 28 | } 29 | 30 | public override string ToString() 31 | { 32 | var sb = new StringBuilder("{\"results\":["); 33 | foreach (var result in Results) 34 | sb.Append($"{{\"label\":\"{result.Label}\",\"probability\":{result.Probability}}}, \"region\":{{\"x\":{result.Region.X.ToString(CultureInfo.InvariantCulture)},\"y\":{result.Region.Y.ToString(CultureInfo.InvariantCulture)},\"w\":{result.Region.Width.ToString(CultureInfo.InvariantCulture)},\"h\":{result.Region.Height.ToString(CultureInfo.InvariantCulture)}}}}},"); 35 | return sb.ToString().Trim(',')+"]}"; 36 | } 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /TensorFlow/ITensorProcessor.cs: -------------------------------------------------------------------------------- 1 | using Emgu.TF; 2 | using SixLabors.ImageSharp; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using static Plugins.Main; 8 | 9 | namespace Plugins 10 | { 11 | 12 | 13 | internal interface ITensorProcessor 14 | { 15 | event EventHandlers.ProcessorEventHandler ProcessorReady; 16 | event EventHandlers.ProcessorResultHandler ResultGenerated; 17 | bool Ready { get; } 18 | Task Init(); 19 | void Recognise(Tensor t); 20 | bool IsAudio { get; } 21 | Size SizeRequired { get; } 22 | void Close(); 23 | Session Session { get; } 24 | 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /TensorFlow/MainClass.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using SixLabors.ImageSharp; 4 | using SixLabors.ImageSharp.Processing; 5 | using SixLabors.ImageSharp.Drawing.Processing; 6 | using SixLabors.ImageSharp.PixelFormats; 7 | using PluginUtils; 8 | using SixLabors.Fonts; 9 | using System.Linq; 10 | using Emgu.TF; 11 | using System.Threading.Tasks; 12 | using Plugins.Processors; 13 | using System.Runtime.InteropServices; 14 | using System.Text; 15 | using System.Diagnostics; 16 | using System.Dynamic; 17 | using System.Text.Json; 18 | 19 | namespace Plugins 20 | { 21 | public class Main : PluginBase, ICamera 22 | { 23 | private Font _drawFont; 24 | private bool _needUpdate = false; 25 | private ITensorProcessor _processor; 26 | private static readonly string[] fontfams = new[] { "Verdana", "Arial", "Helvetica", "Geneva", "FreeMono", "DejaVu Sans" }; 27 | 28 | public Main() : base() 29 | { 30 | //get cross platform font family 31 | FontFamily fam = SystemFonts.Collection.Families.First(); 32 | foreach (var fontfam in fontfams) 33 | { 34 | if (SystemFonts.Collection.TryGet(fontfam, out fam)) 35 | break; 36 | } 37 | 38 | _drawFont = SystemFonts.CreateFont(fam.Name, 20, FontStyle.Regular); 39 | } 40 | 41 | public string Supports 42 | { 43 | get 44 | { 45 | return "video"; 46 | } 47 | } 48 | 49 | public override List GetCustomEvents() 50 | { 51 | return new List() { "Tensor Triggered", "Tensor Result" }; 52 | } 53 | 54 | public override void SetConfiguration(string json) 55 | { 56 | base.SetConfiguration(json); 57 | _needUpdate = true; 58 | 59 | } 60 | 61 | public override void ProcessAgentEvent(string ev) 62 | { 63 | switch (ev) 64 | { 65 | case "MotionAlert": 66 | break; 67 | case "MotionDetect": 68 | break; 69 | case "ManualAlert": 70 | break; 71 | case "RecordingStart": 72 | break; 73 | case "RecordingStop": 74 | break; 75 | case "AudioAlert": 76 | break; 77 | case "AudioDetect": 78 | break; 79 | } 80 | } 81 | 82 | private void Initialize() 83 | { 84 | if (_processor!=null) 85 | { 86 | if (!_processor.Ready) //wait till it's ready before we close it 87 | return; 88 | _processor.ProcessorReady -= _processor_ProcessorReady; 89 | _processor.Close(); 90 | } 91 | 92 | _processor = null; 93 | 94 | switch (ConfigObject.Model) 95 | { 96 | case "Inception": 97 | _processor = new InceptionProcessor(); 98 | break; 99 | case "MaskRcnnInceptionV2Coco": 100 | _processor = new MaskRCNNProcessor(); 101 | break; 102 | case "MultiboxGraph": 103 | _processor = new MultiBoxGraphProcessor(); 104 | break; 105 | case "Resnet": 106 | _processor = new ResnetProcessor(); 107 | break; 108 | } 109 | if (_processor != null) 110 | { 111 | _processor.ProcessorReady += _processor_ProcessorReady; 112 | _processor.ResultGenerated += _processor_ResultGenerated; 113 | Task.Run(() => _processor.Init()); 114 | } 115 | } 116 | 117 | private void _processor_ResultGenerated(ITensorProcessor sender, EventHandlers.ResultEventArgs e) 118 | { 119 | var results = e.Results.Where(p=>p.Probability> ConfigObject.MinConfidence).ToList(); 120 | lockedResults = results; 121 | 122 | 123 | foreach(var result in results) 124 | Results.Add(new ResultInfo("Tensor Result", result.Label, result.Label, e.ToString())); 125 | 126 | 127 | } 128 | 129 | private void _processor_ProcessorReady(ITensorProcessor sender, EventArgs e) 130 | { 131 | Session.Device[] devices = GetSessionDevices(sender.Session); 132 | StringBuilder sb = new StringBuilder(); 133 | foreach (Session.Device d in devices) 134 | { 135 | sb.Append($"{d.Type}: {d.Name}{Environment.NewLine}"); 136 | } 137 | Debug.WriteLine($"Session Devices:{Environment.NewLine}{sb}"); 138 | } 139 | 140 | private static Session.Device[] GetSessionDevices(Session session) 141 | { 142 | if (session == null) 143 | return null; 144 | return session.ListDevices(null); 145 | } 146 | 147 | private Task _processorTask; 148 | public void ProcessVideoFrame(IntPtr frame, System.Drawing.Size sz, int channels, int stride) 149 | { 150 | if (_needUpdate) 151 | { 152 | _needUpdate = false; 153 | Initialize(); 154 | return; 155 | } 156 | 157 | var _area = Rectangle.Empty; 158 | if (!string.IsNullOrEmpty(ConfigObject.Area)) 159 | { 160 | dynamic zone = JsonSerializer.Deserialize(ConfigObject.Area, new JsonSerializerOptions 161 | { 162 | // Allows case-insensitive matching of property names 163 | PropertyNameCaseInsensitive = true 164 | }); 165 | var d = zone[0]; 166 | var x = Convert.ToInt32(d["x"].Value); 167 | var y = Convert.ToInt32(d["y"].Value); 168 | var w = Convert.ToInt32(d["w"].Value); 169 | var h = Convert.ToInt32(d["h"].Value); 170 | 171 | var r = Utils.ScalePercentageRectangle(new System.Drawing.Rectangle(x,y,w,h), sz); 172 | r.Height = r.Width; 173 | if (r.Top + r.Height > sz.Height) 174 | r.Height = sz.Height - r.Top; 175 | if (r.Left + r.Width > sz.Width) 176 | r.Width= sz.Width - r.Left; 177 | r.Height = Math.Min(r.Height, r.Width); 178 | r.Width = Math.Min(r.Width, r.Height); 179 | 180 | 181 | _area = new Rectangle(r.X, r.Y, r.Width, r.Height); 182 | } 183 | 184 | 185 | if (_processor?.Ready ?? false) 186 | { 187 | if (!_processor.IsAudio) 188 | { 189 | if (!Utils.TaskRunning(_processorTask)) { 190 | if (_area == Rectangle.Empty) 191 | return; 192 | 193 | Tensor t; 194 | var targetSize = _processor.SizeRequired == Size.Empty ? new Size(_area.Width,_area.Height) : _processor.SizeRequired; 195 | unsafe 196 | { 197 | using (var image = Image.WrapMemory(frame.ToPointer(), stride * sz.Height, sz.Width, sz.Height)) 198 | { 199 | using (Image copy = image.Clone(x => x.Resize(targetSize.Width, targetSize.Height, KnownResamplers.Bicubic, _area, new Rectangle(0, 0, targetSize.Width, targetSize.Height), true))) 200 | { 201 | Memory data; 202 | if (copy.DangerousTryGetSinglePixelMemory(out data)) 203 | { 204 | var bytes = MemoryMarshal.AsBytes(data.Span); 205 | t = new Tensor(DataType.Float, new int[] { 1, targetSize.Height, targetSize.Width, 3 }); 206 | var dataPtr = t.DataPointer; 207 | using (var mh = data.Pin()) 208 | { 209 | var step = Emgu.TF.Util.Toolbox.Pixel24ToPixelFloat((IntPtr)mh.Pointer, targetSize.Width, targetSize.Height, 0, 1.0f / 255.0f, false, false, dataPtr); 210 | _processorTask = Task.Run(() => _processor.Recognise(t)); 211 | } 212 | } 213 | } 214 | } 215 | 216 | } 217 | // 218 | } 219 | } 220 | } 221 | 222 | if (ConfigObject.Overlay && _lockedResults!=null) { 223 | var lres = lockedResults.ToList(); 224 | if (lres.Count == 0) 225 | return; 226 | unsafe 227 | { 228 | using (var image = Image.WrapMemory(frame.ToPointer(), stride * sz.Height, sz.Width, sz.Height)) 229 | { 230 | foreach (var l in lres) 231 | { 232 | if (l.Region != Rectangle.Empty) 233 | { 234 | var box = TranslateToOriginalArea(l.Region, _area); 235 | image.Mutate(x => x.Draw(Color.Red, 2, box)); 236 | image.Mutate(x => x.DrawText($"{l.Label} ({l.Probability}%)", _drawFont, Color.White, new PointF(box.X + 10, box.Y + 20))); 237 | } 238 | } 239 | } 240 | } 241 | } 242 | 243 | } 244 | private static float[] ConvertByteToFloat(byte[] array) 245 | { 246 | float[] floatValues = new float[array.Length / 3]; 247 | 248 | int j = 0; 249 | for (int i = 0; i < array.Length; i+=3) 250 | { 251 | int v = Bytes2Int(array[i],array[i+1],array[i+2]); 252 | floatValues[j] = v; 253 | j++; 254 | } 255 | return floatValues; 256 | } 257 | 258 | private static int Bytes2Int(byte b1, byte b2, byte b3) 259 | { 260 | int r = 0; 261 | byte b0 = 0xff; 262 | 263 | if ((b1 & 0x80) != 0) r |= b0 << 24; 264 | r |= b1 << 16; 265 | r |= b2 << 8; 266 | r |= b3; 267 | return r; 268 | } 269 | 270 | private RectangleF TranslateToOriginalArea(RectangleF from, Rectangle source) 271 | { 272 | return new RectangleF(source.Left + from.Width * source.Width,source.Top + from.Height * source.Height, from.Width * source.Width,from.Height * source.Height); 273 | } 274 | 275 | private List _lockedResults = null; 276 | private List lockedResults 277 | { 278 | get 279 | { 280 | lock (this) 281 | return _lockedResults; 282 | } 283 | set 284 | { 285 | lock (this) 286 | _lockedResults = value.ToList(); 287 | } 288 | } 289 | 290 | ~Main() 291 | { 292 | Dispose(false); 293 | } 294 | } 295 | } 296 | -------------------------------------------------------------------------------- /TensorFlow/Processors/InceptionProcessor.cs: -------------------------------------------------------------------------------- 1 | using Emgu.TF; 2 | using Emgu.TF.Models; 3 | using SixLabors.ImageSharp; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Threading.Tasks; 7 | using Tensorflow; 8 | using static Plugins.EventHandlers; 9 | 10 | namespace Plugins.Processors 11 | { 12 | internal class InceptionProcessor: ProcessorBase, ITensorProcessor 13 | { 14 | private Inception model; 15 | public event EventHandlers.ProcessorEventHandler ProcessorReady; 16 | public event EventHandlers.ProcessorResultHandler ResultGenerated; 17 | 18 | public Size SizeRequired => new Size(224, 224);//, 299); 19 | private bool _coldSession = true; 20 | 21 | 22 | public async Task Init() 23 | { 24 | //inceptionGraph = new Inception(); 25 | 26 | //inceptionGraph.OnDownloadProgressChanged += InceptionGraph_OnDownloadProgressChanged; 27 | //inceptionGraph.Init( 28 | // new string[] { "tensorflow_inception_graph.pb", "imagenet_comp_graph_label_strings.txt" }, 29 | // "https://github.com/emgucv/models/blob/master/inception/", 30 | // "Placeholder", 31 | // "final_result"); 32 | 33 | 34 | model = new Inception(null, SessionOptions); 35 | model.OnDownloadProgressChanged += Model_OnDownloadProgressChanged; 36 | 37 | //use a retrained model to recognize flowers 38 | 39 | try 40 | { 41 | //await model.Init( 42 | // new string[] { "optimized_graph.pb", "output_labels.txt" }, 43 | // "https://github.com/emgucv/models/raw/master/inception_flower_retrain/", 44 | // "Placeholder", 45 | // "final_result"); 46 | 47 | 48 | await model.Init( 49 | new string[] { "tensorflow_inception_graph.pb", "imagenet_comp_graph_label_strings.txt" }, 50 | "https://github.com/dotnet/machinelearning-samples/blob/main/samples/csharp/getting-started/DeepLearning_ImageClassification_TensorFlow/ImageClassification/assets/inputs/inception/", 51 | null, 52 | null); 53 | } 54 | catch (Exception ex) 55 | { 56 | var m = ex.Message; 57 | } 58 | 59 | 60 | 61 | Ready = true; 62 | if (CloseWhenReady) 63 | { 64 | model.Dispose(); 65 | return; 66 | } 67 | 68 | ProcessorReady?.Invoke(this, EventArgs.Empty); 69 | } 70 | 71 | private void Model_OnDownloadProgressChanged(long? totalBytesToReceive, long bytesReceived, double? progressPercentage) 72 | { 73 | 74 | } 75 | 76 | public Session Session => model?.Session; 77 | 78 | public void Close() 79 | { 80 | if (Ready) 81 | model?.Dispose(); 82 | else 83 | CloseWhenReady = true; 84 | } 85 | 86 | 87 | public void Recognise(Tensor t) 88 | { 89 | var results = model.Recognize(t); 90 | List reslist = new List(); 91 | foreach(var result in results) 92 | { 93 | if (!double.IsNaN(result[0].Probability)) 94 | reslist.Add(new ResultEntry() { Label = result[0].Label, Probability = Convert.ToInt32(result[0].Probability * 100) }); 95 | } 96 | if (reslist.Count > 0) 97 | ResultGenerated?.Invoke(this, new ResultEventArgs(reslist)); 98 | } 99 | 100 | private void InceptionGraph_OnDownloadProgressChanged(object sender, System.Net.DownloadProgressChangedEventArgs e) 101 | { 102 | if (e.TotalBytesToReceive == 0) 103 | { 104 | 105 | } 106 | } 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /TensorFlow/Processors/MaskRCNNProcessor.cs: -------------------------------------------------------------------------------- 1 | using Emgu.TF; 2 | using Emgu.TF.Models; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Threading.Tasks; 6 | using SixLabors.ImageSharp; 7 | using static Plugins.EventHandlers; 8 | 9 | namespace Plugins.Processors 10 | { 11 | internal class MaskRCNNProcessor : ProcessorBase, ITensorProcessor 12 | { 13 | private MaskRcnnInceptionV2Coco model; 14 | public Size SizeRequired => Size.Empty; 15 | 16 | public Session Session => null; 17 | 18 | public event ProcessorEventHandler ProcessorReady; 19 | public event ProcessorResultHandler ResultGenerated; 20 | 21 | public void Close() 22 | { 23 | if (Ready) 24 | model?.Dispose(); 25 | else 26 | CloseWhenReady = true; 27 | } 28 | 29 | public async Task Init() 30 | { 31 | model = new MaskRcnnInceptionV2Coco(null, SessionOptions); 32 | 33 | await model.Init(); 34 | if (CloseWhenReady) 35 | { 36 | model.Dispose(); 37 | return; 38 | } 39 | 40 | Ready = true; 41 | ProcessorReady?.Invoke(this, EventArgs.Empty); 42 | } 43 | 44 | public void Recognise(Tensor t) 45 | { 46 | var results = model.Recognize(t); 47 | List reslist = new List(); 48 | foreach (var result in results) 49 | { 50 | if (!double.IsNaN(result[0].Probability)) 51 | reslist.Add(new ResultEntry() { Label = result[0].Label, Probability = Convert.ToInt32(result[0].Probability * 100), Region = GenRectangle(result[0].Region)}); 52 | } 53 | if (reslist.Count > 0) 54 | ResultGenerated?.Invoke(this, new EventHandlers.ResultEventArgs(reslist)); 55 | } 56 | 57 | 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /TensorFlow/Processors/MultiBoxGraphProcessor.cs: -------------------------------------------------------------------------------- 1 | using Emgu.TF; 2 | using Emgu.TF.Models; 3 | using System; 4 | using System.Collections.Generic; 5 | using SixLabors.ImageSharp; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | using Tensorflow; 9 | using static Plugins.EventHandlers; 10 | 11 | namespace Plugins.Processors 12 | { 13 | internal class MultiBoxGraphProcessor : ProcessorBase, ITensorProcessor 14 | { 15 | private MultiboxGraph model; 16 | public Size SizeRequired => new Size(224, 224); 17 | 18 | public Session Session => null; 19 | 20 | public event ProcessorEventHandler ProcessorReady; 21 | public event ProcessorResultHandler ResultGenerated; 22 | 23 | public void Close() 24 | { 25 | if (Ready) 26 | model?.Dispose(); 27 | else 28 | CloseWhenReady = true; 29 | } 30 | 31 | public async Task Init() 32 | { 33 | model = new MultiboxGraph(null, SessionOptions); 34 | 35 | await model.Init(); 36 | if (CloseWhenReady) 37 | { 38 | model.Dispose(); 39 | return; 40 | } 41 | 42 | Ready = true; 43 | ProcessorReady?.Invoke(this, EventArgs.Empty); 44 | } 45 | 46 | public void Recognise(Tensor t) 47 | { 48 | var results = model.Detect(t); 49 | List reslist = new List(); 50 | foreach (var result in results) 51 | { 52 | if (!double.IsNaN(result.Scores)) 53 | reslist.Add(new ResultEntry() { Label = "Found", Probability = Convert.ToInt32(result.Scores * 100), Region = GenRectangle(result.DecodedLocations) }); 54 | } 55 | if (reslist.Count > 0) 56 | ResultGenerated?.Invoke(this, new EventHandlers.ResultEventArgs(reslist)); 57 | } 58 | 59 | 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /TensorFlow/Processors/ProcessorBase.cs: -------------------------------------------------------------------------------- 1 | using Emgu.TF; 2 | using SixLabors.ImageSharp; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Text; 6 | using Tensorflow; 7 | 8 | namespace Plugins.Processors 9 | { 10 | internal class ProcessorBase 11 | { 12 | 13 | public bool CloseWhenReady = false; 14 | 15 | 16 | public bool Ready { get; internal set; } 17 | 18 | public bool IsAudio => false; 19 | 20 | internal RectangleF GenRectangle(float[] r) 21 | { 22 | float x1 = r[0]; 23 | float y1 = r[1]; 24 | float x2 = r[2]; 25 | float y2 = r[3]; 26 | return new RectangleF(y1, x1, y2 - y1, x2 - x1); 27 | } 28 | 29 | private SessionOptions _so = null; 30 | public SessionOptions SessionOptions 31 | { 32 | get 33 | { 34 | if (_so != null) 35 | return _so; 36 | _so = new SessionOptions(); 37 | ConfigProto config = new ConfigProto 38 | { 39 | LogDevicePlacement = true 40 | }; 41 | if (TfInvoke.IsGoogleCudaEnabled) 42 | { 43 | config.GpuOptions = new GPUOptions 44 | { 45 | AllowGrowth = true 46 | }; 47 | } 48 | _so.SetConfig(config.ToProtobuf()); 49 | return _so; 50 | } 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /TensorFlow/Processors/ResnetProcessor.cs: -------------------------------------------------------------------------------- 1 | using Emgu.TF; 2 | using Emgu.TF.Models; 3 | using System; 4 | using System.Collections.Generic; 5 | using SixLabors.ImageSharp; 6 | using System.Threading.Tasks; 7 | using Tensorflow; 8 | using static Plugins.EventHandlers; 9 | 10 | namespace Plugins.Processors 11 | { 12 | internal class ResnetProcessor : ProcessorBase, ITensorProcessor 13 | { 14 | private Resnet model; 15 | public Size SizeRequired => new Size(224, 224); 16 | 17 | public Session Session => null; 18 | 19 | public event ProcessorEventHandler ProcessorReady; 20 | public event ProcessorResultHandler ResultGenerated; 21 | 22 | public void Close() 23 | { 24 | if (Ready) 25 | model?.Dispose(); 26 | else 27 | CloseWhenReady = true; 28 | } 29 | 30 | public async Task Init() 31 | { 32 | model = new Resnet(null, SessionOptions); 33 | 34 | await model.Init(); 35 | if (CloseWhenReady) 36 | { 37 | model.Dispose(); 38 | return; 39 | } 40 | 41 | MetaGraphDef metaGraphDef = MetaGraphDef.Parser.ParseFrom(model.MetaGraphDefBuffer.Data); 42 | var signatureDef = metaGraphDef.SignatureDef["serving_default"]; 43 | var inputNode = signatureDef.Inputs; 44 | var outputNode = signatureDef.Outputs; 45 | 46 | HashSet opNames = new HashSet(); 47 | HashSet couldBeInputs = new HashSet(); 48 | HashSet couldBeOutputs = new HashSet(); 49 | foreach (Operation op in model.Graph) 50 | { 51 | 52 | String name = op.Name; 53 | opNames.Add(name); 54 | 55 | if (op.NumInputs == 0 && op.OpType.Equals("Placeholder")) 56 | { 57 | couldBeInputs.Add(op.Name); 58 | AttrMetadata dtypeMeta = op.GetAttrMetadata("dtype"); 59 | AttrMetadata shapeMeta = op.GetAttrMetadata("shape"); 60 | Emgu.TF.DataType type = op.GetAttrType("dtype"); 61 | Int64[] shape = op.GetAttrShape("shape"); 62 | Emgu.TF.Buffer valueBuffer = op.GetAttrValueProto("shape"); 63 | Emgu.TF.Buffer shapeBuffer = op.GetAttrTensorShapeProto("shape"); 64 | TensorShapeProto shapeProto = TensorShapeProto.Parser.ParseFrom(shapeBuffer.Data); 65 | } 66 | 67 | if (op.OpType.Equals("Const")) 68 | { 69 | AttrMetadata dtypeMeta = op.GetAttrMetadata("dtype"); 70 | AttrMetadata valueMeta = op.GetAttrMetadata("value"); 71 | using (Tensor valueTensor = op.GetAttrTensor("value")) 72 | { 73 | var dim = valueTensor.Dim; 74 | } 75 | } 76 | 77 | if (op.OpType.Equals("Conv2D")) 78 | { 79 | AttrMetadata stridesMeta = op.GetAttrMetadata("strides"); 80 | AttrMetadata paddingMeta = op.GetAttrMetadata("padding"); 81 | AttrMetadata boolMeta = op.GetAttrMetadata("use_cudnn_on_gpu"); 82 | Int64[] strides = op.GetAttrIntList("strides"); 83 | bool useCudnn = op.GetAttrBool("use_cudnn_on_gpu"); 84 | String padding = op.GetAttrString("padding"); 85 | } 86 | 87 | foreach (Output output in op.Outputs) 88 | { 89 | int[] shape = model.Graph.GetTensorShape(output); 90 | if (output.NumConsumers == 0) 91 | { 92 | couldBeOutputs.Add(name); 93 | } 94 | } 95 | 96 | Emgu.TF.Buffer buffer = model.Graph.GetOpDef(op.OpType); 97 | OpDef opDef = OpDef.Parser.ParseFrom(buffer.Data); 98 | } 99 | 100 | using (Emgu.TF.Buffer versionDef = model.Graph.Versions()) 101 | { 102 | int l = versionDef.Length; 103 | } 104 | 105 | Ready = true; 106 | ProcessorReady?.Invoke(this, EventArgs.Empty); 107 | } 108 | 109 | public void Recognise(Tensor t) 110 | { 111 | Resnet.RecognitionResult[][] results = model.Recognize(t); 112 | List reslist = new List(); 113 | foreach (var result in results) 114 | { 115 | if (!double.IsNaN(result[0].Probability)) 116 | reslist.Add(new ResultEntry() { Label = result[0].Label, Probability = Convert.ToInt32(result[0].Probability * 100)}); 117 | } 118 | 119 | if (reslist.Count > 0) 120 | ResultGenerated?.Invoke(this, new EventHandlers.ResultEventArgs(reslist)); 121 | } 122 | 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /TensorFlow/README.md: -------------------------------------------------------------------------------- 1 | # AgentDVR-Plugins: TensorFlow 2 | 3 | Download Agent DVR here: 4 | https://www.ispyconnect.com/download.aspx 5 | 6 | See General information on plugins here: 7 | https://github.com/ispysoftware/AgentDVR-Plugins 8 | 9 | The tensorflow plugin uses machine learning to process live video and recognise objects. It raises events in Agent DVR you can use to perform actions. 10 | 11 | ![tensorflow](https://user-images.githubusercontent.com/800093/163336733-a9a751dc-53a7-4bf6-9839-5c587da3605f.png) 12 | 13 | This plugin is currently under (sporadic) development. For well integrated AI object recognition please see 14 | 15 | https://www.ispyconnect.com/userguide-agent-deepstack-ai.aspx 16 | 17 | General notes on creating plugins: 18 | 19 | https://www.ispyconnect.com/userguide-agent-plugins.aspx 20 | -------------------------------------------------------------------------------- /TensorFlow/TensorFlow.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | win-x64 5 | Plugins 6 | TensorFlow 7 | net9.0 8 | DeveloperInABox 9 | Video Surveillance Software 10 | 2025 DeveloperInABox 11 | false 12 | true 13 | true 14 | x64 15 | 1.3.0.0 16 | 1.3.0.0 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /TensorFlow/config.cs: -------------------------------------------------------------------------------- 1 | public partial class configuration { 2 | 3 | private string modelField; 4 | 5 | private string areaField; 6 | 7 | private bool overlayField; 8 | 9 | private int minConfidenceField; 10 | 11 | public configuration() { 12 | this.modelField = "Inception"; 13 | this.areaField = ""; 14 | this.overlayField = true; 15 | this.minConfidenceField = 50; 16 | } 17 | 18 | /// 19 | public string Model { 20 | get { 21 | return this.modelField; 22 | } 23 | set { 24 | this.modelField = value; 25 | } 26 | } 27 | 28 | /// 29 | public string Area { 30 | get { 31 | return this.areaField; 32 | } 33 | set { 34 | this.areaField = value; 35 | } 36 | } 37 | 38 | /// 39 | public bool Overlay { 40 | get { 41 | return this.overlayField; 42 | } 43 | set { 44 | this.overlayField = value; 45 | } 46 | } 47 | 48 | /// 49 | public int MinConfidence { 50 | get { 51 | return this.minConfidenceField; 52 | } 53 | set { 54 | this.minConfidenceField = value; 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /TensorFlow/json/config_en.json: -------------------------------------------------------------------------------- 1 | { 2 | "header": "TensorFlow", 3 | "okResult": "UploadJSON", 4 | "name": "EditPluginConfiguration", 5 | "origin": "Plugin", 6 | "version": "VERSION", 7 | "sections": [ 8 | { 9 | "header": "General", 10 | "displayType": "Form", 11 | "items": [ 12 | { 13 | "text": "Settings", 14 | "type": "Header" 15 | }, 16 | { 17 | "text": "Model", 18 | "value": false, 19 | "type": "Select", 20 | "bindto": "Model", 21 | "options": [ 22 | { 23 | "text": "Inception", 24 | "value": "Inception", 25 | "noTranslate": true 26 | }, 27 | { 28 | "text": "MaskRcnnInceptionV2Coco", 29 | "value": "MaskRcnnInceptionV2Coco", 30 | "noTranslate": true 31 | }, 32 | { 33 | "text": "MultiboxGraph", 34 | "value": "MultiboxGraph", 35 | "noTranslate": true 36 | }, 37 | { 38 | "text": "Resnet", 39 | "value": "Resnet", 40 | "noTranslate": true 41 | } 42 | ] 43 | }, 44 | { 45 | "text": "Area", 46 | "value": "", 47 | "type": "ScreenAreaManager", 48 | "width": 320, 49 | "height": 240, 50 | "help": "Draw a square area to monitor.", 51 | "bindto": "Area", 52 | "max": 1, 53 | "makeSquare": true 54 | }, 55 | { 56 | "text": "Overlay Results", 57 | "value": "", 58 | "type": "Boolean", 59 | "help": "Draw a square area to monitor.", 60 | "bindto": "Overlay" 61 | }, 62 | { 63 | "text": "Minimum Confidence", 64 | "value": "", 65 | "type": "Slider", 66 | "min": 0, 67 | "max": 100, 68 | "help": "Minimum percentage confidence.", 69 | "bindto": "MinConfidence" 70 | } 71 | ] 72 | } 73 | ] 74 | } -------------------------------------------------------------------------------- /TensorFlow/readme.txt: -------------------------------------------------------------------------------- 1 | This project currently targets win-x64 2 | to build for other platforms, install the relevant nuget package for your platform for emgu.tf.runtime 3 | then edit the project file and change the win-x64 to match your platform. 4 | -------------------------------------------------------------------------------- /Test/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reflection; 3 | 4 | namespace Test 5 | { 6 | class Program 7 | { 8 | static void Main(string[] args) 9 | { 10 | var plugins = new string[] { "Demo", "Barcode", "Gain", "TensorFlow", "Weather" }; 11 | foreach (var plugin in plugins) 12 | { 13 | try 14 | { 15 | var path = System.Reflection.Assembly.GetEntryAssembly().Location; 16 | Assembly ass = Assembly.LoadFrom(path.Substring(0, path.IndexOf(@"\Test\")) + @"\" + plugin + @"\bin\Debug\net9.0\" + plugin + ".dll"); 17 | var ins = ass.CreateInstance("Plugins.Main", true); 18 | Console.WriteLine("Instantiated " + plugin + ".dll"); 19 | } 20 | catch (Exception ex) { Console.WriteLine(ex.ToString()); } 21 | } 22 | Console.ReadLine(); 23 | 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Test/Test.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net9.0 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /Weather/Models.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace Plugins 8 | { 9 | // Models for OpenWeatherMap OneCall API 10 | public class WeatherResponse 11 | { 12 | public double lat { get; set; } 13 | public double lon { get; set; } 14 | public string timezone { get; set; } 15 | public int timezone_offset { get; set; } 16 | public Current current { get; set; } 17 | public string message { get; set; } // For error messages 18 | } 19 | 20 | public class Current 21 | { 22 | public long dt { get; set; } 23 | public long sunrise { get; set; } 24 | public long sunset { get; set; } 25 | public double temp { get; set; } 26 | public double feels_like { get; set; } 27 | public int pressure { get; set; } 28 | public int humidity { get; set; } 29 | public double dew_point { get; set; } 30 | public double uvi { get; set; } 31 | public int clouds { get; set; } 32 | public int visibility { get; set; } 33 | public double wind_speed { get; set; } 34 | public int wind_deg { get; set; } 35 | public double? wind_gust { get; set; } // Optional 36 | public List weather { get; set; } 37 | } 38 | 39 | public class Weather 40 | { 41 | public int id { get; set; } 42 | public string main { get; set; } 43 | public string description { get; set; } 44 | public string icon { get; set; } 45 | } 46 | 47 | // Models for OpenWeatherMap Standard API 48 | public class StandardWeatherResponse 49 | { 50 | public List weather { get; set; } 51 | public MainInfo main { get; set; } 52 | public WindInfo wind { get; set; } 53 | public CloudsInfo clouds { get; set; } 54 | public long dt { get; set; } 55 | public SysInfo sys { get; set; } 56 | public int timezone { get; set; } 57 | public long id { get; set; } 58 | public string name { get; set; } 59 | public int cod { get; set; } 60 | public string message { get; set; } // For error messages 61 | } 62 | 63 | public class MainInfo 64 | { 65 | public double temp { get; set; } 66 | public double feels_like { get; set; } 67 | public double temp_min { get; set; } 68 | public double temp_max { get; set; } 69 | public int pressure { get; set; } 70 | public int humidity { get; set; } 71 | } 72 | 73 | public class WindInfo 74 | { 75 | public double speed { get; set; } 76 | public int deg { get; set; } 77 | public double? gust { get; set; } // Optional 78 | } 79 | 80 | public class CloudsInfo 81 | { 82 | public int all { get; set; } 83 | } 84 | 85 | public class SysInfo 86 | { 87 | public int type { get; set; } 88 | public int id { get; set; } 89 | public string country { get; set; } 90 | public long sunrise { get; set; } 91 | public long sunset { get; set; } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /Weather/README.md: -------------------------------------------------------------------------------- 1 | # AgentDVR-Plugins: Weather 2 | 3 | 4 | Download Agent DVR here: 5 | https://www.ispyconnect.com/download.aspx 6 | 7 | See General information on plugins here: 8 | https://github.com/ispysoftware/AgentDVR-Plugins 9 | 10 | Displays live weather information on the camera feed. You will need an API key from 11 | 12 | https://openweathermap.org/ 13 | 14 | Note: API key approval for openweathermap can take up to 20 minutes. 15 | 16 | General notes on creating plugins: 17 | 18 | https://www.ispyconnect.com/userguide-agent-plugins.aspx 19 | 20 | 21 | -------------------------------------------------------------------------------- /Weather/Weather.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Plugins 5 | Weather 6 | net9.0 7 | DeveloperInABox 8 | Video Surveillance Software 9 | 2025 DeveloperInABox 10 | false 11 | true 12 | 1.7.3.0 13 | 1.7.3.0 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | -------------------------------------------------------------------------------- /Weather/config.cs: -------------------------------------------------------------------------------- 1 | public partial class configuration { 2 | 3 | private string aPIKeyField; 4 | 5 | private string uRLField; 6 | 7 | private string latLngField; 8 | 9 | private int updateFrequencyField; 10 | 11 | private int fontSizeField; 12 | 13 | private string aPIversionField; 14 | 15 | private string foregroundField; 16 | 17 | private string backgroundField; 18 | 19 | private bool displayBackgroundField; 20 | 21 | private int positionField; 22 | 23 | private string unitsField; 24 | 25 | private int gustLimitField; 26 | 27 | private int tempLimitField; 28 | 29 | private string statusEventField; 30 | 31 | private string displayTypeField; 32 | 33 | private string formatField; 34 | 35 | public configuration() { 36 | this.aPIKeyField = ""; 37 | this.uRLField = ""; 38 | this.latLngField = ""; 39 | this.updateFrequencyField = 3600; 40 | this.fontSizeField = 12; 41 | this.aPIversionField = "3.0"; 42 | this.foregroundField = "#ffffff"; 43 | this.backgroundField = "#202020"; 44 | this.displayBackgroundField = true; 45 | this.positionField = 1; 46 | this.unitsField = "standard"; 47 | this.gustLimitField = 20; 48 | this.tempLimitField = 40; 49 | this.statusEventField = ""; 50 | this.displayTypeField = "full"; 51 | this.formatField = "{icon}{main}: {description} \r\n{wind} {windDir} {gust} \r\n{temp} {feelsLike} \r\n{hum" + 52 | "idity} {uvi}"; 53 | } 54 | 55 | /// 56 | public string APIKey { 57 | get { 58 | return this.aPIKeyField; 59 | } 60 | set { 61 | this.aPIKeyField = value; 62 | } 63 | } 64 | 65 | /// 66 | public string URL { 67 | get { 68 | return this.uRLField; 69 | } 70 | set { 71 | this.uRLField = value; 72 | } 73 | } 74 | 75 | /// 76 | public string LatLng { 77 | get { 78 | return this.latLngField; 79 | } 80 | set { 81 | this.latLngField = value; 82 | } 83 | } 84 | 85 | /// 86 | public int UpdateFrequency { 87 | get { 88 | return this.updateFrequencyField; 89 | } 90 | set { 91 | this.updateFrequencyField = value; 92 | } 93 | } 94 | 95 | /// 96 | public int FontSize { 97 | get { 98 | return this.fontSizeField; 99 | } 100 | set { 101 | this.fontSizeField = value; 102 | } 103 | } 104 | 105 | /// 106 | public string APIversion { 107 | get { 108 | return this.aPIversionField; 109 | } 110 | set { 111 | this.aPIversionField = value; 112 | } 113 | } 114 | 115 | /// 116 | public string Foreground { 117 | get { 118 | return this.foregroundField; 119 | } 120 | set { 121 | this.foregroundField = value; 122 | } 123 | } 124 | 125 | /// 126 | public string Background { 127 | get { 128 | return this.backgroundField; 129 | } 130 | set { 131 | this.backgroundField = value; 132 | } 133 | } 134 | 135 | /// 136 | public bool DisplayBackground { 137 | get { 138 | return this.displayBackgroundField; 139 | } 140 | set { 141 | this.displayBackgroundField = value; 142 | } 143 | } 144 | 145 | /// 146 | public int Position { 147 | get { 148 | return this.positionField; 149 | } 150 | set { 151 | this.positionField = value; 152 | } 153 | } 154 | 155 | /// 156 | public string Units { 157 | get { 158 | return this.unitsField; 159 | } 160 | set { 161 | this.unitsField = value; 162 | } 163 | } 164 | 165 | /// 166 | public int GustLimit { 167 | get { 168 | return this.gustLimitField; 169 | } 170 | set { 171 | this.gustLimitField = value; 172 | } 173 | } 174 | 175 | /// 176 | public int TempLimit { 177 | get { 178 | return this.tempLimitField; 179 | } 180 | set { 181 | this.tempLimitField = value; 182 | } 183 | } 184 | 185 | /// 186 | public string StatusEvent { 187 | get { 188 | return this.statusEventField; 189 | } 190 | set { 191 | this.statusEventField = value; 192 | } 193 | } 194 | 195 | /// 196 | public string DisplayType { 197 | get { 198 | return this.displayTypeField; 199 | } 200 | set { 201 | this.displayTypeField = value; 202 | } 203 | } 204 | 205 | /// 206 | public string Format { 207 | get { 208 | return this.formatField; 209 | } 210 | set { 211 | this.formatField = value; 212 | } 213 | } 214 | } 215 | -------------------------------------------------------------------------------- /Weather/icons/01d.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ispysoftware/AgentDVR-Plugins/c6f0a9e81afca19cf4bd535058976a29416570b2/Weather/icons/01d.png -------------------------------------------------------------------------------- /Weather/icons/01n.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ispysoftware/AgentDVR-Plugins/c6f0a9e81afca19cf4bd535058976a29416570b2/Weather/icons/01n.png -------------------------------------------------------------------------------- /Weather/icons/02d.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ispysoftware/AgentDVR-Plugins/c6f0a9e81afca19cf4bd535058976a29416570b2/Weather/icons/02d.png -------------------------------------------------------------------------------- /Weather/icons/02n.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ispysoftware/AgentDVR-Plugins/c6f0a9e81afca19cf4bd535058976a29416570b2/Weather/icons/02n.png -------------------------------------------------------------------------------- /Weather/icons/03d.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ispysoftware/AgentDVR-Plugins/c6f0a9e81afca19cf4bd535058976a29416570b2/Weather/icons/03d.png -------------------------------------------------------------------------------- /Weather/icons/03n.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ispysoftware/AgentDVR-Plugins/c6f0a9e81afca19cf4bd535058976a29416570b2/Weather/icons/03n.png -------------------------------------------------------------------------------- /Weather/icons/04d.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ispysoftware/AgentDVR-Plugins/c6f0a9e81afca19cf4bd535058976a29416570b2/Weather/icons/04d.png -------------------------------------------------------------------------------- /Weather/icons/04n.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ispysoftware/AgentDVR-Plugins/c6f0a9e81afca19cf4bd535058976a29416570b2/Weather/icons/04n.png -------------------------------------------------------------------------------- /Weather/icons/09d.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ispysoftware/AgentDVR-Plugins/c6f0a9e81afca19cf4bd535058976a29416570b2/Weather/icons/09d.png -------------------------------------------------------------------------------- /Weather/icons/09n.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ispysoftware/AgentDVR-Plugins/c6f0a9e81afca19cf4bd535058976a29416570b2/Weather/icons/09n.png -------------------------------------------------------------------------------- /Weather/icons/10d.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ispysoftware/AgentDVR-Plugins/c6f0a9e81afca19cf4bd535058976a29416570b2/Weather/icons/10d.png -------------------------------------------------------------------------------- /Weather/icons/10n.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ispysoftware/AgentDVR-Plugins/c6f0a9e81afca19cf4bd535058976a29416570b2/Weather/icons/10n.png -------------------------------------------------------------------------------- /Weather/icons/11d.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ispysoftware/AgentDVR-Plugins/c6f0a9e81afca19cf4bd535058976a29416570b2/Weather/icons/11d.png -------------------------------------------------------------------------------- /Weather/icons/11n.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ispysoftware/AgentDVR-Plugins/c6f0a9e81afca19cf4bd535058976a29416570b2/Weather/icons/11n.png -------------------------------------------------------------------------------- /Weather/icons/13d.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ispysoftware/AgentDVR-Plugins/c6f0a9e81afca19cf4bd535058976a29416570b2/Weather/icons/13d.png -------------------------------------------------------------------------------- /Weather/icons/13n.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ispysoftware/AgentDVR-Plugins/c6f0a9e81afca19cf4bd535058976a29416570b2/Weather/icons/13n.png -------------------------------------------------------------------------------- /Weather/icons/50d.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ispysoftware/AgentDVR-Plugins/c6f0a9e81afca19cf4bd535058976a29416570b2/Weather/icons/50d.png -------------------------------------------------------------------------------- /Weather/icons/50n.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ispysoftware/AgentDVR-Plugins/c6f0a9e81afca19cf4bd535058976a29416570b2/Weather/icons/50n.png -------------------------------------------------------------------------------- /Weather/json/config_en.json: -------------------------------------------------------------------------------- 1 | { 2 | "header": "Weather", 3 | "okResult": "UploadJSON", 4 | "name": "EditPluginConfiguration", 5 | "origin": "Plugin", 6 | "version": "VERSION", 7 | "sections": [ 8 | { 9 | "header": "General", 10 | "displayType": "Form", 11 | "items": [ 12 | { 13 | "text": "Data Endpoint", 14 | "type": "Header" 15 | }, 16 | { 17 | "text": "URL", 18 | "value": "", 19 | "type": "String", 20 | "bindto": "URL", 21 | "live": true, 22 | "help": "Enter a URL to the weather endpoint or leave this blank and use the options below to configure one. " 23 | }, 24 | { 25 | "text": "OR Use OneCall:", 26 | "type": "Header" 27 | }, 28 | { 29 | "text": "API Key", 30 | "value": "", 31 | "type": "String", 32 | "bindto": "APIKey", 33 | "live": true, 34 | "help": "API key from openweathermap.org" 35 | }, 36 | { 37 | "text": "API Version", 38 | "value": "0", 39 | "type": "Select", 40 | "bindto": "APIVersion", 41 | "options": [ 42 | { 43 | "text": "3.0", 44 | "value": "3.0" 45 | }, 46 | { 47 | "text": "2.0", 48 | "value": "2.0" 49 | } 50 | ], 51 | "live": true, 52 | "help": "OpenWeatherMap only supports v2.0 with old accounts. New accounts need to use v3.0 and create a subscription on openweathermap.org (with a free quota)." 53 | }, 54 | { 55 | "text": "Lat, Lng", 56 | "value": "", 57 | "type": "MapControl", 58 | "bindto": "LatLng", 59 | "live": true, 60 | "help": "Latitude and Longitude eg: -33.8649255,155.0023731" 61 | }, 62 | { 63 | "text": "Settings", 64 | "type": "Header" 65 | }, 66 | { 67 | "text": "Update Frequency", 68 | "value": "", 69 | "type": "Slider", 70 | "range": false, 71 | "min": 1, 72 | "max": 1200, 73 | "bindto": "UpdateFrequency", 74 | "live": true, 75 | "help": "(Seconds)" 76 | }, 77 | { 78 | "text": "Units", 79 | "value": "0", 80 | "type": "Select", 81 | "bindto": "Units", 82 | "options": [ 83 | { 84 | "text": "Standard", 85 | "value": "standard" 86 | }, 87 | { 88 | "text": "Metric", 89 | "value": "metric" 90 | }, 91 | { 92 | "text": "Imperial", 93 | "value": "imperial" 94 | } 95 | ], 96 | "live": true 97 | }, 98 | { 99 | "text": "Font Size", 100 | "value": "", 101 | "type": "Int32", 102 | "range": false, 103 | "min": 8, 104 | "max": 100, 105 | "bindto": "FontSize", 106 | "live": true 107 | }, 108 | { 109 | "text": "Foreground", 110 | "value": "", 111 | "type": "Color", 112 | "range": false, 113 | "min": 8, 114 | "max": 100, 115 | "bindto": "Foreground", 116 | "live": true 117 | }, 118 | { 119 | "text": "Background", 120 | "value": "", 121 | "type": "Color", 122 | "bindto": "Background", 123 | "live": true 124 | }, 125 | { 126 | "text": "Show Background", 127 | "value": "", 128 | "type": "Boolean", 129 | "bindto": "DisplayBackground", 130 | "live": true 131 | }, 132 | { 133 | "text": "Position", 134 | "value": "0", 135 | "type": "Select", 136 | "bindto": "Position", 137 | "options": [ 138 | { 139 | "text": "None", 140 | "value": 0 141 | }, 142 | { 143 | "text": "Top Left", 144 | "value": 1 145 | }, 146 | { 147 | "text": "Top Center", 148 | "value": 2 149 | }, 150 | { 151 | "text": "Top Right", 152 | "value": 3 153 | }, 154 | { 155 | "text": "Left", 156 | "value": 7 157 | }, 158 | { 159 | "text": "Middle", 160 | "value": 8 161 | }, 162 | { 163 | "text": "Right", 164 | "value": 9 165 | }, 166 | { 167 | "text": "Bottom Left", 168 | "value": 4 169 | }, 170 | { 171 | "text": "Bottom Center", 172 | "value": 5 173 | }, 174 | { 175 | "text": "Bottom Right", 176 | "value": 6 177 | } 178 | ], 179 | "live": true 180 | }, 181 | { 182 | "text": "Format", 183 | "value": "0", 184 | "type": "TextArea", 185 | "bindto": "Format", 186 | "live": true, 187 | "help": "String format for weather info. Available tags: {icon} {main} {description} {wind} {windDir} {windDeg} {gust} {temp} {feelsLike} {humidity} {uvi}" 188 | } 189 | ] 190 | }, 191 | { 192 | "header": "Events", 193 | "displayType": "Form", 194 | "help": "Generate temperature and gust events if greater than a set limit or if the main status changes. See actions in the camera to respond to these events.", 195 | "items": [ 196 | { 197 | "text": "Temperature Limit", 198 | "value": "", 199 | "type": "Int32", 200 | "bindto": "TempLimit" 201 | }, 202 | { 203 | "text": "Gust Limit", 204 | "value": "", 205 | "type": "Int32", 206 | "bindto": "GustLimit" 207 | }, 208 | { 209 | "text": "Status", 210 | "value": "", 211 | "type": "Select", 212 | "bindto": "StatusEvent", 213 | "options": [ 214 | { 215 | "text": "None", 216 | "value": "" 217 | }, 218 | { 219 | "text": "Ash", 220 | "value": "Ash" 221 | }, 222 | { 223 | "text": "Clouds", 224 | "value": "Clouds" 225 | }, 226 | { 227 | "text": "Drizzle", 228 | "value": "Drizzle" 229 | }, 230 | { 231 | "text": "Dust", 232 | "value": "Dust" 233 | }, 234 | { 235 | "text": "Fog", 236 | "value": "Fog" 237 | }, 238 | { 239 | "text": "Haze", 240 | "value": "Haze" 241 | }, 242 | { 243 | "text": "Mist", 244 | "value": "Mist" 245 | }, 246 | { 247 | "text": "Rain", 248 | "value": "Rain" 249 | }, 250 | { 251 | "text": "Sand", 252 | "value": "Sand" 253 | }, 254 | { 255 | "text": "Smoke", 256 | "value": "Smoke" 257 | }, 258 | { 259 | "text": "Snow", 260 | "value": "Snow" 261 | }, 262 | { 263 | "text": "Squall", 264 | "value": "Squall" 265 | }, 266 | { 267 | "text": "Thunderstorm", 268 | "value": "Thunderstorm" 269 | }, 270 | { 271 | "text": "Tornado", 272 | "value": "Tornado" 273 | } 274 | ] 275 | } 276 | ] 277 | } 278 | ] 279 | } -------------------------------------------------------------------------------- /Weather/readme.txt: -------------------------------------------------------------------------------- 1 | Please see https://www.ispyconnect.com/userguide-agent-plugins.aspx for instructions 2 | --------------------------------------------------------------------------------