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 readonly DependencyProperty ScrollIntoViewItemProperty = DependencyProperty.RegisterAttached(
|
||||
"ScrollIntoViewItem",
|
||||
typeof(object),
|
||||
typeof(DataGridAutoScrollSelectionBehavior),
|
||||
new PropertyMetadata(null, OnScrollIntoViewItemChanged));
|
||||
|
||||
private static readonly DependencyProperty SuppressAutoScrollUntilProperty = DependencyProperty.RegisterAttached(
|
||||
"SuppressAutoScrollUntil",
|
||||
typeof(DateTime),
|
||||
@ -22,6 +28,9 @@ public static class DataGridAutoScrollSelectionBehavior
|
||||
public static void SetIsEnabled(DependencyObject d, bool value) => d.SetValue(IsEnabledProperty, value);
|
||||
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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
if (sender is not DataGrid dg)
|
||||
|
||||
@ -21,6 +21,7 @@ public sealed class MediaAnalysisResult
|
||||
/// <summary>Основной видеопоток: самый крупный по площади кадра (не первый подряд в JSON — иначе cover/mjpeg может оказаться «основным»).</summary>
|
||||
public MediaStreamInfo? PrimaryVideo =>
|
||||
VideoStreams
|
||||
.Where(static v => !v.IsAttachedPicture)
|
||||
.OrderByDescending(static v => ((long)(v.Width ?? 0)) * (v.Height ?? 0))
|
||||
.ThenByDescending(static v => v.IsDefault ? 1 : 0)
|
||||
.FirstOrDefault();
|
||||
|
||||
@ -23,6 +23,7 @@ public sealed class MediaStreamInfo
|
||||
public string? ColorSpace { get; init; }
|
||||
public string? ColorPrimaries { get; init; }
|
||||
public string? ColorTransfer { get; init; }
|
||||
public bool IsAttachedPicture { get; init; }
|
||||
public string? SubtitleFormat { get; init; }
|
||||
public string? FileNameTag { get; init; }
|
||||
public bool IsForcedByDisposition { get; init; }
|
||||
|
||||
@ -68,6 +68,7 @@ public static class MediaAnalysisParser
|
||||
|
||||
var primaryVideoBitrate = streams
|
||||
.Where(x => x.Kind == MediaStreamKind.Video)
|
||||
.Where(x => !x.IsAttachedPicture)
|
||||
.OrderByDescending(static v => ((long)(v.Width ?? 0)) * (v.Height ?? 0))
|
||||
.ThenByDescending(static v => v.IsDefault ? 1 : 0)
|
||||
.Select(static v => v.BitRateBps)
|
||||
@ -117,6 +118,7 @@ public static class MediaAnalysisParser
|
||||
ColorSpace = GetStr(s, "color_space", null),
|
||||
ColorPrimaries = GetStr(s, "color_primaries", null),
|
||||
ColorTransfer = GetStr(s, "color_transfer", null),
|
||||
IsAttachedPicture = GetDisposition(s, "attached_pic", false),
|
||||
BitRateBps = ParseLong(s, "bit_rate") ?? ParseLong(s, "max_bit_rate"),
|
||||
DurationSeconds = streamDuration
|
||||
};
|
||||
|
||||
@ -56,6 +56,7 @@ public sealed class ConversionViewModel : INotifyPropertyChanged
|
||||
private string? _currentRunId;
|
||||
private HashSet<ConversionQueueItem> _currentRunItems = new();
|
||||
private string _executionPhaseCaption = string.Empty;
|
||||
private ConversionQueueItem? _queueItemToReveal;
|
||||
private bool _copyQueueItemErrorMenuVisible;
|
||||
private string _toastMessage = string.Empty;
|
||||
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>
|
||||
public int OverallProgressPercent
|
||||
{
|
||||
@ -549,6 +565,16 @@ public sealed class ConversionViewModel : INotifyPropertyChanged
|
||||
OpenBulkFileConversionSettingsCommand.RaiseCanExecuteChanged();
|
||||
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)
|
||||
|
||||
@ -206,7 +206,7 @@ public sealed class MergeViewModel : INotifyPropertyChanged
|
||||
|
||||
private void ExecuteSelectOutputFile()
|
||||
{
|
||||
var suggestFile = "movies_merged.mkv";
|
||||
var suggestFile = "movies.mkv";
|
||||
string? suggestDir = null;
|
||||
try
|
||||
{
|
||||
@ -634,7 +634,7 @@ public sealed class MergeViewModel : INotifyPropertyChanged
|
||||
Number = i + 1,
|
||||
Status = "Готов",
|
||||
};
|
||||
item.SyncAutoPartName($"Часть {i + 1} - {Path.GetFileNameWithoutExtension(fileName)}");
|
||||
item.SyncAutoPartName(BuildDefaultPartName(i));
|
||||
Files.Add(item);
|
||||
}
|
||||
|
||||
@ -664,12 +664,17 @@ public sealed class MergeViewModel : INotifyPropertyChanged
|
||||
: _recentPaths.GetInitialDirectory(RecentPathScenario.Merge);
|
||||
|
||||
var fileName = parents.Count == 1
|
||||
? $"{new DirectoryInfo(parents[0]).Name}_merged.mkv"
|
||||
: "movies_merged.mkv";
|
||||
? $"{new DirectoryInfo(parents[0]).Name}.mkv"
|
||||
: "movies.mkv";
|
||||
|
||||
return Path.Combine(dir, fileName);
|
||||
}
|
||||
|
||||
private static string BuildDefaultPartName(int zeroBasedIndex)
|
||||
{
|
||||
return $"Часть {zeroBasedIndex + 1}";
|
||||
}
|
||||
|
||||
private void AfterFilesChanged()
|
||||
{
|
||||
ProgressPercent = 0;
|
||||
@ -686,8 +691,7 @@ public sealed class MergeViewModel : INotifyPropertyChanged
|
||||
for (var i = 0; i < Files.Count; i++)
|
||||
{
|
||||
Files[i].Number = i + 1;
|
||||
var fn = Files[i].FileName;
|
||||
Files[i].SyncAutoPartName($"Часть {i + 1} - {Path.GetFileNameWithoutExtension(fn)}");
|
||||
Files[i].SyncAutoPartName(BuildDefaultPartName(i));
|
||||
}
|
||||
|
||||
UpdateValidationState();
|
||||
@ -735,7 +739,7 @@ public sealed class MergeViewModel : INotifyPropertyChanged
|
||||
var name = Path.GetFileName(fullOutput);
|
||||
if (string.IsNullOrWhiteSpace(name))
|
||||
{
|
||||
error = "Укажите имя итогового файла (например, …\\movies_merged.mkv).";
|
||||
error = "Укажите имя итогового файла (например, …\\movies.mkv).";
|
||||
}
|
||||
else if (name.IndexOfAny(Path.GetInvalidFileNameChars()) >= 0)
|
||||
{
|
||||
@ -795,12 +799,15 @@ public sealed class MergeViewModel : INotifyPropertyChanged
|
||||
return;
|
||||
}
|
||||
|
||||
var deletedItems = new List<MergeFileItem>();
|
||||
var deleteErrors = new List<string>();
|
||||
foreach (var source in ordered.Select(f => f.FullPath))
|
||||
foreach (var item in ordered)
|
||||
{
|
||||
var source = item.FullPath;
|
||||
try
|
||||
{
|
||||
File.Delete(source);
|
||||
deletedItems.Add(item);
|
||||
_logging.Info($"удален исходник после объединения: {source}", "merge");
|
||||
}
|
||||
catch (Exception ex)
|
||||
@ -810,6 +817,8 @@ public sealed class MergeViewModel : INotifyPropertyChanged
|
||||
}
|
||||
}
|
||||
|
||||
RemoveDeletedSourcesFromTable(deletedItems);
|
||||
|
||||
if (deleteErrors.Count == 0)
|
||||
{
|
||||
return;
|
||||
@ -822,6 +831,27 @@ public sealed class MergeViewModel : INotifyPropertyChanged
|
||||
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)
|
||||
{
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||
|
||||
@ -179,7 +179,7 @@
|
||||
ScrollViewer.HorizontalScrollBarVisibility="Auto"
|
||||
behaviors:ConversionQueueDropTargetBehavior.IsDropTargetEnabled="True"
|
||||
behaviors:DataGridRowDoubleClickCommandBehavior.Command="{Binding OpenFileConversionSettingsCommand}"
|
||||
behaviors:DataGridAutoScrollSelectionBehavior.IsEnabled="True"
|
||||
behaviors:DataGridAutoScrollSelectionBehavior.ScrollIntoViewItem="{Binding QueueItemToReveal}"
|
||||
SelectionChanged="QueueDataGrid_SelectionChanged">
|
||||
<DataGrid.InputBindings>
|
||||
<KeyBinding Key="Delete"
|
||||
|
||||
@ -23,25 +23,16 @@
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<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 Grid.Row="0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<DataGrid x:Name="FilesGrid"
|
||||
Grid.Column="0"
|
||||
<Grid Grid.Column="0"
|
||||
Background="Transparent"
|
||||
behaviors:MergeDropTargetBehavior.IsEnabled="True">
|
||||
<DataGrid x:Name="FilesGrid"
|
||||
ItemsSource="{Binding Files}"
|
||||
SelectedItem="{Binding SelectedItem, Mode=TwoWay}"
|
||||
SelectionMode="Extended"
|
||||
@ -76,7 +67,22 @@
|
||||
<DataGridTextColumn Header="Размер, МБ" Binding="{Binding SizeMb, Mode=OneWay}" IsReadOnly="True" Width="90" />
|
||||
<DataGridTextColumn Header="Имя части" Binding="{Binding PartName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Width="2*" MinWidth="200" />
|
||||
</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">
|
||||
<Button MinWidth="150"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user