mirror of
https://github.com/azaion/annotations.git
synced 2026-04-22 22:56:29 +00:00
271 lines
10 KiB
C#
271 lines
10 KiB
C#
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<AnnotationClass> AnnotationClasses { get; set; } = new();
|
|
private bool _suspendLayout;
|
|
|
|
public Dictionary<string, List<AnnotationInfo>> 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<AnnotationClass>(_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) =>
|
|
{
|
|
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));
|
|
|
|
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;
|
|
}
|
|
|
|
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<VideoFileInfo>(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();
|
|
}
|
|
}
|