Merge pull request #41 from mateuszskoczek/features/playlist_search

Features/playlist search
This commit is contained in:
2024-03-02 19:51:02 +01:00
committed by GitHub
Unverified
50 changed files with 1231 additions and 78 deletions

View File

@@ -0,0 +1,207 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="ApplyDirectoryButton.ToolTipService.ToolTip" xml:space="preserve">
<value>Selected directory will be applied to all videos in playlist</value>
</data>
<data name="CreateAndStartButton.Content" xml:space="preserve">
<value>Create download tasks and start</value>
</data>
<data name="CreateButton.Content" xml:space="preserve">
<value>Create download tasks</value>
</data>
<data name="DirectorySettingsCard.Header" xml:space="preserve">
<value>Directory</value>
</data>
<data name="DirectorySettingsCardButton.Content" xml:space="preserve">
<value>Browse</value>
</data>
<data name="FilenameSettingsCard.Header" xml:space="preserve">
<value>Filename</value>
</data>
<data name="FileOptionsHeader.Text" xml:space="preserve">
<value>File options</value>
</data>
<data name="FileTypeSettingsCard.Description" xml:space="preserve">
<value>If original video is not in selected type, it will be converted</value>
</data>
<data name="FileTypeSettingsCard.Header" xml:space="preserve">
<value>File type</value>
</data>
<data name="FilterAuthorTextBlock.Text" xml:space="preserve">
<value>Author</value>
</data>
<data name="FilterAuthorTextBox.PlaceholderText" xml:space="preserve">
<value>Enter regular expression</value>
</data>
<data name="FilterDateTextBlock.Text" xml:space="preserve">
<value>Date</value>
</data>
<data name="FilterDurationTextBlock.Text" xml:space="preserve">
<value>Duration</value>
</data>
<data name="FilterRemovedButton.Content" xml:space="preserve">
<value>Restore</value>
</data>
<data name="FilterRemovedTextBlock.Text" xml:space="preserve">
<value>Removed</value>
</data>
<data name="FilterTitleTextBlock.Text" xml:space="preserve">
<value>Title</value>
</data>
<data name="FilterTitleTextBox.PlaceholderText" xml:space="preserve">
<value>Enter regular expression</value>
</data>
<data name="FilterViewsTextBlock.Text" xml:space="preserve">
<value>Views</value>
</data>
<data name="FilterWindow.Title" xml:space="preserve">
<value>Filter</value>
</data>
<data name="HiddenTextBlock.Text" xml:space="preserve">
<value>Hidden: </value>
</data>
<data name="MediaOptionsHeader.Text" xml:space="preserve">
<value>Media options</value>
</data>
<data name="MediaTypeOnlyAudio.Text" xml:space="preserve">
<value>Only audio</value>
</data>
<data name="MediaTypeOnlyVideo.Text" xml:space="preserve">
<value>Only video</value>
</data>
<data name="MediaTypeOriginal.Text" xml:space="preserve">
<value>Original</value>
</data>
<data name="MediaTypeSettingsCard.Header" xml:space="preserve">
<value>Media type</value>
</data>
<data name="QualitySettingsCard.Header" xml:space="preserve">
<value>Quality</value>
</data>
<data name="TrimEndSettingsCard.Header" xml:space="preserve">
<value>End at</value>
</data>
<data name="TrimSettingsGroup.Header" xml:space="preserve">
<value>Trim</value>
</data>
<data name="TrimStartSettingsCard.Header" xml:space="preserve">
<value>Start at</value>
</data>
</root>

View File

@@ -10,8 +10,8 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.4.240211001" />
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.22621.2428" />
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.5.240227000" />
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.22621.3233" />
</ItemGroup>
<ItemGroup>

View File

@@ -140,7 +140,7 @@ namespace VDownload.Core.Tasks
await _settingsService.Load();
string tempDirectory = $"{_settingsService.Data.Common.Temp.Directory}\\{_configurationService.Common.Path.Temp.TasksDirectory}\\{_id}";
string tempDirectory = $"{_settingsService.Data.Common.Temp.Directory}\\{_configurationService.Common.Path.Temp.TasksDirectory}\\{Id}";
Directory.CreateDirectory(tempDirectory);
List<string> content = new List<string>()

View File

@@ -0,0 +1,146 @@
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using VDownload.Models;
using VDownload.Services.Data.Settings;
using VDownload.Services.UI.StoragePicker;
namespace VDownload.Core.ViewModels.Home.Helpers
{
public partial class VideoViewModel : ObservableObject
{
#region SERVICES
protected readonly ISettingsService _settingsService;
protected readonly IStoragePickerService _storagePickerService;
#endregion
#region PROPERTIES
[ObservableProperty]
protected Uri _thumbnailUrl;
[ObservableProperty]
protected string _title;
[ObservableProperty]
protected string _author;
[ObservableProperty]
protected DateTime _publishDate;
[ObservableProperty]
protected TimeSpan _duration;
[ObservableProperty]
protected long _views;
[ObservableProperty]
protected ObservableCollection<VideoStream> _streams;
[ObservableProperty]
protected VideoStream _selectedStream;
[ObservableProperty]
protected MediaType _mediaType;
[ObservableProperty]
protected TimeSpan _trimStart;
[ObservableProperty]
protected TimeSpan _trimEnd;
[ObservableProperty]
protected string _directoryPath;
[ObservableProperty]
protected string _filename;
[ObservableProperty]
protected VideoExtension _videoExtension;
[ObservableProperty]
protected AudioExtension _audioExtension;
public Video Video { get; protected set; }
#endregion
#region CONSTRUCTORS
public VideoViewModel(Video video, ISettingsService settingsService, IStoragePickerService storagePickerService)
{
_settingsService = settingsService;
_storagePickerService = storagePickerService;
Video = video;
ThumbnailUrl = video.ThumbnailUrl;
Title = video.Title;
Author = video.Author;
PublishDate = video.PublishDate;
Duration = video.Duration;
Views = video.Views;
Streams = [.. video.Streams];
SelectedStream = Streams[0];
MediaType = _settingsService.Data.Common.Tasks.DefaultMediaType;
TrimStart = TimeSpan.Zero;
TrimEnd = Duration;
DirectoryPath = _settingsService.Data.Common.Tasks.DefaultOutputDirectory;
Filename = Title.Length > 50 ? Title.Substring(0, 50) : Title;
VideoExtension = _settingsService.Data.Common.Tasks.DefaultVideoExtension;
AudioExtension = _settingsService.Data.Common.Tasks.DefaultAudioExtension;
}
#endregion
#region PUBLIC METHODS
public VideoDownloadOptions BuildDownloadOptions()
{
return new VideoDownloadOptions(Duration)
{
MediaType = this.MediaType,
SelectedStream = this.SelectedStream,
TrimStart = this.TrimStart,
TrimEnd = this.TrimEnd,
Directory = this.DirectoryPath,
Filename = string.Join("_", this.Filename.Split(Path.GetInvalidFileNameChars())),
Extension = (this.MediaType == MediaType.OnlyAudio ? this.AudioExtension.ToString() : this.VideoExtension.ToString()).ToLower(),
};
}
#endregion
#region COMMANDS
[RelayCommand]
public async Task Browse()
{
string? newDirectory = await _storagePickerService.OpenDirectory();
if (newDirectory is not null)
{
this.DirectoryPath = newDirectory;
}
}
#endregion
}
}

