fix: 빠른 변환 진행 표시(제목 동적+%) + 영상→이미지 프레임 추출 + GPU 가속
사용자 리포트: mp4 변환 시 %안나오고 멈춤, JPEG 고정 문구, GPU 가속 부재.
- QuickProgressWindow: 제목/타이틀바를 출력 형식으로 동적화('JPEG 고정' 해결), % 텍스트 + indeterminate 표시
- FfmpegProvider: 영상→이미지(jpg/png/webp/bmp) 대표 프레임 추출 직접 엣지 (mp4→jpg가 무거운 mp4→gif→jpg 멀티홉 거치던 문제 해결)
- GPU 가속: ConvertOptions.VideoPreferGpu(기본 on) — NVENC 시도 후 실패 시 CPU 자동 폴백, 설정창 토글로 제어
- 테스트 38개 통과
This commit is contained in:
parent
573d04521d
commit
ef10c139cd
8 changed files with 87 additions and 17 deletions
|
|
@ -94,12 +94,13 @@ public partial class App : Application
|
|||
log.AppendLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff}] Quick start → {outputExtension}, {files.Count} file(s)");
|
||||
foreach (var f in files) log.AppendLine($" src: {f}");
|
||||
|
||||
var progress = new QuickProgressWindow(files.Count);
|
||||
var progress = new QuickProgressWindow(files.Count, outputExtension);
|
||||
progress.Show();
|
||||
|
||||
try
|
||||
{
|
||||
var options = ConvertOptions.Quick();
|
||||
options.VideoPreferGpu = Settings.Get("video.gpu") != "false";
|
||||
var reporter = new Progress<ConvertProgress>(p => progress.Report(p));
|
||||
var results = await Engine.ConvertManyAsync(files, outputExtension, options, reporter);
|
||||
|
||||
|
|
|
|||
|
|
@ -304,6 +304,8 @@ public partial class MainWindow : Wpf.Ui.Controls.FluentWindow
|
|||
var targetLang = AiTargetLangBox?.Text?.Trim();
|
||||
opts.Ai.TargetLanguage = string.IsNullOrEmpty(targetLang) ? null : targetLang;
|
||||
|
||||
opts.VideoPreferGpu = ((App)Application.Current).Settings.Get("video.gpu") != "false";
|
||||
|
||||
return opts;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:ui="http://schemas.lepo.co/wpfui/2022/xaml"
|
||||
Title="JPEG로 빠른 변환"
|
||||
Title="빠른 변환"
|
||||
Width="560" Height="220"
|
||||
ExtendsContentIntoTitleBar="True"
|
||||
WindowBackdropType="Mica"
|
||||
|
|
@ -14,7 +14,7 @@
|
|||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
<ui:TitleBar Grid.Row="0" Title="JPEG로 빠른 변환"/>
|
||||
<ui:TitleBar Grid.Row="0" x:Name="WindowTitleBar" Title="빠른 변환"/>
|
||||
<Grid Grid.Row="1" Margin="32,12,32,24">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
|
|
@ -23,7 +23,15 @@
|
|||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<TextBlock Text="변환 중…" Style="{StaticResource TextSubtitle}"/>
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Text="변환 중…" Style="{StaticResource TextSubtitle}"/>
|
||||
<TextBlock x:Name="PercentText" Grid.Column="1" Text="0%"
|
||||
Style="{StaticResource TextSubtitle}" VerticalAlignment="Center"/>
|
||||
</Grid>
|
||||
<TextBlock x:Name="StatusText" Grid.Row="1" Margin="0,4,0,12"
|
||||
Style="{StaticResource TextCaption}" TextTrimming="CharacterEllipsis"/>
|
||||
<ProgressBar x:Name="OverallProgress" Grid.Row="2" Height="6"
|
||||
|
|
|
|||
|
|
@ -9,10 +9,17 @@ public partial class QuickProgressWindow : FluentWindow
|
|||
private readonly int _total;
|
||||
private string? _firstSuccessOutput;
|
||||
|
||||
public QuickProgressWindow(int total)
|
||||
public QuickProgressWindow(int total, string? outputExtension = null)
|
||||
{
|
||||
_total = total;
|
||||
InitializeComponent();
|
||||
|
||||
var label = string.IsNullOrWhiteSpace(outputExtension)
|
||||
? "빠른 변환"
|
||||
: $"{outputExtension.TrimStart('.').ToUpperInvariant()}(으)로 변환";
|
||||
Title = label;
|
||||
WindowTitleBar.Title = label;
|
||||
|
||||
StatusText.Text = $"0 / {_total}";
|
||||
}
|
||||
|
||||
|
|
@ -20,7 +27,11 @@ public partial class QuickProgressWindow : FluentWindow
|
|||
{
|
||||
if (!CheckAccess()) { Dispatcher.Invoke(() => Report(p)); return; }
|
||||
var overall = _total == 0 ? 0 : (p.Index + p.FileProgress) / _total;
|
||||
OverallProgress.Value = Math.Clamp(overall, 0, 1);
|
||||
var clamped = Math.Clamp(overall, 0, 1);
|
||||
OverallProgress.Value = clamped;
|
||||
// 진행률이 멈춘 듯 보이지 않도록 % 표시 (영상 트랜스코딩처럼 오래 걸려도 단계 진행이 보이게)
|
||||
OverallProgress.IsIndeterminate = clamped <= 0;
|
||||
PercentText.Text = clamped <= 0 ? "처리 중…" : $"{(int)Math.Round(clamped * 100)}%";
|
||||
StatusText.Text = $"{Math.Min(p.Index + 1, _total)} / {_total} — {Path.GetFileName(p.CurrentPath)}";
|
||||
}
|
||||
|
||||
|
|
@ -33,7 +44,9 @@ public partial class QuickProgressWindow : FluentWindow
|
|||
var failed = results.Count(r => r.Status == ConvertStatus.Failed);
|
||||
var outputs = results.Sum(r => r.OutputPaths.Count);
|
||||
|
||||
OverallProgress.IsIndeterminate = false;
|
||||
OverallProgress.Value = 1;
|
||||
PercentText.Text = failed > 0 ? "완료(일부 실패)" : "100%";
|
||||
StatusText.Text = $"성공 {success}개 (출력 {outputs}), 건너뜀 {skipped}, 실패 {failed}";
|
||||
CloseButton.IsEnabled = true;
|
||||
|
||||
|
|
|
|||
|
|
@ -102,7 +102,9 @@
|
|||
<StackPanel>
|
||||
<TextBlock Text="외부 도구" Style="{StaticResource FsBodyStyle}" FontWeight="SemiBold" FontSize="15"/>
|
||||
<TextBlock Text="설치하면 영상/오디오·한글/Word 변환이 자동 활성화됩니다."
|
||||
Style="{StaticResource FsCaptionStyle}" TextWrapping="Wrap" Margin="0,4,0,16"/>
|
||||
Style="{StaticResource FsCaptionStyle}" TextWrapping="Wrap" Margin="0,4,0,12"/>
|
||||
<CheckBox x:Name="GpuToggle" Content="영상 변환 시 GPU 가속(NVENC) 시도 — 없으면 CPU 자동 전환"
|
||||
Foreground="{StaticResource FsTextSecondary}" IsChecked="True" Margin="0,0,0,16"/>
|
||||
|
||||
<!-- FFmpeg -->
|
||||
<Grid Margin="0,0,0,14">
|
||||
|
|
|
|||
|
|
@ -35,6 +35,8 @@ public partial class SettingsWindow : Wpf.Ui.Controls.FluentWindow
|
|||
|
||||
SetKeyStatus(OpenAiDot, OpenAiStatus, _settings.Contains("openai.apikey"), HasEnv("OPENAI_API_KEY"));
|
||||
SetKeyStatus(AnthropicDot, AnthropicStatus, _settings.Contains("anthropic.apikey"), HasEnv("ANTHROPIC_API_KEY"));
|
||||
|
||||
GpuToggle.IsChecked = _settings.Get("video.gpu") != "false"; // 기본 켜짐
|
||||
}
|
||||
|
||||
private static bool HasEnv(string name) => !string.IsNullOrEmpty(Environment.GetEnvironmentVariable(name));
|
||||
|
|
@ -126,6 +128,7 @@ public partial class SettingsWindow : Wpf.Ui.Controls.FluentWindow
|
|||
if (string.IsNullOrEmpty(model)) _settings.Remove("ai.model");
|
||||
else _settings.Set("ai.model", model);
|
||||
|
||||
_settings.Set("video.gpu", GpuToggle.IsChecked == true ? "true" : "false");
|
||||
if (OpenAiKeyBox.Password.Length > 0) _settings.Set("openai.apikey", OpenAiKeyBox.Password);
|
||||
if (AnthropicKeyBox.Password.Length > 0) _settings.Set("anthropic.apikey", AnthropicKeyBox.Password);
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue