using System.Collections.ObjectModel; using System.Drawing; using System.IO; using System.Reflection; using System.Windows; using Azaion.Annotator.DTO; using Azaion.Annotator.Extensions; using LibVLCSharp.Shared; using MediatR; using Microsoft.WindowsAPICodePack.Dialogs; using Point = System.Windows.Point; using Size = System.Windows.Size; 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; private readonly TimeSpan _annotationTime = TimeSpan.FromSeconds(1); public ObservableCollection AnnotationClasses { get; set; } = new(); private bool _suspendLayout; public Dictionary> 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 = 3) { _ = 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) => { Dispatcher.Invoke(() => videoSlider.Value = _mediaPlayer.Position * videoSlider.Maximum); Dispatcher.Invoke(() => StatusClock.Text = $"{TimeSpan.FromMilliseconds(_mediaPlayer.Time):mm\\:ss} / {_formState.CurrentVideoLength:mm\\:ss}"); var curTime = _formState.GetTimeName(TimeSpan.FromMilliseconds(_mediaPlayer.Time)); if (!Annotations.TryGetValue(curTime, out var annotationInfos)) return; var annotations = annotationInfos.Select(info => { var annClass = _config.AnnotationClasses[info.ClassNumber]; var annInfo = info.ToCanvasCoordinates(Editor.RenderSize, _formState.CurrentVideoSize); var annotation = Dispatcher.Invoke(() => Editor.CreateAnnotation(annClass, annInfo)); return annotation; }).ToList(); //remove annotations: either in 1 sec, either earlier if there is next annotation in a dictionary var timeStr = curTime.Split("_").LastOrDefault(); if (!int.TryParse(timeStr, out var time)) return; var ts = TimeSpan.FromMilliseconds(time * 100); var timeSpanRemove = Enumerable.Range(1, (int)_annotationTime.TotalMilliseconds / 100 - 1) .Select(x => { var timeNext = TimeSpan.FromMilliseconds(x * 100); var fName = _formState.GetTimeName(ts.Add(timeNext)); return Annotations.ContainsKey(fName) ? timeNext : (TimeSpan?)null; }).FirstOrDefault(x => x != null) ?? _annotationTime; _ = Task.Run(async () => { await Task.Delay(timeSpanRemove); Dispatcher.Invoke(() => Editor.RemoveAnnotations(annotations)); }); }; videoSlider.ValueChanged += (value, newValue) => _mediaPlayer.Position = (float)(newValue / videoSlider.Maximum); videoSlider.KeyDown += (sender, args) => _mediator.Publish(new KeyEvent(sender, args)); SizeChanged += (sender, args) => { if (!_suspendLayout) _config.WindowSize = args.NewSize; }; LocationChanged += (_, _) => { if (!_suspendLayout) _config.WindowLocation = new Point(Left, Top); }; Editor.FormState = _formState; Editor.Mediator = _mediator; } public void LoadExistingAnnotations() { var dirInfo = new DirectoryInfo(_config.LabelsDirectory); if (!dirInfo.Exists) return; var files = dirInfo.GetFiles($"{_formState.VideoName}_*"); Annotations = files.ToDictionary(f => Path.GetFileNameWithoutExtension(f.Name), f => { var str = File.ReadAllText(f.FullName); return str.Split(Environment.NewLine).Select(AnnotationInfo.Parse) .Where(x => x != null) .ToList(); })!; } private void ReloadFiles() { var dir = new DirectoryInfo(_config.VideosDirectory); if (!dir.Exists) return; 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) }; }).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(Assembly.GetExecutingAssembly().Location) }; 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 OpenHelpWindowClick(object sender, RoutedEventArgs e) { _helpWindow.Show(); _helpWindow.Activate(); } }