Toolbox: Improve danmaku merge

This commit is contained in:
genteure 2021-11-21 00:08:31 +08:00
parent 2151393a77
commit 7ec8b1d405
13 changed files with 282 additions and 48 deletions

View File

@ -27,6 +27,18 @@ namespace BililiveRecorder.ToolBox.Tool.DanmakuMerger
ErrorMessage = "At least 2 input files required" ErrorMessage = "At least 2 input files required"
}; };
if (request.Offsets is not null)
{
if (request.Offsets.Length != inputLength)
{
return new CommandResponse<DanmakuMergerResponse>
{
Status = ResponseStatus.Error,
ErrorMessage = "The number of offsets should match the number of input files."
};
}
}
var files = new FileStream[inputLength]; var files = new FileStream[inputLength];
var readers = new XmlReader?[inputLength]; var readers = new XmlReader?[inputLength];
@ -34,14 +46,16 @@ namespace BililiveRecorder.ToolBox.Tool.DanmakuMerger
XmlWriter? writer = null; XmlWriter? writer = null;
XElement recordInfo; XElement recordInfo;
DateTimeOffset baseTime;
TimeSpan[] timeDiff; TimeSpan[] timeDiff;
try // finally try // finally
{
{
// 读取文件开头并计算时间差
try try
{ {
DateTimeOffset baseTime;
// 打开输入文件 // 打开输入文件
for (var i = 0; i < inputLength; i++) for (var i = 0; i < inputLength; i++)
{ {
@ -50,7 +64,7 @@ namespace BililiveRecorder.ToolBox.Tool.DanmakuMerger
readers[i] = XmlReader.Create(file, null); readers[i] = XmlReader.Create(file, null);
} }
// 计算时间差 // 读取XML文件开头
var startTimes = new (DateTimeOffset time, XElement element)[inputLength]; var startTimes = new (DateTimeOffset time, XElement element)[inputLength];
for (var i = 0; i < inputLength; i++) for (var i = 0; i < inputLength; i++)
{ {
@ -72,11 +86,23 @@ namespace BililiveRecorder.ToolBox.Tool.DanmakuMerger
} }
} }
if (request.Offsets is not null)
{
// 使用传递进来的参数作为时间差
timeDiff = request.Offsets.Select(x => TimeSpan.FromSeconds(x)).ToArray();
var b = startTimes[Array.IndexOf(timeDiff, timeDiff.Min())];
recordInfo = b.element;
baseTime = b.time;
}
else
{
// 使用文件内的开始时间作为时间差
var b = startTimes.OrderBy(x => x.time).First(); var b = startTimes.OrderBy(x => x.time).First();
recordInfo = b.element; recordInfo = b.element;
baseTime = b.time; baseTime = b.time;
timeDiff = startTimes.Select(x => x.time - baseTime).ToArray(); timeDiff = startTimes.Select(x => x.time - baseTime).ToArray();
} }
}
catch (Exception ex) catch (Exception ex)
{ {
return new CommandResponse<DanmakuMergerResponse> return new CommandResponse<DanmakuMergerResponse>

View File

@ -6,6 +6,8 @@ namespace BililiveRecorder.ToolBox.Tool.DanmakuMerger
{ {
public string[] Inputs { get; set; } = Array.Empty<string>(); public string[] Inputs { get; set; } = Array.Empty<string>();
public int[]? Offsets { get; set; } = null;
public string Output { get; set; } = string.Empty; public string Output { get; set; } = string.Empty;
} }
} }

View File

@ -0,0 +1,66 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using System.Xml;
using System.Xml.Linq;
namespace BililiveRecorder.ToolBox.Tool.DanmakuStartTime
{
public class DanmakuStartTimeHandler : ICommandHandler<DanmakuStartTimeRequest, DanmakuStartTimeResponse>
{
public string Name => "Read Danmaku start_time";
public async Task<CommandResponse<DanmakuStartTimeResponse>> Handle(DanmakuStartTimeRequest request, CancellationToken cancellationToken, ProgressCallback? progress)
{
List<DanmakuStartTimeResponse.DanmakuStartTime> result = new();
try
{
progress?.Invoke(0);
var finished = 0;
double total = request.Inputs.Length;
Parallel.ForEach(request.Inputs, input =>
{
try
{
using var file = File.Open(input, FileMode.Open, FileAccess.Read, FileShare.Read);
var r = XmlReader.Create(file, null);
r.ReadStartElement("i");
while (r.Name != "i")
{
if (r.Name == "BililiveRecorderRecordInfo")
{
var el = (XNode.ReadFrom(r) as XElement)!;
var time = (DateTimeOffset)el.Attribute("start_time");
result.Add(new DanmakuStartTimeResponse.DanmakuStartTime { Path = input, StartTime = time });
Interlocked.Increment(ref finished);
progress?.Invoke(finished / total);
break;
}
else
{
r.Skip();
}
}
}
catch (Exception) { }
});
}
catch (Exception ex)
{
return new CommandResponse<DanmakuStartTimeResponse>
{
Status = ResponseStatus.Error,
Exception = ex,
ErrorMessage = ex.Message
};
}
return new CommandResponse<DanmakuStartTimeResponse> { Status = ResponseStatus.OK, Data = new DanmakuStartTimeResponse { StartTimes = result.ToArray() } };
}
}
}

