Add FFprobe Path and DRM Video Duration Match Threshold to the config page

This commit is contained in:
whimsical-c4lic0 2026-02-17 01:36:57 -06:00
parent fccee9a520
commit da40f3d0c5
4 changed files with 186 additions and 1 deletions

View File

@ -10,6 +10,7 @@ internal static class ConfigValidationService
ValidatePath(config.DownloadPath, nameof(Config.DownloadPath), errors, requireExistingFile: false);
ValidatePath(config.FFmpegPath, nameof(Config.FFmpegPath), errors, requireExistingFile: true);
ValidatePath(config.FFprobePath, nameof(Config.FFprobePath), errors, requireExistingFile: true);
if (config.Timeout.HasValue && config.Timeout.Value <= 0 && config.Timeout.Value != -1)
{

View File

@ -49,8 +49,12 @@ public partial class MainWindowViewModel(
{
[nameof(Config.FFmpegPath)] =
"Path to the FFmpeg executable. If blank, OF-DL will try the app directory and PATH.",
[nameof(Config.FFprobePath)] =
"Path to the FFprobe executable. If blank, OF-DL will try FFmpeg's directory, the app directory, and PATH.",
[nameof(Config.DownloadPath)] =
"Base download folder. If blank, OF-DL uses __user_data__/sites/OnlyFans/{username}.",
[nameof(Config.DrmVideoDurationMatchThreshold)] =
"Minimum DRM video duration match threshold. Higher values are stricter. 100% requires an exact duration match. 98% is the recommended value.",
[nameof(Config.DownloadVideos)] = "Download video media when enabled.",
[nameof(Config.DownloadImages)] = "Download image media when enabled.",
[nameof(Config.DownloadAudios)] = "Download audio media when enabled.",
@ -164,6 +168,7 @@ public partial class MainWindowViewModel(
[ObservableProperty] private string _errorMessage = string.Empty;
private string _actualFfmpegPath = string.Empty;
private string _actualFfprobePath = string.Empty;
private string _actualDownloadPath = string.Empty;
[ObservableProperty] [NotifyPropertyChangedFor(nameof(FfmpegPathDisplay))]
@ -172,12 +177,21 @@ public partial class MainWindowViewModel(
[ObservableProperty] [NotifyPropertyChangedFor(nameof(HasFfmpegPathError))]
private string _ffmpegPathError = string.Empty;
[ObservableProperty] [NotifyPropertyChangedFor(nameof(HasFfprobePathError))]
private string _ffprobePath = string.Empty;
[ObservableProperty] [NotifyPropertyChangedFor(nameof(HasFfprobePathError))]
private string _ffprobePathError = string.Empty;
[ObservableProperty] [NotifyPropertyChangedFor(nameof(DownloadPathDisplay))]
private string _downloadPath = string.Empty;
[ObservableProperty] [NotifyPropertyChangedFor(nameof(HasDownloadPathError))]
private string _downloadPathError = string.Empty;
[ObservableProperty] [NotifyPropertyChangedFor(nameof(DrmVideoDurationMatchThresholdPercentLabel))]
private double _drmVideoDurationMatchThresholdPercent = 98;
[ObservableProperty] [NotifyPropertyChangedFor(nameof(HasMediaTypesError))]
private string _mediaTypesError = string.Empty;
@ -218,6 +232,8 @@ public partial class MainWindowViewModel(
public bool HasFfmpegPathError => !string.IsNullOrWhiteSpace(FfmpegPathError);
public bool HasFfprobePathError => !string.IsNullOrWhiteSpace(FfprobePathError);
public bool HasDownloadPathError => !string.IsNullOrWhiteSpace(DownloadPathError);
public bool HasMediaTypesError => !string.IsNullOrWhiteSpace(MediaTypesError);
@ -233,8 +249,16 @@ public partial class MainWindowViewModel(
public string FfmpegPathHelpText => GetConfigHelpText(nameof(Config.FFmpegPath));
public string FfprobePathHelpText => GetConfigHelpText(nameof(Config.FFprobePath));
public string DownloadPathHelpText => GetConfigHelpText(nameof(Config.DownloadPath));
public string DrmVideoDurationMatchThresholdHelpText =>
GetConfigHelpText(nameof(Config.DrmVideoDurationMatchThreshold));
public string DrmVideoDurationMatchThresholdPercentLabel =>
$"{Math.Round(DrmVideoDurationMatchThresholdPercent)}%";
public string SelectedUsersSummary =>
$"{AvailableUsers.Count(user => user.IsSelected)} / {AvailableUsers.Count} selected";
@ -319,6 +343,16 @@ public partial class MainWindowViewModel(
FfmpegPathError = string.Empty;
}
public void SetFfprobePath(string? path)
{
string normalizedPath = NormalizePathForDisplay(path);
_actualFfprobePath = normalizedPath;
FfprobePath = HidePrivateInfo && !string.IsNullOrWhiteSpace(normalizedPath)
? "[Hidden for Privacy]"
: normalizedPath;
FfprobePathError = string.Empty;
}
public void SetDownloadPath(string? path)
{
string normalizedPath = NormalizePathForDisplay(path);
@ -760,6 +794,16 @@ public partial class MainWindowViewModel(
FfmpegPathError = string.Empty;
}
partial void OnFfprobePathChanged(string value)
{
if (value != "[Hidden for Privacy]")
{
_actualFfprobePath = value;
}
FfprobePathError = string.Empty;
}
partial void OnDownloadPathChanged(string value)
{
if (value != "[Hidden for Privacy]")
@ -1009,6 +1053,12 @@ public partial class MainWindowViewModel(
continue;
}
if (error.Key == nameof(Config.FFprobePath))
{
FfprobePathError = error.Value;
continue;
}
if (fieldMap.TryGetValue(error.Key, out ConfigFieldViewModel? field))
{
field.SetError(error.Value);
@ -1122,12 +1172,18 @@ public partial class MainWindowViewModel(
_actualFfmpegPath = ffmpegPath;
FfmpegPath = HidePrivateInfo && !string.IsNullOrWhiteSpace(ffmpegPath) ? "[Hidden for Privacy]" : ffmpegPath;
string ffprobePath = NormalizePathForDisplay(config.FFprobePath);
_actualFfprobePath = ffprobePath;
FfprobePath = HidePrivateInfo && !string.IsNullOrWhiteSpace(ffprobePath) ? "[Hidden for Privacy]" : ffprobePath;
string downloadPath = ResolveDownloadPathForDisplay(config.DownloadPath);
_actualDownloadPath = downloadPath;
DownloadPath = HidePrivateInfo && !string.IsNullOrWhiteSpace(downloadPath)
? "[Hidden for Privacy]"
: downloadPath;
DrmVideoDurationMatchThresholdPercent = Math.Clamp(config.DrmVideoDurationMatchThreshold * 100, 0, 100);
ClearSpecialConfigErrors();
PopulateSelectionOptions(MediaTypeOptions, s_mediaTypeOptions, config);
@ -1168,11 +1224,19 @@ public partial class MainWindowViewModel(
? string.Empty
: EscapePathForConfig(normalizedFfmpegPath);
string ffprobePathToUse = HidePrivateInfo ? _actualFfprobePath : FfprobePath;
string normalizedFfprobePath = NormalizePathForDisplay(ffprobePathToUse);
config.FFprobePath = string.IsNullOrWhiteSpace(normalizedFfprobePath)
? string.Empty
: EscapePathForConfig(normalizedFfprobePath);
string downloadPathToUse = HidePrivateInfo ? _actualDownloadPath : DownloadPath;
string normalizedDownloadPath = NormalizePathForDisplay(downloadPathToUse);
config.DownloadPath = string.IsNullOrWhiteSpace(normalizedDownloadPath)
? EscapePathForConfig(s_defaultDownloadPath)
: EscapePathForConfig(normalizedDownloadPath);
config.DrmVideoDurationMatchThreshold =
Math.Clamp(Math.Round(DrmVideoDurationMatchThresholdPercent / 100d, 2), 0d, 1d);
ApplySelectionOptionsToConfig(config, MediaTypeOptions);
ApplySelectionOptionsToConfig(config, MediaSourceOptions);
@ -1208,13 +1272,14 @@ public partial class MainWindowViewModel(
private void ClearSpecialConfigErrors()
{
FfmpegPathError = string.Empty;
FfprobePathError = string.Empty;
DownloadPathError = string.Empty;
MediaTypesError = string.Empty;
MediaSourcesError = string.Empty;
}
private bool HasSpecialConfigErrors() =>
HasFfmpegPathError || HasDownloadPathError || HasMediaTypesError || HasMediaSourcesError;
HasFfmpegPathError || HasFfprobePathError || HasDownloadPathError || HasMediaTypesError || HasMediaSourcesError;
private static string ResolveDownloadPathForDisplay(string? configuredPath)
{
@ -1404,7 +1469,9 @@ public partial class MainWindowViewModel(
or nameof(Config.NonInteractiveModePurchasedTab)
or nameof(Config.DisableBrowserAuth)
or nameof(Config.FFmpegPath)
or nameof(Config.FFprobePath)
or nameof(Config.DownloadPath)
or nameof(Config.DrmVideoDurationMatchThreshold)
or nameof(Config.DownloadVideos)
or nameof(Config.DownloadImages)
or nameof(Config.DownloadAudios)

View File

@ -142,6 +142,42 @@
Foreground="#FF5A5A"
Text="{Binding FfmpegPathError}"
TextWrapping="Wrap" />
<StackPanel Orientation="Horizontal" Spacing="6" Margin="0,8,0,0">
<TextBlock FontWeight="SemiBold"
Foreground="#1F2A44"
Text="FFprobe Path" />
<Button Width="20"
Height="20"
MinWidth="20"
MinHeight="20"
Padding="0"
HorizontalContentAlignment="Center"
VerticalContentAlignment="Center"
FontSize="12"
FontWeight="Bold"
Background="#EAF0FB"
BorderBrush="#C5D4EC"
BorderThickness="1"
CornerRadius="10"
Content="?"
ToolTip.Tip="{Binding FfprobePathHelpText}" />
</StackPanel>
<Grid ColumnDefinitions="*,Auto">
<TextBox Grid.Column="0"
VerticalAlignment="Center"
Text="{Binding FfprobePath}"
Watermark="Select ffprobe executable" />
<Button Grid.Column="1"
Margin="8,0,0,0"
Classes="secondary"
Content="Browse..."
Click="OnBrowseFfprobePathClick" />
</Grid>
<TextBlock IsVisible="{Binding HasFfprobePathError}"
Foreground="#FF5A5A"
Text="{Binding FfprobePathError}"
TextWrapping="Wrap" />
</StackPanel>
</Border>
@ -291,6 +327,51 @@
Foreground="#FF5A5A"
Text="{Binding ViewModel.DownloadPathError, RelativeSource={RelativeSource AncestorType=views:MainWindow}, FallbackValue=''}"
TextWrapping="Wrap" />
<Grid Margin="0,8,0,2" ColumnDefinitions="190,*">
<Grid Grid.Column="0"
ColumnDefinitions="*,Auto"
Margin="0,6,10,0"
ClipToBounds="True">
<TextBlock Grid.Column="0"
FontWeight="SemiBold"
Foreground="#1F2A44"
Text="DRM Video Duration Match Threshold"
TextWrapping="Wrap"
VerticalAlignment="Top" />
<Button Grid.Column="1"
Width="20"
Height="20"
MinWidth="20"
MinHeight="20"
Margin="6,0,0,0"
Padding="0"
VerticalAlignment="Top"
HorizontalAlignment="Right"
HorizontalContentAlignment="Center"
VerticalContentAlignment="Center"
FontSize="12"
FontWeight="Bold"
Background="#EAF0FB"
BorderBrush="#C5D4EC"
BorderThickness="1"
CornerRadius="10"
Content="?"
ToolTip.Tip="{Binding ViewModel.DrmVideoDurationMatchThresholdHelpText, RelativeSource={RelativeSource AncestorType=views:MainWindow}, FallbackValue=''}" />
</Grid>
<Grid Grid.Column="1" ColumnDefinitions="*,Auto" VerticalAlignment="Center">
<Slider Grid.Column="0"
Minimum="0"
Maximum="100"
Value="{Binding ViewModel.DrmVideoDurationMatchThresholdPercent, RelativeSource={RelativeSource AncestorType=views:MainWindow}, FallbackValue=98}" />
<TextBlock Grid.Column="1"
Margin="10,0,0,0"
VerticalAlignment="Center"
FontWeight="SemiBold"
Foreground="#4A5B78"
Text="{Binding ViewModel.DrmVideoDurationMatchThresholdPercentLabel, RelativeSource={RelativeSource AncestorType=views:MainWindow}, FallbackValue='98%'}" />
</Grid>
</Grid>
</StackPanel>
<StackPanel IsVisible="{Binding HasSpecificDateFilterFields}"
Margin="0,0,0,6"

View File

@ -70,6 +70,42 @@ public partial class MainWindow : Window
vm.SetFfmpegPath(selectedFile.Name);
}
private async void OnBrowseFfprobePathClick(object? sender, RoutedEventArgs e)
{
if (DataContext is not MainWindowViewModel vm)
{
return;
}
TopLevel? topLevel = TopLevel.GetTopLevel(this);
if (topLevel?.StorageProvider == null)
{
return;
}
IReadOnlyList<IStorageFile> selectedFiles = await topLevel.StorageProvider.OpenFilePickerAsync(
new FilePickerOpenOptions
{
Title = "Select FFprobe executable",
AllowMultiple = false
});
IStorageFile? selectedFile = selectedFiles.FirstOrDefault();
if (selectedFile == null)
{
return;
}
string? localPath = selectedFile.TryGetLocalPath();
if (!string.IsNullOrWhiteSpace(localPath))
{
vm.SetFfprobePath(localPath);
return;
}
vm.SetFfprobePath(selectedFile.Name);
}
private async void OnBrowseDownloadPathClick(object? sender, RoutedEventArgs e)
{
if (DataContext is not MainWindowViewModel vm)