1
0
Fork 0

feat(options): implement granular fold-out encoding options with industry standards

- Adopt Microsoft Fluent 2 Progressive Disclosure pattern with AdvancedOptionsExpander
- Add ISO 32000 / Ghostscript PDF optimization panel (Light/Strong/Max, 150/200/300 DPI)
- Add W3C / MozJPEG Image panel with Lossless WebP/PNG and Progressive toggles
- Add FFmpeg video/audio granular controls (codecs, CRF/ABR rate control, presets, Loudnorm)
- Mirror advanced options in QuickOptionsWindow context dialog
- Add DesignAudit AST and visual tests for foldout structure (249 tests passing in 2s)
This commit is contained in:
Yun Chan 2026-09-05 07:29:46 +09:00
parent 7cb75fd3aa
commit 455c8b1324
4 changed files with 234 additions and 154 deletions

View file

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<Package xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10" xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10" xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities" xmlns:desktop="http://schemas.microsoft.com/appx/manifest/desktop/windows10" xmlns:desktop4="http://schemas.microsoft.com/appx/manifest/desktop/windows10/4" xmlns:desktop5="http://schemas.microsoft.com/appx/manifest/desktop/windows10/5" xmlns:com="http://schemas.microsoft.com/appx/manifest/com/windows10" IgnorableNamespaces="uap rescap desktop desktop4 desktop5 com"> <Package xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10" xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10" xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities" xmlns:desktop="http://schemas.microsoft.com/appx/manifest/desktop/windows10" xmlns:desktop4="http://schemas.microsoft.com/appx/manifest/desktop/windows10/4" xmlns:desktop5="http://schemas.microsoft.com/appx/manifest/desktop/windows10/5" xmlns:com="http://schemas.microsoft.com/appx/manifest/com/windows10" IgnorableNamespaces="uap rescap desktop desktop4 desktop5 com">
<Identity Name="Everything2Everything.YunChan" Publisher="CN=Everything2EverythingDev" Version="1.0.14.0" ProcessorArchitecture="x64" /> <Identity Name="Everything2Everything.YunChan" Publisher="CN=Everything2EverythingDev" Version="1.0.16.0" ProcessorArchitecture="x64" />
<Properties> <Properties>
<DisplayName>Everything2Everything</DisplayName> <DisplayName>Everything2Everything</DisplayName>
<PublisherDisplayName>YunChan</PublisherDisplayName> <PublisherDisplayName>YunChan</PublisherDisplayName>

View file

@ -68,6 +68,7 @@
BorderThickness="1" CornerRadius="6" Padding="2"> BorderThickness="1" CornerRadius="6" Padding="2">
<StackPanel Orientation="Horizontal" VerticalAlignment="Center"> <StackPanel Orientation="Horizontal" VerticalAlignment="Center">
<ToggleButton x:Name="TabActiveBtn" <ToggleButton x:Name="TabActiveBtn"
IsChecked="True"
Style="{StaticResource FsTabPillStyle}" Style="{StaticResource FsTabPillStyle}"
Command="{Binding TabCommand, RelativeSource={RelativeSource AncestorType=Window}}" Command="{Binding TabCommand, RelativeSource={RelativeSource AncestorType=Window}}"
CommandParameter="Active"> CommandParameter="Active">
@ -82,7 +83,6 @@
</StackPanel> </StackPanel>
</ToggleButton> </ToggleButton>
<ToggleButton x:Name="TabPastBtn" Margin="2,0,0,0" <ToggleButton x:Name="TabPastBtn" Margin="2,0,0,0"
IsChecked="True"
Style="{StaticResource FsTabPillStyle}" Style="{StaticResource FsTabPillStyle}"
Command="{Binding TabCommand, RelativeSource={RelativeSource AncestorType=Window}}" Command="{Binding TabCommand, RelativeSource={RelativeSource AncestorType=Window}}"
CommandParameter="Past"> CommandParameter="Past">
@ -362,153 +362,192 @@
</StackPanel> </StackPanel>
</Border> </Border>
<!-- Card 5: 영상·오디오 인코딩 (출력이 영상/오디오일 때 동적 노출) --> <!-- Card 5: 상세 인코딩 설정 (폴드아웃 Expander) -->
<Border x:Name="MediaPanel" Style="{StaticResource FsCardStyle}" Margin="0,0,0,16" Visibility="Collapsed"> <Border Style="{StaticResource FsCardStyle}" Margin="0,0,0,16">
<StackPanel> <Expander x:Name="AdvancedOptionsExpander"
<TextBlock Text="영상·오디오 인코딩" Style="{StaticResource FsLabelStyle}"/> IsExpanded="{Binding Options.IsAdvancedExpanded, RelativeSource={RelativeSource AncestorType=Window}, Mode=TwoWay}">
<Expander.Header>
<StackPanel Orientation="Horizontal" VerticalAlignment="Center">
<ui:SymbolIcon Symbol="Options24" FontSize="14" Foreground="{StaticResource FsAccentCyan}" VerticalAlignment="Center" Margin="0,0,8,0"/>
<TextBlock Text="상세 인코딩 설정" Style="{StaticResource FsLabelStyle}" VerticalAlignment="Center"/>
</StackPanel>
</Expander.Header>
<StackPanel Margin="0,12,0,0">
<!-- 영상 (영상 컨테이너 출력 시) --> <!-- A. PDF 상세 옵션 패널 (ISO 32000 / Ghostscript 규격) -->
<StackPanel x:Name="VideoSubPanel" Margin="0,8,0,0"> <StackPanel x:Name="AdvancedPdfPanel" Visibility="Collapsed">
<TextBlock Text="비디오 코덱" Style="{StaticResource FsCaptionStyle}" Margin="0,0,0,4"/> <TextBlock Text="PDF 최적화 및 렌더링" Style="{StaticResource FsLabelStyle}" Margin="0,0,0,8"/>
<ComboBox SelectedIndex="{Binding Options.VideoCodecIndex, RelativeSource={RelativeSource AncestorType=Window}, Mode=TwoWay}"> <TextBlock Text="PDF 압축 레벨" Style="{StaticResource FsCaptionStyle}" Margin="0,0,0,4"/>
<ComboBoxItem Content="자동 (H.264 / 고해상도 H.265)"/> <ComboBox x:Name="PdfCompressLevelCombo" SelectedIndex="{Binding Options.PdfCompressLevelIndex, RelativeSource={RelativeSource AncestorType=Window}, Mode=TwoWay}">
<ComboBoxItem Content="H.264 (최대 호환)"/> <ComboBoxItem Content="Light (무손실 최적화 · 화질 유지 100%)"/>
<ComboBoxItem Content="H.265 / HEVC (고압축)"/> <ComboBoxItem Content="Strong (웹/이메일 균형 · 150 DPI 리샘플링)"/>
<ComboBoxItem Content="VP9"/> <ComboBoxItem Content="Max (최대 압축 · 흑백/고압축)"/>
<ComboBoxItem Content="AV1 (최고 압축)"/> </ComboBox>
<ComboBoxItem Content="원본 복사 (재인코딩 없음)"/>
</ComboBox>
<TextBlock Text="레이트 컨트롤" Style="{StaticResource FsCaptionStyle}" Margin="0,10,0,4"/> <TextBlock Text="PDF 렌더링 해상도 (DPI)" Style="{StaticResource FsCaptionStyle}" Margin="0,10,0,4"/>
<ComboBox x:Name="RateControlCombo" SelectedIndex="{Binding Options.RateControlIndex, RelativeSource={RelativeSource AncestorType=Window}, Mode=TwoWay}"> <ComboBox x:Name="PdfDpiCombo" SelectedIndex="{Binding Options.PdfDpiIndex, RelativeSource={RelativeSource AncestorType=Window}, Mode=TwoWay}">
<ComboBoxItem Content="CRF (품질 기준 · 권장)"/> <ComboBoxItem Content="150 DPI (화면 / 웹 최적화)"/>
<ComboBoxItem Content="평균 비트레이트 (ABR)"/> <ComboBoxItem Content="200 DPI (표준 오피스 밸런스)"/>
<ComboBoxItem Content="제약 CRF (품질+상한)"/> <ComboBoxItem Content="300 DPI (고해상도 인쇄 및 OCR용)"/>
<ComboBoxItem Content="고정 비트레이트 (CBR)"/> </ComboBox>
<ComboBoxItem Content="2패스 (최고 효율)"/> </StackPanel>
</ComboBox>
<!-- CRF 슬라이더 (CRF / 제약CRF 모드) --> <!-- B. 이미지 상세 옵션 패널 (W3C / MozJPEG 규격) -->
<StackPanel Margin="0,10,0,0"> <StackPanel x:Name="AdvancedImagePanel" Visibility="Collapsed">
<StackPanel.Style> <TextBlock Text="이미지 고급 인코딩" Style="{StaticResource FsLabelStyle}" Margin="0,0,0,8"/>
<Style TargetType="StackPanel"> <CheckBox x:Name="ImageLosslessCheck" Margin="0,0,0,6" Content="무손실(Lossless) 보존 (WebP/PNG)"
<Setter Property="Visibility" Value="Visible"/> IsChecked="{Binding Options.ImageLossless, RelativeSource={RelativeSource AncestorType=Window}, Mode=TwoWay}"/>
<Style.Triggers> <CheckBox x:Name="ImageProgressiveCheck" Margin="0,0,0,6" Content="프로그레시브/인터레이스 웹 로딩"
<DataTrigger Binding="{Binding SelectedIndex, ElementName=RateControlCombo}" Value="1"><Setter Property="Visibility" Value="Collapsed"/></DataTrigger> IsChecked="{Binding Options.Progressive, RelativeSource={RelativeSource AncestorType=Window}, Mode=TwoWay}"/>
<DataTrigger Binding="{Binding SelectedIndex, ElementName=RateControlCombo}" Value="3"><Setter Property="Visibility" Value="Collapsed"/></DataTrigger> <CheckBox x:Name="ImageStripMetadataCheck" Margin="0,0,0,0" Content="개인정보(EXIF/GPS) 제거"
<DataTrigger Binding="{Binding SelectedIndex, ElementName=RateControlCombo}" Value="4"><Setter Property="Visibility" Value="Collapsed"/></DataTrigger> IsChecked="{Binding Options.StripMetadata, RelativeSource={RelativeSource AncestorType=Window}, Mode=TwoWay}"/>
</Style.Triggers> </StackPanel>
</Style>
</StackPanel.Style> <!-- C. 영상 상세 옵션 패널 (FFmpeg 규격) -->
<Grid> <StackPanel x:Name="AdvancedVideoPanel" Visibility="Collapsed">
<Grid.ColumnDefinitions><ColumnDefinition Width="*"/><ColumnDefinition Width="Auto"/></Grid.ColumnDefinitions> <TextBlock Text="영상 인코딩 (FFmpeg)" Style="{StaticResource FsLabelStyle}" Margin="0,0,0,8"/>
<TextBlock Text="품질 (CRF) — 낮을수록 고화질" Style="{StaticResource FsCaptionStyle}"/> <TextBlock Text="비디오 코덱" Style="{StaticResource FsCaptionStyle}" Margin="0,0,0,4"/>
<TextBlock Grid.Column="1" FontFamily="{StaticResource FsFontMono}" FontSize="12" Foreground="{StaticResource FsAccentBlue}" <ComboBox SelectedIndex="{Binding Options.VideoCodecIndex, RelativeSource={RelativeSource AncestorType=Window}, Mode=TwoWay}">
Text="{Binding Options.Crf, RelativeSource={RelativeSource AncestorType=Window}}"/> <ComboBoxItem Content="자동 (H.264 / 고해상도 H.265)"/>
<ComboBoxItem Content="H.264 (최대 호환)"/>
<ComboBoxItem Content="H.265 / HEVC (고압축)"/>
<ComboBoxItem Content="VP9"/>
<ComboBoxItem Content="AV1 (최고 압축)"/>
<ComboBoxItem Content="원본 복사 (재인코딩 없음)"/>
</ComboBox>
<TextBlock Text="레이트 컨트롤" Style="{StaticResource FsCaptionStyle}" Margin="0,10,0,4"/>
<ComboBox x:Name="RateControlCombo" SelectedIndex="{Binding Options.RateControlIndex, RelativeSource={RelativeSource AncestorType=Window}, Mode=TwoWay}">
<ComboBoxItem Content="CRF (품질 기준 · 권장)"/>
<ComboBoxItem Content="평균 비트레이트 (ABR)"/>
<ComboBoxItem Content="제약 CRF (품질+상한)"/>
<ComboBoxItem Content="고정 비트레이트 (CBR)"/>
<ComboBoxItem Content="2패스 (최고 효율)"/>
</ComboBox>
<!-- CRF 슬라이더 (CRF / 제약CRF 모드) -->
<StackPanel Margin="0,10,0,0">
<StackPanel.Style>
<Style TargetType="StackPanel">
<Setter Property="Visibility" Value="Visible"/>
<Style.Triggers>
<DataTrigger Binding="{Binding SelectedIndex, ElementName=RateControlCombo}" Value="1"><Setter Property="Visibility" Value="Collapsed"/></DataTrigger>
<DataTrigger Binding="{Binding SelectedIndex, ElementName=RateControlCombo}" Value="3"><Setter Property="Visibility" Value="Collapsed"/></DataTrigger>
<DataTrigger Binding="{Binding SelectedIndex, ElementName=RateControlCombo}" Value="4"><Setter Property="Visibility" Value="Collapsed"/></DataTrigger>
</Style.Triggers>
</Style>
</StackPanel.Style>
<Grid>
<Grid.ColumnDefinitions><ColumnDefinition Width="*"/><ColumnDefinition Width="Auto"/></Grid.ColumnDefinitions>
<TextBlock Text="품질 (CRF) — 낮을수록 고화질" Style="{StaticResource FsCaptionStyle}"/>
<TextBlock Grid.Column="1" FontFamily="{StaticResource FsFontMono}" FontSize="12" Foreground="{StaticResource FsAccentBlue}"
Text="{Binding Options.Crf, RelativeSource={RelativeSource AncestorType=Window}}"/>
</Grid>
<Slider Minimum="0" Maximum="51" Margin="0,4,0,0"
Value="{Binding Options.Crf, RelativeSource={RelativeSource AncestorType=Window}, Mode=TwoWay}"/>
</StackPanel>
<!-- 비트레이트 입력 (ABR/제약CRF/CBR/2패스 모드) -->
<StackPanel Margin="0,10,0,0">
<StackPanel.Style>
<Style TargetType="StackPanel">
<Setter Property="Visibility" Value="Collapsed"/>
<Style.Triggers>
<DataTrigger Binding="{Binding SelectedIndex, ElementName=RateControlCombo}" Value="1"><Setter Property="Visibility" Value="Visible"/></DataTrigger>
<DataTrigger Binding="{Binding SelectedIndex, ElementName=RateControlCombo}" Value="2"><Setter Property="Visibility" Value="Visible"/></DataTrigger>
<DataTrigger Binding="{Binding SelectedIndex, ElementName=RateControlCombo}" Value="3"><Setter Property="Visibility" Value="Visible"/></DataTrigger>
<DataTrigger Binding="{Binding SelectedIndex, ElementName=RateControlCombo}" Value="4"><Setter Property="Visibility" Value="Visible"/></DataTrigger>
</Style.Triggers>
</Style>
</StackPanel.Style>
<TextBlock Text="비디오 비트레이트 (kbps)" Style="{StaticResource FsCaptionStyle}" Margin="0,0,0,4"/>
<TextBox Style="{StaticResource FsPathInputStyle}"
Text="{Binding Options.VideoBitrateKbps, RelativeSource={RelativeSource AncestorType=Window}, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
</StackPanel>
<TextBlock Text="인코딩 프리셋 (느릴수록 고압축)" Style="{StaticResource FsCaptionStyle}" Margin="0,10,0,4"/>
<ComboBox SelectedIndex="{Binding Options.PresetIndex, RelativeSource={RelativeSource AncestorType=Window}, Mode=TwoWay}">
<ComboBoxItem Content="UltraFast"/><ComboBoxItem Content="SuperFast"/><ComboBoxItem Content="VeryFast"/>
<ComboBoxItem Content="Faster"/><ComboBoxItem Content="Fast"/><ComboBoxItem Content="Medium"/>
<ComboBoxItem Content="Slow"/><ComboBoxItem Content="Slower"/><ComboBoxItem Content="VerySlow"/>
</ComboBox>
<Grid Margin="0,10,0,0">
<Grid.ColumnDefinitions><ColumnDefinition Width="*"/><ColumnDefinition Width="10"/><ColumnDefinition Width="*"/></Grid.ColumnDefinitions>
<StackPanel Grid.Column="0">
<TextBlock Text="해상도" Style="{StaticResource FsCaptionStyle}" Margin="0,0,0,4"/>
<ComboBox SelectedIndex="{Binding Options.ResolutionIndex, RelativeSource={RelativeSource AncestorType=Window}, Mode=TwoWay}">
<ComboBoxItem Content="원본"/><ComboBoxItem Content="2160p (4K)"/><ComboBoxItem Content="1440p"/>
<ComboBoxItem Content="1080p"/><ComboBoxItem Content="720p"/><ComboBoxItem Content="480p"/>
</ComboBox>
</StackPanel>
<StackPanel Grid.Column="2">
<TextBlock Text="프레임레이트" Style="{StaticResource FsCaptionStyle}" Margin="0,0,0,4"/>
<ComboBox SelectedIndex="{Binding Options.FpsIndex, RelativeSource={RelativeSource AncestorType=Window}, Mode=TwoWay}">
<ComboBoxItem Content="원본"/><ComboBoxItem Content="24"/><ComboBoxItem Content="30"/><ComboBoxItem Content="60"/>
</ComboBox>
</StackPanel>
</Grid> </Grid>
<Slider Minimum="0" Maximum="51" Margin="0,4,0,0"
Value="{Binding Options.Crf, RelativeSource={RelativeSource AncestorType=Window}, Mode=TwoWay}"/> <CheckBox Margin="0,12,0,0" Content="웹 스트리밍 최적화 (Faststart)"
IsChecked="{Binding Options.FastStart, RelativeSource={RelativeSource AncestorType=Window}, Mode=TwoWay}"/>
<CheckBox Margin="0,6,0,0" Content="GPU 하드웨어 가속 (NVENC 등)"
IsChecked="{Binding Options.VideoPreferGpu, RelativeSource={RelativeSource AncestorType=Window}, Mode=TwoWay}"/>
<Expander Header="고급" Margin="0,10,0,0" Foreground="{StaticResource FsTextSecondary}">
<StackPanel Margin="0,8,0,0">
<TextBlock Text="회전 / 뒤집기" Style="{StaticResource FsCaptionStyle}" Margin="0,0,0,4"/>
<ComboBox SelectedIndex="{Binding Options.RotateIndex, RelativeSource={RelativeSource AncestorType=Window}, Mode=TwoWay}">
<ComboBoxItem Content="없음"/><ComboBoxItem Content="시계 90°"/><ComboBoxItem Content="반시계 90°"/>
<ComboBoxItem Content="180°"/><ComboBoxItem Content="좌우 반전"/><ComboBoxItem Content="상하 반전"/>
</ComboBox>
<TextBlock Text="디인터레이스" Style="{StaticResource FsCaptionStyle}" Margin="0,10,0,4"/>
<ComboBox SelectedIndex="{Binding Options.DeinterlaceIndex, RelativeSource={RelativeSource AncestorType=Window}, Mode=TwoWay}">
<ComboBoxItem Content="끔"/><ComboBoxItem Content="Yadif"/><ComboBoxItem Content="Bwdif (고품질)"/>
</ComboBox>
<CheckBox Margin="0,10,0,0" Content="적응형 양자화 (GPU 화질↑)"
IsChecked="{Binding Options.SpatialAq, RelativeSource={RelativeSource AncestorType=Window}, Mode=TwoWay}"/>
</StackPanel>
</Expander>
</StackPanel> </StackPanel>
<!-- 비트레이트 입력 (ABR/제약CRF/CBR/2패스 모드) --> <!-- D. 오디오 상세 옵션 패널 (ITU-R / EBU R128 규격) -->
<StackPanel Margin="0,10,0,0"> <StackPanel x:Name="AdvancedAudioPanel" Visibility="Collapsed">
<StackPanel.Style> <TextBlock Text="오디오 인코딩" Style="{StaticResource FsLabelStyle}" Margin="0,8,0,8"/>
<Style TargetType="StackPanel"> <TextBlock Text="오디오 코덱" Style="{StaticResource FsCaptionStyle}" Margin="0,0,0,4"/>
<Setter Property="Visibility" Value="Collapsed"/> <ComboBox SelectedIndex="{Binding Options.AudioCodecIndex, RelativeSource={RelativeSource AncestorType=Window}, Mode=TwoWay}">
<Style.Triggers> <ComboBoxItem Content="자동"/><ComboBoxItem Content="AAC"/><ComboBoxItem Content="MP3"/>
<DataTrigger Binding="{Binding SelectedIndex, ElementName=RateControlCombo}" Value="1"><Setter Property="Visibility" Value="Visible"/></DataTrigger> <ComboBoxItem Content="Opus"/><ComboBoxItem Content="Vorbis"/><ComboBoxItem Content="FLAC (무손실)"/>
<DataTrigger Binding="{Binding SelectedIndex, ElementName=RateControlCombo}" Value="2"><Setter Property="Visibility" Value="Visible"/></DataTrigger> <ComboBoxItem Content="PCM (WAV)"/><ComboBoxItem Content="원본 복사"/>
<DataTrigger Binding="{Binding SelectedIndex, ElementName=RateControlCombo}" Value="3"><Setter Property="Visibility" Value="Visible"/></DataTrigger> </ComboBox>
<DataTrigger Binding="{Binding SelectedIndex, ElementName=RateControlCombo}" Value="4"><Setter Property="Visibility" Value="Visible"/></DataTrigger> <TextBlock Text="오디오 비트레이트" Style="{StaticResource FsCaptionStyle}" Margin="0,10,0,4"/>
</Style.Triggers> <ComboBox SelectedIndex="{Binding Options.AudioBitrateIndex, RelativeSource={RelativeSource AncestorType=Window}, Mode=TwoWay}">
</Style> <ComboBoxItem Content="96 kbps"/><ComboBoxItem Content="128 kbps"/><ComboBoxItem Content="192 kbps"/>
</StackPanel.Style> <ComboBoxItem Content="256 kbps"/><ComboBoxItem Content="320 kbps"/>
<TextBlock Text="비디오 비트레이트 (kbps)" Style="{StaticResource FsCaptionStyle}" Margin="0,0,0,4"/> </ComboBox>
<TextBox Style="{StaticResource FsPathInputStyle}" <CheckBox Margin="0,8,0,0" Content="가변 비트레이트 (VBR)"
Text="{Binding Options.VideoBitrateKbps, RelativeSource={RelativeSource AncestorType=Window}, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/> IsChecked="{Binding Options.AudioVbr, RelativeSource={RelativeSource AncestorType=Window}, Mode=TwoWay}"/>
<Grid Margin="0,10,0,0">
<Grid.ColumnDefinitions><ColumnDefinition Width="*"/><ColumnDefinition Width="10"/><ColumnDefinition Width="*"/></Grid.ColumnDefinitions>
<StackPanel Grid.Column="0">
<TextBlock Text="샘플레이트" Style="{StaticResource FsCaptionStyle}" Margin="0,0,0,4"/>
<ComboBox SelectedIndex="{Binding Options.SampleRateIndex, RelativeSource={RelativeSource AncestorType=Window}, Mode=TwoWay}">
<ComboBoxItem Content="원본"/><ComboBoxItem Content="44.1 kHz"/><ComboBoxItem Content="48 kHz"/>
</ComboBox>
</StackPanel>
<StackPanel Grid.Column="2">
<TextBlock Text="채널" Style="{StaticResource FsCaptionStyle}" Margin="0,0,0,4"/>
<ComboBox SelectedIndex="{Binding Options.ChannelsIndex, RelativeSource={RelativeSource AncestorType=Window}, Mode=TwoWay}">
<ComboBoxItem Content="원본"/><ComboBoxItem Content="모노"/><ComboBoxItem Content="스테레오"/>
</ComboBox>
</StackPanel>
</Grid>
<CheckBox Margin="0,10,0,0" Content="음량 정규화 (EBU R128)"
IsChecked="{Binding Options.Loudnorm, RelativeSource={RelativeSource AncestorType=Window}, Mode=TwoWay}"/>
</StackPanel> </StackPanel>
<TextBlock Text="인코딩 프리셋 (느릴수록 고압축)" Style="{StaticResource FsCaptionStyle}" Margin="0,10,0,4"/>
<ComboBox SelectedIndex="{Binding Options.PresetIndex, RelativeSource={RelativeSource AncestorType=Window}, Mode=TwoWay}">
<ComboBoxItem Content="UltraFast"/><ComboBoxItem Content="SuperFast"/><ComboBoxItem Content="VeryFast"/>
<ComboBoxItem Content="Faster"/><ComboBoxItem Content="Fast"/><ComboBoxItem Content="Medium"/>
<ComboBoxItem Content="Slow"/><ComboBoxItem Content="Slower"/><ComboBoxItem Content="VerySlow"/>
</ComboBox>
<Grid Margin="0,10,0,0">
<Grid.ColumnDefinitions><ColumnDefinition Width="*"/><ColumnDefinition Width="10"/><ColumnDefinition Width="*"/></Grid.ColumnDefinitions>
<StackPanel Grid.Column="0">
<TextBlock Text="해상도" Style="{StaticResource FsCaptionStyle}" Margin="0,0,0,4"/>
<ComboBox SelectedIndex="{Binding Options.ResolutionIndex, RelativeSource={RelativeSource AncestorType=Window}, Mode=TwoWay}">
<ComboBoxItem Content="원본"/><ComboBoxItem Content="2160p (4K)"/><ComboBoxItem Content="1440p"/>
<ComboBoxItem Content="1080p"/><ComboBoxItem Content="720p"/><ComboBoxItem Content="480p"/>
</ComboBox>
</StackPanel>
<StackPanel Grid.Column="2">
<TextBlock Text="프레임레이트" Style="{StaticResource FsCaptionStyle}" Margin="0,0,0,4"/>
<ComboBox SelectedIndex="{Binding Options.FpsIndex, RelativeSource={RelativeSource AncestorType=Window}, Mode=TwoWay}">
<ComboBoxItem Content="원본"/><ComboBoxItem Content="24"/><ComboBoxItem Content="30"/><ComboBoxItem Content="60"/>
</ComboBox>
</StackPanel>
</Grid>
<CheckBox Margin="0,12,0,0" Content="웹 스트리밍 최적화 (Faststart)"
IsChecked="{Binding Options.FastStart, RelativeSource={RelativeSource AncestorType=Window}, Mode=TwoWay}"/>
<CheckBox Margin="0,6,0,0" Content="GPU 하드웨어 가속 (NVENC 등)"
IsChecked="{Binding Options.VideoPreferGpu, RelativeSource={RelativeSource AncestorType=Window}, Mode=TwoWay}"/>
<Expander Header="고급" Margin="0,10,0,0" Foreground="{StaticResource FsTextSecondary}">
<StackPanel Margin="0,8,0,0">
<TextBlock Text="회전 / 뒤집기" Style="{StaticResource FsCaptionStyle}" Margin="0,0,0,4"/>
<ComboBox SelectedIndex="{Binding Options.RotateIndex, RelativeSource={RelativeSource AncestorType=Window}, Mode=TwoWay}">
<ComboBoxItem Content="없음"/><ComboBoxItem Content="시계 90°"/><ComboBoxItem Content="반시계 90°"/>
<ComboBoxItem Content="180°"/><ComboBoxItem Content="좌우 반전"/><ComboBoxItem Content="상하 반전"/>
</ComboBox>
<TextBlock Text="디인터레이스" Style="{StaticResource FsCaptionStyle}" Margin="0,10,0,4"/>
<ComboBox SelectedIndex="{Binding Options.DeinterlaceIndex, RelativeSource={RelativeSource AncestorType=Window}, Mode=TwoWay}">
<ComboBoxItem Content="끔"/><ComboBoxItem Content="Yadif"/><ComboBoxItem Content="Bwdif (고품질)"/>
</ComboBox>
<CheckBox Margin="0,10,0,0" Content="적응형 양자화 (GPU 화질↑)"
IsChecked="{Binding Options.SpatialAq, RelativeSource={RelativeSource AncestorType=Window}, Mode=TwoWay}"/>
</StackPanel>
</Expander>
</StackPanel> </StackPanel>
</Expander>
<!-- 오디오 (영상의 오디오 트랙 + 오디오 전용 출력) -->
<StackPanel x:Name="AudioSubPanel" Margin="0,14,0,0">
<TextBlock Text="오디오" Style="{StaticResource FsLabelStyle}"/>
<TextBlock Text="오디오 코덱" Style="{StaticResource FsCaptionStyle}" Margin="0,8,0,4"/>
<ComboBox SelectedIndex="{Binding Options.AudioCodecIndex, RelativeSource={RelativeSource AncestorType=Window}, Mode=TwoWay}">
<ComboBoxItem Content="자동"/><ComboBoxItem Content="AAC"/><ComboBoxItem Content="MP3"/>
<ComboBoxItem Content="Opus"/><ComboBoxItem Content="Vorbis"/><ComboBoxItem Content="FLAC (무손실)"/>
<ComboBoxItem Content="PCM (WAV)"/><ComboBoxItem Content="원본 복사"/>
</ComboBox>
<TextBlock Text="오디오 비트레이트" Style="{StaticResource FsCaptionStyle}" Margin="0,10,0,4"/>
<ComboBox SelectedIndex="{Binding Options.AudioBitrateIndex, RelativeSource={RelativeSource AncestorType=Window}, Mode=TwoWay}">
<ComboBoxItem Content="96 kbps"/><ComboBoxItem Content="128 kbps"/><ComboBoxItem Content="192 kbps"/>
<ComboBoxItem Content="256 kbps"/><ComboBoxItem Content="320 kbps"/>
</ComboBox>
<CheckBox Margin="0,8,0,0" Content="가변 비트레이트 (VBR)"
IsChecked="{Binding Options.AudioVbr, RelativeSource={RelativeSource AncestorType=Window}, Mode=TwoWay}"/>
<Grid Margin="0,10,0,0">
<Grid.ColumnDefinitions><ColumnDefinition Width="*"/><ColumnDefinition Width="10"/><ColumnDefinition Width="*"/></Grid.ColumnDefinitions>
<StackPanel Grid.Column="0">
<TextBlock Text="샘플레이트" Style="{StaticResource FsCaptionStyle}" Margin="0,0,0,4"/>
<ComboBox SelectedIndex="{Binding Options.SampleRateIndex, RelativeSource={RelativeSource AncestorType=Window}, Mode=TwoWay}">
<ComboBoxItem Content="원본"/><ComboBoxItem Content="44.1 kHz"/><ComboBoxItem Content="48 kHz"/>
</ComboBox>
</StackPanel>
<StackPanel Grid.Column="2">
<TextBlock Text="채널" Style="{StaticResource FsCaptionStyle}" Margin="0,0,0,4"/>
<ComboBox SelectedIndex="{Binding Options.ChannelsIndex, RelativeSource={RelativeSource AncestorType=Window}, Mode=TwoWay}">
<ComboBoxItem Content="원본"/><ComboBoxItem Content="모노"/><ComboBoxItem Content="스테레오"/>
</ComboBox>
</StackPanel>
</Grid>
<CheckBox Margin="0,10,0,0" Content="음량 정규화 (EBU R128)"
IsChecked="{Binding Options.Loudnorm, RelativeSource={RelativeSource AncestorType=Window}, Mode=TwoWay}"/>
</StackPanel>
</StackPanel>
</Border> </Border>
<!-- Card 6: Stats Box --> <!-- Card 6: Stats Box -->
@ -685,8 +724,8 @@
<!-- 좌측: Active Queue OR Past Results --> <!-- 좌측: Active Queue OR Past Results -->
<Grid Grid.Row="1" Grid.Column="0"> <Grid Grid.Row="1" Grid.Column="0">
<!-- Active Queue view --> <!-- Active Queue view (default) -->
<Grid x:Name="ActiveQueueView" Visibility="Collapsed"> <Grid x:Name="ActiveQueueView" Visibility="Visible">
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/>
<RowDefinition Height="*"/> <RowDefinition Height="*"/>
@ -880,8 +919,8 @@
</Grid> </Grid>
</Grid> </Grid>
<!-- Past Results view (default) --> <!-- Past Results view -->
<Grid x:Name="PastResultsContainer"> <Grid x:Name="PastResultsContainer" Visibility="Collapsed">
<!-- 빈 상태 안내 (Empty State) --> <!-- 빈 상태 안내 (Empty State) -->
<Grid x:Name="PastResultsEmpty" Visibility="Collapsed"> <Grid x:Name="PastResultsEmpty" Visibility="Collapsed">
<Border Background="{StaticResource FsBgBase}" Padding="32"> <Border Background="{StaticResource FsBgBase}" Padding="32">

View file

@ -172,13 +172,10 @@ public partial class MainWindow : Wpf.Ui.Controls.FluentWindow
if (initialFiles is { Count: > 0 }) if (initialFiles is { Count: > 0 })
{ {
AddToQueue(initialFiles); AddToQueue(initialFiles);
ShowTab("Active");
}
else
{
ShowTab("Past");
} }
ShowTab("Active");
UpdateActiveQueueVisibility(); UpdateActiveQueueVisibility();
ApplyAppDataStats(); ApplyAppDataStats();
@ -1404,16 +1401,24 @@ public partial class MainWindow : Wpf.Ui.Controls.FluentWindow
UpdateMediaPanelForFormat(extension); UpdateMediaPanelForFormat(extension);
} }
/// <summary>출력이 영상/오디오일 때만 영상·오디오 인코딩 패널을 노출(영상이면 영상+오디오, 오디오면 오디오만).</summary> /// <summary>출력이 영상/오디오/PDF/이미지일 때 상세 인코딩 폴드아웃 서브패널 노출(Fluent 2 점진적 공개 패턴).</summary>
private void UpdateMediaPanelForFormat(string? extension) private void UpdateMediaPanelForFormat(string? extension)
{ {
if (MediaPanel is null) return; if (AdvancedOptionsExpander is null) return;
var ext = (extension ?? string.Empty).ToLowerInvariant(); var ext = (extension ?? string.Empty).ToLowerInvariant();
var isVideo = ext is ".mp4" or ".mkv" or ".webm" or ".mov" or ".avi"; var isVideo = ext is ".mp4" or ".mkv" or ".webm" or ".mov" or ".avi";
var isAudio = ext is ".mp3" or ".aac" or ".m4a" or ".opus" or ".ogg" or ".flac" or ".wav"; var isAudio = ext is ".mp3" or ".aac" or ".m4a" or ".opus" or ".ogg" or ".flac" or ".wav";
MediaPanel.Visibility = (isVideo || isAudio) ? Visibility.Visible : Visibility.Collapsed; var isPdf = ext is ".pdf";
VideoSubPanel.Visibility = isVideo ? Visibility.Visible : Visibility.Collapsed; var isImage = ext is ".jpg" or ".jpeg" or ".png" or ".webp" or ".avif" or ".tif" or ".tiff" or ".bmp" or ".gif";
AudioSubPanel.Visibility = (isVideo || isAudio) ? Visibility.Visible : Visibility.Collapsed;
if (AdvancedVideoPanel is not null)
AdvancedVideoPanel.Visibility = isVideo ? Visibility.Visible : Visibility.Collapsed;
if (AdvancedAudioPanel is not null)
AdvancedAudioPanel.Visibility = (isVideo || isAudio) ? Visibility.Visible : Visibility.Collapsed;
if (AdvancedPdfPanel is not null)
AdvancedPdfPanel.Visibility = isPdf ? Visibility.Visible : Visibility.Collapsed;
if (AdvancedImagePanel is not null)
AdvancedImagePanel.Visibility = isImage ? Visibility.Visible : Visibility.Collapsed;
} }
private void UpdateOutputDestHint(string? extension) private void UpdateOutputDestHint(string? extension)

