Compare commits
1 Commits
master
...
ffmpeg-msb
Author | SHA1 | Date | |
---|---|---|---|
|
6dbfd290fa |
29
.github/workflows/main.yml
vendored
29
.github/workflows/main.yml
vendored
@ -60,8 +60,6 @@ jobs:
|
|||||||
- win-x86
|
- win-x86
|
||||||
- win-x64
|
- win-x64
|
||||||
- linux-arm64
|
- linux-arm64
|
||||||
# Linux x86 is not supported by .NET
|
|
||||||
# - linux-x86
|
|
||||||
- linux-x64
|
- linux-x64
|
||||||
- osx-arm64
|
- osx-arm64
|
||||||
- osx-x64
|
- osx-x64
|
||||||
@ -73,6 +71,10 @@ jobs:
|
|||||||
artifact-name-base: YoutubeDownloader
|
artifact-name-base: YoutubeDownloader
|
||||||
- bundle-ffmpeg: false
|
- bundle-ffmpeg: false
|
||||||
artifact-name-base: YoutubeDownloader.Bare
|
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' }}
|
runs-on: ${{ startsWith(matrix.rid, 'win-') && 'windows-latest' || startsWith(matrix.rid, 'osx-') && 'macos-latest' || 'ubuntu-latest' }}
|
||||||
timeout-minutes: 10
|
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:Version=${{ github.ref_type == 'tag' && github.ref_name || format('999.9.9-ci-{0}', github.sha) }}
|
||||||
-p:CSharpier_Bypass=true
|
-p:CSharpier_Bypass=true
|
||||||
-p:DownloadFFmpeg=${{ matrix.bundle-ffmpeg }}
|
-p:DownloadFFmpeg=${{ matrix.bundle-ffmpeg }}
|
||||||
-p:PublishMacOSBundle=${{ startsWith(matrix.rid, 'osx-') }}
|
|
||||||
--output YoutubeDownloader/bin/publish
|
--output YoutubeDownloader/bin/publish
|
||||||
--configuration Release
|
--configuration Release
|
||||||
--runtime ${{ matrix.rid }}
|
--runtime ${{ matrix.rid }}
|
||||||
--self-contained
|
--self-contained
|
||||||
|
|
||||||
- name: Upload app binaries
|
- name: Upload artifacts
|
||||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||||
with:
|
with:
|
||||||
name: ${{ matrix.artifact-name-base }}.${{ matrix.rid }}
|
name: ${{ matrix.artifact-name-base }}.${{ matrix.rid }}
|
||||||
@ -143,8 +144,6 @@ jobs:
|
|||||||
- win-x86
|
- win-x86
|
||||||
- win-x64
|
- win-x64
|
||||||
- linux-arm64
|
- linux-arm64
|
||||||
# Linux x86 is not supported by .NET
|
|
||||||
# - linux-x86
|
|
||||||
- linux-x64
|
- linux-x64
|
||||||
- osx-arm64
|
- osx-arm64
|
||||||
- osx-x64
|
- osx-x64
|
||||||
@ -156,6 +155,10 @@ jobs:
|
|||||||
artifact-name-base: YoutubeDownloader
|
artifact-name-base: YoutubeDownloader
|
||||||
- bundle-ffmpeg: false
|
- bundle-ffmpeg: false
|
||||||
artifact-name-base: YoutubeDownloader.Bare
|
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
|
runs-on: ubuntu-latest
|
||||||
timeout-minutes: 10
|
timeout-minutes: 10
|
||||||
@ -165,21 +168,19 @@ jobs:
|
|||||||
contents: write
|
contents: write
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Download app binaries
|
- name: Download artifacts
|
||||||
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
||||||
with:
|
with:
|
||||||
name: ${{ matrix.artifact-name-base }}.${{ matrix.rid }}
|
name: ${{ matrix.artifact-name-base }}.${{ matrix.rid }}
|
||||||
path: YoutubeDownloader/
|
path: YoutubeDownloader/
|
||||||
|
|
||||||
- name: Set permissions
|
- name: Set permissions (app)
|
||||||
if: ${{ !startsWith(matrix.rid, 'win-') }}
|
if: ${{ !startsWith(matrix.rid, 'win-') }}
|
||||||
run: |
|
run: chmod +x YoutubeDownloader/YoutubeDownloader
|
||||||
[ -f YoutubeDownloader/YoutubeDownloader ] && chmod +x YoutubeDownloader/YoutubeDownloader
|
|
||||||
[ -f YoutubeDownloader/ffmpeg ] && chmod +x YoutubeDownloader/ffmpeg
|
|
||||||
|
|
||||||
# macOS bundle
|
- name: Set permissions (FFmpeg)
|
||||||
[ -f YoutubeDownloader/YoutubeDownloader.app/Contents/MacOS/YoutubeDownloader] && chmod +x YoutubeDownloader/YoutubeDownloader.app/Contents/MacOS/YoutubeDownloader
|
if: ${{ !startsWith(matrix.rid, 'win-') && matrix.bundle-ffmpeg }}
|
||||||
[ -f YoutubeDownloader/YoutubeDownloader.app/Contents/MacOS/ffmpeg ] && chmod +x YoutubeDownloader/YoutubeDownloader.app/Contents/MacOS/ffmpeg
|
run: chmod +x YoutubeDownloader/ffmpeg
|
||||||
|
|
||||||
- name: Create package
|
- name: Create package
|
||||||
# Change into the artifacts directory to avoid including the directory itself in the zip archive
|
# Change into the artifacts directory to avoid including the directory itself in the zip archive
|
||||||
|
3
.gitignore
vendored
3
.gitignore
vendored
@ -8,8 +8,5 @@
|
|||||||
bin/
|
bin/
|
||||||
obj/
|
obj/
|
||||||
|
|
||||||
# Avalonia
|
|
||||||
.avalonia-build-tasks/
|
|
||||||
|
|
||||||
# Test results
|
# Test results
|
||||||
TestResults/
|
TestResults/
|
@ -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)**
|
- 🟢 **[Stable release](https://github.com/Tyrrrz/YoutubeDownloader/releases/latest)**
|
||||||
- 🟠 [CI build](https://github.com/Tyrrrz/YoutubeDownloader/actions/workflows/main.yml)
|
- 🟠 [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**:
|
> **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.
|
> 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.
|
||||||
|
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
using System.Collections.Generic;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Gress;
|
||||||
using YoutubeDownloader.Core.Utils;
|
using YoutubeDownloader.Core.Utils;
|
||||||
using YoutubeExplode;
|
using YoutubeExplode;
|
||||||
using YoutubeExplode.Channels;
|
using YoutubeExplode.Channels;
|
||||||
@ -120,4 +122,33 @@ public class QueryResolver(IReadOnlyList<Cookie>? initialCookies = null)
|
|||||||
?? await TryResolveChannelAsync(query, cancellationToken)
|
?? await TryResolveChannelAsync(query, cancellationToken)
|
||||||
?? await ResolveSearchAsync(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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,28 +1,6 @@
|
|||||||
using System;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using YoutubeExplode.Videos;
|
using YoutubeExplode.Videos;
|
||||||
|
|
||||||
namespace YoutubeDownloader.Core.Resolving;
|
namespace YoutubeDownloader.Core.Resolving;
|
||||||
|
|
||||||
public record QueryResult(QueryResultKind Kind, string Title, IReadOnlyList<IVideo> Videos)
|
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()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<ItemGroup>
|
<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="Gress" Version="2.1.1" />
|
||||||
<PackageReference Include="JsonExtensions" Version="1.2.0" />
|
<PackageReference Include="JsonExtensions" Version="1.2.0" />
|
||||||
<PackageReference Include="TagLibSharp" Version="2.3.0" />
|
<PackageReference Include="TagLibSharp" Version="2.3.0" />
|
||||||
<PackageReference Include="YoutubeExplode" Version="6.5.4" />
|
<PackageReference Include="YoutubeExplode" Version="6.5.4" />
|
||||||
<PackageReference Include="YoutubeExplode.Converter" Version="6.5.4" />
|
<PackageReference Include="YoutubeExplode.Converter" Version="6.5.4" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
@ -5,7 +5,7 @@ VisualStudioVersion = 17.7.33920.267
|
|||||||
MinimumVisualStudioVersion = 10.0.40219.1
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "YoutubeDownloader", "YoutubeDownloader\YoutubeDownloader.csproj", "{AF6D645E-DDDD-4034-B644-D5328CC893C1}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "YoutubeDownloader", "YoutubeDownloader\YoutubeDownloader.csproj", "{AF6D645E-DDDD-4034-B644-D5328CC893C1}"
|
||||||
EndProject
|
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
|
ProjectSection(SolutionItems) = preProject
|
||||||
Directory.Build.props = Directory.Build.props
|
Directory.Build.props = Directory.Build.props
|
||||||
License.txt = License.txt
|
License.txt = License.txt
|
||||||
|
@ -8,7 +8,6 @@
|
|||||||
xmlns:materialControls="clr-namespace:Material.Styles.Controls;assembly=Material.Styles"
|
xmlns:materialControls="clr-namespace:Material.Styles.Controls;assembly=Material.Styles"
|
||||||
xmlns:materialIcons="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
|
xmlns:materialIcons="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
|
||||||
xmlns:materialStyles="clr-namespace:Material.Styles.Themes;assembly=Material.Styles"
|
xmlns:materialStyles="clr-namespace:Material.Styles.Themes;assembly=Material.Styles"
|
||||||
Name="YoutubeDownloader"
|
|
||||||
ActualThemeVariantChanged="Application_OnActualThemeVariantChanged">
|
ActualThemeVariantChanged="Application_OnActualThemeVariantChanged">
|
||||||
<Application.DataTemplates>
|
<Application.DataTemplates>
|
||||||
<framework:ViewManager />
|
<framework:ViewManager />
|
||||||
|
@ -46,7 +46,7 @@ if (Test-Path $OutputPath) {
|
|||||||
Write-Host "Downloading FFmpeg for $Platform..."
|
Write-Host "Downloading FFmpeg for $Platform..."
|
||||||
$http = New-Object System.Net.WebClient
|
$http = New-Object System.Net.WebClient
|
||||||
try {
|
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 {
|
} finally {
|
||||||
$http.Dispose()
|
$http.Dispose()
|
||||||
}
|
}
|
||||||
|
@ -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 = "")
|
public async Task<string?> PromptDirectoryPathAsync(string defaultDirPath = "")
|
||||||
@ -74,21 +74,19 @@ public class DialogManager : IDisposable
|
|||||||
Application.Current?.ApplicationLifetime?.TryGetTopLevel()
|
Application.Current?.ApplicationLifetime?.TryGetTopLevel()
|
||||||
?? throw new ApplicationException("Could not find the top-level visual element.");
|
?? 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
|
new FolderPickerOpenOptions
|
||||||
{
|
{
|
||||||
AllowMultiple = false,
|
AllowMultiple = false,
|
||||||
SuggestedStartLocation = await topLevel.StorageProvider.TryGetFolderFromPathAsync(
|
SuggestedStartLocation = startLocation,
|
||||||
defaultDirPath
|
|
||||||
),
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
var directory = result.FirstOrDefault();
|
return folderPickResult.FirstOrDefault()?.Path.LocalPath;
|
||||||
if (directory is null)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
return directory.TryGetLocalPath() ?? directory.Path.ToString();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose() => _dialogLock.Dispose();
|
public void Dispose() => _dialogLock.Dispose();
|
||||||
|
@ -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
|
|
||||||
}
|
|
@ -1,5 +1,4 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
@ -22,7 +21,6 @@ namespace YoutubeDownloader.ViewModels.Components;
|
|||||||
public partial class DashboardViewModel : ViewModelBase
|
public partial class DashboardViewModel : ViewModelBase
|
||||||
{
|
{
|
||||||
private readonly ViewModelManager _viewModelManager;
|
private readonly ViewModelManager _viewModelManager;
|
||||||
private readonly SnackbarManager _snackbarManager;
|
|
||||||
private readonly DialogManager _dialogManager;
|
private readonly DialogManager _dialogManager;
|
||||||
private readonly SettingsService _settingsService;
|
private readonly SettingsService _settingsService;
|
||||||
|
|
||||||
@ -30,15 +28,24 @@ public partial class DashboardViewModel : ViewModelBase
|
|||||||
private readonly ResizableSemaphore _downloadSemaphore = new();
|
private readonly ResizableSemaphore _downloadSemaphore = new();
|
||||||
private readonly AutoResetProgressMuxer _progressMuxer;
|
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(
|
public DashboardViewModel(
|
||||||
ViewModelManager viewModelManager,
|
ViewModelManager viewModelManager,
|
||||||
SnackbarManager snackbarManager,
|
|
||||||
DialogManager dialogManager,
|
DialogManager dialogManager,
|
||||||
SettingsService settingsService
|
SettingsService settingsService
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
_viewModelManager = viewModelManager;
|
_viewModelManager = viewModelManager;
|
||||||
_snackbarManager = snackbarManager;
|
|
||||||
_dialogManager = dialogManager;
|
_dialogManager = dialogManager;
|
||||||
_settingsService = settingsService;
|
_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 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 ObservableCollection<DownloadViewModel> Downloads { get; } = [];
|
||||||
|
|
||||||
|
public bool IsProgressIndeterminate => IsBusy && Progress.Current.Fraction is <= 0 or >= 1;
|
||||||
|
|
||||||
private bool CanShowAuthSetup() => !IsBusy;
|
private bool CanShowAuthSetup() => !IsBusy;
|
||||||
|
|
||||||
[RelayCommand(CanExecute = nameof(CanShowAuthSetup))]
|
[RelayCommand(CanExecute = nameof(CanShowAuthSetup))]
|
||||||
@ -183,41 +179,18 @@ public partial class DashboardViewModel : ViewModelBase
|
|||||||
var resolver = new QueryResolver(_settingsService.LastAuthCookies);
|
var resolver = new QueryResolver(_settingsService.LastAuthCookies);
|
||||||
var downloader = new VideoDownloader(_settingsService.LastAuthCookies);
|
var downloader = new VideoDownloader(_settingsService.LastAuthCookies);
|
||||||
|
|
||||||
// Split queries by newlines
|
var result = await resolver.ResolveAsync(
|
||||||
var queries = Query.Split(
|
Query.Split(
|
||||||
'\n',
|
"\n",
|
||||||
StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries
|
StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries
|
||||||
|
),
|
||||||
|
progress
|
||||||
);
|
);
|
||||||
|
|
||||||
// Process individual queries
|
// Single video
|
||||||
var queryResults = new List<QueryResult>();
|
if (result.Videos.Count == 1)
|
||||||
foreach (var (i, query) in queries.Index())
|
|
||||||
{
|
{
|
||||||
try
|
var video = result.Videos.Single();
|
||||||
{
|
|
||||||
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 downloadOptions = await downloader.GetDownloadOptionsAsync(
|
var downloadOptions = await downloader.GetDownloadOptionsAsync(
|
||||||
video.Id,
|
video.Id,
|
||||||
@ -236,14 +209,14 @@ public partial class DashboardViewModel : ViewModelBase
|
|||||||
Query = "";
|
Query = "";
|
||||||
}
|
}
|
||||||
// Multiple videos
|
// Multiple videos
|
||||||
else if (queryResult.Videos.Count > 1)
|
else if (result.Videos.Count > 1)
|
||||||
{
|
{
|
||||||
var downloads = await _dialogManager.ShowDialogAsync(
|
var downloads = await _dialogManager.ShowDialogAsync(
|
||||||
_viewModelManager.CreateDownloadMultipleSetupViewModel(
|
_viewModelManager.CreateDownloadMultipleSetupViewModel(
|
||||||
queryResult.Title,
|
result.Title,
|
||||||
queryResult.Videos,
|
result.Videos,
|
||||||
// Pre-select videos if they come from a single query and not from search
|
// Pre-select videos if they come from a single query and not from search
|
||||||
queryResult.Kind
|
result.Kind
|
||||||
is not QueryResultKind.Search
|
is not QueryResultKind.Search
|
||||||
and not QueryResultKind.Aggregate
|
and not QueryResultKind.Aggregate
|
||||||
)
|
)
|
||||||
|
@ -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.
|
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.
|
Auto-updates are disabled for development builds. If you want to switch to a stable release, please download it manually.
|
||||||
|
|
||||||
Click SEE RELEASES if you want to download a stable release instead.
|
|
||||||
""",
|
""",
|
||||||
"SEE RELEASES",
|
"SEE RELEASES",
|
||||||
"CLOSE"
|
"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.
|
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.
|
Click DOWNLOAD to go to the FFmpeg download page.
|
||||||
""",
|
""",
|
||||||
|
@ -137,7 +137,7 @@
|
|||||||
HorizontalScrollBarVisibility="Disabled"
|
HorizontalScrollBarVisibility="Disabled"
|
||||||
IsVisible="{Binding !!Downloads.Count}"
|
IsVisible="{Binding !!Downloads.Count}"
|
||||||
ItemsSource="{Binding Downloads}"
|
ItemsSource="{Binding Downloads}"
|
||||||
VerticalScrollBarVisibility="Visible">
|
VerticalScrollBarVisibility="Auto">
|
||||||
<DataGrid.ContextMenu>
|
<DataGrid.ContextMenu>
|
||||||
<ContextMenu>
|
<ContextMenu>
|
||||||
<MenuItem Command="{Binding RemoveSuccessfulDownloadsCommand}" Header="Remove successful downloads" />
|
<MenuItem Command="{Binding RemoveSuccessfulDownloadsCommand}" Header="Remove successful downloads" />
|
||||||
|
@ -20,7 +20,7 @@
|
|||||||
CloseOnClickAway="False"
|
CloseOnClickAway="False"
|
||||||
DisableOpeningAnimation="True"
|
DisableOpeningAnimation="True"
|
||||||
Loaded="DialogHost_OnLoaded">
|
Loaded="DialogHost_OnLoaded">
|
||||||
<materialStyles:SnackbarHost HostName="Root" SnackbarMaxCounts="3">
|
<materialStyles:SnackbarHost HostName="Root">
|
||||||
<ContentControl Content="{Binding Dashboard}" />
|
<ContentControl Content="{Binding Dashboard}" />
|
||||||
</materialStyles:SnackbarHost>
|
</materialStyles:SnackbarHost>
|
||||||
</dialogHostAvalonia:DialogHost>
|
</dialogHostAvalonia:DialogHost>
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<OutputType>WinExe</OutputType>
|
<OutputType>WinExe</OutputType>
|
||||||
<ApplicationIcon>..\favicon.ico</ApplicationIcon>
|
<ApplicationIcon>..\favicon.ico</ApplicationIcon>
|
||||||
@ -10,33 +11,29 @@
|
|||||||
<BuiltInComInteropSupport>true</BuiltInComInteropSupport>
|
<BuiltInComInteropSupport>true</BuiltInComInteropSupport>
|
||||||
<AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>
|
<AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<DownloadFFmpeg>true</DownloadFFmpeg>
|
<DownloadFFmpeg>true</DownloadFFmpeg>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<AvaloniaResource Include="..\favicon.ico" Link="favicon.ico" />
|
<AvaloniaResource Include="..\favicon.ico" Link="favicon.ico" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<None
|
<None Include="ffmpeg.exe" CopyToOutputDirectory="PreserveNewest" Condition="Exists('ffmpeg.exe')" />
|
||||||
Include="ffmpeg.exe"
|
|
||||||
CopyToOutputDirectory="PreserveNewest"
|
|
||||||
Condition="Exists('ffmpeg.exe')"
|
|
||||||
/>
|
|
||||||
<None Include="ffmpeg" CopyToOutputDirectory="PreserveNewest" Condition="Exists('ffmpeg')" />
|
<None Include="ffmpeg" CopyToOutputDirectory="PreserveNewest" Condition="Exists('ffmpeg')" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="AsyncImageLoader.Avalonia" Version="3.3.0" />
|
<PackageReference Include="AsyncImageLoader.Avalonia" Version="3.3.0" />
|
||||||
<PackageReference Include="Avalonia" Version="11.3.0" />
|
<PackageReference Include="Avalonia" Version="11.2.6" />
|
||||||
<PackageReference Include="Avalonia.Controls.DataGrid" Version="11.3.0" />
|
<PackageReference Include="Avalonia.Controls.DataGrid" Version="11.2.6" />
|
||||||
<PackageReference Include="Avalonia.Desktop" Version="11.3.0" />
|
<PackageReference Include="Avalonia.Desktop" Version="11.2.6" />
|
||||||
<PackageReference
|
<PackageReference Include="Avalonia.Diagnostics" Version="11.2.6" Condition="'$(Configuration)' == 'Debug'" />
|
||||||
Include="Avalonia.Diagnostics"
|
|
||||||
Version="11.3.0"
|
|
||||||
Condition="'$(Configuration)' == 'Debug'"
|
|
||||||
/>
|
|
||||||
<PackageReference Include="Cogwheel" Version="2.1.0" />
|
<PackageReference Include="Cogwheel" Version="2.1.0" />
|
||||||
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.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="Deorcify" Version="1.1.0" PrivateAssets="all" />
|
||||||
<PackageReference Include="DialogHost.Avalonia" Version="0.9.2" />
|
<PackageReference Include="DialogHost.Avalonia" Version="0.9.2" />
|
||||||
<PackageReference Include="Gress" Version="2.1.1" />
|
<PackageReference Include="Gress" Version="2.1.1" />
|
||||||
@ -48,9 +45,11 @@
|
|||||||
<PackageReference Include="WebView.Avalonia" Version="11.0.0.1" />
|
<PackageReference Include="WebView.Avalonia" Version="11.0.0.1" />
|
||||||
<PackageReference Include="WebView.Avalonia.Desktop" Version="11.0.0.1" />
|
<PackageReference Include="WebView.Avalonia.Desktop" Version="11.0.0.1" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\YoutubeDownloader.Core\YoutubeDownloader.Core.csproj" />
|
<ProjectReference Include="..\YoutubeDownloader.Core\YoutubeDownloader.Core.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<!-- Avalonia.WebView is completely incompatible with trimming -->
|
<!-- Avalonia.WebView is completely incompatible with trimming -->
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<TrimmerRootAssembly Include="Avalonia.WebView" />
|
<TrimmerRootAssembly Include="Avalonia.WebView" />
|
||||||
@ -64,26 +63,10 @@
|
|||||||
<TrimmerRootAssembly Include="WebView.Avalonia" />
|
<TrimmerRootAssembly Include="WebView.Avalonia" />
|
||||||
<TrimmerRootAssembly Include="WebView.Core" />
|
<TrimmerRootAssembly Include="WebView.Core" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<Target
|
|
||||||
Name="DownloadFFmpeg"
|
<Target Name="DownloadFFmpeg" BeforeTargets="Restore;PreBuildEvent" Condition="$(DownloadFFmpeg) AND !Exists('ffmpeg.exe') AND !Exists('ffmpeg')">
|
||||||
BeforeTargets="Restore;PreBuildEvent"
|
<Exec Command="pwsh -ExecutionPolicy Bypass -File $(ProjectDir)/Download-FFmpeg.ps1 -Platform $(RuntimeIdentifier) -OutputPath $(ProjectDir)" LogStandardErrorAsError="true" Condition="'$(RuntimeIdentifier)' != ''" />
|
||||||
Condition="$(DownloadFFmpeg) AND !Exists('ffmpeg.exe') AND !Exists('ffmpeg')"
|
<Exec Command="pwsh -ExecutionPolicy Bypass -File $(ProjectDir)/Download-FFmpeg.ps1 -OutputPath $(ProjectDir)" LogStandardErrorAsError="true" Condition="'$(RuntimeIdentifier)' == ''" />
|
||||||
>
|
|
||||||
<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>
|
</Target>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
BIN
favicon.icns
BIN
favicon.icns
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user