Compare commits

..

1 Commits

Author SHA1 Message Date
Tyrrrz
6dbfd290fa Download FFmpeg via a project target 2025-05-11 02:05:00 +03:00
17 changed files with 114 additions and 245 deletions

View File

@ -60,8 +60,6 @@ jobs:
- win-x86
- win-x64
- linux-arm64
# Linux x86 is not supported by .NET
# - linux-x86
- linux-x64
- osx-arm64
- osx-x64
@ -73,6 +71,10 @@ jobs:
artifact-name-base: YoutubeDownloader
- bundle-ffmpeg: false
artifact-name-base: YoutubeDownloader.Bare
exclude:
# FFmpeg builds for these platforms are not easily available
- bundle-ffmpeg: true
rid: linux-arm64
runs-on: ${{ startsWith(matrix.rid, 'win-') && 'windows-latest' || startsWith(matrix.rid, 'osx-') && 'macos-latest' || 'ubuntu-latest' }}
timeout-minutes: 10
@ -96,13 +98,12 @@ jobs:
-p:Version=${{ github.ref_type == 'tag' && github.ref_name || format('999.9.9-ci-{0}', github.sha) }}
-p:CSharpier_Bypass=true
-p:DownloadFFmpeg=${{ matrix.bundle-ffmpeg }}
-p:PublishMacOSBundle=${{ startsWith(matrix.rid, 'osx-') }}
--output YoutubeDownloader/bin/publish
--configuration Release
--runtime ${{ matrix.rid }}
--self-contained
- name: Upload app binaries
- name: Upload artifacts
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: ${{ matrix.artifact-name-base }}.${{ matrix.rid }}
@ -143,8 +144,6 @@ jobs:
- win-x86
- win-x64
- linux-arm64
# Linux x86 is not supported by .NET
# - linux-x86
- linux-x64
- osx-arm64
- osx-x64
@ -156,6 +155,10 @@ jobs:
artifact-name-base: YoutubeDownloader
- bundle-ffmpeg: false
artifact-name-base: YoutubeDownloader.Bare
exclude:
# FFmpeg builds for these platforms are not easily available
- bundle-ffmpeg: true
rid: linux-arm64
runs-on: ubuntu-latest
timeout-minutes: 10
@ -165,21 +168,19 @@ jobs:
contents: write
steps:
- name: Download app binaries
- name: Download artifacts
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
with:
name: ${{ matrix.artifact-name-base }}.${{ matrix.rid }}
path: YoutubeDownloader/
- name: Set permissions
- name: Set permissions (app)
if: ${{ !startsWith(matrix.rid, 'win-') }}
run: |
[ -f YoutubeDownloader/YoutubeDownloader ] && chmod +x YoutubeDownloader/YoutubeDownloader
[ -f YoutubeDownloader/ffmpeg ] && chmod +x YoutubeDownloader/ffmpeg
# macOS bundle
[ -f YoutubeDownloader/YoutubeDownloader.app/Contents/MacOS/YoutubeDownloader] && chmod +x YoutubeDownloader/YoutubeDownloader.app/Contents/MacOS/YoutubeDownloader
[ -f YoutubeDownloader/YoutubeDownloader.app/Contents/MacOS/ffmpeg ] && chmod +x YoutubeDownloader/YoutubeDownloader.app/Contents/MacOS/ffmpeg
run: chmod +x YoutubeDownloader/YoutubeDownloader
- name: Set permissions (FFmpeg)
if: ${{ !startsWith(matrix.rid, 'win-') && matrix.bundle-ffmpeg }}
run: chmod +x YoutubeDownloader/ffmpeg
- name: Create package
# Change into the artifacts directory to avoid including the directory itself in the zip archive

5
.gitignore vendored
View File

@ -8,8 +8,5 @@
bin/
obj/
# Avalonia
.avalonia-build-tasks/
# Test results
TestResults/
TestResults/

View File