View File

@ -0,0 +1,9 @@
using System;
namespace BililiveRecorder.ToolBox.Tool.DanmakuStartTime
{
public class DanmakuStartTimeRequest : ICommandRequest<DanmakuStartTimeResponse>
{
public string[] Inputs { get; set; } = Array.Empty<string>();
}
}

View File

@ -0,0 +1,30 @@
using System;
using Spectre.Console;
namespace BililiveRecorder.ToolBox.Tool.DanmakuStartTime
{
public class DanmakuStartTimeResponse : IResponseData
{
public DanmakuStartTime[] StartTimes { get; set; } = Array.Empty<DanmakuStartTime>();
public void PrintToConsole()
{
var t = new Table()
.AddColumns("Start Time", "File Path")
.Border(TableBorder.Rounded);
foreach (var item in this.StartTimes)
{
t.AddRow(item.StartTime.ToString().EscapeMarkup(), item.Path.EscapeMarkup());
}
AnsiConsole.Render(t);
}
public class DanmakuStartTime
{
public string Path { get; set; } = string.Empty;
public DateTimeOffset StartTime { get; set; }
}
}
}

View File

@ -4,6 +4,7 @@ using System.CommandLine.Invocation;
using System.Threading.Tasks; using System.Threading.Tasks;
using BililiveRecorder.ToolBox.Tool.Analyze; using BililiveRecorder.ToolBox.Tool.Analyze;
using BililiveRecorder.ToolBox.Tool.DanmakuMerger; using BililiveRecorder.ToolBox.Tool.DanmakuMerger;
using BililiveRecorder.ToolBox.Tool.DanmakuStartTime;
using BililiveRecorder.ToolBox.Tool.Export; using BililiveRecorder.ToolBox.Tool.Export;
using BililiveRecorder.ToolBox.Tool.Fix; using BililiveRecorder.ToolBox.Tool.Fix;
using Newtonsoft.Json; using Newtonsoft.Json;
@ -32,10 +33,16 @@ namespace BililiveRecorder.ToolBox
c.Add(new Argument<string>("output", "example: output.brec.xml.gz")); c.Add(new Argument<string>("output", "example: output.brec.xml.gz"));
}); });
this.RegisterCommand<DanmakuStartTimeHandler, DanmakuStartTimeRequest, DanmakuStartTimeResponse>("danmaku-start-time", null, c =>
{
c.Add(new Argument<string[]>("inputs", "example: 1.xml 2.xml ..."));
});
this.RegisterCommand<DanmakuMergerHandler, DanmakuMergerRequest, DanmakuMergerResponse>("danmaku-merge", null, c => this.RegisterCommand<DanmakuMergerHandler, DanmakuMergerRequest, DanmakuMergerResponse>("danmaku-merge", null, c =>
{ {
c.Add(new Argument<string>("output", "example: output.xml")); c.Add(new Argument<string>("output", "example: output.xml"));
c.Add(new Argument<string[]>("inputs", "example: 1.xml 2.xml ...")); c.Add(new Argument<string[]>("inputs", "example: 1.xml 2.xml ..."));
c.Add(new Option<int[]?>("--offsets", "Use offsets provided instead of calculating from starttime attribute."));
}); });
} }

View File

