Update merge workflow behavior
This commit is contained in:
parent
726408a44f
commit
787716e221
@ -7,6 +7,12 @@ namespace EmbyToolbox.Behaviors;
|
|||||||
|
|
||||||
public static class DataGridAutoScrollSelectionBehavior
|
public static class DataGridAutoScrollSelectionBehavior
|
||||||
{
|
{
|
||||||
|
public static readonly DependencyProperty ScrollIntoViewItemProperty = DependencyProperty.RegisterAttached(
|
||||||
|
"ScrollIntoViewItem",
|
||||||
|
typeof(object),
|
||||||
|
typeof(DataGridAutoScrollSelectionBehavior),
|
||||||
|
new PropertyMetadata(null, OnScrollIntoViewItemChanged));
|
||||||
|
|
||||||
private static readonly DependencyProperty SuppressAutoScrollUntilProperty = DependencyProperty.RegisterAttached(
|
private static readonly DependencyProperty SuppressAutoScrollUntilProperty = DependencyProperty.RegisterAttached(
|
||||||
"SuppressAutoScrollUntil",
|
"SuppressAutoScrollUntil",
|
||||||
typeof(DateTime),
|
typeof(DateTime),
|
||||||
@ -22,6 +28,9 @@ public static class DataGridAutoScrollSelectionBehavior
|
|||||||
public static void SetIsEnabled(DependencyObject d, bool value) => d.SetValue(IsEnabledProperty, value);
|
public static void SetIsEnabled(DependencyObject d, bool value) => d.SetValue(IsEnabledProperty, value);
|
||||||
public static bool GetIsEnabled(DependencyObject d) => (bool)d.GetValue(IsEnabledProperty);
|
public static bool GetIsEnabled(DependencyObject d) => (bool)d.GetValue(IsEnabledProperty);
|
||||||
|
|
||||||
|
public static void SetScrollIntoViewItem(DependencyObject d, object? value) => d.SetValue(ScrollIntoViewItemProperty, value);
|
||||||
|
public static object? GetScrollIntoViewItem(DependencyObject d) => d.GetValue(ScrollIntoViewItemProperty);
|
||||||
|
|
||||||
private static void OnChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
private static void OnChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||||
{
|
{
|
||||||
if (d is not DataGrid dg)
|
if (d is not DataGrid dg)
|
||||||
@ -46,6 +55,25 @@ public static class DataGridAutoScrollSelectionBehavior
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void OnScrollIntoViewItemChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||||
|
{
|
||||||
|
if (d is not DataGrid dg || e.NewValue is null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
dg.Dispatcher.BeginInvoke(
|
||||||
|
DispatcherPriority.Background,
|
||||||
|
() =>
|
||||||
|
{
|
||||||
|
var item = GetScrollIntoViewItem(dg);
|
||||||
|
if (item is not null)
|
||||||
|
{
|
||||||
|
dg.ScrollIntoView(item);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private static void OnPreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
|
private static void OnPreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
|
||||||
{
|
{
|
||||||
if (sender is not DataGrid dg)
|
if (sender is not DataGrid dg)
|
||||||
|
|||||||
@ -21,6 +21,7 @@ public sealed class MediaAnalysisResult
|
|||||||
/// <summary>Основной видеопоток: самый крупный по площади кадра (не первый подряд в JSON — иначе cover/mjpeg может оказаться «основным»).</summary>
|
/// <summary>Основной видеопоток: самый крупный по площади кадра (не первый подряд в JSON — иначе cover/mjpeg может оказаться «основным»).</summary>
|
||||||
public MediaStreamInfo? PrimaryVideo =>
|
public MediaStreamInfo? PrimaryVideo =>
|
||||||
VideoStreams
|
VideoStreams
|
||||||
|
.Where(static v => !v.IsAttachedPicture)
|
||||||
.OrderByDescending(static v => ((long)(v.Width ?? 0)) * (v.Height ?? 0))
|
.OrderByDescending(static v => ((long)(v.Width ?? 0)) * (v.Height ?? 0))
|
||||||
.ThenByDescending(static v => v.IsDefault ? 1 : 0)
|
.ThenByDescending(static v => v.IsDefault ? 1 : 0)
|
||||||
.FirstOrDefault();
|
.FirstOrDefault();
|
||||||
|
|||||||
@ -23,6 +23,7 @@ public sealed class MediaStreamInfo
|
|||||||
public string? ColorSpace { get; init; }
|
public string? ColorSpace { get; init; }
|
||||||
public string? ColorPrimaries { get; init; }
|
public string? ColorPrimaries { get; init; }
|
||||||
public string? ColorTransfer { get; init; }
|
public string? ColorTransfer { get; init; }
|
||||||
|
public bool IsAttachedPicture { get; init; }
|
||||||
public string? SubtitleFormat { get; init; }
|
public string? SubtitleFormat { get; init; }
|
||||||
public string? FileNameTag { get; init; }
|
public string? FileNameTag { get; init; }
|
||||||
public bool IsForcedByDisposition { get; init; }
|
public bool IsForcedByDisposition { get; init; }
|
||||||
|
|||||||
@ -68,6 +68,7 @@ public static class MediaAnalysisParser
|
|||||||
|
|
||||||
var primaryVideoBitrate = streams
|
var primaryVideoBitrate = streams
|
||||||
.Where(x => x.Kind == MediaStreamKind.Video)
|
.Where(x => x.Kind == MediaStreamKind.Video)
|
||||||
|
.Where(x => !x.IsAttachedPicture)
|
||||||
.OrderByDescending(static v => ((long)(v.Width ?? 0)) * (v.Height ?? 0))
|
.OrderByDescending(static v => ((long)(v.Width ?? 0)) * (v.Height ?? 0))
|
||||||
.ThenByDescending(static v => v.IsDefault ? 1 : 0)
|
.ThenByDescending(static v => v.IsDefault ? 1 : 0)
|
||||||
.Select(static v => v.BitRateBps)
|
.Select(static v => v.BitRateBps)
|
||||||
@ -117,6 +118,7 @@ public static class MediaAnalysisParser
|
|||||||
ColorSpace = GetStr(s, "color_space", null),
|
ColorSpace = GetStr(s, "color_space", null),
|
||||||
ColorPrimaries = GetStr(s, "color_primaries", null),
|
ColorPrimaries = GetStr(s, "color_primaries", null),
|
||||||
ColorTransfer = GetStr(s, "color_transfer", null),
|
ColorTransfer = GetStr(s, "color_transfer", null),
|
||||||
|
IsAttachedPicture = GetDisposition(s, "attached_pic", false),
|
||||||
BitRateBps = ParseLong(s, "bit_rate") ?? ParseLong(s, "max_bit_rate"),
|
BitRateBps = ParseLong(s, "bit_rate") ?? ParseLong(s, "max_bit_rate"),
|
||||||
DurationSeconds = streamDuration
|
DurationSeconds = streamDuration
|
||||||
};
|
};
|
||||||
|
|||||||
@ -56,6 +56,7 @@ public sealed class ConversionViewModel : INotifyPropertyChanged
|
|||||||
private string? _currentRunId;
|
private string? _currentRunId;
|
||||||
private HashSet<ConversionQueueItem> _currentRunItems = new();
|
private HashSet<ConversionQueueItem> _currentRunItems = new();
|
||||||
private string _executionPhaseCaption = string.Empty;
|
private string _executionPhaseCaption = string.Empty;
|
||||||
|
private ConversionQueueItem? _queueItemToReveal;
|
||||||
private bool _copyQueueItemErrorMenuVisible;
|
private bool _copyQueueItemErrorMenuVisible;
|
||||||
private string _toastMessage = string.Empty;
|
private string _toastMessage = string.Empty;
|
||||||
private bool _isToastVisible;
|
private bool _isToastVisible;
|
||||||
@ -232,6 +233,21 @@ public sealed class ConversionViewModel : INotifyPropertyChanged
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ConversionQueueItem? QueueItemToReveal
|
||||||
|
{
|
||||||
|
get => _queueItemToReveal;
|
||||||
|
private set
|
||||||
|
{
|
||||||
|
if (ReferenceEquals(_queueItemToReveal, value))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_queueItemToReveal = value;
|
||||||
|
OnPropertyChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>Отображаемый общий прогресс (Floor от средних DisplayProgressPercent; без 100%, пока есть активные задачи).</summary>
|
/// <summary>Отображаемый общий прогресс (Floor от средних DisplayProgressPercent; без 100%, пока есть активные задачи).</summary>
|
||||||
public int OverallProgressPercent
|
public int OverallProgressPercent
|
||||||
{
|
{
|
||||||
@ -549,6 +565,16 @@ public sealed class ConversionViewModel : INotifyPropertyChanged
|
|||||||
OpenBulkFileConversionSettingsCommand.RaiseCanExecuteChanged();
|
OpenBulkFileConversionSettingsCommand.RaiseCanExecuteChanged();
|
||||||
RecalculateOverallProgress();
|
RecalculateOverallProgress();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (s is ConversionQueueItem changedItem
|
||||||
|
&& e.PropertyName is nameof(ConversionQueueItem.Status) or nameof(ConversionQueueItem.ProcessedInCurrentRun)
|
||||||
|
&& IsExecutionRunning
|
||||||
|
&& string.Equals(changedItem.Status, ConversionQueueStatus.Done, StringComparison.Ordinal)
|
||||||
|
&& changedItem.ProcessedInCurrentRun)
|
||||||
|
{
|
||||||
|
QueueItemToReveal = null;
|
||||||
|
QueueItemToReveal = changedItem;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnTaskProfileChanged(ConversionQueueItem item)
|
private void OnTaskProfileChanged(ConversionQueueItem item)
|
||||||
|
|||||||
@ -206,7 +206,7 @@ public sealed class MergeViewModel : INotifyPropertyChanged
|
|||||||
|
|
||||||
private void ExecuteSelectOutputFile()
|
private void ExecuteSelectOutputFile()
|
||||||
{
|
{
|
||||||
var suggestFile = "movies_merged.mkv";
|
var suggestFile = "movies.mkv";
|
||||||
string? suggestDir = null;
|
string? suggestDir = null;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@ -634,7 +634,7 @@ public sealed class MergeViewModel : INotifyPropertyChanged
|
|||||||
Number = i + 1,
|
Number = i + 1,
|
||||||
Status = "Готов",
|
Status = "Готов",
|
||||||
};
|
};
|
||||||
item.SyncAutoPartName($"Часть {i + 1} - {Path.GetFileNameWithoutExtension(fileName)}");
|
item.SyncAutoPartName(BuildDefaultPartName(i));
|
||||||
Files.Add(item);
|
Files.Add(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -664,12 +664,17 @@ public sealed class MergeViewModel : INotifyPropertyChanged
|
|||||||
: _recentPaths.GetInitialDirectory(RecentPathScenario.Merge);
|
: _recentPaths.GetInitialDirectory(RecentPathScenario.Merge);
|
||||||
|
|
||||||
var fileName = parents.Count == 1
|
var fileName = parents.Count == 1
|
||||||
? $"{new DirectoryInfo(parents[0]).Name}_merged.mkv"
|
? $"{new DirectoryInfo(parents[0]).Name}.mkv"
|
||||||
: "movies_merged.mkv";
|
: "movies.mkv";
|
||||||
|
|
||||||
return Path.Combine(dir, fileName);
|
return Path.Combine(dir, fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static string BuildDefaultPartName(int zeroBasedIndex)
|
||||||
|
{
|
||||||
|
return $"Часть {zeroBasedIndex + 1}";
|
||||||
|
}
|
||||||
|
|
||||||
private void AfterFilesChanged()
|
private void AfterFilesChanged()
|
||||||
{
|
{
|
||||||
ProgressPercent = 0;
|
ProgressPercent = 0;
|
||||||
@ -686,8 +691,7 @@ public sealed class MergeViewModel : INotifyPropertyChanged
|
|||||||
for (var i = 0; i < Files.Count; i++)
|
for (var i = 0; i < Files.Count; i++)
|
||||||
{
|
{
|
||||||
Files[i].Number = i + 1;
|
Files[i].Number = i + 1;
|
||||||
var fn = Files[i].FileName;
|
Files[i].SyncAutoPartName(BuildDefaultPartName(i));
|
||||||
Files[i].SyncAutoPartName($"Часть {i + 1} - {Path.GetFileNameWithoutExtension(fn)}");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
UpdateValidationState();
|
UpdateValidationState();
|
||||||
@ -735,7 +739,7 @@ public sealed class MergeViewModel : INotifyPropertyChanged
|
|||||||
var name = Path.GetFileName(fullOutput);
|
var name = Path.GetFileName(fullOutput);
|
||||||
if (string.IsNullOrWhiteSpace(name))
|
if (string.IsNullOrWhiteSpace(name))
|
||||||
{
|
{
|
||||||
error = "Укажите имя итогового файла (например, …\\movies_merged.mkv).";
|
error = "Укажите имя итогового файла (например, …\\movies.mkv).";
|
||||||
}
|
}
|
||||||
else if (name.IndexOfAny(Path.GetInvalidFileNameChars()) >= 0)
|
else if (name.IndexOfAny(Path.GetInvalidFileNameChars()) >= 0)
|
||||||
{
|
{
|
||||||
@ -795,12 +799,15 @@ public sealed class MergeViewModel : INotifyPropertyChanged
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var deletedItems = new List<MergeFileItem>();
|
||||||
var deleteErrors = new List<string>();
|
var deleteErrors = new List<string>();
|
||||||
foreach (var source in ordered.Select(f => f.FullPath))
|
foreach (var item in ordered)
|
||||||
{
|
{
|
||||||
|
var source = item.FullPath;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
File.Delete(source);
|
File.Delete(source);
|
||||||
|
deletedItems.Add(item);
|
||||||
_logging.Info($"удален исходник после объединения: {source}", "merge");
|
_logging.Info($"удален исходник после объединения: {source}", "merge");
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@ -810,6 +817,8 @@ public sealed class MergeViewModel : INotifyPropertyChanged
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
RemoveDeletedSourcesFromTable(deletedItems);
|
||||||
|
|
||||||
if (deleteErrors.Count == 0)
|
if (deleteErrors.Count == 0)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
@ -822,6 +831,27 @@ public sealed class MergeViewModel : INotifyPropertyChanged
|
|||||||
MessageBoxImage.Warning);
|
MessageBoxImage.Warning);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void RemoveDeletedSourcesFromTable(IReadOnlyList<MergeFileItem> deletedItems)
|
||||||
|
{
|
||||||
|
if (deletedItems.Count == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var item in deletedItems)
|
||||||
|
{
|
||||||
|
Files.Remove(item);
|
||||||
|
_selectedItems.Remove(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (SelectedItem is not null && deletedItems.Contains(SelectedItem))
|
||||||
|
{
|
||||||
|
SelectedItem = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
RenumberMergeRows();
|
||||||
|
}
|
||||||
|
|
||||||
private void OnPropertyChanged([CallerMemberName] string? propertyName = null)
|
private void OnPropertyChanged([CallerMemberName] string? propertyName = null)
|
||||||
{
|
{
|
||||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||||
|
|||||||
@ -179,7 +179,7 @@
|
|||||||
ScrollViewer.HorizontalScrollBarVisibility="Auto"
|
ScrollViewer.HorizontalScrollBarVisibility="Auto"
|
||||||
behaviors:ConversionQueueDropTargetBehavior.IsDropTargetEnabled="True"
|
behaviors:ConversionQueueDropTargetBehavior.IsDropTargetEnabled="True"
|
||||||
behaviors:DataGridRowDoubleClickCommandBehavior.Command="{Binding OpenFileConversionSettingsCommand}"
|
behaviors:DataGridRowDoubleClickCommandBehavior.Command="{Binding OpenFileConversionSettingsCommand}"
|
||||||
behaviors:DataGridAutoScrollSelectionBehavior.IsEnabled="True"
|
behaviors:DataGridAutoScrollSelectionBehavior.ScrollIntoViewItem="{Binding QueueItemToReveal}"
|
||||||
SelectionChanged="QueueDataGrid_SelectionChanged">
|
SelectionChanged="QueueDataGrid_SelectionChanged">
|
||||||
<DataGrid.InputBindings>
|
<DataGrid.InputBindings>
|
||||||
<KeyBinding Key="Delete"
|
<KeyBinding Key="Delete"
|
||||||
|
|||||||
@ -23,25 +23,16 @@
|
|||||||
<RowDefinition Height="Auto" />
|
<RowDefinition Height="Auto" />
|
||||||
</Grid.RowDefinitions>
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
<Grid Grid.Row="0"
|
<Grid Grid.Row="0">
|
||||||
behaviors:MergeDropTargetBehavior.IsEnabled="True">
|
|
||||||
<Grid.Style>
|
|
||||||
<Style TargetType="Grid">
|
|
||||||
<Setter Property="Background" Value="Transparent" />
|
|
||||||
<Style.Triggers>
|
|
||||||
<DataTrigger Binding="{Binding IsMergeDropHighlight}" Value="True">
|
|
||||||
<Setter Property="Background" Value="{DynamicResource Ui.Brush.MergeDropOverlay}" />
|
|
||||||
</DataTrigger>
|
|
||||||
</Style.Triggers>
|
|
||||||
</Style>
|
|
||||||
</Grid.Style>
|
|
||||||
<Grid.ColumnDefinitions>
|
<Grid.ColumnDefinitions>
|
||||||
<ColumnDefinition Width="*" />
|
<ColumnDefinition Width="*" />
|
||||||
<ColumnDefinition Width="Auto" />
|
<ColumnDefinition Width="Auto" />
|
||||||
</Grid.ColumnDefinitions>
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
<DataGrid x:Name="FilesGrid"
|
<Grid Grid.Column="0"
|
||||||
Grid.Column="0"
|
Background="Transparent"
|
||||||
|
behaviors:MergeDropTargetBehavior.IsEnabled="True">
|
||||||
|
<DataGrid x:Name="FilesGrid"
|
||||||
ItemsSource="{Binding Files}"
|
ItemsSource="{Binding Files}"
|
||||||
SelectedItem="{Binding SelectedItem, Mode=TwoWay}"
|
SelectedItem="{Binding SelectedItem, Mode=TwoWay}"
|
||||||
SelectionMode="Extended"
|
SelectionMode="Extended"
|
||||||
@ -76,7 +67,22 @@
|
|||||||
<DataGridTextColumn Header="Размер, МБ" Binding="{Binding SizeMb, Mode=OneWay}" IsReadOnly="True" Width="90" />
|
<DataGridTextColumn Header="Размер, МБ" Binding="{Binding SizeMb, Mode=OneWay}" IsReadOnly="True" Width="90" />
|
||||||
<DataGridTextColumn Header="Имя части" Binding="{Binding PartName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Width="2*" MinWidth="200" />
|
<DataGridTextColumn Header="Имя части" Binding="{Binding PartName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Width="2*" MinWidth="200" />
|
||||||
</DataGrid.Columns>
|
</DataGrid.Columns>
|
||||||
</DataGrid>
|
</DataGrid>
|
||||||
|
<Border IsHitTestVisible="False">
|
||||||
|
<Border.Style>
|
||||||
|
<Style TargetType="Border">
|
||||||
|
<Setter Property="Background" Value="Transparent" />
|
||||||
|
<Setter Property="Visibility" Value="Collapsed" />
|
||||||
|
<Style.Triggers>
|
||||||
|
<DataTrigger Binding="{Binding IsMergeDropHighlight}" Value="True">
|
||||||
|
<Setter Property="Background" Value="{DynamicResource Ui.Brush.MergeDropOverlay}" />
|
||||||
|
<Setter Property="Visibility" Value="Visible" />
|
||||||
|
</DataTrigger>
|
||||||
|
</Style.Triggers>
|
||||||
|
</Style>
|
||||||
|
</Border.Style>
|
||||||
|
</Border>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
<StackPanel Grid.Column="1" Margin="8,0,0,0">
|
<StackPanel Grid.Column="1" Margin="8,0,0,0">
|
||||||
<Button MinWidth="150"
|
<Button MinWidth="150"
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user