Compare commits
6 Commits
affae8ff48
...
0a78f444bd
Author | SHA1 | Date | |
---|---|---|---|
![]() |
0a78f444bd | ||
![]() |
a95d3f997f | ||
![]() |
c3fb8b9c6f | ||
![]() |
2bbbc32322 | ||
![]() |
aa5c6d3f6d | ||
![]() |
da070f5e57 |
1
.gitattributes
vendored
Normal file
1
.gitattributes
vendored
Normal file
@ -0,0 +1 @@
|
||||
src/CasaBot/CasaBotApp/dvr-scanner/** filter=lfs diff=lfs merge=lfs -text
|
13
src/CasaBot/AutoScan/AutoScan.csproj
Normal file
13
src/CasaBot/AutoScan/AutoScan.csproj
Normal file
@ -0,0 +1,13 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="9.0.2" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
22
src/CasaBot/AutoScan/AutoScanApp.cs
Normal file
22
src/CasaBot/AutoScan/AutoScanApp.cs
Normal file
@ -0,0 +1,22 @@
|
||||
using AutoScan.Options;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace AutoScan;
|
||||
|
||||
public class AutoScanApp
|
||||
{
|
||||
private readonly AutoScanOptions _options;
|
||||
private readonly ILogger<AutoScanApp> _logger;
|
||||
|
||||
public AutoScanApp(AutoScanOptions options, ILogger<AutoScanApp> logger)
|
||||
{
|
||||
_options = options;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
private void Run()
|
||||
{
|
||||
_logger.LogInformation("AutoScanApp is running...");
|
||||
_logger.LogInformation("Waiting for next scan at {At}.", _options.At);
|
||||
}
|
||||
}
|
15
src/CasaBot/AutoScan/Options/AutoScanOptions.cs
Normal file
15
src/CasaBot/AutoScan/Options/AutoScanOptions.cs
Normal file
@ -0,0 +1,15 @@
|
||||
namespace AutoScan.Options;
|
||||
|
||||
public class AutoScanOptions
|
||||
{
|
||||
public bool Enabled { get; set; }
|
||||
public string? At { get; set; }
|
||||
public bool FromDayBefore { get; set; }
|
||||
public string? From { get; set; }
|
||||
public string? To { get; set; }
|
||||
public int MaxAmount { get; set; }
|
||||
public string? MediaFolder { get; set; }
|
||||
public ShinobiOptions? Shinobi { get; set; }
|
||||
public ScannerOptions? Scanner { get; set; }
|
||||
public ScreenshotOptions? Screenshot { get; set; }
|
||||
}
|
8
src/CasaBot/AutoScan/Options/ScannerOptions.cs
Normal file
8
src/CasaBot/AutoScan/Options/ScannerOptions.cs
Normal file
@ -0,0 +1,8 @@
|
||||
namespace AutoScan.Options;
|
||||
|
||||
public class ScannerOptions
|
||||
{
|
||||
public string? Exe { get; set; }
|
||||
public string? ConfigFile { get; set; }
|
||||
public string? DetectionFolder { get; set; }
|
||||
}
|
7
src/CasaBot/AutoScan/Options/ScreenshotOptions.cs
Normal file
7
src/CasaBot/AutoScan/Options/ScreenshotOptions.cs
Normal file
@ -0,0 +1,7 @@
|
||||
namespace AutoScan.Options;
|
||||
|
||||
public class ScreenshotOptions
|
||||
{
|
||||
public string? Folder { get; set; }
|
||||
public int OffsetSeconds { get; set; }
|
||||
}
|
9
src/CasaBot/AutoScan/Options/ShinobiOptions.cs
Normal file
9
src/CasaBot/AutoScan/Options/ShinobiOptions.cs
Normal file
@ -0,0 +1,9 @@
|
||||
namespace AutoScan.Options;
|
||||
|
||||
public class ShinobiOptions
|
||||
{
|
||||
public string? URL { get; set; }
|
||||
public string? APIKey { get; set; }
|
||||
public string? GroupId { get; set; }
|
||||
public string? MonitorId { get; set; }
|
||||
}
|
@ -2,6 +2,8 @@
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CasaBotApp", "CasaBotApp\CasaBotApp.csproj", "{FF1AF6E7-88E4-488B-B6FB-BDAC126DD94E}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AutoScan", "AutoScan\AutoScan.csproj", "{13D75ACB-7913-4C4B-B696-9BD7383012AF}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
@ -12,5 +14,9 @@ Global
|
||||
{FF1AF6E7-88E4-488B-B6FB-BDAC126DD94E}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{FF1AF6E7-88E4-488B-B6FB-BDAC126DD94E}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{FF1AF6E7-88E4-488B-B6FB-BDAC126DD94E}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{13D75ACB-7913-4C4B-B696-9BD7383012AF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{13D75ACB-7913-4C4B-B696-9BD7383012AF}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{13D75ACB-7913-4C4B-B696-9BD7383012AF}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{13D75ACB-7913-4C4B-B696-9BD7383012AF}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
|
@ -58,7 +58,40 @@ public class BotHandler
|
||||
_bot.SendMessage(subscriber, message);
|
||||
}
|
||||
}
|
||||
|
||||
public void Subscribe(long id)
|
||||
{
|
||||
if (_subscribers.Any(s => s.Identifier == id))
|
||||
{
|
||||
_logger.LogWarning("User {Id} is already subscribed", id);
|
||||
return;
|
||||
}
|
||||
|
||||
_subscribers.Add(new ChatId(id));
|
||||
_logger.LogInformation("User {Id} subscribed to receive messages", id);
|
||||
}
|
||||
|
||||
public async Task UpdatePhoto(string path)
|
||||
{
|
||||
if (_bot is null)
|
||||
{
|
||||
_logger.LogWarning("Bot is not initialized yet");
|
||||
return;
|
||||
}
|
||||
|
||||
if (_subscribers.Count == 0)
|
||||
{
|
||||
_logger.LogWarning("No subscribers to send message to");
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var subscriber in _subscribers)
|
||||
{
|
||||
await using var stream = File.OpenRead(path);
|
||||
var inputFile = InputFile.FromStream(stream, Path.GetFileName(path));
|
||||
await _bot.SendPhoto(subscriber, inputFile, "Detected object");
|
||||
}
|
||||
}
|
||||
private async Task SendImageTest(long id)
|
||||
{
|
||||
if (_bot is null)
|
||||
|
@ -18,4 +18,22 @@
|
||||
<PackageReference Include="Telegram.Bot" Version="22.3.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Update="dvr-scanner\**\*">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Update="appsettings.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\AutoScan\AutoScan.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
|
||||
</Project>
|
||||
|
64
src/CasaBot/CasaBotApp/DVRScanner.cs
Normal file
64
src/CasaBot/CasaBotApp/DVRScanner.cs
Normal file
@ -0,0 +1,64 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace CasaBotApp;
|
||||
|
||||
public class DVRScanner
|
||||
{
|
||||
private readonly ILogger<DVRScanner> _logger;
|
||||
private const string _mediaPath = @".\media"; // Replace with your desired file path
|
||||
private const string _dvrScannerFile = @".\dvr-scanner\dvr-scan.exe";
|
||||
private const string _dvrScannerConfig = @".\dvr-scanner\dvr-scan.cfg";
|
||||
|
||||
//.\dvr-scanner\dvr-scan.exe -i .\media\*.mp4 -c .\dvr-scanner\dvr-scan.cfg
|
||||
public DVRScanner(ILogger<DVRScanner> logger)
|
||||
{
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task ScanVideos(CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
//make sure the directory exists
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(_mediaPath)!);
|
||||
|
||||
|
||||
_logger.LogDebug("Scanning videos...");
|
||||
var process = new Process
|
||||
{
|
||||
StartInfo = new ProcessStartInfo
|
||||
{
|
||||
FileName = _dvrScannerFile,
|
||||
Arguments = $"-i {_mediaPath}\\*.mp4 -c {_dvrScannerConfig}",
|
||||
RedirectStandardOutput = true,
|
||||
UseShellExecute = false,
|
||||
CreateNoWindow = true,
|
||||
|
||||
|
||||
|
||||
}
|
||||
};
|
||||
process.Start();
|
||||
cancellationToken.Register(() =>
|
||||
{
|
||||
_logger.LogDebug("DVR Process status: ID: {Id}, HasExited: {HasExited}, Responding: {Responding}",
|
||||
process.Id, process.HasExited, process.Responding);
|
||||
if(process.HasExited) return;
|
||||
|
||||
_logger.LogWarning("DVR Process is still running, killing it...");
|
||||
process.Kill();
|
||||
});
|
||||
|
||||
_logger.LogDebug("DVR Process started with ID: {Id}", process.Id);
|
||||
await process.WaitForExitAsync(cancellationToken);
|
||||
|
||||
|
||||
_logger.LogInformation("Videos scanned successfully!");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "An error occurred while scanning the videos");
|
||||
}
|
||||
}
|
||||
}
|
87
src/CasaBot/CasaBotApp/FFMPEGWrapper.cs
Normal file
87
src/CasaBot/CasaBotApp/FFMPEGWrapper.cs
Normal file
@ -0,0 +1,87 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace CasaBotApp;
|
||||
|
||||
public class FfmpegWrapper
|
||||
{
|
||||
private readonly ILogger<FfmpegWrapper> _logger;
|
||||
|
||||
public FfmpegWrapper(ILogger<FfmpegWrapper> logger)
|
||||
{
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
private const string FFMPEG_PATH = @".\dvr-scanner\ffmpeg.exe";
|
||||
//Include method for get duration of a video, extract the frame at a specific time
|
||||
|
||||
public async Task<TimeSpan> GetVideoDuration(string videoPath)
|
||||
{
|
||||
try
|
||||
{
|
||||
_logger.LogDebug("Getting video duration...");
|
||||
var ffmpegProcess = new Process
|
||||
{
|
||||
StartInfo = new ProcessStartInfo
|
||||
{
|
||||
FileName = FFMPEG_PATH,
|
||||
Arguments = $"-i {videoPath}",
|
||||
RedirectStandardOutput = false,
|
||||
UseShellExecute = false,
|
||||
CreateNoWindow = true,
|
||||
RedirectStandardError = true
|
||||
}
|
||||
};
|
||||
ffmpegProcess.Start();
|
||||
|
||||
var standardError = await ffmpegProcess.StandardError.ReadToEndAsync();
|
||||
await ffmpegProcess.WaitForExitAsync();
|
||||
|
||||
// Parse the duration from the ffmpeg output
|
||||
var durationString = System.Text.RegularExpressions.Regex.Match(standardError, @"Duration: (\d{2}):(\d{2}):(\d{2})\.(\d{2})").Groups;
|
||||
var ts = new TimeSpan(0,
|
||||
int.Parse(durationString[1].Value),
|
||||
int.Parse(durationString[2].Value),
|
||||
int.Parse(durationString[3].Value),
|
||||
int.Parse(durationString[4].Value) * 10
|
||||
);
|
||||
return ts;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "An error occurred while getting the video duration");
|
||||
throw new Exception("There was an error getting the video duration", ex);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task ExtractFrame(string videoPath, string outputPath, TimeSpan time)
|
||||
{
|
||||
try
|
||||
{
|
||||
var timeParam = $"{time.Minutes:D2}:{time.Seconds:D2}.{time.Milliseconds:D2}";
|
||||
_logger.LogDebug("Extracting frame: {videoPath} at {time} to {outputPath}", videoPath, timeParam, outputPath);
|
||||
var ffmpegProcess = new Process
|
||||
{
|
||||
StartInfo = new ProcessStartInfo
|
||||
{
|
||||
FileName = FFMPEG_PATH,
|
||||
Arguments = $"-y -i {videoPath} -ss {timeParam} -vframes 1 {outputPath}",
|
||||
RedirectStandardOutput = false,
|
||||
UseShellExecute = false,
|
||||
CreateNoWindow = true,
|
||||
RedirectStandardError = false
|
||||
}
|
||||
};
|
||||
ffmpegProcess.Start();
|
||||
_logger.LogDebug("Waiting for frame extraction... process ID: {Id}", ffmpegProcess.Id);
|
||||
await ffmpegProcess.WaitForExitAsync();
|
||||
_logger.LogInformation("Frame extracted successfully!");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "An error occurred while extracting the frame");
|
||||
throw new Exception("There was an error extracting the frame", ex);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,4 +1,5 @@
|
||||
using CasaBotApp;
|
||||
using AutoScan;
|
||||
using CasaBotApp;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
@ -14,25 +15,30 @@ IConfigurationRoot configuration = new ConfigurationBuilder()
|
||||
|
||||
var serviceCollection = new ServiceCollection();
|
||||
|
||||
serviceCollection.AddSingleton(configuration);
|
||||
serviceCollection.AddLogging(builder =>
|
||||
{
|
||||
builder.AddConfiguration(configuration.GetSection("Logging"));
|
||||
builder.AddConsole();
|
||||
});
|
||||
serviceCollection.AddSingleton(new BotConfiguration()
|
||||
{
|
||||
Token = configuration.GetValue<string>("TelegramToken") ?? ""
|
||||
});
|
||||
serviceCollection.Configure<AutoScan.Options.AutoScanOptions>(configuration.GetSection("AutoScan"));
|
||||
|
||||
serviceCollection.AddSingleton<BotHandler>();
|
||||
serviceCollection.AddSingleton<ShinobiConnector>();
|
||||
serviceCollection.AddSingleton<DVRScanner>();
|
||||
serviceCollection.AddSingleton<FfmpegWrapper>();
|
||||
serviceCollection.AddSingleton<AutoScanApp>();
|
||||
serviceCollection.AddHttpClient();
|
||||
|
||||
var serviceProvider = serviceCollection.BuildServiceProvider();
|
||||
|
||||
var logger = serviceProvider.GetService<ILogger<Program>>()!;
|
||||
|
||||
var botHandler = serviceProvider.GetService<BotHandler>();
|
||||
if (botHandler is null)
|
||||
{
|
||||
Console.WriteLine("Bot is not initialized");
|
||||
return;
|
||||
}
|
||||
var botHandler = serviceProvider.GetService<BotHandler>()!;
|
||||
|
||||
using var cts = new CancellationTokenSource();
|
||||
|
||||
@ -40,8 +46,38 @@ botHandler.Start(cts.Token);
|
||||
|
||||
_ = SendMessageToSubscribers(cts.Token);
|
||||
|
||||
var videoFileName = configuration.GetValue<string>("VideoFileName") ?? "video.mp4";
|
||||
if (configuration.GetValue<bool>("Fetch"))
|
||||
{
|
||||
var shinobiConnector = serviceProvider.GetService<ShinobiConnector>()!;
|
||||
await shinobiConnector.FetchLastVideo(videoFileName);
|
||||
}
|
||||
|
||||
Console.WriteLine($"bot is running... Press Enter to terminate");
|
||||
if (configuration.GetValue<bool>("Scan"))
|
||||
{
|
||||
var dvrScanner = serviceProvider.GetService<DVRScanner>()!;
|
||||
await dvrScanner.ScanVideos(cts.Token);
|
||||
}
|
||||
|
||||
if(configuration.GetValue<bool>("Screenshot"))
|
||||
{
|
||||
var detected = "2025-02-12T07-00-02.DSME_0001.avi";
|
||||
var ffmpegWrapper = serviceProvider.GetService<FfmpegWrapper>()!;
|
||||
var duration = await ffmpegWrapper.GetVideoDuration($@".\media\detected\{detected}");
|
||||
logger.LogInformation("Video duration: {Duration}", duration);
|
||||
var middleTime = (duration / 2).Add(TimeSpan.FromSeconds(0.5));
|
||||
|
||||
//Extract frame at middle time
|
||||
var screenshotPath = $@".\media\detected\{detected}-ss.png";
|
||||
await ffmpegWrapper.ExtractFrame($@".\media\detected\{detected}", screenshotPath, middleTime);
|
||||
logger.LogInformation("Screenshot extracted at {MiddleTime}", middleTime);
|
||||
|
||||
//botHandler.Subscribe(115151151);
|
||||
await botHandler.UpdatePhoto(screenshotPath);
|
||||
}
|
||||
|
||||
logger.LogInformation("Bot started");
|
||||
logger.LogInformation("Press any key to stop the bot...");
|
||||
Console.ReadLine();
|
||||
cts.Cancel(); // stop the bot
|
||||
return;
|
||||
|
61
src/CasaBot/CasaBotApp/ShinobiConnector.cs
Normal file
61
src/CasaBot/CasaBotApp/ShinobiConnector.cs
Normal file
@ -0,0 +1,61 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace CasaBotApp;
|
||||
|
||||
public class ShinobiConnector
|
||||
{
|
||||
private readonly ILogger<ShinobiConnector> _logger;
|
||||
private readonly string _shinobivUrl = "https://shinobi.francelsoft.com";
|
||||
private readonly string _apikey = "OGD6nsGGzA1NL48M5Tg7Wbzto62oPl";
|
||||
private readonly string _groupId = "aInxuWCYLI";
|
||||
private readonly string _monitorId = "mQ3kQ5qjKK";
|
||||
private readonly HttpClient _httpClient;
|
||||
|
||||
public ShinobiConnector(ILogger<ShinobiConnector> logger, HttpClient httpClient)
|
||||
{
|
||||
_logger = logger;
|
||||
_httpClient = httpClient;
|
||||
}
|
||||
|
||||
public async Task FetchLastVideo(string filename = "2025-02-12T08-00-01.mp4")
|
||||
{
|
||||
//fetch video from shinobi endpoint using example file "2025-02-12T08-00-01.mp4"
|
||||
const string fetchVideoEndpoint = "/{0}/videos/{1}/{2}/{3}";
|
||||
var endpoint = string.Format(_shinobivUrl+fetchVideoEndpoint, _apikey, _groupId, _monitorId, filename);
|
||||
_logger.LogInformation("Fetching video from endpoint: {Endpoint}", endpoint);
|
||||
|
||||
//fetch video
|
||||
const string mediaPath = @".\media\"; // Replace with your desired file path
|
||||
var videoPath = mediaPath + filename;
|
||||
|
||||
try
|
||||
{
|
||||
//make sure the directory exists
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(mediaPath)!);
|
||||
|
||||
_logger.LogDebug("Cleaning media folder");
|
||||
CleanDirectory(mediaPath);
|
||||
_logger.LogDebug("Downloading video...");
|
||||
var videoData = await _httpClient.GetByteArrayAsync(endpoint);
|
||||
|
||||
//Write the video to the file
|
||||
await File.WriteAllBytesAsync(videoPath, videoData);
|
||||
_logger.LogInformation("Video downloaded successfully!");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "An error occurred while downloading the video");
|
||||
}
|
||||
}
|
||||
|
||||
private void CleanDirectory(string path)
|
||||
{
|
||||
DirectoryInfo di = new DirectoryInfo(path);
|
||||
foreach (var file in di.GetFiles())
|
||||
{
|
||||
file.Delete();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
33
src/CasaBot/CasaBotApp/appsettings.json
Normal file
33
src/CasaBot/CasaBotApp/appsettings.json
Normal file
@ -0,0 +1,33 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Debug",
|
||||
"System": "Information",
|
||||
"Microsoft": "Information"
|
||||
}
|
||||
},
|
||||
"AutoScan": {
|
||||
"Enabled": false,
|
||||
"At": "07:00",
|
||||
"FromDayBefore": true,
|
||||
"From": "23:00",
|
||||
"To": "05:00",
|
||||
"MaxAmount": 1,
|
||||
"MediaFolder": "./media/originals",
|
||||
"Shinobi": {
|
||||
"URL": "http://localhost:8080",
|
||||
"APIKey": "APIKEY",
|
||||
"GroupId": "Group",
|
||||
"MonitorId": "Monitor"
|
||||
},
|
||||
"Scanner": {
|
||||
"Exe": "./dvr-scanner/dvr.exe",
|
||||
"ConfigFile": "./dvr-scanner/dvr-scan.cfg",
|
||||
"DetectionFolder": "./media/detections"
|
||||
},
|
||||
"Screenshot": {
|
||||
"Folder": "./media/screenshots",
|
||||
"OffsetSeconds": 0
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user