@ -123,6 +123,7 @@
<Compile Include="Converters\ValueConverterGroup.cs" /> <Compile Include="Converters\ValueConverterGroup.cs" />
<Compile Include="Models\AboutModel.cs" /> <Compile Include="Models\AboutModel.cs" />
<Compile Include="Models\Commands.cs" /> <Compile Include="Models\Commands.cs" />
<Compile Include="Models\DanmakuFileWithOffset.cs" />
<Compile Include="Models\LogModel.cs" /> <Compile Include="Models\LogModel.cs" />
<Compile Include="Models\PollyPolicyModel.cs" /> <Compile Include="Models\PollyPolicyModel.cs" />
<Compile Include="Models\RootModel.cs" /> <Compile Include="Models\RootModel.cs" />

View File

@ -23,6 +23,7 @@
</Grid.RowDefinitions> </Grid.RowDefinitions>
<StackPanel> <StackPanel>
<ContentControl Content="{Binding}" ContentTemplate="{StaticResource RoomDialogHeader}"/> <ContentControl Content="{Binding}" ContentTemplate="{StaticResource RoomDialogHeader}"/>
<TextBlock TextAlignment="Center" Text="Tip: 这里是房间独立设置,是对↙全局设置↙的覆盖"/>
<Separator/> <Separator/>
</StackPanel> </StackPanel>
<ScrollViewer Grid.Row="1"> <ScrollViewer Grid.Row="1">

View File

@ -0,0 +1,37 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.CompilerServices;
#nullable enable
namespace BililiveRecorder.WPF.Models
{
internal class DanmakuFileWithOffset : INotifyPropertyChanged
{
private string path;
private DateTimeOffset startTime;
private int offset;
public string Path { get => this.path; set => this.SetField(ref this.path, value); }
public DateTimeOffset StartTime { get => this.startTime; set => this.SetField(ref this.startTime, value); }
public int Offset { get => this.offset; set => this.SetField(ref this.offset, value); }
public DanmakuFileWithOffset(string path)
{
this.path = path;
}
public event PropertyChangedEventHandler? PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
protected bool SetField<T>(ref T location, T value, [CallerMemberName] string propertyName = "")
{
if (EqualityComparer<T>.Default.Equals(location, value))
return false;
location = value;
this.OnPropertyChanged(propertyName);
return true;
}
public override int GetHashCode() => HashCode.Combine(this.path, this.startTime, this.offset);
}
}

View File

@ -91,6 +91,11 @@
</MenuItem.Icon> </MenuItem.Icon>
</MenuItem> </MenuItem>
</MenuItem> </MenuItem>
<MenuItem Header="{l:Loc RoomListPage_Menu_Links_Sponsor}" Command="{x:Static m:Commands.OpenLink}" CommandParameter="https://rec.danmuji.org/link/sponsor/">
<MenuItem.Icon>
<ui:PathIcon Style="{StaticResource PathIconDataOpenInNew}"/>
</MenuItem.Icon>
</MenuItem>
</Menu> </Menu>
<Separator/> <Separator/>
</StackPanel> </StackPanel>

View File