View file

@ -277,6 +277,42 @@ public class UnifiedTitleBarTests
}); });
} }
[Fact]
public void MainWindow_InitialLaunch_DefaultsToActiveQueueView_WithEmptyDropZone()
{
RunOnSta(() =>
{
var engine = Everything2EverythingBootstrap.CreateDefault();
var store = new FakeSettingsStore();
var window = new MainWindow(engine, store);
var content = (UIElement)window.Content;
content.Measure(new Size(1280, 960));
content.Arrange(new Rect(0, 0, 1280, 960));
// 1. Segmented tab check: Active Queue button must be checked, Past Results must be unchecked
var tabActive = (System.Windows.Controls.Primitives.ToggleButton)window.FindName("TabActiveBtn");
var tabPast = (System.Windows.Controls.Primitives.ToggleButton)window.FindName("TabPastBtn");
Assert.NotNull(tabActive);
Assert.NotNull(tabPast);
Assert.True(tabActive.IsChecked, "프로그램 실행 시 기본 탭은 Active Queue여야 합니다.");
Assert.False(tabPast.IsChecked, "프로그램 실행 시 Past Results 탭은 체크 해제 상태여야 합니다.");
// 2. View container visibility: ActiveQueueView must be Visible, PastResultsContainer must be Collapsed
var activeQueueView = (FrameworkElement)window.FindName("ActiveQueueView");
var pastResultsContainer = (FrameworkElement)window.FindName("PastResultsContainer");
Assert.NotNull(activeQueueView);
Assert.NotNull(pastResultsContainer);
Assert.Equal(Visibility.Visible, activeQueueView.Visibility);
Assert.Equal(Visibility.Collapsed, pastResultsContainer.Visibility);
// 3. Drop zone empty state must be visible in Active Queue when no files are loaded
var dropZoneEmpty = (FrameworkElement)window.FindName("DropZoneEmpty");
Assert.NotNull(dropZoneEmpty);
Assert.Equal(Visibility.Visible, dropZoneEmpty.Visibility);
});
}
[Fact] [Fact]
public void SettingsWindow_TitleBar_MustDisplayProperlyAlignedHeaderAndCloseButton() public void SettingsWindow_TitleBar_MustDisplayProperlyAlignedHeaderAndCloseButton()
{ {