View File

@@ -1,12 +1,21 @@
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using VDownload.Core.Tasks;
using VDownload.Core.ViewModels.Home.Helpers;
using VDownload.Models;
using VDownload.Services.Data.Settings;
using VDownload.Services.UI.StoragePicker;
using VDownload.Sources.Twitch.Configuration.Models;
using SimpleToolkit.MVVM;
using System.Text.RegularExpressions;
namespace VDownload.Core.ViewModels.Home
{
@@ -14,7 +23,10 @@ namespace VDownload.Core.ViewModels.Home
{
#region SERVICES
protected readonly IDownloadTaskManager _tasksManager;
protected readonly ISettingsService _settingsService;
protected readonly IStoragePickerService _storagePickerService;
#endregion
@@ -22,7 +34,9 @@ namespace VDownload.Core.ViewModels.Home
#region FIELDS
protected Playlist _playlist;
protected List<VideoViewModel> _removedVideos;
#endregion
@@ -33,14 +47,140 @@ namespace VDownload.Core.ViewModels.Home
[ObservableProperty]
protected string _name;
[ObservableProperty]
protected ObservableDictionary<VideoViewModel, bool> _videos;
[ObservableProperty]
protected int _removedCount;
[ObservableProperty]
protected int _hiddenCount;
[ObservableProperty]
protected bool _isSomethingHidden;
public string TitleFilter
{
get => _titleFilter;
set
{
SetProperty(ref _titleFilter, value, nameof(TitleFilter));
UpdateFilter();
}
}
protected string _titleFilter;
public string AuthorFilter
{
get => _authorFilter;
set
{
SetProperty(ref _authorFilter, value, nameof(AuthorFilter));
UpdateFilter();
}
}
protected string _authorFilter;
public long MinViewsFilter
{
get => _minViewsFilter;
set
{
SetProperty(ref _minViewsFilter, value, nameof(MinViewsFilter));
UpdateFilter();
}
}
protected long _minViewsFilter;
public long MaxViewsFilter
{
get => _maxViewsFilter;
set
{
SetProperty(ref _maxViewsFilter, value, nameof(MaxViewsFilter));
UpdateFilter();
}
}
protected long _maxViewsFilter;
[ObservableProperty]
protected long _minViews;
[ObservableProperty]
protected long _maxViews;
public DateTimeOffset MinDateFilter
{
get => _minDateFilter;
set
{
SetProperty(ref _minDateFilter, value, nameof(MinDateFilter));
UpdateFilter();
}
}
protected DateTimeOffset _minDateFilter;
public DateTimeOffset MaxDateFilter
{
get => _maxDateFilter;
set
{
SetProperty(ref _maxDateFilter, value, nameof(MaxDateFilter));
UpdateFilter();
}
}
protected DateTimeOffset _maxDateFilter;
[ObservableProperty]
protected DateTimeOffset _minDate;
[ObservableProperty]
protected DateTimeOffset _maxDate;
public TimeSpan MinDurationFilter
{
get => _minDurationFilter;
set
{
SetProperty(ref _minDurationFilter, value, nameof(MinDurationFilter));
UpdateFilter();
}
}
protected TimeSpan _minDurationFilter;
public TimeSpan MaxDurationFilter
{
get => _maxDurationFilter;
set
{
SetProperty(ref _maxDurationFilter, value, nameof(MaxDurationFilter));
UpdateFilter();
}
}
protected TimeSpan _maxDurationFilter;
[ObservableProperty]
protected TimeSpan _minDuration;
[ObservableProperty]
protected TimeSpan _maxDuration;
#endregion
#region CONSTRUCTORS
public HomePlaylistViewModel()
{
public HomePlaylistViewModel(IDownloadTaskManager tasksManager, ISettingsService settingsService, IStoragePickerService storagePickerService)
{
_tasksManager = tasksManager;
_settingsService = settingsService;
_storagePickerService = storagePickerService;
_removedVideos = new List<VideoViewModel>();
_videos = new ObservableDictionary<VideoViewModel, bool>();
}
#endregion
@@ -49,9 +189,41 @@ namespace VDownload.Core.ViewModels.Home
#region PUBLIC METHODS
public async Task LoadPlaylist(Playlist playlist)
public void LoadPlaylist(Playlist playlist)
{
Name = playlist.Name;
_playlist = playlist;
ParallelQuery<Video> playlistQuery = playlist.AsParallel();
_removedVideos.Clear();
_titleFilter = string.Empty;
_authorFilter = string.Empty;
IEnumerable<long> views = playlistQuery.Select(x => x.Views);
MinViews = views.Min();
MaxViews = views.Max();
_minViewsFilter = MinViews;
_maxViewsFilter = MaxViews;
IEnumerable<DateTimeOffset> date = playlist.Select(x => new DateTimeOffset(x.PublishDate));
MinDate = date.Min();
MaxDate = date.Max();
_minDateFilter = MinDate;
_maxDateFilter = MaxDate;
IEnumerable<TimeSpan> duration = playlistQuery.Select(x => x.Duration);
MinDuration = duration.Min();
MaxDuration = duration.Max();
_minDurationFilter = MinDuration;
_maxDurationFilter = MaxDuration;
Name = _playlist.Name;
Videos.Clear();
foreach (Video video in playlist)
{
Videos.Add(new VideoViewModel(video, _settingsService, _storagePickerService), true);
}
UpdateFilter();
}
#endregion
@@ -60,7 +232,105 @@ namespace VDownload.Core.ViewModels.Home
#region COMMANDS
[RelayCommand]
public async Task SelectDirectory()
{
string? newDirectory = await _storagePickerService.OpenDirectory();
if (newDirectory is not null)
{
foreach (VideoViewModel video in Videos.Keys)
{
video.DirectoryPath = newDirectory;
}
}
}
[RelayCommand]
public void RemoveVideo(VideoViewModel video)
{
Videos[video] = false;
_removedVideos.Add(video);
UpdateFilter();
}
[RelayCommand]
public void RestoreRemovedVideos()
{
foreach(VideoViewModel video in _removedVideos)
{
Videos[video] = true;
}
_removedVideos.Clear();
UpdateFilter();
}
[RelayCommand]
public void CreateTasksAndDownload() => CreateTasks(true);
[RelayCommand]
public void CreateTasks() => CreateTasks(false);
#endregion
#region PRIVATE METHODS
protected void CreateTasks(bool download)
{
IEnumerable<VideoViewModel> videos = Videos.Cast<ObservableKeyValuePair<VideoViewModel, bool>>()
.Where(x => x.Value)
.Select(x => x.Key);
foreach (VideoViewModel video in videos)
{
DownloadTask task = _tasksManager.AddTask(video.Video, video.BuildDownloadOptions());
if (download)
{
task.Enqueue();
}
}
CloseRequested?.Invoke(this, EventArgs.Empty);
}
protected void UpdateFilter()
{
Regex titleRegex = new Regex(TitleFilter);
Regex authorRegex = new Regex(AuthorFilter);
foreach (ObservableKeyValuePair<VideoViewModel, bool> item in Videos)
{
VideoViewModel video = item.Key;
bool hide =
(
_removedVideos.Contains(video)
||
!titleRegex.IsMatch(video.Title)
||
!authorRegex.IsMatch(video.Author)
||
MinViewsFilter > video.Views
||
MaxViewsFilter < video.Views
||
MinDateFilter.Date > video.PublishDate.Date
||
MaxDateFilter.Date < video.PublishDate.Date
||
MinDurationFilter > video.Duration
||
MaxDurationFilter < video.Duration
);
item.Value = !hide;
}
RemovedCount = _removedVideos.Count;
HiddenCount = Videos.Values.Where(x => !x).Count();
IsSomethingHidden = HiddenCount > 0;
}
#endregion

View File

@@ -234,7 +234,7 @@ namespace VDownload.Core.ViewModels.Home
return;
}
await _playlistViewModel.LoadPlaylist(playlist);
_playlistViewModel.LoadPlaylist(playlist);
MainContent = _playlistView;

View File

@@ -10,10 +10,11 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.4.240211001" />
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.22621.2428" />
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.5.240227000" />
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.22621.3233" />
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.2.2" />
<PackageReference Include="SimpleToolkit.UI.Models" Version="1.4.0" />
<PackageReference Include="SimpleToolkit.MVVM" Version="1.7.2" />
<PackageReference Include="SimpleToolkit.UI.Models" Version="1.7.2" />
</ItemGroup>
<ItemGroup>

View File

@@ -46,7 +46,7 @@
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Image Grid.Column="0"
Source="{Binding Video.ThumbnailUrl}"
Source="{Binding Video.ThumbnailUrl, TargetNullValue={StaticResource ImageOtherThumbnail}}"
VerticalAlignment="Stretch"/>
<Grid Grid.Column="1"
Margin="10"
@@ -327,10 +327,10 @@
</i:Interaction.Behaviors>
</AppBarButton>
<AppBarButton Grid.Row="1"
Width="40"
Height="48"
Command="{Binding ElementName=Root, Path=DataContext.StartCancelTaskCommand}"
CommandParameter="{Binding}">
Width="40"
Height="48"
Command="{Binding ElementName=Root, Path=DataContext.StartCancelTaskCommand}"
CommandParameter="{Binding}">
<i:Interaction.Behaviors>
<ic:DataTriggerBehavior Binding="{Binding Status, Converter={StaticResource ObjectToIntConverter}}"
ComparisonCondition="LessThan"

View File

@@ -6,8 +6,16 @@
xmlns:local="using:VDownload.Core.Views.Home"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:i="using:Microsoft.Xaml.Interactivity"
xmlns:ic="using:Microsoft.Xaml.Interactions.Core"
xmlns:m="using:VDownload.Models"
xmlns:ct="using:CommunityToolkit.WinUI"
xmlns:ctc="using:CommunityToolkit.WinUI.Controls"
xmlns:ctuc="using:CommunityToolkit.WinUI.UI.Controls"
xmlns:c="using:SimpleToolkit.UI.WinUI.Controls"
mc:Ignorable="d"
Background="{ThemeResource ViewBackgroundColor}">
Background="{ThemeResource ViewBackgroundColor}"
x:Name="Root">
<Grid Padding="15"
RowSpacing="20">
@@ -20,7 +28,6 @@
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0"
VerticalAlignment="Center"
@@ -28,25 +35,390 @@
FontWeight="Bold"
FontSize="20"
TextWrapping="WrapWholeWords"/>
<AppBarButton Grid.Column="2"
Margin="-5"
Icon="Filter"
Width="40"
Height="48">
<AppBarButton.Resources>
<TeachingTip x:Name="FilterWindow">
</TeachingTip>
</AppBarButton.Resources>
</AppBarButton>
<StackPanel Grid.Column="1"
Orientation="Horizontal"
Margin="-5"
Spacing="10">
<TextBlock VerticalAlignment="Center"
Visibility="{Binding IsSomethingHidden, Converter={StaticResource BoolToVisibilityConverter}}">
<Run x:Uid="/VDownload.Core.Strings/HomePlaylistViewResources/HiddenTextBlock"/><Run Text="{Binding HiddenCount}"/>
</TextBlock>
<AppBarToggleButton x:Name="FilterButton"
Icon="Filter"
Width="40"
Height="48">
<AppBarToggleButton.Resources>
<TeachingTip x:Name="FilterWindow"
x:Uid="/VDownload.Core.Strings/HomePlaylistViewResources/FilterWindow"
Target="{Binding ElementName=FilterButton}"
PreferredPlacement="BottomLeft"
IsOpen="{Binding ElementName=FilterButton, Path=IsChecked, Mode=TwoWay}">
<Grid VerticalAlignment="Top"
Margin="0,10,0,0"
ColumnSpacing="10"
RowSpacing="10">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBlock x:Uid="/VDownload.Core.Strings/HomePlaylistViewResources/FilterTitleTextBlock"
Grid.Row="0"
Grid.Column="0"
VerticalAlignment="Center"/>
<TextBox x:Uid="/VDownload.Core.Strings/HomePlaylistViewResources/FilterTitleTextBox"
Grid.Row="0"
Grid.Column="1"
Text="{Binding TitleFilter, Mode=TwoWay}"/>
<TextBlock x:Uid="/VDownload.Core.Strings/HomePlaylistViewResources/FilterAuthorTextBlock"
Grid.Row="1"
Grid.Column="0"
VerticalAlignment="Center"/>
<TextBox x:Uid="/VDownload.Core.Strings/HomePlaylistViewResources/FilterAuthorTextBox"
Grid.Row="1"
Grid.Column="1"
Text="{Binding AuthorFilter, Mode=TwoWay}"/>
<TextBlock x:Uid="/VDownload.Core.Strings/HomePlaylistViewResources/FilterViewsTextBlock"
Grid.Row="2"
Grid.Column="0"
VerticalAlignment="Center"/>
<Grid Grid.Row="2"
Grid.Column="1"
ColumnSpacing="5">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<NumberBox Grid.Column="0"
SpinButtonPlacementMode="Compact"
Minimum="{Binding MinViews}"
Value="{Binding MinViewsFilter, Mode=TwoWay}"
Maximum="{Binding MaxViewsFilter}"
SmallChange="1"
LargeChange="10"/>
<TextBlock Grid.Column="1"
VerticalAlignment="Center"
Text="-"/>
<NumberBox Grid.Column="2"
SpinButtonPlacementMode="Compact"
Minimum="{Binding MinViewsFilter}"
Value="{Binding MaxViewsFilter, Mode=TwoWay}"
Maximum="{Binding MaxViews}"
SmallChange="1"
LargeChange="10"/>
</Grid>
<TextBlock x:Uid="/VDownload.Core.Strings/HomePlaylistViewResources/FilterDateTextBlock"
Grid.Row="3"
Grid.Column="0"
VerticalAlignment="Center"/>
<Grid Grid.Row="3"
Grid.Column="1"
ColumnSpacing="5">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<CalendarDatePicker Grid.Column="0"
MinDate="{Binding MinDate}"
Date="{Binding MinDateFilter, Mode=TwoWay}"
MaxDate="{Binding MaxDateFilter}"/>
<TextBlock Grid.Column="1"
VerticalAlignment="Center"
Text="-"/>
<CalendarDatePicker Grid.Column="2"
MinDate="{Binding MinDateFilter}"
Date="{Binding MaxDateFilter, Mode=TwoWay}"
MaxDate="{Binding MaxDate}"/>
</Grid>
<TextBlock x:Uid="/VDownload.Core.Strings/HomePlaylistViewResources/FilterDurationTextBlock"
Grid.Row="4"
Grid.Column="0"
VerticalAlignment="Center"/>
<Grid Grid.Row="4"
Grid.Column="1"
RowSpacing="5">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<c:TimeSpanControl Grid.Row="0"
Minimum="{Binding MinDuration}"
Value="{Binding MinDurationFilter, Mode=TwoWay}"
Maximum="{Binding MaxDurationFilter}"/>
<c:TimeSpanControl Grid.Row="1"
Minimum="{Binding MinDurationFilter}"
Value="{Binding MaxDurationFilter, Mode=TwoWay}"
Maximum="{Binding MaxDuration}"/>
</Grid>
<TextBlock x:Uid="/VDownload.Core.Strings/HomePlaylistViewResources/FilterRemovedTextBlock"
Grid.Row="5"
Grid.Column="0"
VerticalAlignment="Center"/>
<Grid Grid.Row="5"
Grid.Column="1">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0"
VerticalAlignment="Center"
Text="{Binding RemovedCount}"/>
<Button x:Uid="/VDownload.Core.Strings/HomePlaylistViewResources/FilterRemovedButton"
Grid.Column="1"
Command="{Binding RestoreRemovedVideosCommand}"/>
</Grid>
</Grid>
</TeachingTip>
</AppBarToggleButton.Resources>
</AppBarToggleButton>
<AppBarButton x:Uid="/VDownload.Core.Strings/HomePlaylistViewResources/ApplyDirectoryButton"
Icon="Folder"
Width="40"
Height="48"
Command="{Binding SelectDirectoryCommand}"/>
</StackPanel>
</Grid>
<ItemsControl Grid.Row="1">
</ItemsControl>
<ScrollViewer Grid.Row="1">
<ItemsControl ItemsSource="{Binding Videos}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Expander HorizontalAlignment="Stretch"
Margin="0,0,0,10"
CornerRadius="10"
HorizontalContentAlignment="Stretch"
Visibility="{Binding Value, Converter={StaticResource BoolToVisibilityConverter}}">
<Expander.Header>
<Grid Padding="-16,0,-16,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Image Grid.Column="0"
Source="{Binding Key.ThumbnailUrl, TargetNullValue={StaticResource ImageOtherThumbnail}}"
Height="100"/>
<Grid Grid.Column="1"
Margin="10"
RowSpacing="10">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0"
FontSize="16"
Text="{Binding Key.Title}"
FontWeight="SemiBold"
TextTrimming="CharacterEllipsis"/>
<Grid Grid.Row="1"
RowSpacing="10"
ColumnSpacing="10">
<Grid.Resources>
<x:Double x:Key="IconSize">17</x:Double>
<x:Double x:Key="FontSize">13</x:Double>
</Grid.Resources>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Image Grid.Column="0"
Grid.Row="0"
VerticalAlignment="Center"
Source="{ThemeResource ImageHomePlaylistViewAuthor}"
Width="{StaticResource IconSize}"/>
<TextBlock Grid.Column="1"
Grid.Row="0"
FontSize="{StaticResource FontSize}"
VerticalAlignment="Center"
Text="{Binding Key.Author}"/>
<Image Grid.Column="0"
Grid.Row="1"
VerticalAlignment="Center"
Source="{ThemeResource ImageHomePlaylistViewDate}"
Width="{StaticResource IconSize}"/>
<TextBlock Grid.Column="1"
Grid.Row="1"
FontSize="{StaticResource FontSize}"
VerticalAlignment="Center"
Text="{Binding Key.PublishDate}"/>
<Image Grid.Column="2"
Grid.Row="0"
VerticalAlignment="Center"
Source="{ThemeResource ImageHomePlaylistViewTime}"
Width="{StaticResource IconSize}"/>
<TextBlock Grid.Column="3"
Grid.Row="0"
FontSize="{StaticResource FontSize}"
VerticalAlignment="Center"
Text="{Binding Key.Duration}"/>
<Image Grid.Column="2"
Grid.Row="1"
VerticalAlignment="Center"
Source="{ThemeResource ImageHomePlaylistViewViews}"
Width="{StaticResource IconSize}"/>
<TextBlock Grid.Column="3"
Grid.Row="1"
FontSize="{StaticResource FontSize}"
VerticalAlignment="Center"
Text="{Binding Key.Views}"/>
</Grid>
</Grid>
<AppBarButton Grid.Column="2"
Width="40"
Height="48"
Icon="Delete"
VerticalAlignment="Center"
Command="{Binding ElementName=Root, Path=DataContext.RemoveVideoCommand}"
CommandParameter="{Binding Key}"/>
</Grid>
</Expander.Header>
<Expander.Content>
<StackPanel Spacing="20">
<StackPanel Spacing="5">
<TextBlock x:Uid="/VDownload.Core.Strings/HomePlaylistViewResources/MediaOptionsHeader"
FontWeight="Bold"
FontSize="15"/>
<ctc:SettingsCard x:Uid="/VDownload.Core.Strings/HomePlaylistViewResources/QualitySettingsCard">
<ctc:SettingsCard.HeaderIcon>
<BitmapIcon ShowAsMonochrome="False"
UriSource="{ThemeResource ImageHomePlaylistViewMedia}"/>
</ctc:SettingsCard.HeaderIcon>
<ComboBox ItemsSource="{Binding Key.Streams}"
SelectedItem="{Binding Key.SelectedStream, Mode=TwoWay}"/>
</ctc:SettingsCard>
<ctc:SettingsCard x:Uid="/VDownload.Core.Strings/HomePlaylistViewResources/MediaTypeSettingsCard">
<ctc:SettingsCard.HeaderIcon>
<BitmapIcon ShowAsMonochrome="False"
UriSource="{ThemeResource ImageHomePlaylistViewMedia}"/>
</ctc:SettingsCard.HeaderIcon>
<ComboBox ItemsSource="{ct:EnumValues Type=m:MediaType}"
SelectedItem="{Binding Key.MediaType, Mode=TwoWay}">
<ComboBox.ItemTemplate>
<DataTemplate>
<ctuc:SwitchPresenter Value="{Binding Converter={StaticResource ObjectToStringConverter}}">
<ctuc:Case Value="Original">
<TextBlock x:Uid="/VDownload.Core.Strings/HomePlaylistViewResources/MediaTypeOriginal"/>
</ctuc:Case>
<ctuc:Case Value="OnlyVideo">
<TextBlock x:Uid="/VDownload.Core.Strings/HomePlaylistViewResources/MediaTypeOnlyVideo"/>
</ctuc:Case>
<ctuc:Case Value="OnlyAudio">
<TextBlock x:Uid="/VDownload.Core.Strings/HomePlaylistViewResources/MediaTypeOnlyAudio"/>
</ctuc:Case>
</ctuc:SwitchPresenter>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</ctc:SettingsCard>
<ctc:SettingsExpander x:Uid="/VDownload.Core.Strings/HomePlaylistViewResources/TrimSettingsGroup">
<ctc:SettingsExpander.HeaderIcon>
<BitmapIcon ShowAsMonochrome="False"
UriSource="{ThemeResource ImageHomePlaylistViewTrim}"/>
</ctc:SettingsExpander.HeaderIcon>
<ctc:SettingsExpander.Items>
<ctc:SettingsCard x:Uid="/VDownload.Core.Strings/HomePlaylistViewResources/TrimStartSettingsCard">
<c:TimeSpanControl Value="{Binding Key.TrimStart, Mode=TwoWay}"
Maximum="{Binding Key.TrimEnd, Mode=OneWay}"/>
</ctc:SettingsCard>
<ctc:SettingsCard x:Uid="/VDownload.Core.Strings/HomePlaylistViewResources/TrimEndSettingsCard">
<c:TimeSpanControl Minimum="{Binding Key.TrimStart, Mode=OneWay}"
Value="{Binding Key.TrimEnd, Mode=TwoWay}"
Maximum="{Binding Key.Duration, Mode=OneWay}"/>
</ctc:SettingsCard>
</ctc:SettingsExpander.Items>
</ctc:SettingsExpander>
</StackPanel>
<StackPanel Spacing="5">
<TextBlock x:Uid="/VDownload.Core.Strings/HomePlaylistViewResources/FileOptionsHeader"
FontWeight="Bold"
FontSize="15"/>
<ctc:SettingsCard x:Uid="/VDownload.Core.Strings/HomePlaylistViewResources/DirectorySettingsCard"
Description="{Binding Key.DirectoryPath}">
<ctc:SettingsCard.HeaderIcon>
<BitmapIcon ShowAsMonochrome="False"
UriSource="{ThemeResource ImageHomePlaylistViewDirectory}"/>
</ctc:SettingsCard.HeaderIcon>
<Button x:Uid="/VDownload.Core.Strings/HomePlaylistViewResources/DirectorySettingsCardButton"
Command="{Binding Key.BrowseCommand}"/>
</ctc:SettingsCard>
<ctc:SettingsCard x:Uid="/VDownload.Core.Strings/HomePlaylistViewResources/FilenameSettingsCard">
<ctc:SettingsCard.HeaderIcon>
<BitmapIcon ShowAsMonochrome="False"
UriSource="{ThemeResource ImageHomePlaylistViewFilename}"/>
</ctc:SettingsCard.HeaderIcon>
<TextBox Text="{Binding Key.Filename, Mode=TwoWay}"/>
</ctc:SettingsCard>
<ctc:SettingsCard x:Uid="/VDownload.Core.Strings/HomePlaylistViewResources/FileTypeSettingsCard">
<ctc:SettingsCard.HeaderIcon>
<BitmapIcon ShowAsMonochrome="False"
UriSource="{ThemeResource ImageHomePlaylistViewExtension}"/>
</ctc:SettingsCard.HeaderIcon>
<i:Interaction.Behaviors>
<ic:DataTriggerBehavior Binding="{Binding Key.MediaType, Converter={StaticResource ObjectToStringConverter}}"
ComparisonCondition="Equal"
Value="OnlyAudio">
<ic:ChangePropertyAction PropertyName="Content">
<ic:ChangePropertyAction.Value>
<ComboBox ItemsSource="{ct:EnumValues Type=m:AudioExtension}"
SelectedItem="{Binding Key.AudioExtension, Mode=TwoWay}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</ic:ChangePropertyAction.Value>
</ic:ChangePropertyAction>
</ic:DataTriggerBehavior>
<ic:DataTriggerBehavior Binding="{Binding Key.MediaType, Converter={StaticResource ObjectToStringConverter}}"
ComparisonCondition="NotEqual"
Value="OnlyAudio">
<ic:ChangePropertyAction PropertyName="Content">
<ic:ChangePropertyAction.Value>
<ComboBox ItemsSource="{ct:EnumValues Type=m:VideoExtension}"
SelectedItem="{Binding Key.VideoExtension, Mode=TwoWay}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</ic:ChangePropertyAction.Value>
</ic:ChangePropertyAction>
</ic:DataTriggerBehavior>
</i:Interaction.Behaviors>
</ctc:SettingsCard>
</StackPanel>
</StackPanel>
</Expander.Content>
</Expander>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
<StackPanel Grid.Row="2"
HorizontalAlignment="Right"
Orientation="Horizontal">
Orientation="Horizontal"
Spacing="10">
<Button x:Uid="/VDownload.Core.Strings/HomePlaylistViewResources/CreateAndStartButton"
Command="{Binding CreateTasksAndDownloadCommand}"/>
<Button x:Uid="/VDownload.Core.Strings/HomePlaylistViewResources/CreateButton"
Style="{StaticResource AccentButtonStyle}"
Command="{Binding CreateTasksCommand}"/>
</StackPanel>
</Grid>
</Page>

View File

@@ -31,7 +31,7 @@
</Grid.ColumnDefinitions>
<Border Grid.Column="0"
CornerRadius="{ThemeResource ControlCornerRadius}">
<Image Source="{Binding ThumbnailUrl}"
<Image Source="{Binding ThumbnailUrl, TargetNullValue={StaticResource ImageOtherThumbnail}}"
VerticalAlignment="Stretch"/>
</Border>
<StackPanel Grid.Column="1"
@@ -98,13 +98,7 @@
UriSource="{ThemeResource ImageHomeVideoViewQuality}"/>
</ctc:SettingsCard.HeaderIcon>
<ComboBox ItemsSource="{Binding Streams}"
SelectedItem="{Binding SelectedStream, Mode=TwoWay}">
<!-- ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}"/>
</DataTemplate>
</ComboBox.ItemTemplate -->
</ComboBox>
SelectedItem="{Binding SelectedStream, Mode=TwoWay}"/>
</ctc:SettingsCard>
<ctc:SettingsCard x:Uid="/VDownload.Core.Strings/HomeVideoViewResources/MediaTypeSettingsCard">
<ctc:SettingsCard.HeaderIcon>

View File

@@ -16,12 +16,13 @@
<PackageReference Include="CommunityToolkit.WinUI" Version="7.1.2" />
<PackageReference Include="CommunityToolkit.WinUI.Behaviors" Version="8.0.240109" />
<PackageReference Include="CommunityToolkit.WinUI.Controls.SettingsControls" Version="8.0.240109" />
<PackageReference Include="CommunityToolkit.WinUI.Extensions" Version="8.0.240109" />
<PackageReference Include="CommunityToolkit.WinUI.UI.Controls" Version="7.1.2" />
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.4.240211001" />
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.22621.2428" />
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.5.240227000" />
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.22621.3233" />
<PackageReference Include="Microsoft.Xaml.Behaviors.WinUI.Managed" Version="2.0.9" />
<PackageReference Include="SimpleToolkit.UI.WinUI.Behaviors" Version="1.4.0" />
<PackageReference Include="SimpleToolkit.UI.WinUI.Controls" Version="1.4.0" />
<PackageReference Include="SimpleToolkit.UI.WinUI.Behaviors" Version="1.7.2" />
<PackageReference Include="SimpleToolkit.UI.WinUI.Controls" Version="1.7.2" />
</ItemGroup>
<ItemGroup>

View File

@@ -16,7 +16,7 @@ namespace VDownload.Models
public DateTime PublishDate { get; set; }
public TimeSpan Duration { get; set; }
public long Views { get; set; }
public Uri ThumbnailUrl { get; set; }
public Uri? ThumbnailUrl { get; set; }
public Uri Url { get; set; }
public ICollection<VideoStream> Streams { get; private set; }
public Source Source { get; protected set; }

View File

@@ -10,7 +10,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.4.240211001" />
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.22621.2428" />
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.5.240227000" />
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.22621.3233" />
</ItemGroup>
</Project>

View File

@@ -10,8 +10,8 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.4.240211001" />
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.22621.2428" />
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.5.240227000" />
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.22621.3233" />
</ItemGroup>
<ItemGroup>

View File

@@ -10,7 +10,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.4.240211001" />
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.22621.2428" />
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.5.240227000" />
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.22621.3233" />
</ItemGroup>
</Project>

View File

@@ -10,7 +10,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.4.240211001" />
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.22621.2428" />
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.5.240227000" />
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.22621.3233" />
</ItemGroup>
</Project>

View File

@@ -8,6 +8,7 @@ namespace VDownload.Services.UI.StringResources
StringResources BaseViewResources { get; }
StringResources HomeViewResources { get; }
StringResources HomeVideoViewResources { get; }
StringResources HomePlaylistViewResources { get; }
StringResources HomeDownloadsViewResources { get; }
StringResources AuthenticationViewResources { get; }
StringResources NotificationsResources { get; }
@@ -30,6 +31,7 @@ namespace VDownload.Services.UI.StringResources
public StringResources BaseViewResources { get; protected set; }
public StringResources HomeViewResources { get; protected set; }
public StringResources HomeVideoViewResources { get; protected set; }
public StringResources HomePlaylistViewResources { get; protected set; }
public StringResources HomeDownloadsViewResources { get; protected set; }
public StringResources AuthenticationViewResources { get; protected set; }
public StringResources NotificationsResources { get; protected set; }
@@ -47,6 +49,7 @@ namespace VDownload.Services.UI.StringResources
BaseViewResources = BuildResource("BaseViewResources");
HomeViewResources = BuildResource("HomeViewResources");
HomeVideoViewResources = BuildResource("HomeVideoViewResources");
HomePlaylistViewResources = BuildResource("HomePlaylistViewResources");
HomeDownloadsViewResources = BuildResource("HomeDownloadsViewResources");
AuthenticationViewResources = BuildResource("AuthenticationViewResources");
NotificationsResources = BuildResource("NotificationsResources");

View File

@@ -10,8 +10,8 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.4.240211001" />
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.22621.2428" />
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.5.240227000" />
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.22621.3233" />
</ItemGroup>
<ItemGroup>

View File

@@ -13,8 +13,8 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.4.240211001" />
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.22621.2428" />
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.5.240227000" />
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.22621.3233" />
</ItemGroup>
<ItemGroup>

View File

@@ -4,7 +4,7 @@ namespace VDownload.Sources.Twitch.Configuration.Models{
public class Download
{
[ConfigurationKeyName("vod")]
public Vod Vod { get; set; }
public DownloadVod Vod { get; set; }
}
}

View File

@@ -0,0 +1,18 @@
using Microsoft.Extensions.Configuration;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace VDownload.Sources.Twitch.Configuration.Models
{
public class DownloadVod
{
[ConfigurationKeyName("chunk_regex")]
public string ChunkRegex { get; set; }
[ConfigurationKeyName("file_name")]
public string FileName { get; set; }
}
}

View File

@@ -7,7 +7,7 @@ namespace VDownload.Sources.Twitch.Configuration.Models{
public List<string> GeneralRegexes { get; } = new List<string>();
[ConfigurationKeyName("vod")]
public Vod Vod { get; set; }
public SearchVod Vod { get; set; }
[ConfigurationKeyName("clip")]
public Clip Clip { get; set; }

View File

@@ -1,7 +1,7 @@
using Microsoft.Extensions.Configuration;
namespace VDownload.Sources.Twitch.Configuration.Models{
public class Vod
public class SearchVod
{
[ConfigurationKeyName("regexes")]
public List<string> Regexes { get; } = new List<string>();
@@ -9,14 +9,11 @@ namespace VDownload.Sources.Twitch.Configuration.Models{
[ConfigurationKeyName("thumbnail")]
public Thumbnail Thumbnail { get; set; }
[ConfigurationKeyName("live_thumbnail_url_regex")]
public string LiveThumbnailUrlRegex { get; set; }
[ConfigurationKeyName("stream_playlist_regex")]
public string StreamPlaylistRegex { get; set; }
[ConfigurationKeyName("chunk_regex")]
public string ChunkRegex { get; set; }
[ConfigurationKeyName("file_name")]
public string FileName { get; set; }
}
}

View File

@@ -19,8 +19,8 @@ namespace VDownload.Sources.Twitch
{
public interface ITwitchSearchService : ISourceSearchService
{
Task<TwitchPlaylist> SearchPlaylist(string url, int maxVideoCount);
Task<TwitchVideo> SearchVideo(string url);
new Task<TwitchPlaylist> SearchPlaylist(string url, int maxVideoCount);
new Task<TwitchVideo> SearchVideo(string url);
}
@@ -120,17 +120,17 @@ namespace VDownload.Sources.Twitch
List<Task<TwitchVod>> tasks = new List<Task<TwitchVod>>();
string? cursor = null;
List<Api.Helix.GetVideos.Response.Data> videosList;
count = count == 0 ? int.MaxValue : count;
int videos = 0;
do
{
videos = count == 0 || count > 100 ? 100 : count;
videos = count > 100 ? 100 : count;
GetVideosResponse videosResponse = await _apiService.HelixGetUserVideos(channel.Id, token, videos, cursor);
videosList = videosResponse.Data;
count -= videosList.Count;
cursor = videosResponse.Pagination.Cursor;
tasks.AddRange(videosList.Select(ParseVod));
}
while (videosList.Count == videos);
while (tasks.Count < count && videosList.Count == videos);
await Task.WhenAll(tasks);
@@ -143,7 +143,13 @@ namespace VDownload.Sources.Twitch
{
Task<IEnumerable<TwitchVodStream>> streamsTask = GetVodStreams(data.Id);
Thumbnail thumbnail = _configurationService.Twitch.Search.Vod.Thumbnail;
Thumbnail thumbnailConfig = _configurationService.Twitch.Search.Vod.Thumbnail;
Regex liveThumbnailRegex = new Regex(_configurationService.Twitch.Search.Vod.LiveThumbnailUrlRegex);
Uri? thumbnail = null;
if (!liveThumbnailRegex.IsMatch(data.ThumbnailUrl))
{
thumbnail = new Uri(data.ThumbnailUrl.Replace("%{width}", thumbnailConfig.Width.ToString()).Replace("%{height}", thumbnailConfig.Height.ToString()));
}
TwitchVod vod = new TwitchVod
{
Title = data.Title,
@@ -152,7 +158,7 @@ namespace VDownload.Sources.Twitch
PublishDate = data.PublishedAt,
Duration = ParseVodDuration(data.Duration),
Views = data.ViewCount,
ThumbnailUrl = new Uri(data.ThumbnailUrl.Replace("%{width}", thumbnail.Width.ToString()).Replace("%{height}", thumbnail.Height.ToString())),
ThumbnailUrl = thumbnail,
Url = new Uri(data.Url),
};

View File

@@ -12,8 +12,10 @@
<ResourceDictionary Source="Dictionaries/Colors.xaml"/>
<ResourceDictionary Source="Dictionaries/Images/ImagesLogo.xaml"/>
<ResourceDictionary Source="Dictionaries/Images/ImagesSources.xaml"/>
<ResourceDictionary Source="Dictionaries/Images/ImagesOther.xaml"/>
<ResourceDictionary Source="Dictionaries/Images/ImagesBaseView.xaml"/>
<ResourceDictionary Source="Dictionaries/Images/ImagesHomeDownloadsView.xaml"/>
<ResourceDictionary Source="Dictionaries/Images/ImagesHomePlaylistView.xaml"/>
<ResourceDictionary Source="Dictionaries/Images/ImagesHomeVideoView.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

View File

@@ -0,0 +1,36 @@
<?xml version="1.0" encoding="utf-8"?>
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:VDownload.Dictionaries.Images">
<ResourceDictionary.ThemeDictionaries>
<ResourceDictionary x:Key="Light">
<x:String x:Key="ImageHomePlaylistViewAuthor">/Assets/HomePlaylistView/AuthorLight.png</x:String>
<x:String x:Key="ImageHomePlaylistViewDate">/Assets/HomePlaylistView/DateLight.png</x:String>
<x:String x:Key="ImageHomePlaylistViewTime">/Assets/HomePlaylistView/TimeLight.png</x:String>
<x:String x:Key="ImageHomePlaylistViewViews">/Assets/HomePlaylistView/ViewLight.png</x:String>
<x:String x:Key="ImageHomePlaylistViewQuality">/Assets/HomePlaylistView/QualityLight.png</x:String>
<x:String x:Key="ImageHomePlaylistViewMedia">/Assets/HomePlaylistView/MediaLight.png</x:String>
<x:String x:Key="ImageHomePlaylistViewTrimLeft">/Assets/HomePlaylistView/TrimLeftLight.png</x:String>
<x:String x:Key="ImageHomePlaylistViewTrim">/Assets/HomePlaylistView/TrimLight.png</x:String>
<x:String x:Key="ImageHomePlaylistViewTrimRight">/Assets/HomePlaylistView/TrimRightLight.png</x:String>
<x:String x:Key="ImageHomePlaylistViewDirectory">/Assets/HomePlaylistView/DirectoryLight.png</x:String>
<x:String x:Key="ImageHomePlaylistViewFilename">/Assets/HomePlaylistView/FilenameLight.png</x:String>
<x:String x:Key="ImageHomePlaylistViewExtension">/Assets/HomePlaylistView/ExtensionLight.png</x:String>
</ResourceDictionary>
<ResourceDictionary x:Key="Dark">
<x:String x:Key="ImageHomePlaylistViewAuthor">/Assets/HomePlaylistView/AuthorDark.png</x:String>
<x:String x:Key="ImageHomePlaylistViewDate">/Assets/HomePlaylistView/DateDark.png</x:String>
<x:String x:Key="ImageHomePlaylistViewTime">/Assets/HomePlaylistView/TimeDark.png</x:String>
<x:String x:Key="ImageHomePlaylistViewViews">/Assets/HomePlaylistView/ViewDark.png</x:String>
<x:String x:Key="ImageHomePlaylistViewQuality">/Assets/HomePlaylistView/QualityDark.png</x:String>
<x:String x:Key="ImageHomePlaylistViewMedia">/Assets/HomePlaylistView/MediaDark.png</x:String>
<x:String x:Key="ImageHomePlaylistViewTrimLeft">/Assets/HomePlaylistView/TrimLeftDark.png</x:String>
<x:String x:Key="ImageHomePlaylistViewTrim">/Assets/HomePlaylistView/TrimDark.png</x:String>
<x:String x:Key="ImageHomePlaylistViewTrimRight">/Assets/HomePlaylistView/TrimRightDark.png</x:String>
<x:String x:Key="ImageHomePlaylistViewDirectory">/Assets/HomePlaylistView/DirectoryDark.png</x:String>
<x:String x:Key="ImageHomePlaylistViewFilename">/Assets/HomePlaylistView/FilenameDark.png</x:String>
<x:String x:Key="ImageHomePlaylistViewExtension">/Assets/HomePlaylistView/ExtensionDark.png</x:String>
</ResourceDictionary>
</ResourceDictionary.ThemeDictionaries>
</ResourceDictionary>

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<x:String x:Key="ImageOtherThumbnail">/Assets/Other/Thumbnail.png</x:String>
</ResourceDictionary>

View File

@@ -44,6 +44,26 @@
<Content Remove="Assets\HomeDownloadsView\QueuedLight.png" />
<Content Remove="Assets\HomeDownloadsView\TimeDark.png" />
<Content Remove="Assets\HomeDownloadsView\TimeLight.png" />
<Content Remove="Assets\HomePlaylistView\AuthorDark.png" />
<Content Remove="Assets\HomePlaylistView\AuthorLight.png" />
<Content Remove="Assets\HomePlaylistView\DateDark.png" />
<Content Remove="Assets\HomePlaylistView\DateLight.png" />
<Content Remove="Assets\HomePlaylistView\DirectoryDark.png" />
<Content Remove="Assets\HomePlaylistView\DirectoryLight.png" />
<Content Remove="Assets\HomePlaylistView\ExtensionDark.png" />
<Content Remove="Assets\HomePlaylistView\ExtensionLight.png" />
<Content Remove="Assets\HomePlaylistView\FilenameDark.png" />
<Content Remove="Assets\HomePlaylistView\FilenameLight.png" />
<Content Remove="Assets\HomePlaylistView\MediaDark.png" />
<Content Remove="Assets\HomePlaylistView\MediaLight.png" />
<Content Remove="Assets\HomePlaylistView\QualityDark.png" />
<Content Remove="Assets\HomePlaylistView\QualityLight.png" />
<Content Remove="Assets\HomePlaylistView\TimeDark.png" />
<Content Remove="Assets\HomePlaylistView\TimeLight.png" />
<Content Remove="Assets\HomePlaylistView\TrimDark.png" />
<Content Remove="Assets\HomePlaylistView\TrimLight.png" />
<Content Remove="Assets\HomePlaylistView\ViewDark.png" />
<Content Remove="Assets\HomePlaylistView\ViewLight.png" />
<Content Remove="Assets\HomeVideoView\AuthorDark.png" />
<Content Remove="Assets\HomeVideoView\AuthorLight.png" />
<Content Remove="Assets\HomeVideoView\DateDark.png" />
@@ -122,8 +142,10 @@
<None Remove="Dictionaries\Converters.xaml" />
<None Remove="Dictionaries\Images\ImagesBaseView.xaml" />
<None Remove="Dictionaries\Images\ImagesHomeDownloadsView.xaml" />
<None Remove="Dictionaries\Images\ImagesHomePlaylistView.xaml" />
<None Remove="Dictionaries\Images\ImagesHomeVideoView.xaml" />
<None Remove="Dictionaries\Images\ImagesLogo.xaml" />
<None Remove="Dictionaries\Images\ImagesOther.xaml" />
<None Remove="Dictionaries\Images\ImagesSources.xaml" />
</ItemGroup>
@@ -131,9 +153,9 @@
<PackageReference Include="CommunityToolkit.WinUI.Converters" Version="8.0.240109" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.4.240211001" />
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.22621.2428" />
<PackageReference Include="SimpleToolkit.UI.WinUI.Converters" Version="1.4.0" />
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.5.240227000" />
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.22621.3233" />
<PackageReference Include="SimpleToolkit.UI.WinUI.Converters" Version="1.7.2" />
<Manifest Include="$(ApplicationManifest)" />
</ItemGroup>
@@ -163,6 +185,11 @@
<ProjectReference Include="..\VDownload.Sources\VDownload.Sources.Twitch\VDownload.Sources.Twitch\VDownload.Sources.Twitch.csproj" />
<ProjectReference Include="..\VDownload.Sources\VDownload.Sources\VDownload.Sources.csproj" />
</ItemGroup>
<ItemGroup>
<Content Update="Assets\Other\Thumbnail.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<None Update="Assets\BaseView\AuthenticationDark.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
@@ -251,6 +278,66 @@
<None Update="Assets\HomeDownloadsView\TimeLight.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="Assets\HomePlaylistView\AuthorDark.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="Assets\HomePlaylistView\AuthorLight.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="Assets\HomePlaylistView\DateDark.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="Assets\HomePlaylistView\DateLight.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="Assets\HomePlaylistView\DirectoryDark.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="Assets\HomePlaylistView\DirectoryLight.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="Assets\HomePlaylistView\ExtensionDark.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="Assets\HomePlaylistView\ExtensionLight.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="Assets\HomePlaylistView\FilenameDark.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="Assets\HomePlaylistView\FilenameLight.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="Assets\HomePlaylistView\MediaDark.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="Assets\HomePlaylistView\MediaLight.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="Assets\HomePlaylistView\QualityDark.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="Assets\HomePlaylistView\QualityLight.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="Assets\HomePlaylistView\TimeDark.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="Assets\HomePlaylistView\TimeLight.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="Assets\HomePlaylistView\TrimDark.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="Assets\HomePlaylistView\TrimLight.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="Assets\HomePlaylistView\ViewDark.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="Assets\HomePlaylistView\ViewLight.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="Assets\HomeVideoView\AuthorDark.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
@@ -320,6 +407,12 @@
<None Update="configuration.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<Page Update="Dictionaries\Images\ImagesOther.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Update="Dictionaries\Images\ImagesHomePlaylistView.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Update="Dictionaries\Images\ImagesHomeDownloadsView.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>

View File

@@ -148,6 +148,7 @@
"width": 1920,
"height": 1080
},
"live_thumbnail_url_regex": "https:\\/\\/vod-secure\\.twitch\\.tv\\/_404\\/404_processing_%{width}x%{height}\\.png",
"stream_playlist_regex": "#EXT-X-MEDIA:TYPE=VIDEO,GROUP-ID=\"\\w+\",NAME=\"(?<id>.+)\",AUTOSELECT=\\w+,DEFAULT=\\w+\\n#EXT-X-STREAM-INF:BANDWIDTH=\\d+,CODECS=\"(?<video_codec>.+),(?<audio_codec>.+)\",RESOLUTION=(?<width>\\d+)x(?<height>\\d+),VIDEO=\".+\"(?:,FRAME-RATE=(?<framerate>\\d+.\\d+))?\n(?<url>.+)"
},
"clip": {