@ -10,6 +10,7 @@
l:ResxLocalizationProvider.DefaultAssembly="BililiveRecorder.WPF" l:ResxLocalizationProvider.DefaultAssembly="BililiveRecorder.WPF"
l:ResxLocalizationProvider.DefaultDictionary="Strings" l:ResxLocalizationProvider.DefaultDictionary="Strings"
xmlns:c="clr-namespace:BililiveRecorder.WPF.Controls" xmlns:c="clr-namespace:BililiveRecorder.WPF.Controls"
xmlns:m="clr-namespace:BililiveRecorder.WPF.Models"
xmlns:local="clr-namespace:BililiveRecorder.WPF.Pages" xmlns:local="clr-namespace:BililiveRecorder.WPF.Pages"
xmlns:config="clr-namespace:BililiveRecorder.Core.Config.V2;assembly=BililiveRecorder.Core" xmlns:config="clr-namespace:BililiveRecorder.Core.Config.V2;assembly=BililiveRecorder.Core"
xmlns:confiv2="clr-namespace:BililiveRecorder.Core.Config.V2;assembly=BililiveRecorder.Core" xmlns:confiv2="clr-namespace:BililiveRecorder.Core.Config.V2;assembly=BililiveRecorder.Core"
@ -80,14 +81,32 @@
<TextBlock FontSize="13" Text="{l:Loc Settings_FileName_Description_ToolTip}" <TextBlock FontSize="13" Text="{l:Loc Settings_FileName_Description_ToolTip}"
l:ResxLocalizationProvider.DefaultDictionary="Strings"/> l:ResxLocalizationProvider.DefaultDictionary="Strings"/>
</StackPanel.ToolTip> </StackPanel.ToolTip>
<TextBlock Text="{l:Loc Settings_FileName_Description_Text}"/>
<ui:PathIcon Margin="2,0" VerticalAlignment="Center" Height="15" Style="{StaticResource PathIconDataInformationOutline}"/> <ui:PathIcon Margin="2,0" VerticalAlignment="Center" Height="15" Style="{StaticResource PathIconDataInformationOutline}"/>
<TextBlock VerticalAlignment="Center" Text="{l:Loc Settings_FileName_Description_Text}"/>
<Button Margin="5,0,0,0" Command="{x:Static m:Commands.OpenLink}" CommandParameter="https://rec.danmuji.org/docs/basic/settings/#%E5%BD%95%E5%88%B6%E6%96%87%E4%BB%B6%E5%90%8D%E6%A0%BC%E5%BC%8F">
<ui:PathIcon Margin="2,0" VerticalAlignment="Center" Height="15" Style="{StaticResource PathIconDataOpenInNew}"/>
</Button>
</StackPanel> </StackPanel>
<c:SettingWithDefault IsSettingNotUsingDefault="{Binding HasRecordFilenameFormat}" Header="{l:Loc Settings_FileName_Record}"> <c:SettingWithDefault IsSettingNotUsingDefault="{Binding HasRecordFilenameFormat}" Header="{l:Loc Settings_FileName_Record}">
<TextBox Text="{Binding RecordFilenameFormat,Delay=500}" ui:TextBoxHelper.IsDeleteButtonVisible="False"/> <TextBox Text="{Binding RecordFilenameFormat,Delay=500}" ui:TextBoxHelper.IsDeleteButtonVisible="False"/>
</c:SettingWithDefault> </c:SettingWithDefault>
</StackPanel> </StackPanel>
</GroupBox> </GroupBox>
<GroupBox Header="录制画质">
<StackPanel MaxWidth="400" HorizontalAlignment="Left">
<Button Margin="5,0,0,0" Command="{x:Static m:Commands.OpenLink}" CommandParameter="https://rec.danmuji.org/docs/basic/settings/#%E7%9B%B4%E6%92%AD%E7%94%BB%E8%B4%A8">
<StackPanel Orientation="Horizontal">
<ui:PathIcon Width="15" Height="15" Style="{StaticResource PathIconDataInformationOutline}"/>
<ui:PathIcon Margin="5,0,0,0" Width="15" Height="15" Style="{StaticResource PathIconDataOpenInNew}"/>
</StackPanel>
</Button>
<TextBlock Text="逗号分割的录制画质ID"/>
<c:SettingWithDefault IsSettingNotUsingDefault="{Binding HasRecordingQuality}">
<TextBox Text="{Binding RecordingQuality,UpdateSourceTrigger=PropertyChanged,Delay=1000}"
ui:TextBoxHelper.IsDeleteButtonVisible="False" Width="220" HorizontalAlignment="Left"/>
</c:SettingWithDefault>
</StackPanel>
</GroupBox>
<GroupBox Header="{l:Loc Settings_Webhook_Title}"> <GroupBox Header="{l:Loc Settings_Webhook_Title}">
<StackPanel MaxWidth="400" HorizontalAlignment="Left"> <StackPanel MaxWidth="400" HorizontalAlignment="Left">
<TextBlock Text="{l:Loc Settings_Webhook_Address}" Margin="0,0,0,10"/> <TextBlock Text="{l:Loc Settings_Webhook_Address}" Margin="0,0,0,10"/>
@ -99,15 +118,6 @@
Text="{Binding WebHookUrls,UpdateSourceTrigger=PropertyChanged,Delay=1000}" ui:TextBoxHelper.IsDeleteButtonVisible="False"/> Text="{Binding WebHookUrls,UpdateSourceTrigger=PropertyChanged,Delay=1000}" ui:TextBoxHelper.IsDeleteButtonVisible="False"/>
</StackPanel> </StackPanel>
</GroupBox> </GroupBox>
<GroupBox Header="录制画质">
<StackPanel MaxWidth="400" HorizontalAlignment="Left">
<TextBlock Text="逗号分割的录制画质ID"/>
<c:SettingWithDefault IsSettingNotUsingDefault="{Binding HasRecordingQuality}">
<TextBox Text="{Binding RecordingQuality,UpdateSourceTrigger=PropertyChanged,Delay=1000}"
ui:TextBoxHelper.IsDeleteButtonVisible="False" Width="220" HorizontalAlignment="Left"/>
</c:SettingWithDefault>
</StackPanel>
</GroupBox>
</ui:SimpleStackPanel> </ui:SimpleStackPanel>
</ScrollViewer> </ScrollViewer>
</ui:Page> </ui:Page>