@ -42,10 +42,6 @@ To learn more about the war and how you can help, [click here](https://tyrrrz.me
- 🟢 **[Stable release](https://github.com/Tyrrrz/YoutubeDownloader/releases/latest)**
- 🟠 [CI build](https://github.com/Tyrrrz/YoutubeDownloader/actions/workflows/main.yml)
> **Important**:
> To launch the app on MacOS, you need to first remove the downloaded file from quarantine.
> You can do that by running the following command in the terminal: `xattr -rd com.apple.quarantine YoutubeDownloader.app`.
> **Note**:
> If you're unsure which build is right for your system, consult with [this page](https://useragent.cc) to determine your OS and CPU architecture.

View File

@ -1,8 +1,10 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
using Gress;
using YoutubeDownloader.Core.Utils;
using YoutubeExplode;
using YoutubeExplode.Channels;
@ -120,4 +122,33 @@ public class QueryResolver(IReadOnlyList<Cookie>? initialCookies = null)
?? await TryResolveChannelAsync(query, cancellationToken)
?? await ResolveSearchAsync(query, cancellationToken);
}
public async Task<QueryResult> ResolveAsync(
IReadOnlyList<string> queries,
IProgress<Percentage>? progress = null,
CancellationToken cancellationToken = default
)
{
if (queries.Count == 1)
return await ResolveAsync(queries.Single(), cancellationToken);
var videos = new List<IVideo>();
var videoIds = new HashSet<VideoId>();
var completed = 0;
foreach (var query in queries)
{
var result = await ResolveAsync(query, cancellationToken);
foreach (var video in result.Videos)
{
if (videoIds.Add(video.Id))
videos.Add(video);
}
progress?.Report(Percentage.FromFraction(1.0 * ++completed / queries.Count));
}
return new QueryResult(QueryResultKind.Aggregate, $"{queries.Count} queries", videos);
}
}

View File

@ -1,28 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Collections.Generic;
using YoutubeExplode.Videos;
namespace YoutubeDownloader.Core.Resolving;
public record QueryResult(QueryResultKind Kind, string Title, IReadOnlyList<IVideo> Videos)
{
public static QueryResult Aggregate(IReadOnlyList<QueryResult> results)
{
if (!results.Any())
throw new ArgumentException("Cannot aggregate empty results.", nameof(results));
return new QueryResult(
// Single query -> inherit kind, multiple queries -> aggregate
results.Count == 1
? results.Single().Kind
: QueryResultKind.Aggregate,
// Single query -> inherit title, multiple queries -> aggregate
results.Count == 1
? results.Single().Title
: $"{results.Count} queries",
// Combine all videos, deduplicate by ID
results.SelectMany(q => q.Videos).DistinctBy(v => v.Id).ToArray()
);
}
}
public record QueryResult(QueryResultKind Kind, string Title, IReadOnlyList<IVideo> Videos);

View File

@ -1,10 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk">
<ItemGroup>
<PackageReference Include="CSharpier.MsBuild" Version="1.0.2" PrivateAssets="all" />
<PackageReference Include="CSharpier.MsBuild" Version="0.30.6" PrivateAssets="all" />
<PackageReference Include="Gress" Version="2.1.1" />
<PackageReference Include="JsonExtensions" Version="1.2.0" />
<PackageReference Include="TagLibSharp" Version="2.3.0" />
<PackageReference Include="YoutubeExplode" Version="6.5.4" />
<PackageReference Include="YoutubeExplode.Converter" Version="6.5.4" />
</ItemGroup>
</Project>
</Project>

View File

@ -5,7 +5,7 @@ VisualStudioVersion = 17.7.33920.267
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "YoutubeDownloader", "YoutubeDownloader\YoutubeDownloader.csproj", "{AF6D645E-DDDD-4034-B644-D5328CC893C1}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Misc", "Misc", "{131C2561-E5A1-43E8-BF38-40E2E23DB0A4}"
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{131C2561-E5A1-43E8-BF38-40E2E23DB0A4}"
ProjectSection(SolutionItems) = preProject
Directory.Build.props = Directory.Build.props
License.txt = License.txt

View File

@ -8,7 +8,6 @@
xmlns:materialControls="clr-namespace:Material.Styles.Controls;assembly=Material.Styles"
xmlns:materialIcons="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
xmlns:materialStyles="clr-namespace:Material.Styles.Themes;assembly=Material.Styles"
Name="YoutubeDownloader"
ActualThemeVariantChanged="Application_OnActualThemeVariantChanged">
<Application.DataTemplates>
<framework:ViewManager />

View File

@ -46,7 +46,7 @@ if (Test-Path $OutputPath) {
Write-Host "Downloading FFmpeg for $Platform..."
$http = New-Object System.Net.WebClient
try {
$http.DownloadFile("https://github.com/Tyrrrz/FFmpegBin/releases/download/7.1.1/ffmpeg-$Platform.zip", "$OutputPath.zip")
$http.DownloadFile("https://github.com/Tyrrrz/FFmpegBin/releases/download/7.0/ffmpeg-$Platform.zip", "$OutputPath.zip")
} finally {
$http.Dispose()
}

View File

@ -65,7 +65,7 @@ public class DialogManager : IDisposable
}
);
return file?.TryGetLocalPath() ?? file?.Path.ToString();
return file?.Path.LocalPath;
}
public async Task<string?> PromptDirectoryPathAsync(string defaultDirPath = "")
@ -74,21 +74,19 @@ public class DialogManager : IDisposable
Application.Current?.ApplicationLifetime?.TryGetTopLevel()
?? throw new ApplicationException("Could not find the top-level visual element.");
var result = await topLevel.StorageProvider.OpenFolderPickerAsync(
var startLocation = await topLevel.StorageProvider.TryGetFolderFromPathAsync(
defaultDirPath
);
var folderPickResult = await topLevel.StorageProvider.OpenFolderPickerAsync(
new FolderPickerOpenOptions
{
AllowMultiple = false,
SuggestedStartLocation = await topLevel.StorageProvider.TryGetFolderFromPathAsync(
defaultDirPath
),
SuggestedStartLocation = startLocation,
}
);
var directory = result.FirstOrDefault();
if (directory is null)
return null;
return directory.TryGetLocalPath() ?? directory.Path.ToString();
return folderPickResult.FirstOrDefault()?.Path.LocalPath;
}
public void Dispose() => _dialogLock.Dispose();

