1
0
Fork 0

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:
Yun Chan 2026-06-01 21:28:55 +09:00
parent 573d04521d
commit ef10c139cd
8 changed files with 87 additions and 17 deletions

View file

@ -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;
}

View file

@ -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"

View file

@ -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;

View file

@ -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">

View file

@ -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);