using System.Collections.ObjectModel; using System.IO; using System.Windows; using System.Windows.Controls; using System.Windows.Input; using Azaion.Annotator.DTO; using Azaion.Annotator.Extensions; using LibVLCSharp.Shared; using MediatR; using Microsoft.WindowsAPICodePack.Dialogs; using Newtonsoft.Json; using Point = System.Windows.Point; using Size = System.Windows.Size; using IntervalTree; namespace Azaion.Annotator; public partial class MainWindow { private readonly LibVLC _libVLC; private readonly MediaPlayer _mediaPlayer; private readonly IMediator _mediator; private readonly FormState _formState; private readonly Config _config; private readonly HelpWindow _helpWindow; public ObservableCollection AnnotationClasses { get; set; } = new(); private bool _suspendLayout; private readonly TimeSpan _thresholdBefore = TimeSpan.FromMilliseconds(100); private readonly TimeSpan _thresholdAfter = TimeSpan.FromMilliseconds(300); public Dictionary> AnnotationsDict { get; set; } = new(); private IntervalTree> Annotations { get; set; } = new(); public MainWindow(LibVLC libVLC, MediaPlayer mediaPlayer, IMediator mediator, FormState formState, Config config, HelpWindow helpWindow) { InitializeComponent(); _libVLC = libVLC; _mediaPlayer = mediaPlayer; _mediator = mediator; _formState = formState; _config = config; _helpWindow = helpWindow; VideoView.Loaded += VideoView_Loaded; Closed += OnFormClosed; } private void VideoView_Loaded(object sender, RoutedEventArgs e) { Core.Initialize(); InitControls(); _suspendLayout = true; Left = _config.WindowLocation.X; Top = _config.WindowLocation.Y; Width = _config.WindowSize.Width; Height = _config.WindowSize.Height; _suspendLayout = false; ReloadFiles(); if (_config.AnnotationClasses.Count == 0) _config.AnnotationClasses.Add(new AnnotationClass(0)); AnnotationClasses = new ObservableCollection(_config.AnnotationClasses); LvClasses.ItemsSource = AnnotationClasses; LvClasses.SelectedIndex = 0; if (LvFiles.Items.IsEmpty) BlinkHelp(HelpTexts.HelpTextsDict[HelpTextEnum.Initial]); if (_config.ShowHelpOnStart) _helpWindow.Show(); } public void BlinkHelp(string helpText, int times = 2) { _ = Task.Run(async () => { for (int i = 0; i < times; i++) { Dispatcher.Invoke(() => StatusHelp.Text = helpText); await Task.Delay(200); Dispatcher.Invoke(() => StatusHelp.Text = ""); await Task.Delay(200); } Dispatcher.Invoke(() => StatusHelp.Text = helpText); }); } private void InitControls() { VideoView.MediaPlayer = _mediaPlayer; _mediaPlayer.Playing += (sender, args) => { uint vw = 0, vh = 0; _mediaPlayer.Size(0, ref vw, ref vh); _formState.CurrentVideoSize = new Size(vw, vh); _formState.CurrentVideoLength = TimeSpan.FromMilliseconds(_mediaPlayer.Length); }; LvFiles.MouseDoubleClick += async (_, _) => await _mediator.Publish(new PlaybackControlEvent(PlaybackControlEnum.Play)); LvClasses.SelectionChanged += (_, _) => { var selectedClass = (AnnotationClass)LvClasses.SelectedItem; Editor.CurrentAnnClass = selectedClass; _mediator.Publish(new AnnClassSelectedEvent(selectedClass)); }; _mediaPlayer.PositionChanged += (o, args) => { ShowTimeAnnotations(TimeSpan.FromMilliseconds(_mediaPlayer.Time)); }; VideoSlider.ValueChanged += (value, newValue) => _mediaPlayer.Position = (float)(newValue / VideoSlider.Maximum); VideoSlider.KeyDown += (sender, args) => _mediator.Publish(new KeyEvent(sender, args)); Volume.ValueChanged += (_, newValue) => _mediator.Publish(new VolumeChangedEvent((int)newValue)); SizeChanged += (sender, args) => { if (!_suspendLayout) _config.WindowSize = args.NewSize; }; LocationChanged += (_, _) => { if (!_suspendLayout) _config.WindowLocation = new Point(Left, Top); }; Editor.FormState = _formState; Editor.Mediator = _mediator; DgAnnotations.ItemsSource = _formState.AnnotationResults; } private void ShowTimeAnnotations(TimeSpan time) { Dispatcher.Invoke(() => VideoSlider.Value = _mediaPlayer.Position * VideoSlider.Maximum); Dispatcher.Invoke(() => StatusClock.Text = $"{TimeSpan.FromMilliseconds(_mediaPlayer.Time):mm\\:ss} / {_formState.CurrentVideoLength:mm\\:ss}"); Dispatcher.Invoke(() => Editor.ClearExpiredAnnotations(time)); var annotations = Annotations.Query(time).SelectMany(x => x).ToList(); foreach (var ann in annotations) { var annClass = _config.AnnotationClasses[ann.ClassNumber]; var annInfo = new CanvasLabel(ann, Editor.RenderSize, _formState.CurrentVideoSize); Dispatcher.Invoke(() => Editor.CreateAnnotation(annClass, time, annInfo)); } } public void ReloadAnnotations() { _formState.AnnotationResults.Clear(); AnnotationsDict.Clear(); Annotations.Clear(); var labelDir = new DirectoryInfo(_config.LabelsDirectory); if (!labelDir.Exists) return; var labelFiles = labelDir.GetFiles($"{_formState.VideoName}_*"); foreach (var file in labelFiles) { var name = Path.GetFileNameWithoutExtension(file.Name); var time = _formState.GetTime(name)!.Value; var str = File.ReadAllText(file.FullName); var annotations = str.Split(Environment.NewLine).Select(YoloLabel.Parse) .Where(ann => ann != null) .ToList(); AddAnnotation(time, annotations!); } } public async Task AddAnnotation(TimeSpan time, List annotations) { var fName = _formState.GetTimeName(time); AnnotationsDict.Add(time, annotations!); Annotations.Add(time.Subtract(_thresholdBefore), time.Add(_thresholdAfter), annotations); _formState.AnnotationResults.Add(new AnnotationResult(time, fName, annotations, _config)); await File.WriteAllTextAsync($"{_config.ResultsDirectory}/{fName}.json", JsonConvert.SerializeObject(_formState.AnnotationResults)); } private void ReloadFiles() { var dir = new DirectoryInfo(_config.VideosDirectory); if (!dir.Exists) return; var labelNames = new DirectoryInfo(_config.LabelsDirectory).GetFiles() .Select(x => x.Name[..^11]) .GroupBy(x => x) .Select(gr => gr.Key) .ToDictionary(x => x); var files = dir.GetFiles("mp4", "mov").Select(x => { _mediaPlayer.Media = new Media(_libVLC, x.FullName); return new VideoFileInfo { Name = x.Name, Path = x.FullName, Duration = TimeSpan.FromMilliseconds(_mediaPlayer.Media.Duration), HasAnnotations = labelNames.ContainsKey(Path.GetFileNameWithoutExtension(x.Name).Replace(" ", "")) }; }).ToList(); LvFiles.ItemsSource = new ObservableCollection(files); TbFolder.Text = _config.VideosDirectory; BlinkHelp(files.Count == 0 ? HelpTexts.HelpTextsDict[HelpTextEnum.Initial] : HelpTexts.HelpTextsDict[HelpTextEnum.PlayVideo]); } private void OnFormClosed(object? sender, EventArgs e) { _mediaPlayer.Stop(); _mediaPlayer.Dispose(); _libVLC.Dispose(); _config.AnnotationClasses = AnnotationClasses.ToList(); _config.Save(); Application.Current.Shutdown(); } // private void AddClassBtnClick(object sender, RoutedEventArgs e) // { // LvClasses.IsReadOnly = false; // AnnotationClasses.Add(new AnnotationClass(AnnotationClasses.Count)); // LvClasses.SelectedIndex = AnnotationClasses.Count - 1; // } private void OpenFolderItemClick(object sender, RoutedEventArgs e) => OpenFolder(); private void OpenFolderButtonClick(object sender, RoutedEventArgs e) => OpenFolder(); private void OpenFolder() { var dlg = new CommonOpenFileDialog { Title = "Open Video folder", IsFolderPicker = true, InitialDirectory = Path.GetDirectoryName(AppDomain.CurrentDomain.BaseDirectory) }; if (dlg.ShowDialog() != CommonFileDialogResult.Ok) return; if (!string.IsNullOrEmpty(dlg.FileName)) _config.VideosDirectory = dlg.FileName; ReloadFiles(); } private void PlayClick(object sender, RoutedEventArgs e) => _mediator.Publish(new PlaybackControlEvent(PlaybackControlEnum.Play)); private void PauseClick(object sender, RoutedEventArgs e)=> _mediator.Publish(new PlaybackControlEvent(PlaybackControlEnum.Pause)); private void StopClick(object sender, RoutedEventArgs e) => _mediator.Publish(new PlaybackControlEvent(PlaybackControlEnum.Stop)); private void PreviousFrameClick(object sender, RoutedEventArgs e) => _mediator.Publish(new PlaybackControlEvent(PlaybackControlEnum.PreviousFrame)); private void NextFrameClick(object sender, RoutedEventArgs e) => _mediator.Publish(new PlaybackControlEvent(PlaybackControlEnum.NextFrame)); private void SaveAnnotationsClick(object sender, RoutedEventArgs e) => _mediator.Publish(new PlaybackControlEvent(PlaybackControlEnum.SaveAnnotations)); private void RemoveSelectedClick(object sender, RoutedEventArgs e) => _mediator.Publish(new PlaybackControlEvent(PlaybackControlEnum.RemoveSelectedAnns)); private void RemoveAllClick(object sender, RoutedEventArgs e) => _mediator.Publish(new PlaybackControlEvent(PlaybackControlEnum.RemoveAllAnns)); private void TurnOffVolume(object sender, RoutedEventArgs e) => _mediator.Publish(new PlaybackControlEvent(PlaybackControlEnum.TurnOffVolume)); private void TurnOnVolume(object sender, RoutedEventArgs e) => _mediator.Publish(new PlaybackControlEvent(PlaybackControlEnum.TurnOnVolume)); private void OpenHelpWindowClick(object sender, RoutedEventArgs e) { _helpWindow.Show(); _helpWindow.Activate(); } private void DgAnnotationsRowClick(object sender, MouseButtonEventArgs e) { DgAnnotations.MouseDoubleClick += (sender, args) => { Editor.RemoveAllAnns(); var dgRow = ItemsControl.ContainerFromElement((DataGrid)sender, (args.OriginalSource as DependencyObject)!) as DataGridRow; var res = (AnnotationResult)dgRow!.Item; _mediaPlayer.SetPause(true); _mediaPlayer.Time = (long)res.Time.TotalMilliseconds;// + 250; ShowTimeAnnotations(res.Time); }; } }