View File

@ -1,87 +0,0 @@
param(
[Parameter(Mandatory=$true)]
[string]$PublishDirPath,
[Parameter(Mandatory=$true)]
[string]$IconsFilePath,
[Parameter(Mandatory=$true)]
[string]$FullVersion,
[Parameter(Mandatory=$true)]
[string]$ShortVersion
)
$ErrorActionPreference = "Stop"
# Setup paths
$tempDirPath = Join-Path $PublishDirPath "../publish-macos-app-temp"
$bundleName = "YoutubeDownloader.app"
$bundleDirPath = Join-Path $tempDirPath $bundleName
$contentsDirPath = Join-Path $bundleDirPath "Contents"
$macosDirPath = Join-Path $contentsDirPath "MacOS"
$resourcesDirPath = Join-Path $contentsDirPath "Resources"
try {
# Initialize the bundle's directory structure
New-Item -Path $bundleDirPath -ItemType Directory -Force
New-Item -Path $contentsDirPath -ItemType Directory -Force
New-Item -Path $macosDirPath -ItemType Directory -Force
New-Item -Path $resourcesDirPath -ItemType Directory -Force
# Copy icons into the .app's Resources folder
Copy-Item -Path $IconsFilePath -Destination (Join-Path $resourcesDirPath "AppIcon.icns") -Force
# Generate the Info.plist metadata file with the app information
$plistContent = @"
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDisplayName</key>
<string>YoutubeDownloader</string>
<key>CFBundleName</key>
<string>YoutubeDownloader</string>
<key>CFBundleExecutable</key>
<string>YoutubeDownloader</string>
<key>NSHumanReadableCopyright</key>
<string>© Oleksii Holub</string>
<key>CFBundleIdentifier</key>
<string>me.Tyrrrz.YoutubeDownloader</string>
<key>CFBundleSpokenName</key>
<string>YoutubeDownloader</string>
<key>CFBundleIconFile</key>
<string>AppIcon</string>
<key>CFBundleIconName</key>
<string>AppIcon</string>
<key>CFBundleVersion</key>
<string>$FullVersion</string>
<key>CFBundleShortVersionString</key>
<string>$ShortVersion</string>
<key>NSHighResolutionCapable</key>
<true />
<key>CFBundlePackageType</key>
<string>APPL</string>
</dict>
</plist>
"@
Set-Content -Path (Join-Path $contentsDirPath "Info.plist") -Value $plistContent
# Delete the previous bundle if it exists
if (Test-Path (Join-Path $PublishDirPath $bundleName)) {
Remove-Item -Path (Join-Path $PublishDirPath $bundleName) -Recurse -Force
}
# Move all files from the publish directory into the MacOS directory
Get-ChildItem -Path $PublishDirPath | ForEach-Object {
Move-Item -Path $_.FullName -Destination $macosDirPath -Force
}
# Move the final bundle into the publish directory for upload
Move-Item -Path $bundleDirPath -Destination $PublishDirPath -Force
}
finally {
# Clean up the temporary directory
Remove-Item -Path $tempDirPath -Recurse -Force
}