View File

@ -28,19 +28,31 @@
<Button Margin="0,0,5,0" Content="{l:Loc Toolbox_Merge_Button_AddFile}" Click="AddFile_Click"/> <Button Margin="0,0,5,0" Content="{l:Loc Toolbox_Merge_Button_AddFile}" Click="AddFile_Click"/>
<Button Content="{l:Loc Toolbox_Merge_Button_Merge}" Click="Merge_Click"/> <Button Content="{l:Loc Toolbox_Merge_Button_Merge}" Click="Merge_Click"/>
</StackPanel> </StackPanel>
<ListBox Grid.Row="3" Margin="5" x:Name="listBox" AllowDrop="True" Drop="listBox_Drop"> <ListView Grid.Row="3" Margin="5" x:Name="listView" AllowDrop="True" Drop="DragDrop">
<ListBox.ItemTemplate> <ListView.View>
<GridView AllowsColumnReorder="False">
<GridViewColumn>
<GridViewColumn.CellTemplate>
<DataTemplate> <DataTemplate>
<Grid Margin="0,0"> <Button Padding="2" VerticalAlignment="Center" HorizontalAlignment="Center"
<Grid.ColumnDefinitions> Content="{l:Loc Toolbox_Merge_Button_Remove}" Click="RemoveFile_Click"/>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Button Margin="0,0,5,0" Padding="2" Content="{l:Loc Toolbox_Merge_Button_Remove}" Click="RemoveFile_Click"/>
<TextBlock Grid.Column="1" Text="{Binding}" ToolTip="{Binding}" TextWrapping="Wrap" VerticalAlignment="Center"/>
</Grid>
</DataTemplate> </DataTemplate>
</ListBox.ItemTemplate> </GridViewColumn.CellTemplate>
</ListBox> </GridViewColumn>
<GridViewColumn Header="偏移量(秒)" Width="120">
<GridViewColumn.CellTemplate>
<DataTemplate><!--
<ui:NumberBox Minimum="0" SmallChange="1" LargeChange="10" Text="{Binding Offset,UpdateSourceTrigger=PropertyChanged}"
SpinButtonPlacementMode="Inline" ValidationMode="InvalidInputOverwritten"/>
-->
<TextBox Width="100" Text="{Binding Offset,UpdateSourceTrigger=PropertyChanged,Delay=300}"
ui:TextBoxHelper.IsDeleteButtonVisible="False"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="文件路径" DisplayMemberBinding="{Binding Path}"/>
</GridView>
</ListView.View>
</ListView>
</Grid> </Grid>
</ui:Page> </ui:Page>

View File

