add MediaHash. Step1

This commit is contained in:
Oleksandr Bezdieniezhnykh
2025-11-17 07:46:05 +02:00
parent d355f81c63
commit fd95d2ba2c
27 changed files with 421 additions and 288 deletions
+111 -52
View File
@@ -1,6 +1,7 @@
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.IO;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
@@ -21,6 +22,7 @@ using Microsoft.WindowsAPICodePack.Dialogs;
using Size = System.Windows.Size;
using IntervalTree;
using LinqToDB;
using LinqToDB.Data;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using MediaPlayer = LibVLCSharp.Shared.MediaPlayer;
@@ -40,7 +42,6 @@ public partial class Annotator
private readonly ILogger<Annotator> _logger;
private readonly IDbFactory _dbFactory;
private readonly IInferenceService _inferenceService;
private readonly IInferenceClient _inferenceClient;
private bool _suspendLayout;
private bool _gpsPanelVisible;
@@ -52,14 +53,15 @@ public partial class Annotator
private readonly TimeSpan _thresholdBefore = TimeSpan.FromMilliseconds(50);
private readonly TimeSpan _thresholdAfter = TimeSpan.FromMilliseconds(150);
public ObservableCollection<MediaFileInfo> AllMediaFiles { get; set; } = new();
private ObservableCollection<MediaFileInfo> FilteredMediaFiles { get; set; } = new();
public Dictionary<string, MediaFileInfo> MediaFilesDict = new();
public ObservableCollection<MediaFile> AllMediaFiles { get; set; } = new();
private ObservableCollection<MediaFile> FilteredMediaFiles { get; set; } = new();
public Dictionary<string, MediaFile> MediaFilesDict = new();
public IntervalTree<TimeSpan, Annotation> TimedAnnotations { get; set; } = new();
public string MainTitle { get; set; }
public CameraConfig Camera => _appConfig?.CameraConfig ?? new CameraConfig();
private static readonly Guid ReloadTaskId = Guid.NewGuid();
public Annotator(
IConfigUpdater configUpdater,
@@ -86,7 +88,6 @@ public partial class Annotator
_logger = logger;
_dbFactory = dbFactory;
_inferenceService = inferenceService;
_inferenceClient = inferenceClient;
// Ensure bindings (e.g., Camera) resolve immediately
DataContext = this;
@@ -294,9 +295,14 @@ public partial class Annotator
TimedAnnotations.Clear();
Editor.RemoveAllAnns();
var mediaHash = _formState.CurrentMedia?.Hash;
var mediaName = _formState.CurrentMedia?.Name;
var annotations = await _dbFactory.Run(async db =>
await db.Annotations.LoadWith(x => x.Detections)
.Where(x => x.OriginalMediaName == _formState.MediaName)
.Where(x =>
(!string.IsNullOrEmpty(mediaHash) && x.MediaHash == mediaHash) ||
(x.MediaHash == null && x.OriginalMediaName == mediaName))
.OrderBy(x => x.Time)
.ToListAsync(token: _mainCancellationSource.Token));
@@ -304,7 +310,6 @@ public partial class Annotator
_formState.AnnotationResults.Clear();
foreach (var ann in annotations)
{
// Duplicate for speed
TimedAnnotations.Add(ann.Time.Subtract(_thresholdBefore), ann.Time.Add(_thresholdAfter), ann);
_formState.AnnotationResults.Add(ann);
}
@@ -344,55 +349,109 @@ public partial class Annotator
_formState.AnnotationResults.Insert(index, annotation);
}
private async Task ReloadFiles()
public void ReloadFilesThrottled()
{
ThrottleExt.Throttle(async () =>
{
await ReloadFiles();
}, ReloadTaskId, TimeSpan.FromSeconds(4));
}
public async Task ReloadFiles()
{
var dir = new DirectoryInfo(_appConfig?.DirectoriesConfig.VideosDirectory ?? Constants.DEFAULT_VIDEO_DIR);
if (!dir.Exists)
return;
var videoFiles = dir.GetFiles((_appConfig?.AnnotationConfig.VideoFormats ?? Constants.DefaultVideoFormats)
.ToArray()).Select(x =>
{
var media = new Media(_libVlc, x.FullName);
media.Parse();
var fInfo = new MediaFileInfo
{
Name = x.Name,
Path = x.FullName,
MediaType = MediaTypes.Video
};
media.ParsedChanged += (_, _) => fInfo.Duration = TimeSpan.FromMilliseconds(media.Duration);
return fInfo;
}).ToList();
var folderFiles = dir.GetFiles(Constants.DefaultVideoFormats.Concat(Constants.DefaultImageFormats).ToArray())
.Select(x => x.FullName)
.Select(x => new MediaFile(x))
.GroupBy(x => x.Hash)
.ToDictionary(x => x.Key, v => v.First());
var imageFiles = dir.GetFiles((_appConfig?.AnnotationConfig.ImageFormats ?? Constants.DefaultImageFormats).ToArray())
.Select(x => new MediaFileInfo
{
Name = x.Name,
Path = x.FullName,
MediaType = MediaTypes.Image
});
var allFiles = videoFiles.Concat(imageFiles).ToList();
//sync with db
var dbFiles = await _dbFactory.Run(async db =>
await db.MediaFiles
.Where(x => folderFiles.ContainsKey(x.Hash))
.ToDictionaryAsync(x => x.Hash));
var newFiles = folderFiles
.Where(x => !dbFiles.ContainsKey(x.Key))
.Select(x => x.Value)
.ToList();
if (newFiles.Count > 0)
await _dbFactory.RunWrite(async db => await db.BulkCopyAsync(newFiles));
var allFileNames = allFiles.Select(x => x.FName).ToList();
var allFiles = dbFiles.Select(x => x.Value)
.Concat(newFiles)
.ToList();
var labelsDict = await _dbFactory.Run(async db =>
await db.Annotations
.GroupBy(x => x.OriginalMediaName)
.Where(x => allFileNames.Contains(x.Key))
.Select(x => x.Key)
.ToDictionaryAsync(x => x, x => x));
foreach (var mediaFile in allFiles)
mediaFile.HasAnnotations = labelsDict.ContainsKey(mediaFile.FName);
AllMediaFiles = new ObservableCollection<MediaFileInfo>(allFiles);
await SyncAnnotations(allFiles);
AllMediaFiles = new ObservableCollection<MediaFile>(allFiles);
MediaFilesDict = AllMediaFiles.GroupBy(x => x.Name)
.ToDictionary(gr => gr.Key, gr => gr.First());
var selectedIndex = LvFiles.SelectedIndex;
LvFiles.ItemsSource = AllMediaFiles;
LvFiles.SelectedIndex = selectedIndex;
DataContext = this;
}
private async Task SyncAnnotations(List<MediaFile> allFiles)
{
var hashes = allFiles.Select(x => x.Hash).ToList();
var filenames = allFiles.Select(x => x.Name).ToList();
var nameHashMap = allFiles.ToDictionary(x => x.Name.ToFName(), x => x.Hash);
await _dbFactory.RunWrite(async db =>
{
var hashedAnnotations = await db.Annotations
.Where(a => hashes.Contains(a.MediaHash))
.ToDictionaryAsync(x => x.Name);
var fileNameAnnotations = await db.Annotations
.Where(a => filenames.Contains(a.OriginalMediaName))
.ToDictionaryAsync(x => x.Name);
var toUpdate = fileNameAnnotations
.Where(a => !hashedAnnotations.ContainsKey(a.Key))
.Select(a => new { a.Key, MediaHash = nameHashMap.GetValueOrDefault(a.Value.OriginalMediaName) ?? "" })
.ToList();
if (toUpdate.Count > 0)
{
var caseBuilder = new StringBuilder("UPDATE Annotations SET MediaHash = CASE Name ");
var parameters = new List<DataParameter>();
for (int i = 0; i < toUpdate.Count; i++)
{
caseBuilder.Append($"WHEN @name{i} THEN @hash{i} ");
parameters.Add(new DataParameter($"@name{i}", toUpdate[i].Key, DataType.NVarChar));
parameters.Add(new DataParameter($"@hash{i}", toUpdate[i].MediaHash, DataType.NVarChar));
}
caseBuilder.Append("END WHERE Name IN (");
caseBuilder.Append(string.Join(", ", Enumerable.Range(0, toUpdate.Count).Select(i => $"@name{i}")));
caseBuilder.Append(")");
await db.ExecuteAsync(caseBuilder.ToString(), parameters.ToArray());
}
var annotationMediaHashes = hashedAnnotations
.GroupBy(x => x.Value.MediaHash)
.Select(x => x.Key)
.ToList();
var annotationMediaNames = fileNameAnnotations
.GroupBy(x => x.Value.OriginalMediaName)
.Select(x => x.Key)
.ToList();
await db.MediaFiles
.Where(m => annotationMediaHashes.Contains(m.Hash) ||
annotationMediaNames.Contains(m.Name))
.Set(m => m.Status, MediaStatus.Confirmed)
.UpdateAsync();
});
}
private void OnFormClosed(object? sender, EventArgs e)
{
_mainCancellationSource.Cancel();
@@ -406,11 +465,11 @@ public partial class Annotator
private void OpenContainingFolder(object sender, RoutedEventArgs e)
{
var mediaFileInfo = (sender as MenuItem)?.DataContext as MediaFileInfo;
var mediaFileInfo = (sender as MenuItem)?.DataContext as MediaFile;
if (mediaFileInfo == null)
return;
Process.Start("explorer.exe", "/select,\"" + mediaFileInfo.Path +"\"");
Process.Start("explorer.exe", "/select,\"" + mediaFileInfo.MediaUrl +"\"");
}
public void SeekTo(long timeMilliseconds, bool setPause = true)
@@ -446,8 +505,8 @@ public partial class Annotator
private void TbFilter_OnTextChanged(object sender, TextChangedEventArgs e)
{
FilteredMediaFiles = new ObservableCollection<MediaFileInfo>(AllMediaFiles.Where(x => x.Name.ToLower().Contains(TbFilter.Text.ToLower())).ToList());
MediaFilesDict = FilteredMediaFiles.ToDictionary(x => x.FName);
FilteredMediaFiles = new ObservableCollection<MediaFile>(AllMediaFiles.Where(x => x.Name.ToLower().Contains(TbFilter.Text.ToLower())).ToList());
MediaFilesDict = FilteredMediaFiles.ToDictionary(x => x.Name);
LvFiles.ItemsSource = FilteredMediaFiles;
LvFiles.ItemsSource = FilteredMediaFiles;
}
@@ -515,7 +574,7 @@ public partial class Annotator
var files = (FilteredMediaFiles.Count == 0 ? AllMediaFiles : FilteredMediaFiles)
.Skip(LvFiles.SelectedIndex)
.Select(x => x.Path)
.Select(x => x.MediaUrl)
.ToList();
if (files.Count == 0)
return;
@@ -566,15 +625,15 @@ public partial class Annotator
private void DeleteMedia(object sender, RoutedEventArgs e)
{
var mediaFileInfo = (sender as MenuItem)?.DataContext as MediaFileInfo;
var mediaFileInfo = (sender as MenuItem)?.DataContext as MediaFile;
if (mediaFileInfo == null)
return;
DeleteMedia(mediaFileInfo);
}
public void DeleteMedia(MediaFileInfo mediaFileInfo)
public void DeleteMedia(MediaFile mediaFile)
{
var obj = mediaFileInfo.MediaType == MediaTypes.Image
var obj = mediaFile.MediaType == MediaTypes.Image
? "цю картинку "
: "це відео ";
var result = MessageBox.Show($"Видалити {obj}?",
@@ -582,8 +641,8 @@ public partial class Annotator
if (result != MessageBoxResult.Yes)
return;
File.Delete(mediaFileInfo.Path);
AllMediaFiles.Remove(mediaFileInfo);
File.Delete(mediaFile.MediaUrl);
AllMediaFiles.Remove(mediaFile);
}
}