mirror of
https://github.com/azaion/annotations.git
synced 2026-04-22 22:46:30 +00:00
annotation previews
add button controls
This commit is contained in:
@@ -19,4 +19,11 @@
|
|||||||
<PackageReference Include="WindowsAPICodePack" Version="7.0.4" />
|
<PackageReference Include="WindowsAPICodePack" Version="7.0.4" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<None Remove="config.json" />
|
||||||
|
<Content Include="config.json">
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
</Content>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using System.Windows;
|
using System.Windows;
|
||||||
using System.Windows.Controls;
|
using System.Windows.Controls;
|
||||||
|
using System.Windows.Documents;
|
||||||
using System.Windows.Input;
|
using System.Windows.Input;
|
||||||
using System.Windows.Media;
|
using System.Windows.Media;
|
||||||
using System.Windows.Shapes;
|
using System.Windows.Shapes;
|
||||||
@@ -19,6 +20,7 @@ public class CanvasEditor : Canvas
|
|||||||
|
|
||||||
private readonly Line _horizontalLine;
|
private readonly Line _horizontalLine;
|
||||||
private readonly Line _verticalLine;
|
private readonly Line _verticalLine;
|
||||||
|
private readonly TextBlock _classNameHint;
|
||||||
|
|
||||||
private Rectangle _curRec;
|
private Rectangle _curRec;
|
||||||
private AnnotationControl _curAnn;
|
private AnnotationControl _curAnn;
|
||||||
@@ -38,6 +40,9 @@ public class CanvasEditor : Canvas
|
|||||||
_verticalLine.Fill = value.ColorBrush;
|
_verticalLine.Fill = value.ColorBrush;
|
||||||
_horizontalLine.Stroke = value.ColorBrush;
|
_horizontalLine.Stroke = value.ColorBrush;
|
||||||
_horizontalLine.Fill = value.ColorBrush;
|
_horizontalLine.Fill = value.ColorBrush;
|
||||||
|
_classNameHint.Text = value.Name;
|
||||||
|
_classNameHint.Foreground = value.ColorBrush;
|
||||||
|
|
||||||
_newAnnotationRect.Stroke = value.ColorBrush;
|
_newAnnotationRect.Stroke = value.ColorBrush;
|
||||||
_newAnnotationRect.Fill = value.ColorBrush;
|
_newAnnotationRect.Fill = value.ColorBrush;
|
||||||
_currentAnnClass = value;
|
_currentAnnClass = value;
|
||||||
@@ -64,6 +69,14 @@ public class CanvasEditor : Canvas
|
|||||||
StrokeDashArray = [5],
|
StrokeDashArray = [5],
|
||||||
StrokeThickness = 2
|
StrokeThickness = 2
|
||||||
};
|
};
|
||||||
|
_classNameHint = new TextBlock
|
||||||
|
{
|
||||||
|
Text = CurrentAnnClass?.Name ?? "asd",
|
||||||
|
Foreground = new SolidColorBrush(Colors.Blue),
|
||||||
|
Cursor = Cursors.Arrow,
|
||||||
|
FontSize = 16,
|
||||||
|
FontWeight = FontWeights.Bold
|
||||||
|
};
|
||||||
_newAnnotationRect = new Rectangle
|
_newAnnotationRect = new Rectangle
|
||||||
{
|
{
|
||||||
Name = "selector",
|
Name = "selector",
|
||||||
@@ -94,6 +107,7 @@ public class CanvasEditor : Canvas
|
|||||||
Children.Add(_newAnnotationRect);
|
Children.Add(_newAnnotationRect);
|
||||||
Children.Add(_horizontalLine);
|
Children.Add(_horizontalLine);
|
||||||
Children.Add(_verticalLine);
|
Children.Add(_verticalLine);
|
||||||
|
Children.Add(_classNameHint);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void CanvasMouseDown(object sender, MouseButtonEventArgs e)
|
private void CanvasMouseDown(object sender, MouseButtonEventArgs e)
|
||||||
@@ -107,6 +121,8 @@ public class CanvasEditor : Canvas
|
|||||||
var pos = e.GetPosition(this);
|
var pos = e.GetPosition(this);
|
||||||
_horizontalLine.Y1 = _horizontalLine.Y2 = pos.Y;
|
_horizontalLine.Y1 = _horizontalLine.Y2 = pos.Y;
|
||||||
_verticalLine.X1 = _verticalLine.X2 = pos.X;
|
_verticalLine.X1 = _verticalLine.X2 = pos.X;
|
||||||
|
SetLeft(_classNameHint, pos.X + 10);
|
||||||
|
SetTop(_classNameHint, pos.Y - 30);
|
||||||
|
|
||||||
if (e.LeftButton != MouseButtonState.Pressed)
|
if (e.LeftButton != MouseButtonState.Pressed)
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -65,8 +65,8 @@ public class AnnotationInfo
|
|||||||
|
|
||||||
var annInfo = new AnnotationInfo { ClassNumber = this.ClassNumber };
|
var annInfo = new AnnotationInfo { ClassNumber = this.ClassNumber };
|
||||||
|
|
||||||
double left = annInfo.X - annInfo.Width * 2;
|
double left = X - Width / 2;
|
||||||
double top = annInfo.Y - annInfo.Height * 2;
|
double top = Y - Height / 2;
|
||||||
|
|
||||||
if (videoAR > canvasAR) //100% width
|
if (videoAR > canvasAR) //100% width
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -6,9 +6,11 @@ namespace Azaion.Annotator.DTO;
|
|||||||
public class FormState
|
public class FormState
|
||||||
{
|
{
|
||||||
public SelectionState SelectionState { get; set; } = SelectionState.None;
|
public SelectionState SelectionState { get; set; } = SelectionState.None;
|
||||||
|
|
||||||
public string CurrentFile { get; set; } = null!;
|
public string CurrentFile { get; set; } = null!;
|
||||||
public Size CurrentVideoSize { get; set; }
|
public Size CurrentVideoSize { get; set; }
|
||||||
|
|
||||||
public string VideoName => Path.GetFileNameWithoutExtension(CurrentFile).Replace(" ", "");
|
public string VideoName => Path.GetFileNameWithoutExtension(CurrentFile).Replace(" ", "");
|
||||||
|
public TimeSpan CurrentVideoLength { get; set; }
|
||||||
|
|
||||||
public string GetTimeName(TimeSpan ts) => $"{VideoName}_{ts:hmmssf}";
|
public string GetTimeName(TimeSpan ts) => $"{VideoName}_{ts:hmmssf}";
|
||||||
}
|
}
|
||||||
|
|||||||
+5
@@ -8,3 +8,8 @@ public class KeyEvent(object sender, KeyEventArgs args) : INotification
|
|||||||
public object Sender { get; set; } = sender;
|
public object Sender { get; set; } = sender;
|
||||||
public KeyEventArgs Args { get; set; } = args;
|
public KeyEventArgs Args { get; set; } = args;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class PlaybackControlEvent(PlaybackControlEnum playbackControlEnum) : INotification
|
||||||
|
{
|
||||||
|
public PlaybackControlEnum PlaybackControl { get; set; } = playbackControlEnum;
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
namespace Azaion.Annotator.DTO;
|
||||||
|
|
||||||
|
public enum PlaybackControlEnum
|
||||||
|
{
|
||||||
|
None = 0,
|
||||||
|
Play = 1,
|
||||||
|
Pause = 2,
|
||||||
|
Stop = 3,
|
||||||
|
PreviousFrame = 4,
|
||||||
|
NextFrame = 5,
|
||||||
|
SaveAnnotations = 6,
|
||||||
|
RemoveSelectedAnns = 7,
|
||||||
|
RemoveAllAnns = 8
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
namespace Azaion.Annotator;
|
||||||
|
|
||||||
|
public enum HelpTextEnum
|
||||||
|
{
|
||||||
|
None = 0,
|
||||||
|
Initial = 1,
|
||||||
|
PlayVideo = 2,
|
||||||
|
PauseForAnnotations = 3,
|
||||||
|
AnnotationHelp = 4
|
||||||
|
}
|
||||||
|
|
||||||
|
public class HelpTexts
|
||||||
|
{
|
||||||
|
public static Dictionary<HelpTextEnum, string> HelpTextsDict = new()
|
||||||
|
{
|
||||||
|
{ HelpTextEnum.None, "" },
|
||||||
|
{ HelpTextEnum.Initial, "Натисніть Файл - Відкрити папку... та виберіть папку з вашими відео для анотації" },
|
||||||
|
{ HelpTextEnum.PlayVideo, "В списку відео виберіть потрібне та [подвійний клік] чи [Eнтер] на ньому - запустіть його на перегляд" },
|
||||||
|
{ HelpTextEnum.PauseForAnnotations, "В потрібному місці відео де є один з об'єктів для анотації зупиніть його [Пробіл] або кн. на панелі" },
|
||||||
|
{ HelpTextEnum.AnnotationHelp, "Клавішами [1] - [9] або мишкою оберіть потрібний клас та виділіть область з об'єктом. Виділяйте всі що є об'єкти. " +
|
||||||
|
"При потребі [Ctrl] виділяйте анотації та [Del] для видалення. [Eнтер] для збереження і перегляду далі" },
|
||||||
|
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -67,10 +67,10 @@
|
|||||||
Grid.Column="0"
|
Grid.Column="0"
|
||||||
Grid.ColumnSpan="3"
|
Grid.ColumnSpan="3"
|
||||||
Background="Black">
|
Background="Black">
|
||||||
<MenuItem Header="File" Foreground="#FFBDBCBC" Margin="0,3,0,0">
|
<MenuItem Header="Файл" Foreground="#FFBDBCBC" Margin="0,3,0,0">
|
||||||
<MenuItem x:Name="OpenFolderItem"
|
<MenuItem x:Name="OpenFolderItem"
|
||||||
Foreground="Black"
|
Foreground="Black"
|
||||||
IsEnabled="True" Header="Open Folder..." Click="MenuItem_OnClick"/>
|
IsEnabled="True" Header="Відкрити папку..." Click="MenuItem_OnClick"/>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
</Menu>
|
</Menu>
|
||||||
<Grid
|
<Grid
|
||||||
@@ -173,13 +173,20 @@
|
|||||||
<ColumnDefinition Width="28" />
|
<ColumnDefinition Width="28" />
|
||||||
<ColumnDefinition Width="28"/>
|
<ColumnDefinition Width="28"/>
|
||||||
<ColumnDefinition Width="28"/>
|
<ColumnDefinition Width="28"/>
|
||||||
|
<ColumnDefinition Width="28"/>
|
||||||
|
<ColumnDefinition Width="28"/>
|
||||||
|
<ColumnDefinition Width="28"/>
|
||||||
|
<ColumnDefinition Width="28"/>
|
||||||
|
<ColumnDefinition Width="28"/>
|
||||||
</Grid.ColumnDefinitions>
|
</Grid.ColumnDefinitions>
|
||||||
<Button Grid.Column="0" Padding="5" Background="Black" BorderBrush="Black">
|
<Button Grid.Column="0" Padding="5" ToolTip="Включити програвання" Background="Black" BorderBrush="Black"
|
||||||
|
Click="PlayClick">
|
||||||
<Path Stretch="Fill" Fill="LightGray" Data="m295.84 146.049-256-144c-4.96-2.784-11.008-2.72-15.904.128-4.928
|
<Path Stretch="Fill" Fill="LightGray" Data="m295.84 146.049-256-144c-4.96-2.784-11.008-2.72-15.904.128-4.928
|
||||||
2.88-7.936 8.128-7.936 13.824v288c0 5.696 3.008 10.944 7.936 13.824 2.496 1.44 5.28 2.176 8.064 2.176 2.688
|
2.88-7.936 8.128-7.936 13.824v288c0 5.696 3.008 10.944 7.936 13.824 2.496 1.44 5.28 2.176 8.064 2.176 2.688
|
||||||
0 5.408-.672 7.84-2.048l256-144c5.024-2.848 8.16-8.16 8.16-13.952s-3.136-11.104-8.16-13.952z" />
|
0 5.408-.672 7.84-2.048l256-144c5.024-2.848 8.16-8.16 8.16-13.952s-3.136-11.104-8.16-13.952z" />
|
||||||
</Button>
|
</Button>
|
||||||
<Button Grid.Column="1" Padding="2" Width="25" Height="25" Background="Black" BorderBrush="Black">
|
<Button Grid.Column="1" Padding="2" Width="25" Height="25" ToolTip="Пауза/Відновити. Клавіша: [Пробіл]" Background="Black" BorderBrush="Black"
|
||||||
|
Click="PauseClick">
|
||||||
<Image>
|
<Image>
|
||||||
<Image.Source>
|
<Image.Source>
|
||||||
<DrawingImage>
|
<DrawingImage>
|
||||||
@@ -193,20 +200,114 @@
|
|||||||
</Image.Source>
|
</Image.Source>
|
||||||
</Image>
|
</Image>
|
||||||
</Button>
|
</Button>
|
||||||
<Button Grid.Column="2" Padding="2" Width="25" Height="25" Background="Black" BorderBrush="Black">
|
<Button Grid.Column="2" Padding="2" Width="25" Height="25" ToolTip="Зупинити перегляд" Background="Black" BorderBrush="Black"
|
||||||
|
Click="StopClick">
|
||||||
<Path Stretch="Fill" Fill="LightGray" Data="m288 0h-256c-17.632 0-32 14.368-32 32v256c0 17.632 14.368 32 32 32h256c17.632
|
<Path Stretch="Fill" Fill="LightGray" Data="m288 0h-256c-17.632 0-32 14.368-32 32v256c0 17.632 14.368 32 32 32h256c17.632
|
||||||
0 32-14.368 32-32v-256c0-17.632-14.368-32-32-32z" />
|
0 32-14.368 32-32v-256c0-17.632-14.368-32-32-32z" />
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button Grid.Column="3" Padding="2" Width="25" Height="25" ToolTip="На 1 кадр назад. +[Ctrl] на 5 секунд назад. Клавіша: [Вліво]" Background="Black" BorderBrush="Black"
|
||||||
|
Click="PreviousFrameClick">
|
||||||
|
<Image>
|
||||||
|
<Image.Source>
|
||||||
|
<DrawingImage>
|
||||||
|
<DrawingImage.Drawing>
|
||||||
|
<DrawingGroup ClipGeometry="M0,0 V320 H320 V0 H0 Z">
|
||||||
|
<GeometryDrawing Brush="LightGray" Geometry="m23.026 4.99579v22.00155c.00075.77029-.83285 1.25227-1.49993.86724l-19.05188-11.00078c-.66693-.38492-.66693-1.34761
|
||||||
|
0-1.73254l19.05188-11.00078c.62227-.35929 1.49993.0539 1.49993.86531z" />
|
||||||
|
<GeometryDrawing Brush="LightGray" Geometry="m29.026 4h-2c-.554 0-1 .446-1 1v22c0 .554.446 1 1 1h2c.554 0 1-.446 1-1v-22c0-.554-.446-1-1-1z" />
|
||||||
|
</DrawingGroup>
|
||||||
|
</DrawingImage.Drawing>
|
||||||
|
</DrawingImage>
|
||||||
|
</Image.Source>
|
||||||
|
</Image>
|
||||||
|
</Button>
|
||||||
|
<Button Grid.Column="4" Padding="2" Width="25" Height="25" ToolTip="На 1 кадр вперед. +[Ctrl] на 5 секунд вперед. Клавіша: [Вправо]" Background="Black" BorderBrush="Black"
|
||||||
|
Click="NextFrameClick">
|
||||||
|
<Image>
|
||||||
|
<Image.Source>
|
||||||
|
<DrawingImage>
|
||||||
|
<DrawingImage.Drawing>
|
||||||
|
<DrawingGroup ClipGeometry="M0,0 V320 H320 V0 H0 Z">
|
||||||
|
<GeometryDrawing Brush="LightGray" Geometry="m8.974 4.99579v22.00155c-.00075.77029.83285 1.25227 1.49993.86724l19.05188-11.00078c.66693-.38492.66693-1.34761
|
||||||
|
0-1.73254l-19.05188-11.00078c-.62227-.35929-1.49993.0539-1.49993.86531z" />
|
||||||
|
<GeometryDrawing Brush="LightGray" Geometry="m2.974 4h2c.554 0 1 .446 1 1v22c0 .554-.446 1-1 1h-2c-.554 0-1-.446-1-1v-22c0-.554.446-1 1-1z" />
|
||||||
|
</DrawingGroup>
|
||||||
|
</DrawingImage.Drawing>
|
||||||
|
</DrawingImage>
|
||||||
|
</Image.Source>
|
||||||
|
</Image>
|
||||||
|
</Button>
|
||||||
|
<Button Grid.Column="5" Padding="2" Width="25" Height="25" ToolTip="Зберегти анотації та продовжити. Клавіша: [Ентер]" Background="Black" BorderBrush="Black"
|
||||||
|
Click="SaveAnnotationsClick">
|
||||||
|
<Image>
|
||||||
|
<Image.Source>
|
||||||
|
<DrawingImage>
|
||||||
|
<DrawingImage.Drawing>
|
||||||
|
<DrawingGroup ClipGeometry="M0,0 V320 H320 V0 H0 Z">
|
||||||
|
<GeometryDrawing Brush="LightGray" Geometry="m30.71 7.29-6-6a1 1 0 0 0 -.71-.29h-2v8a2 2 0 0 1 -2 2h-8a2 2 0 0
|
||||||
|
1 -2-2v-8h-6a3 3 0 0 0 -3 3v24a3 3 0 0 0 3 3h2v-9a3 3 0 0 1 3-3h14a3 3 0 0 1 3 3v9h2a3 3 0 0 0 3-3v-20a1 1 0 0 0 -.29-.71z" />
|
||||||
|
<GeometryDrawing Brush="LightGray" Geometry="m12 1h8v8h-8z" />
|
||||||
|
<GeometryDrawing Brush="LightGray" Geometry="m23 21h-14a1 1 0 0 0 -1 1v9h16v-9a1 1 0 0 0 -1-1z" />
|
||||||
|
</DrawingGroup>
|
||||||
|
</DrawingImage.Drawing>
|
||||||
|
</DrawingImage>
|
||||||
|
</Image.Source>
|
||||||
|
</Image>
|
||||||
|
</Button>
|
||||||
|
<Button Grid.Column="6" Padding="2" Width="25" Height="25" ToolTip="Видалити обрані анотації. Клавіша: [Del]" Background="Black" BorderBrush="Black"
|
||||||
|
Click="RemoveSelectedClick">
|
||||||
|
<Path Stretch="Fill" Fill="LightGray" Data="M395.439,368.206h18.158v45.395h-45.395v-18.158h27.236V368.206z M109.956,413.601h64.569v-18.158h-64.569V413.601z
|
||||||
|
M239.082,413.601h64.558v-18.158h-64.558V413.601z M18.161,368.206H0.003v45.395h45.395v-18.158H18.161V368.206z M18.161,239.079
|
||||||
|
H0.003v64.562h18.158V239.079z M18.161,109.958H0.003v64.563h18.158V109.958z M0.003,45.395h18.158V18.158h27.237V0H0.003V45.395z
|
||||||
|
M174.519,0h-64.563v18.158h64.563V0z M303.64,0h-64.558v18.158h64.558V0z M368.203,0v18.158h27.236v27.237h18.158V0H368.203z
|
||||||
|
M395.439,303.642h18.158v-64.562h-18.158V303.642z M395.439,174.521h18.158v-64.563h-18.158V174.521z M325.45,93.187
|
||||||
|
c-11.467-11.464-30.051-11.464-41.518,0l-77.135,77.129l-77.129-77.129c-11.476-11.464-30.056-11.464-41.521,0
|
||||||
|
c-11.476,11.47-11.476,30.062,0,41.532l77.118,77.123l-77.124,77.124c-11.476,11.479-11.476,30.062,0,41.529
|
||||||
|
c5.73,5.733,13.243,8.605,20.762,8.605c7.516,0,15.028-2.872,20.765-8.605l77.129-77.124l77.129,77.124
|
||||||
|
c5.728,5.733,13.246,8.605,20.765,8.605c7.513,0,15.025-2.872,20.759-8.605c11.479-11.467,11.479-30.062,0-41.529l-77.124-77.124
|
||||||
|
l77.124-77.123C336.923,123.243,336.923,104.656,325.45,93.187z" />
|
||||||
|
</Button>
|
||||||
|
<Button Grid.Column="7" Padding="2" Width="25" Height="25" ToolTip="Видалити всі аннотації. Клавіша: [X]" Background="Black" BorderBrush="Black"
|
||||||
|
Click="RemoveAllClick">
|
||||||
|
<Image>
|
||||||
|
<Image.Source>
|
||||||
|
<DrawingImage>
|
||||||
|
<DrawingImage.Drawing>
|
||||||
|
<DrawingGroup ClipGeometry="M0,0 V320 H320 V0 H0 Z">
|
||||||
|
<GeometryDrawing Brush="LightGray" Geometry="m66.1455 13.1562c2.2083-4.26338 7.4546-5.92939 11.718-3.72109 4.2702 2.21179
|
||||||
|
5.9335 7.47029 3.7121 11.73549l-8.9288 17.1434c-.3573.6862-.8001 1.3124-1.312 1.8677 2.44 3.6128 3.1963 8.2582 1.6501
|
||||||
|
12.6558-.3523 1.002-.7242 2.0466-1.1108 3.1145-.1645.4546-.6923.659-1.1208.4351l-28.8106-15.0558c-.4666-.2438-.5746-.8639-.2219-1.2547.7171-.7943
|
||||||
|
1.4152-1.5917 2.0855-2.3761 3.1513-3.6881 7.8213-5.7743 12.5381-5.6197.0534-.1099.1097-.2193.1689-.3283z" />
|
||||||
|
<GeometryDrawing Brush="LightGray" Geometry="m37.7187 44.9911c-.3028-.1582-.6723-.1062-.9226.1263-1.7734 1.6478-3.5427
|
||||||
|
3.0861-5.1934 4.1101-5.5739 3.4578-10.1819 4.704-13.0435 5.1463-1.6736.2587-3.032 1.3362-3.6937 2.7335-.6912 1.4595-.6391
|
||||||
|
3.3721.7041 4.8522 1.48 1.6309 3.6724 3.7893 6.8345 6.3861.1854.1523.4298.2121.665.1649 2.2119-.4446 4.5148-.8643
|
||||||
|
6.5245-1.9149.5849-.3058 1.4606-.8505 2.5588-1.7923 1.0935-.9379 2.7579-.8372 3.7175.2247.9595 1.062.8509 2.6831-.2426
|
||||||
|
3.621-1.3886 1.1908-2.596 1.965-3.5534 2.4655-.7833.4094-1.603.7495-2.4399 1.0396-.6358.2203-.7846 1.0771-.2325 1.4619
|
||||||
|
1.5928 1.1099 3.3299 2.2689 5.223 3.4729.9682.6158 1.9229 1.1946 2.8588 1.7383.2671.1552.6002.141.8515-.0387 1.351-.9664
|
||||||
|
2.5145-1.9362 3.463-2.8261 2.1458-2.013 3.9974-4.231 5.4947-6.7819.7286-1.2414 2.3312-1.6783 3.5794-.9757s1.6693 2.2785.9406
|
||||||
|
3.52c-1.7525 2.9859-3.9213 5.6002-6.4356 7.9591-.4351.4082-.9081.8302-1.4172 1.2601-.4505.3805-.3701 1.1048.1642 1.3543 3.184
|
||||||
|
1.4867 5.8634 2.4904 7.7071 3.1131 2.6745.9033 5.5327-.1298 7.0673-2.4281 1.9401-2.9057 5.3476-8.3855 8.2732-15.0533.7591-1.7301
|
||||||
|
1.5313-3.6163 2.2883-5.5494.1485-.3793-.0133-.8092-.3743-.9978z" />
|
||||||
|
<GeometryDrawing Brush="LightGray" Geometry="m22.9737 37.9072c2.0802 0 3.7666-1.6864 3.7666-3.7667 0-2.0802-1.6864-3.7666-3.7666-3.7666-2.0803
|
||||||
|
0-3.7667 1.6864-3.7667 3.7666 0 2.0803 1.6864 3.7667 3.7667 3.7667z" />
|
||||||
|
<GeometryDrawing Brush="LightGray" Geometry="m12.7198 49.4854c2.0802 0 3.7666-1.6864 3.7666-3.7667 0-2.0802-1.6864-3.7666-3.7666-3.7666-2.0803
|
||||||
|
0-3.76667 1.6864-3.76668 3.7666 0 2.0803 1.68638 3.7667 3.76668 3.7667z" />
|
||||||
|
</DrawingGroup>
|
||||||
|
</DrawingImage.Drawing>
|
||||||
|
</DrawingImage>
|
||||||
|
</Image.Source>
|
||||||
|
</Image>
|
||||||
|
</Button>
|
||||||
</Grid>
|
</Grid>
|
||||||
<StatusBar
|
<StatusBar
|
||||||
Grid.Row="5"
|
Grid.Row="5"
|
||||||
Grid.Column="2"
|
Grid.Column="2"
|
||||||
>
|
>
|
||||||
<StatusBarItem>
|
<StatusBarItem>
|
||||||
|
<TextBlock Margin="3 0 0 0" x:Name="StatusClock" FontSize="16"></TextBlock>
|
||||||
</StatusBarItem>
|
</StatusBarItem>
|
||||||
<StatusBarItem>
|
<StatusBarItem>
|
||||||
<TextBlock x:Name="Help" Text="{Binding Path=CurrentHelp}"></TextBlock>
|
<TextBlock Margin="3 0 0 0" x:Name="StatusHelp" Text="{Binding Path=CurrentHelp}"></TextBlock>
|
||||||
</StatusBarItem>
|
</StatusBarItem>
|
||||||
<StatusBarItem>
|
<StatusBarItem>
|
||||||
<TextBlock x:Name="Status"></TextBlock>
|
<TextBlock x:Name="Status"></TextBlock>
|
||||||
|
|||||||
@@ -63,6 +63,7 @@ public partial class MainWindow
|
|||||||
AnnotationClasses = new ObservableCollection<AnnotationClass>(_config.AnnotationClasses);
|
AnnotationClasses = new ObservableCollection<AnnotationClass>(_config.AnnotationClasses);
|
||||||
LvClasses.ItemsSource = AnnotationClasses;
|
LvClasses.ItemsSource = AnnotationClasses;
|
||||||
LvClasses.SelectedIndex = 0;
|
LvClasses.SelectedIndex = 0;
|
||||||
|
CurrentHelp = HelpTexts.HelpTextsDict[HelpTextEnum.Initial];
|
||||||
}
|
}
|
||||||
|
|
||||||
private void InitControls()
|
private void InitControls()
|
||||||
@@ -74,6 +75,7 @@ public partial class MainWindow
|
|||||||
uint vw = 0, vh = 0;
|
uint vw = 0, vh = 0;
|
||||||
_mediaPlayer.Size(0, ref vw, ref vh);
|
_mediaPlayer.Size(0, ref vw, ref vh);
|
||||||
_formState.CurrentVideoSize = new Size(vw, vh);
|
_formState.CurrentVideoSize = new Size(vw, vh);
|
||||||
|
_formState.CurrentVideoLength = TimeSpan.FromMilliseconds(_mediaPlayer.Length);
|
||||||
};
|
};
|
||||||
|
|
||||||
LvFiles.MouseDoubleClick += async (_, _) =>
|
LvFiles.MouseDoubleClick += async (_, _) =>
|
||||||
@@ -91,6 +93,8 @@ public partial class MainWindow
|
|||||||
_mediaPlayer.PositionChanged += (o, args) =>
|
_mediaPlayer.PositionChanged += (o, args) =>
|
||||||
{
|
{
|
||||||
Dispatcher.Invoke(() => videoSlider.Value = _mediaPlayer.Position * videoSlider.Maximum);
|
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));
|
var curTime = _formState.GetTimeName(TimeSpan.FromMilliseconds(_mediaPlayer.Time));
|
||||||
if (!Annotations.TryGetValue(curTime, out var annotationInfos))
|
if (!Annotations.TryGetValue(curTime, out var annotationInfos))
|
||||||
return;
|
return;
|
||||||
@@ -99,14 +103,15 @@ public partial class MainWindow
|
|||||||
{
|
{
|
||||||
var annClass = _config.AnnotationClasses[info.ClassNumber];
|
var annClass = _config.AnnotationClasses[info.ClassNumber];
|
||||||
var annInfo = info.ToCanvasCoordinates(Editor.RenderSize, _formState.CurrentVideoSize);
|
var annInfo = info.ToCanvasCoordinates(Editor.RenderSize, _formState.CurrentVideoSize);
|
||||||
return Editor.CreateAnnotation(annClass, annInfo);
|
var annotation = Dispatcher.Invoke(() => Editor.CreateAnnotation(annClass, annInfo));
|
||||||
|
return annotation;
|
||||||
}).ToList();
|
}).ToList();
|
||||||
|
|
||||||
//remove annotations: either in 1 sec, either earlier if there is next annotation in a dictionary
|
//remove annotations: either in 1 sec, either earlier if there is next annotation in a dictionary
|
||||||
var strs = curTime.Split("_");
|
var strs = curTime.Split("_");
|
||||||
var timeStr = strs.LastOrDefault();
|
var timeStr = strs.LastOrDefault();
|
||||||
var ts = TimeSpan.ParseExact(timeStr, "hmmssf", CultureInfo.InvariantCulture);
|
var ts = TimeSpan.FromMilliseconds(int.Parse(timeStr)*100);
|
||||||
var timeSpanRemove = Enumerable.Range(0, (int)_annotationTime.TotalMilliseconds / 100)
|
var timeSpanRemove = Enumerable.Range(1, ((int)_annotationTime.TotalMilliseconds / 100) - 1)
|
||||||
.Select(x =>
|
.Select(x =>
|
||||||
{
|
{
|
||||||
var time = TimeSpan.FromMilliseconds(x * 100);
|
var time = TimeSpan.FromMilliseconds(x * 100);
|
||||||
@@ -119,6 +124,7 @@ public partial class MainWindow
|
|||||||
await Task.Delay(timeSpanRemove);
|
await Task.Delay(timeSpanRemove);
|
||||||
Dispatcher.Invoke(() => Editor.RemoveAnnotations(annotations));
|
Dispatcher.Invoke(() => Editor.RemoveAnnotations(annotations));
|
||||||
});
|
});
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
videoSlider.ValueChanged += (value, newValue) =>
|
videoSlider.ValueChanged += (value, newValue) =>
|
||||||
@@ -177,6 +183,7 @@ public partial class MainWindow
|
|||||||
var files = dir.GetFiles("mp4", "mov").Select(x =>
|
var files = dir.GetFiles("mp4", "mov").Select(x =>
|
||||||
{
|
{
|
||||||
_mediaPlayer.Media = new Media(_libVLC, x.FullName);
|
_mediaPlayer.Media = new Media(_libVLC, x.FullName);
|
||||||
|
|
||||||
return new VideoFileInfo
|
return new VideoFileInfo
|
||||||
{
|
{
|
||||||
Name = x.Name,
|
Name = x.Name,
|
||||||
@@ -221,4 +228,15 @@ public partial class MainWindow
|
|||||||
ReloadFiles();
|
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));
|
||||||
}
|
}
|
||||||
@@ -8,7 +8,8 @@ namespace Azaion.Annotator;
|
|||||||
|
|
||||||
public class PlayerControlHandler:
|
public class PlayerControlHandler:
|
||||||
INotificationHandler<KeyEvent>,
|
INotificationHandler<KeyEvent>,
|
||||||
INotificationHandler<AnnClassSelectedEvent>
|
INotificationHandler<AnnClassSelectedEvent>,
|
||||||
|
INotificationHandler<PlaybackControlEvent>
|
||||||
{
|
{
|
||||||
private const int STEP = 20;
|
private const int STEP = 20;
|
||||||
private const int LARGE_STEP = 2000;
|
private const int LARGE_STEP = 2000;
|
||||||
@@ -27,6 +28,16 @@ public class PlayerControlHandler:
|
|||||||
_config = config;
|
_config = config;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private readonly Dictionary<Key, PlaybackControlEnum> KeysControlEnumDict = new()
|
||||||
|
{
|
||||||
|
{ Key.Space, PlaybackControlEnum.Pause },
|
||||||
|
{ Key.Left, PlaybackControlEnum.PreviousFrame },
|
||||||
|
{ Key.Right, PlaybackControlEnum.NextFrame },
|
||||||
|
{ Key.Enter, PlaybackControlEnum.SaveAnnotations },
|
||||||
|
{ Key.Delete, PlaybackControlEnum.RemoveSelectedAnns },
|
||||||
|
{ Key.X, PlaybackControlEnum.RemoveAllAnns }
|
||||||
|
};
|
||||||
|
|
||||||
public async Task Handle(AnnClassSelectedEvent notification, CancellationToken cancellationToken) =>
|
public async Task Handle(AnnClassSelectedEvent notification, CancellationToken cancellationToken) =>
|
||||||
SelectClass(notification.AnnotationClass);
|
SelectClass(notification.AnnotationClass);
|
||||||
|
|
||||||
@@ -40,13 +51,9 @@ public class PlayerControlHandler:
|
|||||||
|
|
||||||
public async Task Handle(KeyEvent notification, CancellationToken cancellationToken)
|
public async Task Handle(KeyEvent notification, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
//Console.WriteLine($"Time: {DateTime.UtcNow:hh:mm:ss.fff}. Sender {notification.Sender.GetType().Name} Key {notification.Args.Key}");
|
|
||||||
if (!CatchSenders.Contains(notification.Sender.GetType().Name))
|
if (!CatchSenders.Contains(notification.Sender.GetType().Name))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var isCtrlPressed = notification.Args.KeyboardDevice.IsKeyDown(Key.LeftCtrl) ||
|
|
||||||
notification.Args.KeyboardDevice.IsKeyDown(Key.RightCtrl);
|
|
||||||
var step = isCtrlPressed ? STEP : LARGE_STEP;
|
|
||||||
var key = notification.Args.Key;
|
var key = notification.Args.Key;
|
||||||
var keyNumber = (int?)null;
|
var keyNumber = (int?)null;
|
||||||
|
|
||||||
@@ -57,20 +64,57 @@ public class PlayerControlHandler:
|
|||||||
if (keyNumber.HasValue)
|
if (keyNumber.HasValue)
|
||||||
SelectClass(_mainWindow.AnnotationClasses[keyNumber.Value]);
|
SelectClass(_mainWindow.AnnotationClasses[keyNumber.Value]);
|
||||||
|
|
||||||
switch (key)
|
if (KeysControlEnumDict.TryGetValue(key, out var value))
|
||||||
|
await ControlPlayback(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Handle(PlaybackControlEvent notification, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
case Key.Space:
|
await ControlPlayback(notification.PlaybackControl);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task ControlPlayback(PlaybackControlEnum controlEnum)
|
||||||
|
{
|
||||||
|
var isCtrlPressed = Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl);
|
||||||
|
var step = isCtrlPressed ? LARGE_STEP : STEP;
|
||||||
|
|
||||||
|
switch (controlEnum)
|
||||||
|
{
|
||||||
|
case PlaybackControlEnum.Play:
|
||||||
|
mediaPlayer.Play();
|
||||||
|
break;
|
||||||
|
case PlaybackControlEnum.Pause:
|
||||||
mediaPlayer.Pause();
|
mediaPlayer.Pause();
|
||||||
break;
|
break;
|
||||||
case Key.Left:
|
case PlaybackControlEnum.Stop:
|
||||||
|
mediaPlayer.Stop();
|
||||||
|
break;
|
||||||
|
case PlaybackControlEnum.PreviousFrame:
|
||||||
mediaPlayer.SetPause(true);
|
mediaPlayer.SetPause(true);
|
||||||
mediaPlayer.Time -= step;
|
mediaPlayer.Time -= step;
|
||||||
break;
|
break;
|
||||||
case Key.Right:
|
case PlaybackControlEnum.NextFrame:
|
||||||
mediaPlayer.SetPause(true);
|
mediaPlayer.SetPause(true);
|
||||||
mediaPlayer.Time += step;
|
mediaPlayer.Time += step;
|
||||||
break;
|
break;
|
||||||
case Key.Enter:
|
case PlaybackControlEnum.SaveAnnotations:
|
||||||
|
await SaveAnnotations();
|
||||||
|
break;
|
||||||
|
case PlaybackControlEnum.RemoveSelectedAnns:
|
||||||
|
_mainWindow.Editor.RemoveSelectedAnns();
|
||||||
|
break;
|
||||||
|
case PlaybackControlEnum.RemoveAllAnns:
|
||||||
|
_mainWindow.Editor.RemoveAllAnns();
|
||||||
|
break;
|
||||||
|
case PlaybackControlEnum.None:
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(controlEnum), controlEnum, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task SaveAnnotations()
|
||||||
|
{
|
||||||
if (string.IsNullOrEmpty(_formState.CurrentFile))
|
if (string.IsNullOrEmpty(_formState.CurrentFile))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@@ -80,16 +124,16 @@ public class PlayerControlHandler:
|
|||||||
.ToList();
|
.ToList();
|
||||||
var labels = string.Join(Environment.NewLine, currentAnns.Select(x => x.ToString()));
|
var labels = string.Join(Environment.NewLine, currentAnns.Select(x => x.ToString()));
|
||||||
|
|
||||||
await File.WriteAllTextAsync($"{_config.LabelsDirectory}/{fName}.txt", labels, cancellationToken);
|
if (!Directory.Exists(_config.LabelsDirectory))
|
||||||
|
Directory.CreateDirectory(_config.LabelsDirectory);
|
||||||
|
if (!Directory.Exists(_config.ImagesDirectory))
|
||||||
|
Directory.CreateDirectory(_config.ImagesDirectory);
|
||||||
|
|
||||||
|
await File.WriteAllTextAsync($"{_config.LabelsDirectory}/{fName}.txt", labels);
|
||||||
mediaPlayer.TakeSnapshot(0, $"{_config.ImagesDirectory}/{fName}.jpg", 0, 0);
|
mediaPlayer.TakeSnapshot(0, $"{_config.ImagesDirectory}/{fName}.jpg", 0, 0);
|
||||||
|
|
||||||
_mainWindow.Annotations[fName] = currentAnns;
|
_mainWindow.Annotations[fName] = currentAnns;
|
||||||
_mainWindow.Editor.RemoveAllAnns();
|
_mainWindow.Editor.RemoveAllAnns();
|
||||||
break;
|
mediaPlayer.Play();
|
||||||
|
|
||||||
case Key.Delete:
|
|
||||||
_mainWindow.Editor.RemoveSelectedAnns();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user