@ -6,9 +6,12 @@ using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Windows; using System.Windows;
using System.Windows.Controls; using System.Windows.Controls;
using System.Windows.Threading;
using BililiveRecorder.ToolBox; using BililiveRecorder.ToolBox;
using BililiveRecorder.ToolBox.Tool.DanmakuMerger; using BililiveRecorder.ToolBox.Tool.DanmakuMerger;
using BililiveRecorder.ToolBox.Tool.DanmakuStartTime;
using BililiveRecorder.WPF.Controls; using BililiveRecorder.WPF.Controls;
using BililiveRecorder.WPF.Models;
using Microsoft.WindowsAPICodePack.Dialogs; using Microsoft.WindowsAPICodePack.Dialogs;
using Serilog; using Serilog;
using WPFLocalizeExtension.Extensions; using WPFLocalizeExtension.Extensions;
@ -24,43 +27,38 @@ namespace BililiveRecorder.WPF.Pages
private static readonly ILogger logger = Log.ForContext<ToolboxDanmakuMergerPage>(); private static readonly ILogger logger = Log.ForContext<ToolboxDanmakuMergerPage>();
private static readonly string DesktopPath = Environment.GetFolderPath(Environment.SpecialFolder.Desktop); private static readonly string DesktopPath = Environment.GetFolderPath(Environment.SpecialFolder.Desktop);
private readonly ObservableCollection<string> Files = new ObservableCollection<string>(); private readonly ObservableCollection<DanmakuFileWithOffset> Files = new();
public ToolboxDanmakuMergerPage() public ToolboxDanmakuMergerPage()
{ {
this.InitializeComponent(); this.InitializeComponent();
this.listBox.ItemsSource = this.Files; this.listView.ItemsSource = this.Files;
} }
private void RemoveFile_Click(object sender, RoutedEventArgs e) private void RemoveFile_Click(object sender, RoutedEventArgs e)
{ {
var b = (Button)sender; var b = (Button)sender;
var f = (string)b.DataContext; var f = (DanmakuFileWithOffset)b.DataContext;
this.Files.Remove(f); this.Files.Remove(f);
this.CalculateOffsets();
} }
private void listBox_Drop(object sender, DragEventArgs e) private async void DragDrop(object sender, DragEventArgs e)
{ {
try try
{ {
if (e.Data.GetDataPresent(DataFormats.FileDrop)) if (e.Data.GetDataPresent(DataFormats.FileDrop))
{ {
var files = (string[])e.Data.GetData(DataFormats.FileDrop); var files = (string[])e.Data.GetData(DataFormats.FileDrop);
for (var i = 0; i < files.Length; i++) await this.AddFilesAsync(files.Where(x => x.EndsWith(".xml", StringComparison.InvariantCultureIgnoreCase)).ToArray());
{
var file = files[i];
if (file.EndsWith(".xml", StringComparison.InvariantCultureIgnoreCase))
{
this.Files.Add(file);
}
}
} }
} }
catch (Exception) catch (Exception)
{ } { }
} }
private void AddFile_Click(object sender, RoutedEventArgs e) private async void AddFile_Click(object sender, RoutedEventArgs e)
{ {
var d = new CommonOpenFileDialog var d = new CommonOpenFileDialog
{ {
@ -79,10 +77,39 @@ namespace BililiveRecorder.WPF.Pages
if (d.ShowDialog() != CommonFileDialogResult.Ok) if (d.ShowDialog() != CommonFileDialogResult.Ok)
return; return;
foreach (var file in d.FileNames) await this.AddFilesAsync(d.FileNames.ToArray());
{
this.Files.Add(file);
} }
private async Task AddFilesAsync(string[] paths)
{
var req = new DanmakuStartTimeRequest { Inputs = paths };
var handler = new DanmakuStartTimeHandler();
var resp = await handler.Handle(req, default, default).ConfigureAwait(true);
if (resp.Status != ResponseStatus.OK || resp.Data is null)
return;
var toBeAdded = resp.Data.StartTimes.Select(x => new DanmakuFileWithOffset(x.Path) { StartTime = x.StartTime });
foreach (var file in toBeAdded)
this.Files.Add(file);
_ = this.Dispatcher.BeginInvoke(DispatcherPriority.Normal, (Action)this.CalculateOffsets);
}
private void CalculateOffsets()
{
if (this.Files.Count == 0)
return;
var minTime = this.Files.Min(x => x.StartTime);
foreach (var item in this.Files)
{
item.Offset = (int)(item.StartTime - minTime).TotalSeconds;
}
this.listView.DataContext = null;
this.listView.DataContext = this.Files;
} }
private async void Merge_Click(object sender, RoutedEventArgs e) private async void Merge_Click(object sender, RoutedEventArgs e)
@ -124,7 +151,7 @@ namespace BililiveRecorder.WPF.Pages
EnsureValidNames = true, EnsureValidNames = true,
NavigateToShortcut = true, NavigateToShortcut = true,
OverwritePrompt = false, OverwritePrompt = false,
InitialDirectory = Path.GetDirectoryName(inputPaths[0]), InitialDirectory = Path.GetDirectoryName(inputPaths[0].Path),
}; };
fileDialog.Filters.Add(new CommonFileDialogFilter(LocExtension.GetLocalizedValue<string>("BililiveRecorder.WPF:Strings:Toolbox_Merge_XmlDanmakuFiles"), "*.xml")); fileDialog.Filters.Add(new CommonFileDialogFilter(LocExtension.GetLocalizedValue<string>("BililiveRecorder.WPF:Strings:Toolbox_Merge_XmlDanmakuFiles"), "*.xml"));
@ -137,7 +164,8 @@ namespace BililiveRecorder.WPF.Pages
var req = new DanmakuMergerRequest var req = new DanmakuMergerRequest
{ {
Inputs = inputPaths, Inputs = inputPaths.Select(x => x.Path).ToArray(),
Offsets = inputPaths.Select(x => x.Offset).ToArray(),
Output = outputPath, Output = outputPath,
}; };