View File

@ -1,5 +1,4 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
@ -22,7 +21,6 @@ namespace YoutubeDownloader.ViewModels.Components;
public partial class DashboardViewModel : ViewModelBase
{
private readonly ViewModelManager _viewModelManager;
private readonly SnackbarManager _snackbarManager;
private readonly DialogManager _dialogManager;
private readonly SettingsService _settingsService;
@ -30,15 +28,24 @@ public partial class DashboardViewModel : ViewModelBase
private readonly ResizableSemaphore _downloadSemaphore = new();
private readonly AutoResetProgressMuxer _progressMuxer;
[ObservableProperty]
[NotifyPropertyChangedFor(nameof(IsProgressIndeterminate))]
[NotifyCanExecuteChangedFor(nameof(ProcessQueryCommand))]
[NotifyCanExecuteChangedFor(nameof(ShowAuthSetupCommand))]
[NotifyCanExecuteChangedFor(nameof(ShowSettingsCommand))]
public partial bool IsBusy { get; set; }
[ObservableProperty]
[NotifyCanExecuteChangedFor(nameof(ProcessQueryCommand))]
public partial string? Query { get; set; }
public DashboardViewModel(
ViewModelManager viewModelManager,
SnackbarManager snackbarManager,
DialogManager dialogManager,
SettingsService settingsService
)
{
_viewModelManager = viewModelManager;
_snackbarManager = snackbarManager;
_dialogManager = dialogManager;
_settingsService = settingsService;
@ -60,23 +67,12 @@ public partial class DashboardViewModel : ViewModelBase
);
}
[ObservableProperty]
[NotifyPropertyChangedFor(nameof(IsProgressIndeterminate))]
[NotifyCanExecuteChangedFor(nameof(ProcessQueryCommand))]
[NotifyCanExecuteChangedFor(nameof(ShowAuthSetupCommand))]
[NotifyCanExecuteChangedFor(nameof(ShowSettingsCommand))]
public partial bool IsBusy { get; set; }
public ProgressContainer<Percentage> Progress { get; } = new();
public bool IsProgressIndeterminate => IsBusy && Progress.Current.Fraction is <= 0 or >= 1;
[ObservableProperty]
[NotifyCanExecuteChangedFor(nameof(ProcessQueryCommand))]
public partial string? Query { get; set; }
public ObservableCollection<DownloadViewModel> Downloads { get; } = [];
public bool IsProgressIndeterminate => IsBusy && Progress.Current.Fraction is <= 0 or >= 1;
private bool CanShowAuthSetup() => !IsBusy;
[RelayCommand(CanExecute = nameof(CanShowAuthSetup))]
@ -183,41 +179,18 @@ public partial class DashboardViewModel : ViewModelBase
var resolver = new QueryResolver(_settingsService.LastAuthCookies);
var downloader = new VideoDownloader(_settingsService.LastAuthCookies);
// Split queries by newlines
var queries = Query.Split(
'\n',
StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries
var result = await resolver.ResolveAsync(
Query.Split(
"\n",
StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries
),
progress
);
// Process individual queries
var queryResults = new List<QueryResult>();
foreach (var (i, query) in queries.Index())
// Single video
if (result.Videos.Count == 1)
{
try
{
queryResults.Add(await resolver.ResolveAsync(query));
}
// If it's not the only query in the list, don't interrupt the process
// and report the error via an async notification instead of a sync dialog.
// https://github.com/Tyrrrz/YoutubeDownloader/issues/563
catch (YoutubeExplodeException ex)
when (ex is VideoUnavailableException or PlaylistUnavailableException
&& queries.Length > 1
)
{
_snackbarManager.Notify(ex.Message);
}
progress.Report(Percentage.FromFraction((i + 1.0) / queries.Length));
}
// Aggregate results
var queryResult = QueryResult.Aggregate(queryResults);
// Single video result
if (queryResult.Videos.Count == 1)
{
var video = queryResult.Videos.Single();
var video = result.Videos.Single();
var downloadOptions = await downloader.GetDownloadOptionsAsync(
video.Id,
@ -236,14 +209,14 @@ public partial class DashboardViewModel : ViewModelBase
Query = "";
}
// Multiple videos
else if (queryResult.Videos.Count > 1)
else if (result.Videos.Count > 1)
{
var downloads = await _dialogManager.ShowDialogAsync(
_viewModelManager.CreateDownloadMultipleSetupViewModel(
queryResult.Title,
queryResult.Videos,
result.Title,
result.Videos,
// Pre-select videos if they come from a single query and not from search
queryResult.Kind
result.Kind
is not QueryResultKind.Search
and not QueryResultKind.Aggregate
)

View File

@ -62,9 +62,7 @@ public partial class MainViewModel(
$"""
You're using a development build of {Program.Name}. These builds are not thoroughly tested and may contain bugs.
Auto-updates are disabled for development builds.
Click SEE RELEASES if you want to download a stable release instead.
Auto-updates are disabled for development builds. If you want to switch to a stable release, please download it manually.
""",
"SEE RELEASES",
"CLOSE"
@ -84,7 +82,7 @@ public partial class MainViewModel(
$"""
FFmpeg is required for {Program.Name} to work. Please download it and make it available in the application directory or on the system PATH.
Alternatively, you can also download a version of {Program.Name} that has FFmpeg bundled with it. Look for release assets that are NOT marked as *.Bare.
Alternatively, you can also download a version of {Program.Name} that has FFmpeg bundled with it.
Click DOWNLOAD to go to the FFmpeg download page.
""",

View File

@ -137,7 +137,7 @@
HorizontalScrollBarVisibility="Disabled"
IsVisible="{Binding !!Downloads.Count}"
ItemsSource="{Binding Downloads}"
VerticalScrollBarVisibility="Visible">
VerticalScrollBarVisibility="Auto">
<DataGrid.ContextMenu>
<ContextMenu>
<MenuItem Command="{Binding RemoveSuccessfulDownloadsCommand}" Header="Remove successful downloads" />

View File

@ -20,7 +20,7 @@
CloseOnClickAway="False"
DisableOpeningAnimation="True"
Loaded="DialogHost_OnLoaded">
<materialStyles:SnackbarHost HostName="Root" SnackbarMaxCounts="3">
<materialStyles:SnackbarHost HostName="Root">
<ContentControl Content="{Binding Dashboard}" />
</materialStyles:SnackbarHost>
</dialogHostAvalonia:DialogHost>

View File

@ -1,4 +1,5 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<ApplicationIcon>..\favicon.ico</ApplicationIcon>
@ -10,33 +11,29 @@
<BuiltInComInteropSupport>true</BuiltInComInteropSupport>
<AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>
</PropertyGroup>
<PropertyGroup>
<DownloadFFmpeg>true</DownloadFFmpeg>
</PropertyGroup>
<ItemGroup>
<AvaloniaResource Include="..\favicon.ico" Link="favicon.ico" />
</ItemGroup>
<ItemGroup>
<None
Include="ffmpeg.exe"
CopyToOutputDirectory="PreserveNewest"
Condition="Exists('ffmpeg.exe')"
/>
<None Include="ffmpeg.exe" CopyToOutputDirectory="PreserveNewest" Condition="Exists('ffmpeg.exe')" />
<None Include="ffmpeg" CopyToOutputDirectory="PreserveNewest" Condition="Exists('ffmpeg')" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="AsyncImageLoader.Avalonia" Version="3.3.0" />
<PackageReference Include="Avalonia" Version="11.3.0" />
<PackageReference Include="Avalonia.Controls.DataGrid" Version="11.3.0" />
<PackageReference Include="Avalonia.Desktop" Version="11.3.0" />
<PackageReference
Include="Avalonia.Diagnostics"
Version="11.3.0"
Condition="'$(Configuration)' == 'Debug'"
/>
<PackageReference Include="Avalonia" Version="11.2.6" />
<PackageReference Include="Avalonia.Controls.DataGrid" Version="11.2.6" />
<PackageReference Include="Avalonia.Desktop" Version="11.2.6" />
<PackageReference Include="Avalonia.Diagnostics" Version="11.2.6" Condition="'$(Configuration)' == 'Debug'" />
<PackageReference Include="Cogwheel" Version="2.1.0" />
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
<PackageReference Include="CSharpier.MsBuild" Version="1.0.2" PrivateAssets="all" />
<PackageReference Include="CSharpier.MsBuild" Version="0.30.6" PrivateAssets="all" />
<PackageReference Include="Deorcify" Version="1.1.0" PrivateAssets="all" />
<PackageReference Include="DialogHost.Avalonia" Version="0.9.2" />
<PackageReference Include="Gress" Version="2.1.1" />
@ -48,9 +45,11 @@
<PackageReference Include="WebView.Avalonia" Version="11.0.0.1" />
<PackageReference Include="WebView.Avalonia.Desktop" Version="11.0.0.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\YoutubeDownloader.Core\YoutubeDownloader.Core.csproj" />
</ItemGroup>
<!-- Avalonia.WebView is completely incompatible with trimming -->
<ItemGroup>
<TrimmerRootAssembly Include="Avalonia.WebView" />
@ -64,26 +63,10 @@
<TrimmerRootAssembly Include="WebView.Avalonia" />
<TrimmerRootAssembly Include="WebView.Core" />
</ItemGroup>
<Target
Name="DownloadFFmpeg"
BeforeTargets="Restore;PreBuildEvent"
Condition="$(DownloadFFmpeg) AND !Exists('ffmpeg.exe') AND !Exists('ffmpeg')"
>
<Exec
Command="pwsh -ExecutionPolicy Bypass -File $(ProjectDir)/Download-FFmpeg.ps1 -Platform $(RuntimeIdentifier) -OutputPath $(ProjectDir)"
LogStandardErrorAsError="true"
Condition="'$(RuntimeIdentifier)' != ''"
/>
<Exec
Command="pwsh -ExecutionPolicy Bypass -File $(ProjectDir)/Download-FFmpeg.ps1 -OutputPath $(ProjectDir)"
LogStandardErrorAsError="true"
Condition="'$(RuntimeIdentifier)' == ''"
/>
<Target Name="DownloadFFmpeg" BeforeTargets="Restore;PreBuildEvent" Condition="$(DownloadFFmpeg) AND !Exists('ffmpeg.exe') AND !Exists('ffmpeg')">
<Exec Command="pwsh -ExecutionPolicy Bypass -File $(ProjectDir)/Download-FFmpeg.ps1 -Platform $(RuntimeIdentifier) -OutputPath $(ProjectDir)" LogStandardErrorAsError="true" Condition="'$(RuntimeIdentifier)' != ''" />
<Exec Command="pwsh -ExecutionPolicy Bypass -File $(ProjectDir)/Download-FFmpeg.ps1 -OutputPath $(ProjectDir)" LogStandardErrorAsError="true" Condition="'$(RuntimeIdentifier)' == ''" />
</Target>
<Target Name="PublishMacOSBundle" AfterTargets="Publish" Condition="$(PublishMacOSBundle)">
<Exec
Command="pwsh -ExecutionPolicy Bypass -File $(ProjectDir)/Publish-MacOSBundle.ps1 -PublishDirPath $(PublishDir) -IconsFilePath $(ProjectDir)/../favicon.icns -FullVersion $(Version) -ShortVersion $(AssemblyVersion)"
LogStandardErrorAsError="true"
/>
</Target>
</Project>
</Project>

Binary file not shown.