new_version_init
This commit is contained in:
13
VDownload.Common/AudioExtension.cs
Normal file
13
VDownload.Common/AudioExtension.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace VDownload.Common
|
||||
{
|
||||
public enum AudioExtension
|
||||
{
|
||||
MP3
|
||||
}
|
||||
}
|
||||
21
VDownload.Common/Exceptions/MediaSearchException.cs
Normal file
21
VDownload.Common/Exceptions/MediaSearchException.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace VDownload.Common.Exceptions
|
||||
{
|
||||
public class MediaSearchException : Exception
|
||||
{
|
||||
#region CONSTRUCTORS
|
||||
|
||||
public MediaSearchException() : base() { }
|
||||
|
||||
public MediaSearchException(string message) : base(message) { }
|
||||
|
||||
public MediaSearchException(string message, Exception inner) : base(message, inner) { }
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
21
VDownload.Common/MediaType.cs
Normal file
21
VDownload.Common/MediaType.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace VDownload.Common
|
||||
{
|
||||
public enum MediaType
|
||||
{
|
||||
[Description("Original")]
|
||||
Original,
|
||||
|
||||
[Description("Only video")]
|
||||
OnlyVideo,
|
||||
|
||||
[Description("Only audio")]
|
||||
OnlyAudio,
|
||||
}
|
||||
}
|
||||
24
VDownload.Common/Playlist.cs
Normal file
24
VDownload.Common/Playlist.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using VDownload.Common.Models;
|
||||
|
||||
namespace VDownload.Common
|
||||
{
|
||||
public abstract class Playlist : ObservableObject, IEnumerable<Video>
|
||||
{
|
||||
public IEnumerator<Video> GetEnumerator()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
15
VDownload.Common/Services/ISourceSearchService.cs
Normal file
15
VDownload.Common/Services/ISourceSearchService.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using VDownload.Common.Models;
|
||||
|
||||
namespace VDownload.Common.Services
|
||||
{
|
||||
public interface ISourceSearchService
|
||||
{
|
||||
Task<Video> SearchVideo(string url);
|
||||
Task<Playlist> SearchPlaylist(string url, int maxVideoCount);
|
||||
}
|
||||
}
|
||||
13
VDownload.Common/Source.cs
Normal file
13
VDownload.Common/Source.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace VDownload.Common
|
||||
{
|
||||
public enum Source
|
||||
{
|
||||
Twitch
|
||||
}
|
||||
}
|
||||
13
VDownload.Common/VDownload.Common.csproj
Normal file
13
VDownload.Common/VDownload.Common.csproj
Normal file
@@ -0,0 +1,13 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.2.2" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
58
VDownload.Common/Video.cs
Normal file
58
VDownload.Common/Video.cs
Normal file
@@ -0,0 +1,58 @@
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace VDownload.Common.Models
|
||||
{
|
||||
public abstract partial class Video : ObservableObject
|
||||
{
|
||||
#region PROPERTIES
|
||||
|
||||
[ObservableProperty]
|
||||
protected string _title;
|
||||
|
||||
[ObservableProperty]
|
||||
protected string _description;
|
||||
|
||||
[ObservableProperty]
|
||||
protected string _author;
|
||||
|
||||
[ObservableProperty]
|
||||
protected DateTime _publishDate;
|
||||
|
||||
[ObservableProperty]
|
||||
protected TimeSpan _duration;
|
||||
|
||||
[ObservableProperty]
|
||||
protected int _viewCount;
|
||||
|
||||
[ObservableProperty]
|
||||
protected string? _thumbnailUrl;
|
||||
|
||||
[ObservableProperty]
|
||||
protected ObservableCollection<VideoStream> _streams;
|
||||
|
||||
[ObservableProperty]
|
||||
protected string _url;
|
||||
|
||||
[ObservableProperty]
|
||||
protected Source _source;
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region CONSTRUCTORS
|
||||
|
||||
protected Video()
|
||||
{
|
||||
_streams = new ObservableCollection<VideoStream>();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
13
VDownload.Common/VideoExtension.cs
Normal file
13
VDownload.Common/VideoExtension.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace VDownload.Common
|
||||
{
|
||||
public enum VideoExtension
|
||||
{
|
||||
MP4
|
||||
}
|
||||
}
|
||||
22
VDownload.Common/VideoStream.cs
Normal file
22
VDownload.Common/VideoStream.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace VDownload.Common
|
||||
{
|
||||
public abstract partial class VideoStream : ObservableObject
|
||||
{
|
||||
#region PROPERTIES
|
||||
|
||||
[ObservableProperty]
|
||||
protected string _streamIdentifier;
|
||||
|
||||
[ObservableProperty]
|
||||
private int _width;
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
namespace VDownload.Core.Enums
|
||||
{
|
||||
public enum AudioFileExtension
|
||||
{
|
||||
MP3 = 3,
|
||||
FLAC = 4,
|
||||
WAV = 5,
|
||||
M4A = 6,
|
||||
ALAC = 7,
|
||||
WMA = 8,
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
namespace VDownload.Core.Enums
|
||||
{
|
||||
public enum DownloadTasksAddingRequestSource
|
||||
{
|
||||
Video,
|
||||
Playlist,
|
||||
Subscriptions,
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
namespace VDownload.Core.Enums
|
||||
{
|
||||
public enum MediaFileExtension
|
||||
{
|
||||
MP4 = VideoFileExtension.MP4,
|
||||
WMV = VideoFileExtension.WMV,
|
||||
HEVC = VideoFileExtension.HEVC,
|
||||
MP3 = AudioFileExtension.MP3,
|
||||
FLAC = AudioFileExtension.FLAC,
|
||||
WAV = AudioFileExtension.WAV,
|
||||
M4A = AudioFileExtension.M4A,
|
||||
ALAC = AudioFileExtension.ALAC,
|
||||
WMA = AudioFileExtension.WMA,
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
namespace VDownload.Core.Enums
|
||||
{
|
||||
public enum MediaType
|
||||
{
|
||||
AudioVideo = 0,
|
||||
OnlyAudio = 1,
|
||||
OnlyVideo = 2,
|
||||
}
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
namespace VDownload.Core.Enums
|
||||
{
|
||||
public enum PlaylistSource
|
||||
{
|
||||
TwitchChannel,
|
||||
Null
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
namespace VDownload.Core.Enums
|
||||
{
|
||||
public enum VideoFileExtension
|
||||
{
|
||||
MP4 = 0,
|
||||
WMV = 1,
|
||||
HEVC = 2,
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
namespace VDownload.Core.Enums
|
||||
{
|
||||
public enum VideoSource
|
||||
{
|
||||
TwitchVod,
|
||||
TwitchClip,
|
||||
Null
|
||||
}
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
using System;
|
||||
using VDownload.Core.Enums;
|
||||
|
||||
namespace VDownload.Core.EventArgs
|
||||
{
|
||||
public class DownloadTaskStatusChangedEventArgs : System.EventArgs
|
||||
{
|
||||
#region CONSTRUCTORS
|
||||
|
||||
public DownloadTaskStatusChangedEventArgs(DownloadTaskStatus status)
|
||||
{
|
||||
Status = status;
|
||||
}
|
||||
|
||||
public DownloadTaskStatusChangedEventArgs(DownloadTaskStatus status, DateTime scheduledFor)
|
||||
{
|
||||
Status = status;
|
||||
ScheduledFor = scheduledFor;
|
||||
}
|
||||
|
||||
public DownloadTaskStatusChangedEventArgs(DownloadTaskStatus status, double progress)
|
||||
{
|
||||
Status = status;
|
||||
if (Status == DownloadTaskStatus.Downloading)
|
||||
{
|
||||
DownloadingProgress = progress;
|
||||
}
|
||||
else if (Status == DownloadTaskStatus.Processing)
|
||||
{
|
||||
ProcessingProgress = progress;
|
||||
}
|
||||
}
|
||||
|
||||
public DownloadTaskStatusChangedEventArgs(DownloadTaskStatus status, TimeSpan elapsedTime)
|
||||
{
|
||||
Status = status;
|
||||
ElapsedTime = elapsedTime;
|
||||
}
|
||||
|
||||
public DownloadTaskStatusChangedEventArgs(DownloadTaskStatus status, Exception exception)
|
||||
{
|
||||
Status = status;
|
||||
Exception = exception;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region PROPERTIES
|
||||
|
||||
public DownloadTaskStatus Status { get; private set; }
|
||||
public DateTime ScheduledFor { get; private set; }
|
||||
public double DownloadingProgress { get; private set; }
|
||||
public double ProcessingProgress { get; private set; }
|
||||
public TimeSpan ElapsedTime { get; private set; }
|
||||
public Exception Exception { get; private set; }
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
using VDownload.Core.Enums;
|
||||
using VDownload.Core.Structs;
|
||||
|
||||
namespace VDownload.Core.EventArgs
|
||||
{
|
||||
public class DownloadTasksAddingRequestedEventArgs : System.EventArgs
|
||||
{
|
||||
#region CONSTRUCTORS
|
||||
|
||||
public DownloadTasksAddingRequestedEventArgs(DownloadTask[] downloadTasks, DownloadTasksAddingRequestSource requestSource)
|
||||
{
|
||||
DownloadTasks = downloadTasks;
|
||||
RequestSource = requestSource;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region PROPERTIES
|
||||
|
||||
public DownloadTask[] DownloadTasks { get; private set; }
|
||||
public DownloadTasksAddingRequestSource RequestSource { get; private set; }
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
using VDownload.Core.Interfaces;
|
||||
|
||||
namespace VDownload.Core.EventArgs
|
||||
{
|
||||
public class PlaylistSearchSuccessedEventArgs : System.EventArgs
|
||||
{
|
||||
#region CONSTRUCTORS
|
||||
|
||||
public PlaylistSearchSuccessedEventArgs(IPlaylist playlist)
|
||||
{
|
||||
Playlist = playlist;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region PROPERTIES
|
||||
|
||||
public IPlaylist Playlist { get; private set; }
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
namespace VDownload.Core.EventArgs
|
||||
{
|
||||
public class ProgressChangedEventArgs : System.EventArgs
|
||||
{
|
||||
#region CONSTRUCTORS
|
||||
|
||||
public ProgressChangedEventArgs(double progress, bool isCompleted = false)
|
||||
{
|
||||
Progress = progress;
|
||||
IsCompleted = isCompleted;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region PROPERTIES
|
||||
|
||||
public double Progress { get; private set; }
|
||||
public bool IsCompleted { get; private set; }
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
using VDownload.Core.Interfaces;
|
||||
|
||||
namespace VDownload.Core.EventArgs
|
||||
{
|
||||
public class SubscriptionLoadSuccessedEventArgs : System.EventArgs
|
||||
{
|
||||
#region CONSTRUCTORS
|
||||
|
||||
public SubscriptionLoadSuccessedEventArgs(IVideo[] videos)
|
||||
{
|
||||
Videos = videos;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region PROPERTIES
|
||||
|
||||
public IVideo[] Videos { get; private set; }
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
using VDownload.Core.Interfaces;
|
||||
|
||||
namespace VDownload.Core.EventArgs
|
||||
{
|
||||
public class VideoSearchSuccessedEventArgs : System.EventArgs
|
||||
{
|
||||
#region CONSTRUCTORS
|
||||
|
||||
public VideoSearchSuccessedEventArgs(IVideo video)
|
||||
{
|
||||
Video = video;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region PROPERTIES
|
||||
|
||||
public IVideo Video { get; set; }
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace VDownload.Core.Exceptions
|
||||
{
|
||||
public class MediaNotFoundException : Exception
|
||||
{
|
||||
public MediaNotFoundException() { }
|
||||
public MediaNotFoundException(string message) : base(message) { }
|
||||
public MediaNotFoundException(string message, Exception inner) : base(message, inner) { }
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace VDownload.Core.Exceptions
|
||||
{
|
||||
public class SubscriptionExistsException : Exception
|
||||
{
|
||||
public SubscriptionExistsException() { }
|
||||
public SubscriptionExistsException(string message) : base(message) { }
|
||||
public SubscriptionExistsException(string message, Exception inner) : base(message, inner) { }
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace VDownload.Core.Exceptions
|
||||
{
|
||||
public class TwitchAccessTokenNotFoundException : Exception
|
||||
{
|
||||
public TwitchAccessTokenNotFoundException() { }
|
||||
public TwitchAccessTokenNotFoundException(string message) : base(message) { }
|
||||
public TwitchAccessTokenNotFoundException(string message, Exception inner) :base(message, inner) { }
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace VDownload.Core.Exceptions
|
||||
{
|
||||
public class TwitchAccessTokenNotValidException : Exception
|
||||
{
|
||||
public TwitchAccessTokenNotValidException() { }
|
||||
public TwitchAccessTokenNotValidException(string message) : base(message) { }
|
||||
public TwitchAccessTokenNotValidException(string message, Exception inner) : base(message, inner) { }
|
||||
}
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace VDownload.Core.Extensions
|
||||
{
|
||||
public static class TimeSpanExtension
|
||||
{
|
||||
// To string (TH:)MM:SS
|
||||
public static string ToStringOptTHBaseMMSS(this TimeSpan timeSpan, params TimeSpan[] formatBase)
|
||||
{
|
||||
StringBuilder formattedTimeSpan = new StringBuilder();
|
||||
|
||||
int maxTHLength = 0;
|
||||
foreach (TimeSpan format in formatBase.Concat(new TimeSpan[] { timeSpan }))
|
||||
{
|
||||
int THLength = Math.Floor(format.TotalHours) > 0 ? Math.Floor(timeSpan.TotalHours).ToString().Length : 0;
|
||||
if (THLength > maxTHLength)
|
||||
{
|
||||
maxTHLength = THLength;
|
||||
}
|
||||
}
|
||||
if (maxTHLength > 0)
|
||||
{
|
||||
formattedTimeSpan.Append($"{((int)Math.Floor(timeSpan.TotalHours)).ToString($"D{maxTHLength}")}:");
|
||||
}
|
||||
|
||||
formattedTimeSpan.Append(maxTHLength == 0 ? $"{timeSpan.Minutes}:" : $"{timeSpan.Minutes:00}:");
|
||||
|
||||
formattedTimeSpan.Append($"{timeSpan.Seconds:00}");
|
||||
|
||||
return formattedTimeSpan.ToString();
|
||||
}
|
||||
|
||||
// To string ((TH:)MM:)SS
|
||||
public static string ToStringOptTHMMBaseSS(this TimeSpan timeSpan, params TimeSpan[] formatBase)
|
||||
{
|
||||
StringBuilder formattedTimeSpan = new StringBuilder();
|
||||
|
||||
int maxTHLength = 0;
|
||||
foreach (TimeSpan format in formatBase.Concat(new TimeSpan[] { timeSpan }))
|
||||
{
|
||||
int THLength = Math.Floor(format.TotalHours) > 0 ? Math.Floor(timeSpan.TotalHours).ToString().Length : 0;
|
||||
if (THLength > maxTHLength)
|
||||
{
|
||||
maxTHLength = THLength;
|
||||
}
|
||||
}
|
||||
if (maxTHLength > 0)
|
||||
{
|
||||
formattedTimeSpan.Append($"{((int)Math.Floor(timeSpan.TotalHours)).ToString($"D{maxTHLength}")}:");
|
||||
}
|
||||
|
||||
bool MM = false;
|
||||
if (Math.Floor(timeSpan.TotalMinutes) > 0 || maxTHLength > 0)
|
||||
{
|
||||
formattedTimeSpan.Append(maxTHLength > 0 ? $"{timeSpan.Minutes:00}:" : $"{timeSpan.Minutes}:");
|
||||
MM = true;
|
||||
}
|
||||
|
||||
formattedTimeSpan.Append(MM ? $"{timeSpan.Seconds:00}" : $"{timeSpan.Seconds}");
|
||||
|
||||
return formattedTimeSpan.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using VDownload.Core.Enums;
|
||||
|
||||
namespace VDownload.Core.Interfaces
|
||||
{
|
||||
public interface IPlaylist
|
||||
{
|
||||
#region PROPERTIES
|
||||
|
||||
PlaylistSource Source { get; }
|
||||
string ID { get; }
|
||||
Uri Url { get; }
|
||||
string Name { get; }
|
||||
IVideo[] Videos { get; }
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region METHODS
|
||||
|
||||
Task GetMetadataAsync(CancellationToken cancellationToken = default);
|
||||
|
||||
Task GetVideosAsync(CancellationToken cancellationToken = default);
|
||||
Task GetVideosAsync(int numberOfVideos, CancellationToken cancellationToken = default);
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using VDownload.Core.Enums;
|
||||
using VDownload.Core.Structs;
|
||||
using Windows.Storage;
|
||||
|
||||
namespace VDownload.Core.Interfaces
|
||||
{
|
||||
public interface IVideo
|
||||
{
|
||||
#region PROPERTIES
|
||||
|
||||
VideoSource Source { get; }
|
||||
string ID { get; }
|
||||
Uri Url { get; }
|
||||
string Title { get; }
|
||||
string Author { get; }
|
||||
DateTime Date { get; }
|
||||
TimeSpan Duration { get; }
|
||||
long Views { get; }
|
||||
Uri Thumbnail { get; }
|
||||
BaseStream[] BaseStreams { get; }
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region METHODS
|
||||
|
||||
Task GetMetadataAsync(CancellationToken cancellationToken = default);
|
||||
|
||||
Task GetStreamsAsync(CancellationToken cancellationToken = default);
|
||||
|
||||
Task<StorageFile> DownloadAndTranscodeAsync(StorageFolder downloadingFolder, BaseStream baseStream, MediaFileExtension extension, MediaType mediaType, TrimData trim, CancellationToken cancellationToken = default);
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region EVENTS
|
||||
|
||||
event EventHandler<EventArgs.ProgressChangedEventArgs> DownloadingProgressChanged;
|
||||
event EventHandler<EventArgs.ProgressChangedEventArgs> ProcessingProgressChanged;
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,76 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using VDownload.Core.Enums;
|
||||
using Windows.Media.Editing;
|
||||
using Windows.Media.Transcoding;
|
||||
using Windows.Storage;
|
||||
|
||||
namespace VDownload.Core.Services
|
||||
{
|
||||
public static class Config
|
||||
{
|
||||
#region CONSTANTS
|
||||
|
||||
private static readonly Dictionary<string, object> DefaultSettings = new Dictionary<string, object>()
|
||||
{
|
||||
{ "delete_temp_on_start", true },
|
||||
{ "twitch_vod_passive_trim", true },
|
||||
{ "twitch_vod_downloading_chunk_retry_after_error", false },
|
||||
{ "twitch_vod_downloading_chunk_max_retries", 10 },
|
||||
{ "twitch_vod_downloading_chunk_retries_delay", 5000 },
|
||||
{ "media_transcoding_use_hardware_acceleration", true },
|
||||
{ "media_transcoding_algorithm", (int)MediaVideoProcessingAlgorithm.MrfCrf444 },
|
||||
{ "media_editing_algorithm", (int)MediaTrimmingPreference.Fast },
|
||||
{ "default_max_playlist_videos", 0 },
|
||||
{ "default_media_type", (int)MediaType.AudioVideo },
|
||||
{ "default_filename", "[<date_pub:yyyy.MM.dd>] <title>" },
|
||||
{ "default_video_extension", (int)VideoFileExtension.MP4 },
|
||||
{ "default_audio_extension", (int)AudioFileExtension.MP3 },
|
||||
{ "custom_media_location", false },
|
||||
{ "max_active_video_task", 5 },
|
||||
{ "replace_output_file_if_exists", false },
|
||||
{ "remove_task_when_successfully_ended", false },
|
||||
{ "delete_task_temp_when_ended_with_error", false },
|
||||
{ "show_notification_when_task_ended_successfully", false },
|
||||
{ "show_notification_when_task_ended_unsuccessfully", false },
|
||||
{ "show_warning_when_task_starts_on_metered_network", true },
|
||||
{ "delay_task_when_queued_task_starts_on_metered_network", true }
|
||||
};
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region PUBLIC METHODS
|
||||
|
||||
public static object GetValue(string key)
|
||||
{
|
||||
return ApplicationData.Current.LocalSettings.Values[key];
|
||||
}
|
||||
|
||||
public static void SetValue(string key, object value)
|
||||
{
|
||||
ApplicationData.Current.LocalSettings.Values[key] = value;
|
||||
}
|
||||
|
||||
public static void SetDefault()
|
||||
{
|
||||
foreach (KeyValuePair<string, object> s in DefaultSettings)
|
||||
{
|
||||
ApplicationData.Current.LocalSettings.Values[s.Key] = s.Value;
|
||||
}
|
||||
}
|
||||
|
||||
public static void Rebuild()
|
||||
{
|
||||
foreach (KeyValuePair<string, object> s in DefaultSettings)
|
||||
{
|
||||
if (!ApplicationData.Current.LocalSettings.Values.ContainsKey(s.Key))
|
||||
{
|
||||
ApplicationData.Current.LocalSettings.Values[s.Key] = s.Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,178 +0,0 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using VDownload.Core.Enums;
|
||||
using VDownload.Core.EventArgs;
|
||||
using VDownload.Core.Interfaces;
|
||||
using VDownload.Core.Services;
|
||||
using Windows.ApplicationModel.ExtendedExecution;
|
||||
using Windows.Storage;
|
||||
using Windows.Storage.AccessCache;
|
||||
|
||||
namespace VDownload.Core.Structs
|
||||
{
|
||||
public class DownloadTask
|
||||
{
|
||||
#region CONSTRUCTORS
|
||||
|
||||
public DownloadTask(string id, IVideo video, MediaType mediaType, BaseStream selectedStream, TrimData trim, OutputFile file, double schedule)
|
||||
{
|
||||
Id = id;
|
||||
Video = video;
|
||||
MediaType = mediaType;
|
||||
SelectedStream = selectedStream;
|
||||
Trim = trim;
|
||||
File = file;
|
||||
Schedule = schedule;
|
||||
|
||||
Status = DownloadTaskStatus.Idle;
|
||||
LastStatusChangedEventArgs = new DownloadTaskStatusChangedEventArgs(Status);
|
||||
CancellationTokenSource = new CancellationTokenSource();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region PROPERTIES
|
||||
|
||||
public string Id { get; set; }
|
||||
public IVideo Video { get; set; }
|
||||
public MediaType MediaType { get; set; }
|
||||
public BaseStream SelectedStream { get; set; }
|
||||
public TrimData Trim { get; set; }
|
||||
public OutputFile File { get; set; }
|
||||
public double Schedule { get; set; }
|
||||
public DownloadTaskStatus Status { get; private set; }
|
||||
public DownloadTaskStatusChangedEventArgs LastStatusChangedEventArgs { get; private set; }
|
||||
public CancellationTokenSource CancellationTokenSource { get; private set; }
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region METHODS
|
||||
|
||||
public async Task Run(bool delayWhenOnMeteredConnection)
|
||||
{
|
||||
StatusChanged.Invoke(this, new DownloadTaskStatusChangedEventArgs(Status));
|
||||
|
||||
CancellationTokenSource = new CancellationTokenSource();
|
||||
|
||||
if (Schedule > 0)
|
||||
{
|
||||
DateTime scheduleFor = DateTime.Now.AddMinutes(Schedule);
|
||||
Status = DownloadTaskStatus.Scheduled;
|
||||
LastStatusChangedEventArgs = new DownloadTaskStatusChangedEventArgs(Status, scheduleFor);
|
||||
StatusChanged.Invoke(this, LastStatusChangedEventArgs);
|
||||
while (DateTime.Now < scheduleFor && !CancellationTokenSource.Token.IsCancellationRequested)
|
||||
{
|
||||
await Task.Delay(100);
|
||||
}
|
||||
}
|
||||
|
||||
Status = DownloadTaskStatus.Queued;
|
||||
LastStatusChangedEventArgs = new DownloadTaskStatusChangedEventArgs(Status);
|
||||
StatusChanged.Invoke(this, LastStatusChangedEventArgs);
|
||||
await DownloadTasksCollectionManagement.WaitInQueue(delayWhenOnMeteredConnection, CancellationTokenSource.Token);
|
||||
|
||||
if (!CancellationTokenSource.Token.IsCancellationRequested)
|
||||
{
|
||||
Status = DownloadTaskStatus.Downloading;
|
||||
LastStatusChangedEventArgs = new DownloadTaskStatusChangedEventArgs(Status, 0);
|
||||
StatusChanged.Invoke(this, LastStatusChangedEventArgs);
|
||||
|
||||
StorageFolder tempFolder;
|
||||
if (StorageApplicationPermissions.FutureAccessList.ContainsItem("custom_temp_location"))
|
||||
{
|
||||
tempFolder = await StorageApplicationPermissions.FutureAccessList.GetFolderAsync("custom_temp_location");
|
||||
}
|
||||
else
|
||||
{
|
||||
tempFolder = ApplicationData.Current.TemporaryFolder;
|
||||
}
|
||||
tempFolder = await tempFolder.CreateFolderAsync(Id);
|
||||
|
||||
bool endedWithError = false;
|
||||
|
||||
try
|
||||
{
|
||||
CancellationTokenSource.Token.ThrowIfCancellationRequested();
|
||||
|
||||
Stopwatch taskStopwatch = Stopwatch.StartNew();
|
||||
|
||||
ExtendedExecutionSession session = new ExtendedExecutionSession { Reason = ExtendedExecutionReason.Unspecified };
|
||||
await session.RequestExtensionAsync();
|
||||
CancellationTokenSource.Token.ThrowIfCancellationRequested();
|
||||
|
||||
Video.DownloadingProgressChanged += DownloadingProgressChanged;
|
||||
Video.ProcessingProgressChanged += ProcessingProgressChanged;
|
||||
StorageFile tempOutputFile = await Video.DownloadAndTranscodeAsync(tempFolder, SelectedStream, File.Extension, MediaType, Trim, CancellationTokenSource.Token);
|
||||
|
||||
session.Dispose();
|
||||
|
||||
Status = DownloadTaskStatus.Finalizing;
|
||||
LastStatusChangedEventArgs = new DownloadTaskStatusChangedEventArgs(Status);
|
||||
StatusChanged.Invoke(this, LastStatusChangedEventArgs);
|
||||
|
||||
StorageFile outputFile = await File.Create();
|
||||
|
||||
CancellationTokenSource.Token.ThrowIfCancellationRequested();
|
||||
await tempOutputFile.MoveAndReplaceAsync(outputFile);
|
||||
|
||||
taskStopwatch.Stop();
|
||||
|
||||
Status = DownloadTaskStatus.EndedSuccessfully;
|
||||
LastStatusChangedEventArgs = new DownloadTaskStatusChangedEventArgs(Status, taskStopwatch.Elapsed);
|
||||
StatusChanged.Invoke(this, LastStatusChangedEventArgs);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
endedWithError = true;
|
||||
Status = DownloadTaskStatus.EndedUnsuccessfully;
|
||||
LastStatusChangedEventArgs = new DownloadTaskStatusChangedEventArgs(Status, ex);
|
||||
StatusChanged.Invoke(this, LastStatusChangedEventArgs);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (!endedWithError || (bool)Config.GetValue("delete_task_temp_when_ended_with_error"))
|
||||
{
|
||||
// Delete temporary files
|
||||
await tempFolder.DeleteAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Status = DownloadTaskStatus.EndedUnsuccessfully;
|
||||
LastStatusChangedEventArgs = new DownloadTaskStatusChangedEventArgs(Status, new OperationCanceledException(CancellationTokenSource.Token));
|
||||
StatusChanged.Invoke(this, LastStatusChangedEventArgs);
|
||||
}
|
||||
}
|
||||
|
||||
private void DownloadingProgressChanged(object sender, ProgressChangedEventArgs e)
|
||||
{
|
||||
Status = DownloadTaskStatus.Downloading;
|
||||
LastStatusChangedEventArgs = new DownloadTaskStatusChangedEventArgs(Status, e.Progress);
|
||||
StatusChanged.Invoke(this, LastStatusChangedEventArgs);
|
||||
}
|
||||
|
||||
private void ProcessingProgressChanged(object sender, ProgressChangedEventArgs e)
|
||||
{
|
||||
Status = DownloadTaskStatus.Processing;
|
||||
LastStatusChangedEventArgs = new DownloadTaskStatusChangedEventArgs(Status, e.Progress);
|
||||
StatusChanged.Invoke(this, LastStatusChangedEventArgs);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region EVENT
|
||||
|
||||
public event EventHandler<DownloadTaskStatusChangedEventArgs> StatusChanged;
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
using Microsoft.Toolkit.Uwp.Connectivity;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using VDownload.Core.Structs;
|
||||
|
||||
namespace VDownload.Core.Services
|
||||
{
|
||||
public static class DownloadTasksCollectionManagement
|
||||
{
|
||||
#region PROPERTIES
|
||||
|
||||
private static readonly Dictionary<string, DownloadTask> ChangeableDownloadTasksCollection = new Dictionary<string, DownloadTask>();
|
||||
public static readonly ReadOnlyDictionary<string, DownloadTask> DownloadTasksCollection = new ReadOnlyDictionary<string, DownloadTask>(ChangeableDownloadTasksCollection);
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region PUBLIC METHODS
|
||||
|
||||
public static string GenerateID()
|
||||
{
|
||||
char[] idChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890".ToCharArray();
|
||||
int idLength = 10;
|
||||
|
||||
string id;
|
||||
do
|
||||
{
|
||||
id = "";
|
||||
while (id.Length < idLength)
|
||||
{
|
||||
id += idChars[new Random().Next(0, idChars.Length)];
|
||||
}
|
||||
} while (DownloadTasksCollection.Keys.Contains(id));
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
public static void Add(DownloadTask downloadTask, string id)
|
||||
{
|
||||
ChangeableDownloadTasksCollection[id] = downloadTask;
|
||||
}
|
||||
|
||||
public static void Remove(string id)
|
||||
{
|
||||
ChangeableDownloadTasksCollection.Remove(id);
|
||||
}
|
||||
|
||||
public static async Task WaitInQueue(bool delayWhenOnMeteredConnection, CancellationToken cancellationToken = default)
|
||||
{
|
||||
while ((ChangeableDownloadTasksCollection.Values.Where((DownloadTask task) => task.Status == Enums.DownloadTaskStatus.Downloading || task.Status == Enums.DownloadTaskStatus.Processing || task.Status == Enums.DownloadTaskStatus.Finalizing).Count() >= (int)Config.GetValue("max_active_video_task") || (delayWhenOnMeteredConnection && NetworkHelper.Instance.ConnectionInformation.IsInternetOnMeteredConnection)) && !cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
await Task.Delay(100);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,146 +0,0 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using VDownload.Core.Enums;
|
||||
using VDownload.Core.Structs;
|
||||
using Windows.Foundation;
|
||||
using Windows.Media.Editing;
|
||||
using Windows.Media.MediaProperties;
|
||||
using Windows.Media.Transcoding;
|
||||
using Windows.Storage;
|
||||
using Windows.Storage.Streams;
|
||||
|
||||
namespace VDownload.Core.Services
|
||||
{
|
||||
public class MediaProcessor
|
||||
{
|
||||
#region PUBLIC METHODS
|
||||
|
||||
public async Task Run(StorageFile mediaFile, MediaFileExtension extension, MediaType mediaType, StorageFile outputFile, TrimData trim, CancellationToken cancellationToken = default)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
ProgressChanged(this, new EventArgs.ProgressChangedEventArgs(0));
|
||||
|
||||
MediaTranscoder mediaTranscoder = new MediaTranscoder
|
||||
{
|
||||
HardwareAccelerationEnabled = (bool)Config.GetValue("media_transcoding_use_hardware_acceleration"),
|
||||
VideoProcessingAlgorithm = (MediaVideoProcessingAlgorithm)Config.GetValue("media_transcoding_algorithm"),
|
||||
};
|
||||
if (trim.Start != null) mediaTranscoder.TrimStartTime = trim.Start;
|
||||
if (trim.End != null) mediaTranscoder.TrimStopTime = trim.End;
|
||||
|
||||
using (IRandomAccessStream openedOutputFile = await outputFile.OpenAsync(FileAccessMode.ReadWrite))
|
||||
{
|
||||
PrepareTranscodeResult transcodingPreparated = await mediaTranscoder.PrepareStreamTranscodeAsync(await mediaFile.OpenAsync(FileAccessMode.Read), openedOutputFile, await GetMediaEncodingProfile(mediaFile, extension, mediaType));
|
||||
|
||||
IAsyncActionWithProgress<double> transcodingTask = transcodingPreparated.TranscodeAsync();
|
||||
await transcodingTask.AsTask(cancellationToken, new Progress<double>((percent) => { ProgressChanged(this, new EventArgs.ProgressChangedEventArgs(percent)); }));
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
await openedOutputFile.FlushAsync();
|
||||
transcodingTask.Close();
|
||||
}
|
||||
|
||||
ProgressChanged(this, new EventArgs.ProgressChangedEventArgs(100, true));
|
||||
}
|
||||
public async Task Run(StorageFile audioFile, StorageFile videoFile, VideoFileExtension extension, StorageFile outputFile, TrimData trim, CancellationToken cancellationToken = default)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
ProgressChanged(this, new EventArgs.ProgressChangedEventArgs(0));
|
||||
|
||||
MediaComposition mediaEditor = new MediaComposition();
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
Task<MediaClip> getVideoFileTask = MediaClip.CreateFromFileAsync(videoFile).AsTask();
|
||||
Task<BackgroundAudioTrack> getAudioFileTask = BackgroundAudioTrack.CreateFromFileAsync(audioFile).AsTask();
|
||||
await Task.WhenAll(getVideoFileTask, getAudioFileTask);
|
||||
|
||||
MediaClip videoElement = getVideoFileTask.Result;
|
||||
if (trim.Start != null) videoElement.TrimTimeFromStart = trim.Start;
|
||||
if (trim.End != null) videoElement.TrimTimeFromEnd = trim.End;
|
||||
BackgroundAudioTrack audioElement = getAudioFileTask.Result;
|
||||
if (trim.Start != null) audioElement.TrimTimeFromStart = trim.Start;
|
||||
if (trim.End != null) audioElement.TrimTimeFromEnd = trim.End;
|
||||
|
||||
mediaEditor.Clips.Add(videoElement);
|
||||
mediaEditor.BackgroundAudioTracks.Add(audioElement);
|
||||
|
||||
var renderOperation = mediaEditor.RenderToFileAsync(outputFile, (MediaTrimmingPreference)Config.GetValue("media_editing_algorithm"), await GetMediaEncodingProfile(videoFile, audioFile, (MediaFileExtension)extension, MediaType.AudioVideo));
|
||||
renderOperation.Progress += (info, progress) => { ProgressChanged(this, new EventArgs.ProgressChangedEventArgs(progress)); };
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
await renderOperation.AsTask(cancellationToken);
|
||||
|
||||
ProgressChanged(this, new EventArgs.ProgressChangedEventArgs(100, true));
|
||||
}
|
||||
public async Task Run(StorageFile audioFile, AudioFileExtension extension, StorageFile outputFile, TrimData trim, CancellationToken cancellationToken = default)
|
||||
{
|
||||
await Run(audioFile, (MediaFileExtension)extension, MediaType.OnlyAudio, outputFile, trim, cancellationToken);
|
||||
}
|
||||
public async Task Run(StorageFile videoFile, VideoFileExtension extension, StorageFile outputFile, TrimData trim, CancellationToken cancellationToken = default)
|
||||
{
|
||||
await Run(videoFile, (MediaFileExtension)extension, MediaType.OnlyVideo, outputFile, trim, cancellationToken);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region PRIVATE METHODS
|
||||
|
||||
private static async Task<MediaEncodingProfile> GetMediaEncodingProfile(StorageFile videoFile, StorageFile audioFile, MediaFileExtension extension, MediaType mediaType)
|
||||
{
|
||||
MediaEncodingProfile profile;
|
||||
|
||||
switch (extension)
|
||||
{
|
||||
default:
|
||||
case MediaFileExtension.MP4: profile = MediaEncodingProfile.CreateMp4(VideoEncodingQuality.HD1080p); break;
|
||||
case MediaFileExtension.WMV: profile = MediaEncodingProfile.CreateWmv(VideoEncodingQuality.HD1080p); break;
|
||||
case MediaFileExtension.HEVC: profile = MediaEncodingProfile.CreateHevc(VideoEncodingQuality.HD1080p); break;
|
||||
case MediaFileExtension.MP3: profile = MediaEncodingProfile.CreateMp3(AudioEncodingQuality.High); break;
|
||||
case MediaFileExtension.FLAC: profile = MediaEncodingProfile.CreateFlac(AudioEncodingQuality.High); break;
|
||||
case MediaFileExtension.WAV: profile = MediaEncodingProfile.CreateWav(AudioEncodingQuality.High); break;
|
||||
case MediaFileExtension.M4A: profile = MediaEncodingProfile.CreateM4a(AudioEncodingQuality.High); break;
|
||||
case MediaFileExtension.ALAC: profile = MediaEncodingProfile.CreateAlac(AudioEncodingQuality.High); break;
|
||||
case MediaFileExtension.WMA: profile = MediaEncodingProfile.CreateWma(AudioEncodingQuality.High); break;
|
||||
}
|
||||
|
||||
if (mediaType != MediaType.OnlyAudio)
|
||||
{
|
||||
var videoData = await videoFile.Properties.GetVideoPropertiesAsync();
|
||||
profile.Video.Height = videoData.Height;
|
||||
profile.Video.Width = videoData.Width;
|
||||
profile.Video.Bitrate = videoData.Bitrate;
|
||||
}
|
||||
if (mediaType != MediaType.OnlyVideo)
|
||||
{
|
||||
var audioData = await audioFile.Properties.GetMusicPropertiesAsync();
|
||||
profile.Audio.Bitrate = audioData.Bitrate;
|
||||
if (mediaType == MediaType.AudioVideo) profile.Video.Bitrate -= audioData.Bitrate;
|
||||
}
|
||||
if (mediaType == MediaType.OnlyVideo)
|
||||
{
|
||||
var audioTracks = profile.GetAudioTracks();
|
||||
audioTracks.Clear();
|
||||
profile.SetAudioTracks(audioTracks.AsEnumerable());
|
||||
}
|
||||
|
||||
return profile;
|
||||
}
|
||||
private static async Task<MediaEncodingProfile> GetMediaEncodingProfile(StorageFile audioVideoFile, MediaFileExtension extension, MediaType mediaType)
|
||||
{
|
||||
return await GetMediaEncodingProfile(audioVideoFile, audioVideoFile, extension, mediaType);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region EVENTS
|
||||
|
||||
public event EventHandler<EventArgs.ProgressChangedEventArgs> ProgressChanged;
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using VDownload.Core.Enums;
|
||||
using Windows.Storage;
|
||||
|
||||
namespace VDownload.Core.Services
|
||||
{
|
||||
public class OutputFile
|
||||
{
|
||||
#region CONSTRUCTORS
|
||||
|
||||
public OutputFile(string name, MediaFileExtension extension, StorageFolder location)
|
||||
{
|
||||
Name = name;
|
||||
Extension = extension;
|
||||
Location = location;
|
||||
}
|
||||
|
||||
public OutputFile(string name, MediaFileExtension extension)
|
||||
{
|
||||
Name = name;
|
||||
Extension = extension;
|
||||
Location = null;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region PROPERTIES
|
||||
|
||||
public string Name { get; private set; }
|
||||
public MediaFileExtension Extension { get; private set; }
|
||||
public StorageFolder Location { get; private set; }
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region PUBLIC METHODS
|
||||
|
||||
public async Task<StorageFile> Create()
|
||||
{
|
||||
string filename = $"{Name}.{Extension.ToString().ToLower()}";
|
||||
CreationCollisionOption collisionOption = (bool)Config.GetValue("replace_output_file_if_exists") ? CreationCollisionOption.ReplaceExisting : CreationCollisionOption.GenerateUniqueName;
|
||||
return await(!(Location is null) ? Location.CreateFileAsync(filename, collisionOption) : DownloadsFolder.CreateFileAsync(filename, collisionOption));
|
||||
}
|
||||
|
||||
public string GetPath() => $@"{(Location != null ? Location.Path : $@"{UserDataPaths.GetDefault().Downloads}\VDownload")}\{Name}.{Extension.ToString().ToLower()}";
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using VDownload.Core.Interfaces;
|
||||
|
||||
namespace VDownload.Core.Services
|
||||
{
|
||||
[Serializable]
|
||||
public class Subscription
|
||||
{
|
||||
#region CONSTRUCTORS
|
||||
|
||||
public Subscription(IPlaylist playlist)
|
||||
{
|
||||
Playlist = playlist;
|
||||
SavedVideos = Playlist.Videos;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region PROPERTIES
|
||||
|
||||
public IPlaylist Playlist { get; private set; }
|
||||
public IVideo[] SavedVideos { get; private set; }
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region PUBLIC METHODS
|
||||
|
||||
public async Task<IVideo[]> GetNewVideosAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
await Playlist.GetVideosAsync(cancellationToken);
|
||||
return GetUnsavedVideos();
|
||||
}
|
||||
|
||||
public async Task<IVideo[]> GetNewVideosAndUpdateAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
await Playlist.GetVideosAsync(cancellationToken);
|
||||
IVideo[] newVideos = GetUnsavedVideos();
|
||||
SavedVideos = Playlist.Videos;
|
||||
return newVideos;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region PRIVATE METHODS
|
||||
|
||||
private IVideo[] GetUnsavedVideos()
|
||||
{
|
||||
List<IVideo> newVideos = Playlist.Videos.ToList();
|
||||
foreach (IVideo savedVideo in SavedVideos)
|
||||
{
|
||||
newVideos.RemoveAll((v) => v.Source == savedVideo.Source && v.ID == savedVideo.ID);
|
||||
}
|
||||
return newVideos.ToArray();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,79 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.Serialization.Formatters.Binary;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using VDownload.Core.Exceptions;
|
||||
using Windows.Storage;
|
||||
|
||||
namespace VDownload.Core.Services
|
||||
{
|
||||
public static class SubscriptionsCollectionManagement
|
||||
{
|
||||
#region CONSTANTS
|
||||
|
||||
private static readonly StorageFolder SubscriptionFolderLocation = ApplicationData.Current.LocalFolder;
|
||||
private static readonly string SubscriptionsFolderName = "Subscriptions";
|
||||
private static readonly string SubscriptionFileExtension = "vsub";
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region PUBLIC METHODS
|
||||
|
||||
public static async Task<(Subscription Subscription, StorageFile SubscriptionFile)[]> GetSubscriptionsAsync()
|
||||
{
|
||||
List<(Subscription Subscription, StorageFile SubscriptionFile)> subscriptions = new List<(Subscription Subscription,StorageFile SubscriptionFile)> ();
|
||||
StorageFolder subscriptionsFolder = await SubscriptionFolderLocation.CreateFolderAsync(SubscriptionsFolderName, CreationCollisionOption.OpenIfExists);
|
||||
BinaryFormatter formatter = new BinaryFormatter();
|
||||
foreach (StorageFile file in await subscriptionsFolder.GetFilesAsync())
|
||||
{
|
||||
if (file.Name.EndsWith(SubscriptionFileExtension))
|
||||
{
|
||||
Stream fileStream = await file.OpenStreamForReadAsync();
|
||||
Subscription subscription = (Subscription)formatter.Deserialize(fileStream);
|
||||
subscriptions.Add((subscription, file));
|
||||
}
|
||||
}
|
||||
return subscriptions.ToArray();
|
||||
}
|
||||
|
||||
public static async Task<StorageFile> CreateSubscriptionFileAsync(Subscription subscription)
|
||||
{
|
||||
StorageFolder subscriptionsFolder = await SubscriptionFolderLocation.CreateFolderAsync(SubscriptionsFolderName, CreationCollisionOption.OpenIfExists);
|
||||
try
|
||||
{
|
||||
StorageFile subscriptionFile = await subscriptionsFolder.CreateFileAsync($"{(int)subscription.Playlist.Source}-{subscription.Playlist.ID}.{SubscriptionFileExtension}", CreationCollisionOption.FailIfExists);
|
||||
BinaryFormatter formatter = new BinaryFormatter();
|
||||
Stream subscriptionFileStream = await subscriptionFile.OpenStreamForWriteAsync();
|
||||
formatter.Serialize(subscriptionFileStream, subscription);
|
||||
return subscriptionFile;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if ((uint)ex.HResult == 0x800700B7)
|
||||
{
|
||||
throw new SubscriptionExistsException($"Subscription with id \"{(int)subscription.Playlist.Source}-{subscription.Playlist.ID}\" already exists");
|
||||
}
|
||||
else
|
||||
{
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task UpdateSubscriptionFileAsync(Subscription subscription, StorageFile subscriptionFile)
|
||||
{
|
||||
BinaryFormatter formatter = new BinaryFormatter();
|
||||
Stream subscriptionFileStream = await subscriptionFile.OpenStreamForWriteAsync();
|
||||
formatter.Serialize(subscriptionFileStream, subscription);
|
||||
}
|
||||
|
||||
public static async Task DeleteSubscriptionFileAsync(StorageFile subscriptionFile) => await subscriptionFile.DeleteAsync(StorageDeleteOption.PermanentDelete);
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
using Windows.Storage;
|
||||
|
||||
namespace VDownload.Core.Sources
|
||||
{
|
||||
internal static class AuthorizationData
|
||||
{
|
||||
internal static StorageFolder FolderLocation = ApplicationData.Current.LocalCacheFolder;
|
||||
internal static string FolderName = "AuthData";
|
||||
internal static string FilesExtension = "auth";
|
||||
}
|
||||
}
|
||||
@@ -1,86 +0,0 @@
|
||||
using System.Text.RegularExpressions;
|
||||
using VDownload.Core.Enums;
|
||||
using VDownload.Core.Interfaces;
|
||||
|
||||
namespace VDownload.Core.Services.Sources
|
||||
{
|
||||
public static class Source
|
||||
{
|
||||
#region CONSTANTS
|
||||
|
||||
// VIDEO SOURCES REGULAR EXPRESSIONS
|
||||
private static readonly (Regex Regex, VideoSource Type)[] VideoSources = new (Regex Regex, VideoSource Type)[]
|
||||
{
|
||||
(new Regex(@"^https://www.twitch.tv/videos/(?<id>\d+)"), VideoSource.TwitchVod),
|
||||
(new Regex(@"^https://www.twitch.tv/\S+/clip/(?<id>[^?]+)"), VideoSource.TwitchClip),
|
||||
(new Regex(@"^https://clips.twitch.tv/(?<id>[^?]+)"), VideoSource.TwitchClip),
|
||||
};
|
||||
|
||||
// PLAYLIST SOURCES REGULAR EXPRESSIONS
|
||||
private static readonly (Regex Regex, PlaylistSource Type)[] PlaylistSources = new (Regex Regex, PlaylistSource Type)[]
|
||||
{
|
||||
(new Regex(@"^https://www.twitch.tv/(?<id>[^?/]+)"), PlaylistSource.TwitchChannel),
|
||||
};
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region METHODS
|
||||
|
||||
// GET VIDEO SOURCE
|
||||
public static IVideo GetVideo(string url)
|
||||
{
|
||||
VideoSource source = VideoSource.Null;
|
||||
string id = string.Empty;
|
||||
foreach ((Regex Regex, VideoSource Type) Source in VideoSources)
|
||||
{
|
||||
Match sourceMatch = Source.Regex.Match(url);
|
||||
if (sourceMatch.Success)
|
||||
{
|
||||
source = Source.Type;
|
||||
id = sourceMatch.Groups["id"].Value;
|
||||
}
|
||||
}
|
||||
return GetVideo(source, id);
|
||||
}
|
||||
public static IVideo GetVideo(VideoSource source, string id)
|
||||
{
|
||||
IVideo videoService = null;
|
||||
switch (source)
|
||||
{
|
||||
case VideoSource.TwitchVod: videoService = new Twitch.Vod(id); break;
|
||||
case VideoSource.TwitchClip: videoService = new Twitch.Clip(id); break;
|
||||
}
|
||||
return videoService;
|
||||
}
|
||||
|
||||
// GET PLAYLIST SOURCE
|
||||
public static IPlaylist GetPlaylist(string url)
|
||||
{
|
||||
PlaylistSource source = PlaylistSource.Null;
|
||||
string id = string.Empty;
|
||||
foreach ((Regex Regex, PlaylistSource Type) Source in PlaylistSources)
|
||||
{
|
||||
Match sourceMatch = Source.Regex.Match(url);
|
||||
if (sourceMatch.Success)
|
||||
{
|
||||
source = Source.Type;
|
||||
id = sourceMatch.Groups["id"].Value;
|
||||
}
|
||||
}
|
||||
return GetPlaylist(source, id);
|
||||
}
|
||||
public static IPlaylist GetPlaylist(PlaylistSource source, string id)
|
||||
{
|
||||
IPlaylist playlistService = null;
|
||||
switch (source)
|
||||
{
|
||||
case PlaylistSource.TwitchChannel: playlistService = new Twitch.Channel(id); break;
|
||||
}
|
||||
return playlistService;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,113 +0,0 @@
|
||||
using Newtonsoft.Json.Linq;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using VDownload.Core.Enums;
|
||||
using VDownload.Core.Exceptions;
|
||||
using VDownload.Core.Interfaces;
|
||||
using VDownload.Core.Services.Sources.Twitch.Helpers;
|
||||
|
||||
namespace VDownload.Core.Services.Sources.Twitch
|
||||
{
|
||||
[Serializable]
|
||||
public class Channel : IPlaylist
|
||||
{
|
||||
#region CONSTRUCTORS
|
||||
|
||||
public Channel(string id)
|
||||
{
|
||||
Source = PlaylistSource.TwitchChannel;
|
||||
ID = id;
|
||||
Url = new Uri($"https://twitch.tv/{ID}");
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region PROPERTIES
|
||||
|
||||
public PlaylistSource Source { get; private set; }
|
||||
public string ID { get; private set; }
|
||||
private string UniqueID { get; set; }
|
||||
public Uri Url { get; private set; }
|
||||
public string Name { get; private set; }
|
||||
public IVideo[] Videos { get; private set; }
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region PUBLIC METHODS
|
||||
|
||||
public async Task GetMetadataAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
JToken response = null;
|
||||
using (WebClient client = await Client.Helix())
|
||||
{
|
||||
client.QueryString.Add("login", ID);
|
||||
response = JObject.Parse(await client.DownloadStringTaskAsync("https://api.twitch.tv/helix/users"))["data"];
|
||||
if (((JArray)response).Count > 0) response = response[0];
|
||||
else throw new MediaNotFoundException($"Twitch Channel (ID: {ID}) was not found");
|
||||
}
|
||||
|
||||
UniqueID = (string)response["id"];
|
||||
Name = (string)response["display_name"];
|
||||
}
|
||||
|
||||
public async Task GetVideosAsync(CancellationToken cancellationToken = default) => await GetVideosAsync(0, cancellationToken);
|
||||
public async Task GetVideosAsync(int numberOfVideos, CancellationToken cancellationToken = default)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
string pagination = "";
|
||||
|
||||
List<Vod> videos = new List<Vod>();
|
||||
|
||||
bool getAll = numberOfVideos == 0;
|
||||
int count;
|
||||
JToken[] videosData;
|
||||
List<Task> getStreamsTasks = new List<Task>();
|
||||
do
|
||||
{
|
||||
count = numberOfVideos < 100 && !getAll ? numberOfVideos : 100;
|
||||
|
||||
JToken response = null;
|
||||
using (WebClient client = await Client.Helix())
|
||||
{
|
||||
client.QueryString.Add("user_id", UniqueID);
|
||||
client.QueryString.Add("first", count.ToString());
|
||||
client.QueryString.Add("after", pagination);
|
||||
response = JObject.Parse(await client.DownloadStringTaskAsync("https://api.twitch.tv/helix/videos"));
|
||||
}
|
||||
|
||||
pagination = (string)response["pagination"]["cursor"];
|
||||
|
||||
videosData = response["data"].ToArray();
|
||||
foreach (JToken videoData in videosData)
|
||||
{
|
||||
Vod video = new Vod((string)videoData["id"]);
|
||||
video.GetMetadataAsync(videoData);
|
||||
getStreamsTasks.Add(video.GetStreamsAsync());
|
||||
videos.Add(video);
|
||||
|
||||
numberOfVideos--;
|
||||
}
|
||||
}
|
||||
while ((getAll || numberOfVideos > 0) && count == videosData.Length);
|
||||
|
||||
await Task.WhenAll(getStreamsTasks);
|
||||
|
||||
Videos = videos.ToArray();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,158 +0,0 @@
|
||||
using Newtonsoft.Json.Linq;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web;
|
||||
using VDownload.Core.Enums;
|
||||
using VDownload.Core.Exceptions;
|
||||
using VDownload.Core.Interfaces;
|
||||
using VDownload.Core.Services.Sources.Twitch.Helpers;
|
||||
using VDownload.Core.Structs;
|
||||
using Windows.Storage;
|
||||
|
||||
namespace VDownload.Core.Services.Sources.Twitch
|
||||
{
|
||||
[Serializable]
|
||||
public class Clip : IVideo
|
||||
{
|
||||
#region CONSTRUCTORS
|
||||
|
||||
public Clip(string id)
|
||||
{
|
||||
Source = VideoSource.TwitchClip;
|
||||
ID = id;
|
||||
Url = new Uri($"https://clips.twitch.tv/{ID}");
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region PROPERTIES
|
||||
|
||||
public VideoSource Source { get; private set; }
|
||||
public string ID { get; private set; }
|
||||
public Uri Url { get; private set; }
|
||||
public string Title { get; private set; }
|
||||
public string Author { get; private set; }
|
||||
public DateTime Date { get; private set; }
|
||||
public TimeSpan Duration { get; private set; }
|
||||
public long Views { get; private set; }
|
||||
public Uri Thumbnail { get; private set; }
|
||||
public BaseStream[] BaseStreams { get; private set; }
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region PUBLIC METHODS
|
||||
|
||||
public async Task GetMetadataAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
JToken response = null;
|
||||
using (WebClient client = await Client.Helix())
|
||||
{
|
||||
client.QueryString.Add("id", ID);
|
||||
response = JObject.Parse(await client.DownloadStringTaskAsync("https://api.twitch.tv/helix/clips")).GetValue("data");
|
||||
if (((JArray)response).Count > 0) response = response[0];
|
||||
else throw new MediaNotFoundException($"Twitch Clip (ID: {ID}) was not found");
|
||||
}
|
||||
|
||||
Title = (string)response["title"];
|
||||
Author = (string)response["broadcaster_name"];
|
||||
Date = Convert.ToDateTime(response["created_at"]);
|
||||
Duration = TimeSpan.FromSeconds((double)response["duration"]);
|
||||
Views = (long)response["view_count"];
|
||||
Thumbnail = new Uri((string)response["thumbnail_url"]);
|
||||
}
|
||||
|
||||
public async Task GetStreamsAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
JToken[] response;
|
||||
using (WebClient client = Client.GQL())
|
||||
{
|
||||
response = JArray.Parse(await client.UploadStringTaskAsync("https://gql.twitch.tv/gql", "[{\"operationName\":\"VideoAccessToken_Clip\",\"variables\":{\"slug\":\"" + ID + "\"},\"extensions\":{\"persistedQuery\":{\"version\":1,\"sha256Hash\":\"36b89d2507fce29e5ca551df756d27c1cfe079e2609642b4390aa4c35796eb11\"}}}]"))[0]["data"]["clip"]["videoQualities"].ToArray();
|
||||
}
|
||||
|
||||
List<BaseStream> streams = new List<BaseStream>();
|
||||
|
||||
foreach (JToken streamData in response)
|
||||
{
|
||||
BaseStream stream = new BaseStream()
|
||||
{
|
||||
Url = new Uri((string)streamData["sourceURL"]),
|
||||
Height = int.Parse((string)streamData["quality"]),
|
||||
FrameRate = (int)streamData["frameRate"],
|
||||
};
|
||||
|
||||
streams.Add(stream);
|
||||
}
|
||||
|
||||
BaseStreams = streams.ToArray();
|
||||
}
|
||||
|
||||
public async Task<StorageFile> DownloadAndTranscodeAsync(StorageFolder downloadingFolder, BaseStream baseStream, MediaFileExtension extension, MediaType mediaType, TrimData trim, CancellationToken cancellationToken = default)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
DownloadingProgressChanged(this, new EventArgs.ProgressChangedEventArgs(0));
|
||||
|
||||
JToken videoAccessToken = null;
|
||||
using (WebClient client = Client.GQL())
|
||||
{
|
||||
videoAccessToken = JArray.Parse(await client.UploadStringTaskAsync("https://gql.twitch.tv/gql", "[{\"operationName\":\"VideoAccessToken_Clip\",\"variables\":{\"slug\":\"" + ID + "\"},\"extensions\":{\"persistedQuery\":{\"version\":1,\"sha256Hash\":\"36b89d2507fce29e5ca551df756d27c1cfe079e2609642b4390aa4c35796eb11\"}}}]"))[0]["data"]["clip"]["playbackAccessToken"];
|
||||
}
|
||||
|
||||
// Download
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
StorageFile rawFile = await downloadingFolder.CreateFileAsync("raw.mp4");
|
||||
using (WebClient client = new WebClient())
|
||||
{
|
||||
client.DownloadProgressChanged += (s, a) => { DownloadingProgressChanged(this, new EventArgs.ProgressChangedEventArgs(a.ProgressPercentage)); };
|
||||
client.QueryString.Add("sig", (string)videoAccessToken["signature"]);
|
||||
client.QueryString.Add("token", HttpUtility.UrlEncode((string)videoAccessToken["value"]));
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
using (cancellationToken.Register(client.CancelAsync))
|
||||
{
|
||||
await client.DownloadFileTaskAsync(baseStream.Url, rawFile.Path);
|
||||
}
|
||||
}
|
||||
DownloadingProgressChanged(this, new EventArgs.ProgressChangedEventArgs(100, true));
|
||||
|
||||
// Processing
|
||||
StorageFile outputFile = rawFile;
|
||||
if (extension != MediaFileExtension.MP4 || mediaType != MediaType.AudioVideo || trim.Start != null || trim.End != null)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
outputFile = await downloadingFolder.CreateFileAsync($"transcoded.{extension.ToString().ToLower()}");
|
||||
|
||||
MediaProcessor mediaProcessor = new MediaProcessor();
|
||||
mediaProcessor.ProgressChanged += ProcessingProgressChanged;
|
||||
|
||||
await mediaProcessor.Run(rawFile, extension, mediaType, outputFile, trim, cancellationToken);
|
||||
}
|
||||
|
||||
return outputFile;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region EVENTS
|
||||
|
||||
public event EventHandler<EventArgs.ProgressChangedEventArgs> DownloadingProgressChanged;
|
||||
public event EventHandler<EventArgs.ProgressChangedEventArgs> ProcessingProgressChanged;
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,107 +0,0 @@
|
||||
using Newtonsoft.Json.Linq;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using VDownload.Core.Sources;
|
||||
using VDownload.Core.Structs;
|
||||
using Windows.Storage;
|
||||
|
||||
namespace VDownload.Core.Services.Sources.Twitch.Helpers
|
||||
{
|
||||
public static class Authorization
|
||||
{
|
||||
#region CONSTANTS
|
||||
|
||||
public readonly static string ClientID = "yukkqkwp61wsv3u1pya17crpyaa98y";
|
||||
public readonly static string GQLApiClientID = "kimne78kx3ncx6brgo4mv6wki5h1ko";
|
||||
public readonly static Uri RedirectUrl = new Uri("https://www.vd.com");
|
||||
|
||||
private readonly static string ResponseType = "token";
|
||||
private readonly static string[] Scopes = new[]
|
||||
{
|
||||
"user:read:subscriptions",
|
||||
};
|
||||
public readonly static Uri AuthorizationUrl = new Uri($"https://id.twitch.tv/oauth2/authorize?client_id={ClientID}&redirect_uri={RedirectUrl.OriginalString}&response_type={ResponseType}&scope={string.Join(" ", Scopes)}");
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region METHODS
|
||||
|
||||
public static async Task<string> ReadAccessTokenAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
StorageFolder authDataFolder = await AuthorizationData.FolderLocation.GetFolderAsync(AuthorizationData.FolderName);
|
||||
StorageFile authDataFile = await authDataFolder.GetFileAsync($"Twitch.{AuthorizationData.FilesExtension}");
|
||||
|
||||
return await FileIO.ReadTextAsync(authDataFile);
|
||||
}
|
||||
catch (FileNotFoundException)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task SaveAccessTokenAsync(string accessToken)
|
||||
{
|
||||
StorageFolder authDataFolder = await AuthorizationData.FolderLocation.CreateFolderAsync(AuthorizationData.FolderName, CreationCollisionOption.OpenIfExists);
|
||||
StorageFile authDataFile = await authDataFolder.CreateFileAsync($"Twitch.{AuthorizationData.FilesExtension}", CreationCollisionOption.ReplaceExisting);
|
||||
|
||||
await FileIO.WriteTextAsync(authDataFile, accessToken);
|
||||
}
|
||||
|
||||
public static async Task DeleteAccessTokenAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
StorageFolder authDataFolder = await AuthorizationData.FolderLocation.GetFolderAsync(AuthorizationData.FolderName);
|
||||
StorageFile authDataFile = await authDataFolder.GetFileAsync($"Twitch.{AuthorizationData.FilesExtension}");
|
||||
|
||||
await authDataFile.DeleteAsync();
|
||||
}
|
||||
catch (FileNotFoundException) { }
|
||||
}
|
||||
|
||||
public static async Task<TwitchAccessTokenValidationData> ValidateAccessTokenAsync(string accessToken)
|
||||
{
|
||||
WebClient client = new WebClient { Encoding = Encoding.UTF8 };
|
||||
client.Headers.Add("Authorization", $"Bearer {accessToken}");
|
||||
|
||||
try
|
||||
{
|
||||
JObject response = JObject.Parse(await client.DownloadStringTaskAsync("https://id.twitch.tv/oauth2/validate"));
|
||||
|
||||
string login = response["login"].ToString();
|
||||
DateTime? expirationDate = DateTime.Now.AddSeconds(long.Parse(response["expires_in"].ToString()));
|
||||
|
||||
return new TwitchAccessTokenValidationData(accessToken, true, login, expirationDate);
|
||||
}
|
||||
catch (WebException ex)
|
||||
{
|
||||
if (ex.Response is null)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
else
|
||||
{
|
||||
JObject exInfo = JObject.Parse(new StreamReader(ex.Response.GetResponseStream()).ReadToEnd());
|
||||
if ((int)exInfo["status"] == 401) return new TwitchAccessTokenValidationData(accessToken, false, null, null);
|
||||
else throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task RevokeAccessTokenAsync(string accessToken)
|
||||
{
|
||||
WebClient client = new WebClient { Encoding = Encoding.UTF8 };
|
||||
|
||||
await client.UploadStringTaskAsync(new Uri("https://id.twitch.tv/oauth2/revoke"), $"client_id={ClientID}&token={accessToken}");
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
using VDownload.Core.Exceptions;
|
||||
|
||||
namespace VDownload.Core.Services.Sources.Twitch.Helpers
|
||||
{
|
||||
internal static class Client
|
||||
{
|
||||
internal static async Task<WebClient> Helix()
|
||||
{
|
||||
string accessToken = await Authorization.ReadAccessTokenAsync();
|
||||
if (accessToken == null) throw new TwitchAccessTokenNotFoundException();
|
||||
|
||||
var twitchAccessTokenValidation = await Authorization.ValidateAccessTokenAsync(accessToken);
|
||||
if (!twitchAccessTokenValidation.IsValid) throw new TwitchAccessTokenNotValidException();
|
||||
|
||||
WebClient client = new WebClient();
|
||||
client.Headers.Add("Authorization", $"Bearer {accessToken}");
|
||||
client.Headers.Add("Client-Id", Authorization.ClientID);
|
||||
|
||||
return client;
|
||||
}
|
||||
|
||||
internal static WebClient GQL()
|
||||
{
|
||||
WebClient client = new WebClient();
|
||||
client.Headers.Add("Client-Id", Authorization.GQLApiClientID);
|
||||
|
||||
return client;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,285 +0,0 @@
|
||||
using Newtonsoft.Json.Linq;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using VDownload.Core.Enums;
|
||||
using VDownload.Core.Exceptions;
|
||||
using VDownload.Core.Interfaces;
|
||||
using VDownload.Core.Services.Sources.Twitch.Helpers;
|
||||
using VDownload.Core.Structs;
|
||||
using Windows.Storage;
|
||||
|
||||
namespace VDownload.Core.Services.Sources.Twitch
|
||||
{
|
||||
[Serializable]
|
||||
public class Vod : IVideo
|
||||
{
|
||||
#region CONSTRUCTORS
|
||||
|
||||
public Vod(string id)
|
||||
{
|
||||
Source = VideoSource.TwitchVod;
|
||||
ID = id;
|
||||
Url = new Uri($"https://www.twitch.tv/videos/{ID}");
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region PROPERTIES
|
||||
|
||||
public VideoSource Source { get; private set; }
|
||||
public string ID { get; private set; }
|
||||
public Uri Url { get; private set; }
|
||||
public string Title { get; private set; }
|
||||
public string Author { get; private set; }
|
||||
public DateTime Date { get; private set; }
|
||||
public TimeSpan Duration { get; private set; }
|
||||
public long Views { get; private set; }
|
||||
public Uri Thumbnail { get; private set; }
|
||||
public BaseStream[] BaseStreams { get; private set; }
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region PUBLIC METHODS
|
||||
|
||||
public async Task GetMetadataAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
JToken response = null;
|
||||
using (WebClient client = await Client.Helix())
|
||||
{
|
||||
client.QueryString.Add("id", ID);
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
try
|
||||
{
|
||||
response = JObject.Parse(await client.DownloadStringTaskAsync("https://api.twitch.tv/helix/videos")).GetValue("data")[0];
|
||||
}
|
||||
catch (WebException ex)
|
||||
{
|
||||
if (ex.Response != null && new StreamReader(ex.Response.GetResponseStream()).ReadToEnd().Contains("Not Found")) throw new MediaNotFoundException($"Twitch VOD (ID: {ID}) was not found");
|
||||
else if (ex.Response != null && new StreamReader(ex.Response.GetResponseStream()).ReadToEnd() == string.Empty && ex.Message.Contains("400")) throw new MediaNotFoundException($"Twitch VOD (ID: {ID}) was not found");
|
||||
else throw;
|
||||
}
|
||||
}
|
||||
|
||||
GetMetadataAsync(response);
|
||||
}
|
||||
internal void GetMetadataAsync(JToken response)
|
||||
{
|
||||
Title = ((string)response["title"]).Replace("\n", "");
|
||||
Author = (string)response["user_name"];
|
||||
Date = Convert.ToDateTime(response["created_at"]);
|
||||
Duration = ParseDuration((string)response["duration"]);
|
||||
Views = (long)response["view_count"];
|
||||
Thumbnail = (string)response["thumbnail_url"] == string.Empty ? null : new Uri(((string)response["thumbnail_url"]).Replace("%{width}", "1920").Replace("%{height}", "1080"));
|
||||
}
|
||||
|
||||
public async Task GetStreamsAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
string[] response = null;
|
||||
using (WebClient client = Client.GQL())
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
JToken videoAccessToken = JObject.Parse(await client.UploadStringTaskAsync("https://gql.twitch.tv/gql", "{\"operationName\":\"PlaybackAccessToken_Template\",\"query\":\"query PlaybackAccessToken_Template($login: String!, $isLive: Boolean!, $vodID: ID!, $isVod: Boolean!, $playerType: String!) { streamPlaybackAccessToken(channelName: $login, params: {platform: \\\"web\\\", playerBackend: \\\"mediaplayer\\\", playerType: $playerType}) @include(if: $isLive) { value signature __typename } videoPlaybackAccessToken(id: $vodID, params: {platform: \\\"web\\\", playerBackend: \\\"mediaplayer\\\", playerType: $playerType}) @include(if: $isVod) { value signature __typename }}\",\"variables\":{\"isLive\":false,\"login\":\"\",\"isVod\":true,\"vodID\":\"" + ID + "\",\"playerType\":\"embed\"}}"))["data"]["videoPlaybackAccessToken"];
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
response = (await client.DownloadStringTaskAsync($"http://usher.twitch.tv/vod/{ID}?nauth={videoAccessToken["value"]}&nauthsig={videoAccessToken["signature"]}&allow_source=true&player=twitchweb")).Split("\n");
|
||||
}
|
||||
|
||||
List<BaseStream> streams = new List<BaseStream>();
|
||||
|
||||
Regex streamDataL2Regex = new Regex(@"^#EXT-X-STREAM-INF:BANDWIDTH=\d+,CODECS=""\S+,\S+"",RESOLUTION=\d+x(?<height>\d+),VIDEO=""\w+""(,FRAME-RATE=(?<frame_rate>\d+.\d+))?");
|
||||
|
||||
for (int i = 2; i < response.Length; i += 3)
|
||||
{
|
||||
Match line2 = streamDataL2Regex.Match(response[i + 1]);
|
||||
|
||||
BaseStream stream = new BaseStream()
|
||||
{
|
||||
Url = new Uri(response[i + 2]),
|
||||
Height = int.Parse(line2.Groups["height"].Value),
|
||||
FrameRate = line2.Groups["frame_rate"].Value != string.Empty ? (int)Math.Round(double.Parse(line2.Groups["frame_rate"].Value)) : 0,
|
||||
};
|
||||
|
||||
streams.Add(stream);
|
||||
}
|
||||
|
||||
BaseStreams = streams.ToArray();
|
||||
}
|
||||
|
||||
public async Task<StorageFile> DownloadAndTranscodeAsync(StorageFolder downloadingFolder, BaseStream baseStream, MediaFileExtension extension, MediaType mediaType, TrimData trim, CancellationToken cancellationToken = default)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
DownloadingProgressChanged.Invoke(this, new EventArgs.ProgressChangedEventArgs(0));
|
||||
List<(Uri ChunkUrl, TimeSpan ChunkDuration)> chunksList = await ExtractChunksFromM3U8Async(baseStream.Url, cancellationToken);
|
||||
|
||||
TimeSpan duration = Duration;
|
||||
|
||||
// Passive trim
|
||||
if ((bool)Config.GetValue("twitch_vod_passive_trim") && trim.Start != TimeSpan.Zero && trim.End != duration) (trim, duration) = PassiveVideoTrim(chunksList, trim, Duration);
|
||||
|
||||
// Download
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
StorageFile rawFile = await downloadingFolder.CreateFileAsync("raw.ts");
|
||||
|
||||
double chunksDownloaded = 0;
|
||||
|
||||
Task<byte[]> downloadTask;
|
||||
Task writeTask;
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
downloadTask = DownloadChunkAsync(chunksList[0].ChunkUrl);
|
||||
await downloadTask;
|
||||
for (int i = 1; i < chunksList.Count; i++)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
writeTask = WriteChunkToFileAsync(rawFile, downloadTask.Result);
|
||||
downloadTask = DownloadChunkAsync(chunksList[i].ChunkUrl);
|
||||
await Task.WhenAll(writeTask, downloadTask);
|
||||
DownloadingProgressChanged(this, new EventArgs.ProgressChangedEventArgs(++chunksDownloaded * 100 / chunksList.Count));
|
||||
}
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
await WriteChunkToFileAsync(rawFile, downloadTask.Result);
|
||||
DownloadingProgressChanged(this, new EventArgs.ProgressChangedEventArgs(100, true));
|
||||
|
||||
// Processing
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
StorageFile outputFile = await downloadingFolder.CreateFileAsync($"transcoded.{extension.ToString().ToLower()}");
|
||||
|
||||
MediaProcessor mediaProcessor = new MediaProcessor();
|
||||
mediaProcessor.ProgressChanged += ProcessingProgressChanged;
|
||||
|
||||
await mediaProcessor.Run(rawFile, extension, mediaType, outputFile, trim, cancellationToken);
|
||||
|
||||
// Return output file
|
||||
return outputFile;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region PRIVATE METHODS
|
||||
|
||||
private static async Task<List<(Uri ChunkUrl, TimeSpan ChunkDuration)>> ExtractChunksFromM3U8Async(Uri streamUrl, CancellationToken cancellationToken = default)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
string response = null;
|
||||
using (WebClient client = Client.GQL())
|
||||
{
|
||||
response = await client.DownloadStringTaskAsync(streamUrl);
|
||||
}
|
||||
|
||||
List<(Uri ChunkUrl, TimeSpan ChunkDuration)> chunks = new List<(Uri ChunkUrl, TimeSpan ChunkDuration)>();
|
||||
|
||||
Regex chunkDataRegex = new Regex(@"#EXTINF:(?<duration>\d+.\d+),\n(?<filename>\S+.ts)");
|
||||
|
||||
string chunkLocationPath = streamUrl.AbsoluteUri.Replace(Path.GetFileName(streamUrl.AbsoluteUri), "");
|
||||
|
||||
foreach (Match chunk in chunkDataRegex.Matches(response))
|
||||
{
|
||||
Uri chunkUrl = new Uri($"{chunkLocationPath}{chunk.Groups["filename"].Value}");
|
||||
TimeSpan chunkDuration = TimeSpan.FromSeconds(double.Parse(chunk.Groups["duration"].Value));
|
||||
chunks.Add((chunkUrl, chunkDuration));
|
||||
}
|
||||
|
||||
return chunks;
|
||||
}
|
||||
|
||||
private static (TrimData Trim, TimeSpan NewDuration) PassiveVideoTrim(List<(Uri ChunkUrl, TimeSpan ChunkDuration)> chunksList, TrimData trim, TimeSpan duration)
|
||||
{
|
||||
TimeSpan newDuration = duration;
|
||||
|
||||
while (chunksList[0].ChunkDuration <= trim.Start)
|
||||
{
|
||||
trim.Start = trim.Start.Subtract(chunksList[0].ChunkDuration);
|
||||
trim.End = trim.End.Subtract(chunksList[0].ChunkDuration);
|
||||
newDuration = newDuration.Subtract(chunksList[0].ChunkDuration);
|
||||
chunksList.RemoveAt(0);
|
||||
}
|
||||
|
||||
while (chunksList.Last().ChunkDuration <= newDuration.Subtract(trim.End))
|
||||
{
|
||||
newDuration = newDuration.Subtract(chunksList.Last().ChunkDuration);
|
||||
chunksList.RemoveAt(chunksList.Count - 1);
|
||||
}
|
||||
|
||||
return (trim, newDuration);
|
||||
}
|
||||
|
||||
private static async Task<byte[]> DownloadChunkAsync(Uri chunkUrl, CancellationToken cancellationToken = default)
|
||||
{
|
||||
int retriesCount = 0;
|
||||
while (true)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
try
|
||||
{
|
||||
using (WebClient client = new WebClient())
|
||||
{
|
||||
return await client.DownloadDataTaskAsync(chunkUrl);
|
||||
}
|
||||
}
|
||||
catch (WebException wex)
|
||||
{
|
||||
if ((bool)Config.GetValue("twitch_vod_downloading_chunk_retry_after_error") && retriesCount < (int)Config.GetValue("twitch_vod_downloading_chunk_max_retries"))
|
||||
{
|
||||
retriesCount++;
|
||||
await Task.Delay((int)Config.GetValue("twitch_vod_downloading_chunk_retries_delay"));
|
||||
}
|
||||
else throw wex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static Task WriteChunkToFileAsync(StorageFile file, byte[] chunk)
|
||||
{
|
||||
return Task.Factory.StartNew(() =>
|
||||
{
|
||||
using (var stream = new FileStream(file.Path, FileMode.Append))
|
||||
{
|
||||
stream.Write(chunk, 0, chunk.Length);
|
||||
stream.Close();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static TimeSpan ParseDuration(string duration)
|
||||
{
|
||||
char[] separators = { 'h', 'm', 's' };
|
||||
string[] durationParts = duration.Split(separators, StringSplitOptions.RemoveEmptyEntries).Reverse().ToArray();
|
||||
|
||||
TimeSpan timeSpan = new TimeSpan(
|
||||
durationParts.Count() > 2 ? int.Parse(durationParts[2]) : 0,
|
||||
durationParts.Count() > 1 ? int.Parse(durationParts[1]) : 0,
|
||||
int.Parse(durationParts[0]));
|
||||
|
||||
return timeSpan;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region EVENTS
|
||||
|
||||
public event EventHandler<EventArgs.ProgressChangedEventArgs> DownloadingProgressChanged;
|
||||
public event EventHandler<EventArgs.ProgressChangedEventArgs> ProcessingProgressChanged;
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace VDownload.Core.Structs
|
||||
{
|
||||
[Serializable]
|
||||
public struct BaseStream
|
||||
{
|
||||
#region PROPERTIES
|
||||
|
||||
public Uri Url { get; set; }
|
||||
public int Height { get; set; }
|
||||
public int FrameRate { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region METHODS
|
||||
|
||||
public override string ToString() => $"{Height}p{(FrameRate > 0 ? FrameRate.ToString() : "N/A")}";
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace VDownload.Core.Structs
|
||||
{
|
||||
public struct TrimData
|
||||
{
|
||||
#region PROPERTIES
|
||||
|
||||
public TimeSpan Start { get; set; }
|
||||
public TimeSpan End { get; set; }
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace VDownload.Core.Structs
|
||||
{
|
||||
public struct TwitchAccessTokenValidationData
|
||||
{
|
||||
#region CONSTRUCTORS
|
||||
|
||||
public TwitchAccessTokenValidationData(string accessToken, bool isValid, string login, DateTime? expirationDate)
|
||||
{
|
||||
AccessToken = accessToken;
|
||||
IsValid = isValid;
|
||||
Login = login;
|
||||
ExpirationDate = expirationDate;
|
||||
}
|
||||
|
||||
public static TwitchAccessTokenValidationData Null = new TwitchAccessTokenValidationData(string.Empty, false, string.Empty, null);
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region PROPERTIES
|
||||
|
||||
public string AccessToken { get; private set; }
|
||||
public bool IsValid { get; private set; }
|
||||
public string Login { get; private set; }
|
||||
public DateTime? ExpirationDate { get; private set; }
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,190 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProjectGuid>{65C44D96-9C6C-47AD-A1F5-86BFAF2B15B8}</ProjectGuid>
|
||||
<OutputType>Library</OutputType>
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>VDownload.Core</RootNamespace>
|
||||
<AssemblyName>VDownload.Core</AssemblyName>
|
||||
<DefaultLanguage>en-US</DefaultLanguage>
|
||||
<TargetPlatformIdentifier>UAP</TargetPlatformIdentifier>
|
||||
<TargetPlatformVersion Condition=" '$(TargetPlatformVersion)' == '' ">10.0.22000.0</TargetPlatformVersion>
|
||||
<TargetPlatformMinVersion>10.0.18362.0</TargetPlatformMinVersion>
|
||||
<MinimumVisualStudioVersion>14</MinimumVisualStudioVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<ProjectTypeGuids>{A5A43C5B-DE2A-4C0C-9213-0A381AF9435A};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>bin\Debug\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>bin\Release\</OutputPath>
|
||||
<DefineConstants>TRACE;NETFX_CORE;WINDOWS_UWP</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x86'">
|
||||
<PlatformTarget>x86</PlatformTarget>
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<OutputPath>bin\x86\Debug\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP</DefineConstants>
|
||||
<NoWarn>;2008</NoWarn>
|
||||
<DebugType>full</DebugType>
|
||||
<UseVSHostingProcess>false</UseVSHostingProcess>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x86'">
|
||||
<PlatformTarget>x86</PlatformTarget>
|
||||
<OutputPath>bin\x86\Release\</OutputPath>
|
||||
<DefineConstants>TRACE;NETFX_CORE;WINDOWS_UWP</DefineConstants>
|
||||
<Optimize>true</Optimize>
|
||||
<NoWarn>;2008</NoWarn>
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<UseVSHostingProcess>false</UseVSHostingProcess>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|ARM'">
|
||||
<PlatformTarget>ARM</PlatformTarget>
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<OutputPath>bin\ARM\Debug\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP</DefineConstants>
|
||||
<NoWarn>;2008</NoWarn>
|
||||
<DebugType>full</DebugType>
|
||||
<UseVSHostingProcess>false</UseVSHostingProcess>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|ARM'">
|
||||
<PlatformTarget>ARM</PlatformTarget>
|
||||
<OutputPath>bin\ARM\Release\</OutputPath>
|
||||
<DefineConstants>TRACE;NETFX_CORE;WINDOWS_UWP</DefineConstants>
|
||||
<Optimize>true</Optimize>
|
||||
<NoWarn>;2008</NoWarn>
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<UseVSHostingProcess>false</UseVSHostingProcess>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|ARM64'">
|
||||
<PlatformTarget>ARM64</PlatformTarget>
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<OutputPath>bin\ARM64\Debug\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP</DefineConstants>
|
||||
<NoWarn>;2008</NoWarn>
|
||||
<DebugType>full</DebugType>
|
||||
<UseVSHostingProcess>false</UseVSHostingProcess>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|ARM64'">
|
||||
<PlatformTarget>ARM64</PlatformTarget>
|
||||
<OutputPath>bin\ARM64\Release\</OutputPath>
|
||||
<DefineConstants>TRACE;NETFX_CORE;WINDOWS_UWP</DefineConstants>
|
||||
<Optimize>true</Optimize>
|
||||
<NoWarn>;2008</NoWarn>
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<UseVSHostingProcess>false</UseVSHostingProcess>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<OutputPath>bin\x64\Debug\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP</DefineConstants>
|
||||
<NoWarn>;2008</NoWarn>
|
||||
<DebugType>full</DebugType>
|
||||
<UseVSHostingProcess>false</UseVSHostingProcess>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
<OutputPath>bin\x64\Release\</OutputPath>
|
||||
<DefineConstants>TRACE;NETFX_CORE;WINDOWS_UWP</DefineConstants>
|
||||
<Optimize>true</Optimize>
|
||||
<NoWarn>;2008</NoWarn>
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<UseVSHostingProcess>false</UseVSHostingProcess>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<RestoreProjectStyle>PackageReference</RestoreProjectStyle>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="Enums\AudioFileExtension.cs" />
|
||||
<Compile Include="Enums\MediaFileExtension.cs" />
|
||||
<Compile Include="Enums\MediaType.cs" />
|
||||
<Compile Include="Enums\PlaylistSource.cs" />
|
||||
<Compile Include="Enums\DownloadTasksAddingRequestSource.cs" />
|
||||
<Compile Include="Enums\VideoFileExtension.cs" />
|
||||
<Compile Include="Enums\VideoSource.cs" />
|
||||
<Compile Include="Enums\DownloadTaskStatus.cs" />
|
||||
<Compile Include="EventArgs\DownloadTasksAddingRequestedEventArgs.cs" />
|
||||
<Compile Include="EventArgs\DownloadTaskStatusChangedEventArgs.cs" />
|
||||
<Compile Include="EventArgs\ProgressChangedEventArgs.cs" />
|
||||
<Compile Include="EventArgs\PlaylistSearchSuccessedEventArgs.cs" />
|
||||
<Compile Include="EventArgs\SubscriptionLoadSuccessedEventArgs.cs" />
|
||||
<Compile Include="EventArgs\VideoSearchSuccessedEventArgs.cs" />
|
||||
<Compile Include="Exceptions\MediaNotFoundException.cs" />
|
||||
<Compile Include="Exceptions\SubscriptionExistsException.cs" />
|
||||
<Compile Include="Exceptions\TwitchAccessTokenNotFoundException.cs" />
|
||||
<Compile Include="Exceptions\TwitchAccessTokenNotValidException.cs" />
|
||||
<Compile Include="Extensions\TimeSpanExtension.cs" />
|
||||
<Compile Include="Interfaces\IPlaylist.cs" />
|
||||
<Compile Include="Interfaces\IVideo.cs" />
|
||||
<Compile Include="Services\DownloadTasksCollectionManagement.cs" />
|
||||
<Compile Include="Services\OutputFile.cs" />
|
||||
<Compile Include="Sources\AuthorizationData.cs" />
|
||||
<Compile Include="Sources\Twitch\Helpers\Client.cs" />
|
||||
<Compile Include="Services\Subscription.cs" />
|
||||
<Compile Include="Services\SubscriptionsCollectionManagement.cs" />
|
||||
<Compile Include="Structs\BaseStream.cs" />
|
||||
<Compile Include="Services\DownloadTask.cs" />
|
||||
<Compile Include="Services\Config.cs" />
|
||||
<Compile Include="Services\MediaProcessor.cs" />
|
||||
<Compile Include="Sources\Source.cs" />
|
||||
<Compile Include="Sources\Twitch\Helpers\Authorization.cs" />
|
||||
<Compile Include="Sources\Twitch\Channel.cs" />
|
||||
<Compile Include="Sources\Twitch\Clip.cs" />
|
||||
<Compile Include="Sources\Twitch\Vod.cs" />
|
||||
<Compile Include="Structs\TrimData.cs" />
|
||||
<Compile Include="Structs\TwitchAccessTokenValidationData.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NETCore.UniversalWindowsPlatform">
|
||||
<Version>6.2.13</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.Toolkit.Uwp.Connectivity">
|
||||
<Version>7.1.2</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Newtonsoft.Json">
|
||||
<Version>13.0.1</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="System.ValueTuple">
|
||||
<Version>4.5.0</Version>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Folder Include="Properties\" />
|
||||
</ItemGroup>
|
||||
<PropertyGroup Condition=" '$(VisualStudioVersion)' == '' or '$(VisualStudioVersion)' < '14.0' ">
|
||||
<VisualStudioVersion>14.0</VisualStudioVersion>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(MSBuildExtensionsPath)\Microsoft\WindowsXaml\v$(VisualStudioVersion)\Microsoft.Windows.UI.Xaml.CSharp.targets" />
|
||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||
Other similar extension points exist, see Microsoft.Common.targets.
|
||||
<Target Name="BeforeBuild">
|
||||
</Target>
|
||||
<Target Name="AfterBuild">
|
||||
</Target>
|
||||
-->
|
||||
</Project>
|
||||
30
VDownload.Extensions/StringExtensions.cs
Normal file
30
VDownload.Extensions/StringExtensions.cs
Normal file
@@ -0,0 +1,30 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace VDownload.Extensions
|
||||
{
|
||||
public static class StringExtensions
|
||||
{
|
||||
public static string CreateRandom(int length) => CreateRandom(length, "QWERTYUIOPASDFGHJKLZXCVBNMqwertyuiopasdfghjklzxcvbnm1234567890`~!@#$%^&*()-_=+[{]};:'\"\\|,<.>/?");
|
||||
public static string CreateRandom(int length, IEnumerable<char> characters) => new string(Enumerable.Repeat(characters, length).Select(s => s.ElementAt(Random.Shared.Next(s.Count()))).ToArray());
|
||||
|
||||
public static string Shuffle(this string str)
|
||||
{
|
||||
char[] array = str.ToCharArray();
|
||||
Random rng = Random.Shared;
|
||||
int n = array.Length;
|
||||
while (n > 1)
|
||||
{
|
||||
n--;
|
||||
int k = rng.Next(n + 1);
|
||||
char value = array[k];
|
||||
array[k] = array[n];
|
||||
array[n] = value;
|
||||
}
|
||||
return new string(array);
|
||||
}
|
||||
}
|
||||
}
|
||||
9
VDownload.Extensions/VDownload.Extensions.csproj
Normal file
9
VDownload.Extensions/VDownload.Extensions.csproj
Normal file
@@ -0,0 +1,9 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
20
VDownload.GUI/VDownload.GUI.Controls/TimeSpanControl.xaml
Normal file
20
VDownload.GUI/VDownload.GUI.Controls/TimeSpanControl.xaml
Normal file
@@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<UserControl
|
||||
x:Class="VDownload.GUI.Controls.TimeSpanControl"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="using:VDownload.GUI.Controls"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d"
|
||||
x:Name="Control"
|
||||
Loaded="Control_Loaded">
|
||||
|
||||
<StackPanel Orientation="Horizontal" Spacing="4">
|
||||
<NumberBox x:Name="Hours" SpinButtonPlacementMode="Compact" Minimum="0" Value="0" ValueChanged="ValueChanged"/>
|
||||
<TextBlock VerticalAlignment="Center" Padding="0,0,0,5" Text=":"/>
|
||||
<NumberBox x:Name="Minutes" SpinButtonPlacementMode="Compact" Minimum="0" Value="0" Maximum="59" ValueChanged="ValueChanged"/>
|
||||
<TextBlock VerticalAlignment="Center" Padding="0,0,0,5" Text=":"/>
|
||||
<NumberBox x:Name="Seconds" SpinButtonPlacementMode="Compact" Minimum="0" Value="0" Maximum="59" ValueChanged="ValueChanged"/>
|
||||
</StackPanel>
|
||||
</UserControl>
|
||||
122
VDownload.GUI/VDownload.GUI.Controls/TimeSpanControl.xaml.cs
Normal file
122
VDownload.GUI/VDownload.GUI.Controls/TimeSpanControl.xaml.cs
Normal file
@@ -0,0 +1,122 @@
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Controls.Primitives;
|
||||
using Microsoft.UI.Xaml.Data;
|
||||
using Microsoft.UI.Xaml.Input;
|
||||
using Microsoft.UI.Xaml.Media;
|
||||
using Microsoft.UI.Xaml.Navigation;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices.WindowsRuntime;
|
||||
using Windows.Foundation;
|
||||
using Windows.Foundation.Collections;
|
||||
|
||||
// To learn more about WinUI, the WinUI project structure,
|
||||
// and more about our project templates, see: http://aka.ms/winui-project-info.
|
||||
|
||||
namespace VDownload.GUI.Controls
|
||||
{
|
||||
public sealed partial class TimeSpanControl : UserControl
|
||||
{
|
||||
#region PROPERTIES
|
||||
|
||||
public TimeSpan Value
|
||||
{
|
||||
get => (TimeSpan)GetValue(ValueProperty);
|
||||
set => SetValue(ValueProperty, value);
|
||||
}
|
||||
public static readonly DependencyProperty ValueProperty = DependencyProperty.Register("Value", typeof(TimeSpan), typeof(TimeSpanControl), new PropertyMetadata(TimeSpan.Zero, new PropertyChangedCallback(ValuePropertyChanged)));
|
||||
|
||||
public TimeSpan Maximum
|
||||
{
|
||||
get => (TimeSpan)GetValue(MaximumProperty);
|
||||
set => SetValue(MaximumProperty, value);
|
||||
}
|
||||
public static readonly DependencyProperty MaximumProperty = DependencyProperty.Register("Maximum", typeof(TimeSpan), typeof(TimeSpanControl), new PropertyMetadata(TimeSpan.MaxValue, new PropertyChangedCallback(RangePropertyChanged)));
|
||||
|
||||
public TimeSpan Minimum
|
||||
{
|
||||
get => (TimeSpan)GetValue(MinimumProperty);
|
||||
set => SetValue(MinimumProperty, value);
|
||||
}
|
||||
public static readonly DependencyProperty MinimumProperty = DependencyProperty.Register("Minimum", typeof(TimeSpan), typeof(TimeSpanControl), new PropertyMetadata(TimeSpan.Zero, new PropertyChangedCallback(RangePropertyChanged)));
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region CONSTRUCTORS
|
||||
|
||||
public TimeSpanControl()
|
||||
{
|
||||
this.InitializeComponent();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region PRIVATE METHODS
|
||||
|
||||
private void UpdateOnChanges()
|
||||
{
|
||||
if (this.IsLoaded)
|
||||
{
|
||||
TimeSpan hoursTimeSpan = TimeSpan.FromHours(Hours.Value);
|
||||
TimeSpan minutesTimeSpan = TimeSpan.FromMinutes(Minutes.Value);
|
||||
TimeSpan secondsTimeSpan = TimeSpan.FromSeconds(Seconds.Value);
|
||||
TimeSpan value = secondsTimeSpan + minutesTimeSpan + hoursTimeSpan;
|
||||
if (value >= Maximum)
|
||||
{
|
||||
Hours.Value = Math.Floor(Maximum.TotalHours);
|
||||
Minutes.Value = Maximum.Minutes;
|
||||
Seconds.Value = Maximum.Seconds;
|
||||
}
|
||||
else if (value <= Minimum)
|
||||
{
|
||||
Hours.Value = Math.Floor(Minimum.TotalHours);
|
||||
Minutes.Value = Minimum.Minutes;
|
||||
Seconds.Value = Minimum.Seconds;
|
||||
}
|
||||
Value = value;
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateOnValueChange()
|
||||
{
|
||||
if (this.IsLoaded)
|
||||
{
|
||||
TimeSpan value = Value;
|
||||
if (value > Maximum)
|
||||
{
|
||||
value = Maximum;
|
||||
}
|
||||
else if (value < Minimum)
|
||||
{
|
||||
value = Minimum;
|
||||
}
|
||||
Hours.Value = Math.Floor(value.TotalHours);
|
||||
Minutes.Value = value.Minutes;
|
||||
Seconds.Value = value.Seconds;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region EVENT HANDLERS
|
||||
|
||||
private void ValueChanged(NumberBox sender, NumberBoxValueChangedEventArgs args) => UpdateOnChanges();
|
||||
|
||||
private static void ValuePropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e) => ((TimeSpanControl)obj).UpdateOnValueChange();
|
||||
|
||||
private static void RangePropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e) => ((TimeSpanControl)obj).UpdateOnChanges();
|
||||
|
||||
private void Control_Loaded(object sender, RoutedEventArgs e) => UpdateOnValueChange();
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0-windows10.0.19041.0</TargetFramework>
|
||||
<TargetPlatformMinVersion>10.0.17763.0</TargetPlatformMinVersion>
|
||||
<RootNamespace>VDownload.GUI.Controls</RootNamespace>
|
||||
<RuntimeIdentifiers>win10-x86;win10-x64;win10-arm64</RuntimeIdentifiers>
|
||||
<UseWinUI>true</UseWinUI>
|
||||
<UseRidGraph>true</UseRidGraph>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<None Remove="TimeSpanControl.xaml" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.4.231219000" />
|
||||
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.22621.2428" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<CustomAdditionalCompileInputs Remove="TimeSpanControl.xaml" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Resource Remove="TimeSpanControl.xaml" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,44 @@
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Data;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace VDownload.GUI.Converters
|
||||
{
|
||||
public class BooleanToGridLengthConverter : IValueConverter
|
||||
{
|
||||
#region METHODS
|
||||
|
||||
public object Convert(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
GridLength notVisibleLength = new GridLength(0);
|
||||
if (value is bool visible)
|
||||
{
|
||||
GridLength visibleLength = new GridLength(1, GridUnitType.Star);
|
||||
if (parameter is string width)
|
||||
{
|
||||
if (width.ToLower() == "auto")
|
||||
{
|
||||
visibleLength = GridLength.Auto;
|
||||
}
|
||||
else if (int.TryParse(width, out int result))
|
||||
{
|
||||
visibleLength = new GridLength(result);
|
||||
}
|
||||
}
|
||||
return visible ? visibleLength : notVisibleLength;
|
||||
}
|
||||
return notVisibleLength;
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Data;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace VDownload.GUI.Converters
|
||||
{
|
||||
public class BooleanToGridLengthFillConverter : IValueConverter
|
||||
{
|
||||
#region METHODS
|
||||
|
||||
public object Convert(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
GridLength falseLength = GridLength.Auto;
|
||||
if (value is bool boolean)
|
||||
{
|
||||
GridLength trueLength = new GridLength(1, GridUnitType.Star);
|
||||
return boolean ? trueLength : falseLength;
|
||||
}
|
||||
return falseLength;
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Data;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace VDownload.GUI.Converters
|
||||
{
|
||||
public class BooleanToGridLengthFillReversedConverter : IValueConverter
|
||||
{
|
||||
#region METHODS
|
||||
|
||||
public object Convert(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
GridLength falseLength = GridLength.Auto;
|
||||
if (value is bool boolean)
|
||||
{
|
||||
GridLength trueLength = new GridLength(1, GridUnitType.Star);
|
||||
return boolean ? falseLength : trueLength;
|
||||
}
|
||||
return falseLength;
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Data;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace VDownload.GUI.Converters
|
||||
{
|
||||
public class BooleanToGridLengthReversedConverter : IValueConverter
|
||||
{
|
||||
#region METHODS
|
||||
|
||||
public object Convert(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
GridLength visibleLength = new GridLength(1, GridUnitType.Star);
|
||||
if (value is bool visible)
|
||||
{
|
||||
GridLength notVisibleLength = new GridLength(0);
|
||||
if (parameter is string width)
|
||||
{
|
||||
if (width.ToLower() == "auto")
|
||||
{
|
||||
visibleLength = GridLength.Auto;
|
||||
}
|
||||
else if (int.TryParse(width, out int result))
|
||||
{
|
||||
visibleLength = new GridLength(result);
|
||||
}
|
||||
}
|
||||
return visible ? notVisibleLength : visibleLength;
|
||||
}
|
||||
return visibleLength;
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
using Microsoft.UI.Xaml.Data;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace VDownload.GUI.Converters
|
||||
{
|
||||
public class BooleanToStringConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
if (value is bool boolean)
|
||||
{
|
||||
return boolean.ToString();
|
||||
}
|
||||
return bool.FalseString;
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
if (value is string str)
|
||||
{
|
||||
return str == bool.TrueString;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Data;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace VDownload.GUI.Converters
|
||||
{
|
||||
public class BooleanToVisibilityConverter : IValueConverter
|
||||
{
|
||||
#region METHODS
|
||||
|
||||
public object Convert(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
if (value is bool isVisible)
|
||||
{
|
||||
return isVisible ? Visibility.Visible : Visibility.Collapsed;
|
||||
}
|
||||
return Visibility.Collapsed;
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
if (value is Visibility visibility)
|
||||
{
|
||||
return visibility == Visibility.Visible;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
using Microsoft.UI.Xaml.Data;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace VDownload.GUI.Converters
|
||||
{
|
||||
public class EnumToDescriptionConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
if (value is Enum enumValue)
|
||||
{
|
||||
return enumValue.GetType().GetMember(enumValue.ToString()).FirstOrDefault()?.GetCustomAttribute<DescriptionAttribute>()?.Description;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
22
VDownload.GUI/VDownload.GUI.Converters/EnumToIntConverter.cs
Normal file
22
VDownload.GUI/VDownload.GUI.Converters/EnumToIntConverter.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
using Microsoft.UI.Xaml.Data;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace VDownload.GUI.Converters
|
||||
{
|
||||
public class EnumToIntConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
return (int)value;
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
using Microsoft.UI.Xaml.Data;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace VDownload.GUI.Converters
|
||||
{
|
||||
public class EnumToStringConverter : IValueConverter
|
||||
{
|
||||
#region METHODS
|
||||
|
||||
public object Convert(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
return value.ToString();
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
using Microsoft.UI.Xaml.Data;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace VDownload.GUI.Converters
|
||||
{
|
||||
public class ReverseBooleanConverter : IValueConverter
|
||||
{
|
||||
#region METHODS
|
||||
|
||||
public object Convert(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
if (value is bool boolean)
|
||||
{
|
||||
return !boolean;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, string language) => Convert(value, targetType, parameter, language);
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
using Microsoft.UI.Xaml.Data;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace VDownload.GUI.Converters
|
||||
{
|
||||
public class StringToLowerConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
return value.ToString().ToLower();
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
using Microsoft.UI.Xaml.Data;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace VDownload.GUI.Converters
|
||||
{
|
||||
public class StringToUriConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
if (value is string str)
|
||||
{
|
||||
return new Uri(str, UriKind.RelativeOrAbsolute);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
if (value is Uri uri)
|
||||
{
|
||||
return uri.OriginalString;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,18 @@
|
||||
using System;
|
||||
using Windows.UI.Xaml;
|
||||
using Windows.UI.Xaml.Data;
|
||||
using Microsoft.UI.Xaml.Data;
|
||||
using Microsoft.UI.Xaml;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace VDownload.Converters
|
||||
namespace VDownload.GUI.Converters
|
||||
{
|
||||
public class StringToVisibilityConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
if (string.IsNullOrEmpty((string)value))
|
||||
if (value is not string str || string.IsNullOrWhiteSpace(str))
|
||||
{
|
||||
return Visibility.Collapsed;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0-windows10.0.19041.0</TargetFramework>
|
||||
<TargetPlatformMinVersion>10.0.17763.0</TargetPlatformMinVersion>
|
||||
<RootNamespace>VDownload.GUI.Converters</RootNamespace>
|
||||
<RuntimeIdentifiers>win10-x86;win10-x64;win10-arm64</RuntimeIdentifiers>
|
||||
<UseWinUI>true</UseWinUI>
|
||||
<UseRidGraph>true</UseRidGraph>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.4.231219000" />
|
||||
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.22621.2428" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\VDownload.Services\VDownload.Services\VDownload.Services.csproj" />
|
||||
<ProjectReference Include="..\VDownload.GUI.ViewModels\VDownload.GUI.ViewModels.csproj" />
|
||||
<ProjectReference Include="..\VDownload.GUI.Views\VDownload.GUI.Views.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,65 @@
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Data;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using VDownload.GUI.ViewModels;
|
||||
using VDownload.GUI.Views;
|
||||
using VDownload.Services;
|
||||
|
||||
namespace VDownload.GUI.Converters
|
||||
{
|
||||
public class ViewModelToViewConverter : IValueConverter
|
||||
{
|
||||
#region FIELDS
|
||||
|
||||
private readonly Dictionary<Type, Type> _viewModelViewBinding = new Dictionary<Type, Type>
|
||||
{
|
||||
{ typeof(HomeViewModel), typeof(HomeView) },
|
||||
{ typeof(SettingsViewModel), typeof(SettingsView) },
|
||||
{ typeof(AuthenticationViewModel), typeof(AuthenticationView) }
|
||||
};
|
||||
private readonly Dictionary<Type, Type> _viewViewModelBinding = new Dictionary<Type, Type>
|
||||
{
|
||||
{ typeof(HomeView), typeof(HomeViewModel) },
|
||||
{ typeof(SettingsView), typeof(SettingsViewModel) },
|
||||
{ typeof(AuthenticationView), typeof(AuthenticationViewModel) }
|
||||
};
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region METHODS
|
||||
|
||||
public object Convert(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
if (value is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
if (value is Type type && _viewModelViewBinding.ContainsKey(type))
|
||||
{
|
||||
return ServiceProvider.Instance.GetService(_viewModelViewBinding[type]);
|
||||
}
|
||||
if (_viewModelViewBinding.ContainsKey(value.GetType()))
|
||||
{
|
||||
return ServiceProvider.Instance.GetService(_viewModelViewBinding[value.GetType()]);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
if (_viewViewModelBinding.ContainsKey(value.GetType()))
|
||||
{
|
||||
return _viewViewModelBinding[value.GetType()];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.Xaml.Interactivity;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace VDownload.GUI.Customs.Behaviors
|
||||
{
|
||||
public class EventToCommandBehavior : Behavior<FrameworkElement>
|
||||
{
|
||||
#region FIELDS
|
||||
|
||||
private Delegate _handler;
|
||||
private EventInfo _oldEvent;
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region PROPERTIES
|
||||
|
||||
public string Event { get { return (string)GetValue(EventProperty); } set { SetValue(EventProperty, value); } }
|
||||
public static readonly DependencyProperty EventProperty = DependencyProperty.Register("Event", typeof(string), typeof(EventToCommandBehavior), new PropertyMetadata(null, OnEventChanged));
|
||||
|
||||
public ICommand Command { get { return (ICommand)GetValue(CommandProperty); } set { SetValue(CommandProperty, value); } }
|
||||
public static readonly DependencyProperty CommandProperty = DependencyProperty.Register("Command", typeof(ICommand), typeof(EventToCommandBehavior), new PropertyMetadata(null));
|
||||
|
||||
public bool PassArguments { get { return (bool)GetValue(PassArgumentsProperty); } set { SetValue(PassArgumentsProperty, value); } }
|
||||
public static readonly DependencyProperty PassArgumentsProperty = DependencyProperty.Register("PassArguments", typeof(bool), typeof(EventToCommandBehavior), new PropertyMetadata(false));
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region PRIVATE METHODS
|
||||
|
||||
private static void OnEventChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
EventToCommandBehavior beh = (EventToCommandBehavior)d;
|
||||
|
||||
if (beh.AssociatedObject != null)
|
||||
{
|
||||
beh.AttachHandler((string)e.NewValue);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnAttached()
|
||||
{
|
||||
AttachHandler(this.Event);
|
||||
}
|
||||
|
||||
private void AttachHandler(string eventName)
|
||||
{
|
||||
if (_oldEvent != null)
|
||||
{
|
||||
_oldEvent.RemoveEventHandler(this.AssociatedObject, _handler);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(eventName))
|
||||
{
|
||||
EventInfo ei = this.AssociatedObject.GetType().GetEvent(eventName);
|
||||
if (ei != null)
|
||||
{
|
||||
MethodInfo mi = this.GetType().GetMethod("ExecuteCommand", BindingFlags.Instance | BindingFlags.NonPublic);
|
||||
_handler = Delegate.CreateDelegate(ei.EventHandlerType, this, mi);
|
||||
ei.AddEventHandler(this.AssociatedObject, _handler);
|
||||
_oldEvent = ei;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentException(string.Format("The event '{0}' was not found on type '{1}'", eventName, this.AssociatedObject.GetType().Name));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ExecuteCommand(object sender, object e)
|
||||
{
|
||||
object parameter = this.PassArguments ? e : null;
|
||||
if (this.Command != null && this.Command.CanExecute(parameter))
|
||||
{
|
||||
this.Command.Execute(parameter);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace VDownload.GUI.Customs.Models
|
||||
{
|
||||
public class NavigationViewItem
|
||||
{
|
||||
#region PROPERTIES
|
||||
|
||||
public required string Name { get; init; }
|
||||
public required string IconSource { get; init; }
|
||||
public required Type ViewModel { get; init; }
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0-windows10.0.19041.0</TargetFramework>
|
||||
<TargetPlatformMinVersion>10.0.17763.0</TargetPlatformMinVersion>
|
||||
<RootNamespace>VDownload.GUI.Customs</RootNamespace>
|
||||
<RuntimeIdentifiers>win10-x86;win10-x64;win10-arm64</RuntimeIdentifiers>
|
||||
<UseWinUI>true</UseWinUI>
|
||||
<UseRidGraph>true</UseRidGraph>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.4.231219000" />
|
||||
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.22621.2428" />
|
||||
<PackageReference Include="Microsoft.Xaml.Behaviors.WinUI.Managed" Version="2.0.9" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,15 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace VDownload.GUI.Services.Dialog
|
||||
{
|
||||
public enum DialogResult
|
||||
{
|
||||
Primary,
|
||||
Secondary,
|
||||
Cancelled
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace VDownload.GUI.Services.Dialog
|
||||
{
|
||||
public enum DialogResultOkCancel
|
||||
{
|
||||
Ok,
|
||||
Cancel
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace VDownload.GUI.Services.Dialog
|
||||
{
|
||||
public enum DialogResultYesNo
|
||||
{
|
||||
Yes,
|
||||
No
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace VDownload.GUI.Services.Dialog
|
||||
{
|
||||
public enum DialogResultYesNoCancel
|
||||
{
|
||||
Yes,
|
||||
No,
|
||||
Cancelled
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace VDownload.GUI.Services.Dialog
|
||||
{
|
||||
public interface IDialogService
|
||||
{
|
||||
#region PROPERTIES
|
||||
|
||||
XamlRoot DefaultRoot { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region METHODS
|
||||
|
||||
Task ShowClose(string title, string message);
|
||||
Task<DialogResult> ShowDouble(string title, string message, string primaryButtonText, string secondaryButtonText);
|
||||
Task ShowOk(string title, string message);
|
||||
Task<DialogResultOkCancel> ShowOkCancel(string title, string message);
|
||||
Task ShowSingle(string title, string message, string buttonText);
|
||||
Task<DialogResult> ShowTriple(string title, string message, string primaryButtonText, string secondaryButtonText, string cancelButtonText);
|
||||
Task<DialogResultYesNo> ShowYesNo(string title, string message);
|
||||
Task<DialogResultYesNoCancel> ShowYesNoCancel(string title, string message);
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
|
||||
|
||||
public class DialogService : IDialogService
|
||||
{
|
||||
#region PROPERTIES
|
||||
|
||||
public XamlRoot DefaultRoot { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region PUBLIC METHODS
|
||||
|
||||
public async Task ShowOk(string title, string message) => await ShowSingle(title, message, "OK");
|
||||
public async Task ShowClose(string title, string message) => await ShowSingle(title, message, "Close");
|
||||
public async Task ShowSingle(string title, string message, string buttonText)
|
||||
{
|
||||
ContentDialog contentDialog = BuildDialog(title, message);
|
||||
contentDialog.CloseButtonText = buttonText;
|
||||
await ShowDialog(contentDialog);
|
||||
}
|
||||
|
||||
public async Task<DialogResultOkCancel> ShowOkCancel(string title, string message) => await ShowDouble(title, message, "OK", "Cancel") switch
|
||||
{
|
||||
DialogResult.Primary => DialogResultOkCancel.Ok,
|
||||
_ => DialogResultOkCancel.Cancel
|
||||
};
|
||||
public async Task<DialogResultYesNo> ShowYesNo(string title, string message) => await ShowDouble(title, message, "Yes", "No") switch
|
||||
{
|
||||
DialogResult.Primary => DialogResultYesNo.Yes,
|
||||
_ => DialogResultYesNo.No
|
||||
};
|
||||
public async Task<DialogResult> ShowDouble(string title, string message, string primaryButtonText, string secondaryButtonText)
|
||||
{
|
||||
ContentDialog contentDialog = BuildDialog(title, message);
|
||||
contentDialog.PrimaryButtonText = primaryButtonText;
|
||||
contentDialog.SecondaryButtonText = secondaryButtonText;
|
||||
return await ShowDialog(contentDialog);
|
||||
}
|
||||
|
||||
public async Task<DialogResultYesNoCancel> ShowYesNoCancel(string title, string message) => await ShowTriple(title, message, "Yes", "No", "Cancel") switch
|
||||
{
|
||||
DialogResult.Primary => DialogResultYesNoCancel.Yes,
|
||||
DialogResult.Secondary => DialogResultYesNoCancel.Yes,
|
||||
_ => DialogResultYesNoCancel.Cancelled
|
||||
};
|
||||
public async Task<DialogResult> ShowTriple(string title, string message, string primaryButtonText, string secondaryButtonText, string cancelButtonText)
|
||||
{
|
||||
ContentDialog contentDialog = BuildDialog(title, message);
|
||||
contentDialog.PrimaryButtonText = primaryButtonText;
|
||||
contentDialog.SecondaryButtonText = secondaryButtonText;
|
||||
contentDialog.CloseButtonText = cancelButtonText;
|
||||
return await ShowDialog(contentDialog);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region PRIVATE METHODS
|
||||
|
||||
private ContentDialog BuildDialog(string title, string message)
|
||||
{
|
||||
return new ContentDialog()
|
||||
{
|
||||
Title = title,
|
||||
Content = message,
|
||||
XamlRoot = DefaultRoot
|
||||
};
|
||||
}
|
||||
|
||||
private async Task<DialogResult> ShowDialog(ContentDialog dialog)
|
||||
{
|
||||
ContentDialogResult result = await dialog.ShowAsync();
|
||||
return result switch
|
||||
{
|
||||
ContentDialogResult.Primary => DialogResult.Primary,
|
||||
ContentDialogResult.Secondary => DialogResult.Secondary,
|
||||
_ => DialogResult.Cancelled
|
||||
}; ;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0-windows10.0.19041.0</TargetFramework>
|
||||
<TargetPlatformMinVersion>10.0.17763.0</TargetPlatformMinVersion>
|
||||
<RootNamespace>VDownload.GUI.Services.Dialog</RootNamespace>
|
||||
<RuntimeIdentifiers>win10-x86;win10-x64;win10-arm64</RuntimeIdentifiers>
|
||||
<UseWinUI>true</UseWinUI>
|
||||
<UseRidGraph>true</UseRidGraph>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.4.231219000" />
|
||||
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.22621.2428" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,59 @@
|
||||
using Microsoft.UI.Xaml;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace VDownload.GUI.Services.ResourceDictionaries
|
||||
{
|
||||
public interface IImagesResourceDictionary
|
||||
{
|
||||
// LOGO
|
||||
string Logo { get; }
|
||||
|
||||
// SOURCES
|
||||
string SourcesTwitch { get; }
|
||||
|
||||
// NAVIGATION VIEW
|
||||
string NavigationViewAuthentication { get; }
|
||||
string NavigationViewHome { get; }
|
||||
}
|
||||
|
||||
|
||||
|
||||
public class ImagesResourceDictionary : IImagesResourceDictionary
|
||||
{
|
||||
#region PROPERTIES
|
||||
|
||||
// LOGO
|
||||
public string Logo { get; private set; }
|
||||
|
||||
// SOURCES
|
||||
public string SourcesTwitch { get; private set; }
|
||||
|
||||
// NAVIGATION VIEW
|
||||
public string NavigationViewAuthentication { get; private set; }
|
||||
public string NavigationViewHome { get; private set; }
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region CONSTRUCTORS
|
||||
|
||||
public ImagesResourceDictionary()
|
||||
{
|
||||
Logo = (string)Application.Current.Resources["ImageLogo"];
|
||||
|
||||
SourcesTwitch = (string)Application.Current.Resources["ImageSourcesTwitch"];
|
||||
|
||||
NavigationViewAuthentication = (string)Application.Current.Resources["ImageNavigationViewAuthentication"];
|
||||
NavigationViewHome = (string)Application.Current.Resources["ImageNavigationViewHome"];
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
using Microsoft.UI.Xaml;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace VDownload.GUI.Services.ResourceDictionaries
|
||||
{
|
||||
public interface IResourceDictionariesServices
|
||||
{
|
||||
#region PROPERTIES
|
||||
|
||||
IImagesResourceDictionary Images { get; }
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region METHODS
|
||||
|
||||
T Get<T>(string key);
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
|
||||
|
||||
public class ResourceDictionariesServices : IResourceDictionariesServices
|
||||
{
|
||||
#region PROPERTIES
|
||||
|
||||
public IImagesResourceDictionary Images { get; private set; }
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region CONSTRUCTORS
|
||||
|
||||
public ResourceDictionariesServices(IImagesResourceDictionary imagesResourceDictionary)
|
||||
{
|
||||
Images = imagesResourceDictionary;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region PUBLIC METHODS
|
||||
|
||||
public T Get<T>(string key)
|
||||
{
|
||||
Application.Current.Resources.TryGetValue(key, out object value);
|
||||
if (value is not null && value is T cast)
|
||||
{
|
||||
return cast;
|
||||
}
|
||||
throw new KeyNotFoundException();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0-windows10.0.19041.0</TargetFramework>
|
||||
<TargetPlatformMinVersion>10.0.17763.0</TargetPlatformMinVersion>
|
||||
<RootNamespace>VDownload.GUI.Services.ResourceDictionaries</RootNamespace>
|
||||
<RuntimeIdentifiers>win10-x86;win10-x64;win10-arm64</RuntimeIdentifiers>
|
||||
<UseWinUI>true</UseWinUI>
|
||||
<UseRidGraph>true</UseRidGraph>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.4.231219000" />
|
||||
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.22621.2428" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,30 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace VDownload.GUI.Services.StoragePicker
|
||||
{
|
||||
public class FileSavePickerFileTypeChoice
|
||||
{
|
||||
#region PROPERTIES
|
||||
|
||||
public string Description { get; private set; }
|
||||
public IEnumerable<string> Extensions { get; private set; }
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region CONSTRUCTORS
|
||||
|
||||
public FileSavePickerFileTypeChoice(string description, string[] extensions)
|
||||
{
|
||||
Description = description;
|
||||
Extensions = extensions.Select(x => x.StartsWith('.') ? x : $".{x}");
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,169 @@
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Windows.Storage;
|
||||
using Windows.Storage.Pickers;
|
||||
using WinRT.Interop;
|
||||
|
||||
namespace VDownload.GUI.Services.StoragePicker
|
||||
{
|
||||
public interface IStoragePickerService
|
||||
{
|
||||
#region PROPERTIES
|
||||
|
||||
Window DefaultRoot { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region METHODS
|
||||
|
||||
Task<string?> OpenDirectory();
|
||||
Task<string?> OpenDirectory(StoragePickerStartLocation startLocation);
|
||||
Task<IEnumerable<string>> OpenMultipleFiles();
|
||||
Task<IEnumerable<string>> OpenMultipleFiles(StoragePickerStartLocation startLocation);
|
||||
Task<IEnumerable<string>> OpenMultipleFiles(string[] fileTypes);
|
||||
Task<IEnumerable<string>> OpenMultipleFiles(string[] fileTypes, StoragePickerStartLocation startLocation);
|
||||
Task<string?> OpenSingleFile();
|
||||
Task<string?> OpenSingleFile(StoragePickerStartLocation startLocation);
|
||||
Task<string?> OpenSingleFile(string[] fileTypes);
|
||||
Task<string?> OpenSingleFile(string[] fileTypes, StoragePickerStartLocation startLocation);
|
||||
Task<string?> SaveFile(FileSavePickerFileTypeChoice[] fileTypes, string defaultFileType);
|
||||
Task<string?> SaveFile(FileSavePickerFileTypeChoice[] fileTypes, string defaultFileType, StoragePickerStartLocation startLocation);
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
|
||||
|
||||
public class StoragePickerService : IStoragePickerService
|
||||
{
|
||||
#region PROPERTIES
|
||||
|
||||
public Window DefaultRoot { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region PUBLIC METHODS
|
||||
|
||||
public async Task<string?> OpenDirectory() => await OpenDirectory(StoragePickerStartLocation.Unspecified);
|
||||
public async Task<string?> OpenDirectory(StoragePickerStartLocation startLocation)
|
||||
{
|
||||
FolderPicker picker = new FolderPicker();
|
||||
InitializePicker(picker);
|
||||
|
||||
ConfigureFolderPicker(picker, startLocation);
|
||||
|
||||
StorageFolder directory = await picker.PickSingleFolderAsync();
|
||||
return directory?.Path;
|
||||
}
|
||||
|
||||
public async Task<string?> OpenSingleFile() => await OpenSingleFile(["*"], StoragePickerStartLocation.Unspecified);
|
||||
public async Task<string?> OpenSingleFile(string[] fileTypes) => await OpenSingleFile(fileTypes, StoragePickerStartLocation.Unspecified);
|
||||
public async Task<string?> OpenSingleFile(StoragePickerStartLocation startLocation) => await OpenSingleFile(["*"], startLocation);
|
||||
public async Task<string?> OpenSingleFile(string[] fileTypes, StoragePickerStartLocation startLocation)
|
||||
{
|
||||
FileOpenPicker picker = new FileOpenPicker();
|
||||
InitializePicker(picker);
|
||||
|
||||
ConfigureFileOpenPicker(picker, fileTypes, startLocation);
|
||||
|
||||
StorageFile storageFile = await picker.PickSingleFileAsync();
|
||||
return storageFile?.Path;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<string>> OpenMultipleFiles() => await OpenMultipleFiles(["*"], StoragePickerStartLocation.Unspecified);
|
||||
public async Task<IEnumerable<string>> OpenMultipleFiles(string[] fileTypes) => await OpenMultipleFiles(fileTypes, StoragePickerStartLocation.Unspecified);
|
||||
public async Task<IEnumerable<string>> OpenMultipleFiles(StoragePickerStartLocation startLocation) => await OpenMultipleFiles(["*"], startLocation);
|
||||
public async Task<IEnumerable<string>> OpenMultipleFiles(string[] fileTypes, StoragePickerStartLocation startLocation)
|
||||
{
|
||||
FileOpenPicker picker = new FileOpenPicker();
|
||||
InitializePicker(picker);
|
||||
|
||||
ConfigureFileOpenPicker(picker, fileTypes, startLocation);
|
||||
|
||||
IEnumerable<StorageFile> list = await picker.PickMultipleFilesAsync();
|
||||
return list.Select(x => x.Path);
|
||||
}
|
||||
|
||||
public async Task<string?> SaveFile(FileSavePickerFileTypeChoice[] fileTypes, string defaultFileType) => await SaveFile(fileTypes, defaultFileType, StoragePickerStartLocation.Unspecified);
|
||||
public async Task<string?> SaveFile(FileSavePickerFileTypeChoice[] fileTypes, string defaultFileType, StoragePickerStartLocation startLocation)
|
||||
{
|
||||
FileSavePicker picker = new FileSavePicker();
|
||||
InitializePicker(picker);
|
||||
|
||||
ConfigureFileSavePicker(picker, fileTypes, defaultFileType, startLocation);
|
||||
|
||||
StorageFile file = await picker.PickSaveFileAsync();
|
||||
return file?.Path;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region PRIVATE METHODS
|
||||
|
||||
protected void InitializePicker(object picker)
|
||||
{
|
||||
var hwnd = WindowNative.GetWindowHandle(DefaultRoot);
|
||||
InitializeWithWindow.Initialize(picker, hwnd);
|
||||
}
|
||||
|
||||
protected void ConfigureFolderPicker(FolderPicker picker, StoragePickerStartLocation startLocation)
|
||||
{
|
||||
if (startLocation != StoragePickerStartLocation.Unspecified)
|
||||
{
|
||||
picker.SuggestedStartLocation = (PickerLocationId)startLocation;
|
||||
}
|
||||
}
|
||||
|
||||
protected void ConfigureFileOpenPicker(FileOpenPicker picker, string[] fileTypes, StoragePickerStartLocation startLocation)
|
||||
{
|
||||
foreach (string fileType in fileTypes)
|
||||
{
|
||||
picker.FileTypeFilter.Add(fileType);
|
||||
}
|
||||
|
||||
if (startLocation != StoragePickerStartLocation.Unspecified)
|
||||
{
|
||||
picker.SuggestedStartLocation = (PickerLocationId)startLocation;
|
||||
}
|
||||
}
|
||||
|
||||
protected void ConfigureFileSavePicker(FileSavePicker picker, FileSavePickerFileTypeChoice[] fileTypes, string defaultFileType, StoragePickerStartLocation startLocation)
|
||||
{
|
||||
if (startLocation != StoragePickerStartLocation.Unspecified)
|
||||
{
|
||||
picker.SuggestedStartLocation = (PickerLocationId)startLocation;
|
||||
}
|
||||
|
||||
foreach (FileSavePickerFileTypeChoice fileType in fileTypes)
|
||||
{
|
||||
picker.FileTypeChoices.Add(fileType.Description, fileType.Extensions.ToList());
|
||||
}
|
||||
|
||||
if (!defaultFileType.StartsWith('.'))
|
||||
{
|
||||
defaultFileType = $".{defaultFileType}";
|
||||
}
|
||||
|
||||
if (!fileTypes.Any(x => x.Extensions.Contains(defaultFileType)))
|
||||
{
|
||||
picker.FileTypeChoices.Add("Default", [defaultFileType]);
|
||||
}
|
||||
|
||||
picker.DefaultFileExtension = defaultFileType;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace VDownload.GUI.Services.StoragePicker
|
||||
{
|
||||
public enum StoragePickerStartLocation
|
||||
{
|
||||
Documents = 0,
|
||||
Computer = 1,
|
||||
Desktop = 2,
|
||||
Downloads = 3,
|
||||
Music = 5,
|
||||
Pictures = 6,
|
||||
Videos = 7,
|
||||
Unspecified = 999
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0-windows10.0.19041.0</TargetFramework>
|
||||
<TargetPlatformMinVersion>10.0.17763.0</TargetPlatformMinVersion>
|
||||
<RootNamespace>VDownload.GUI.Services.StoragePicker</RootNamespace>
|
||||
<RuntimeIdentifiers>win10-x86;win10-x64;win10-arm64</RuntimeIdentifiers>
|
||||
<UseWinUI>true</UseWinUI>
|
||||
<UseRidGraph>true</UseRidGraph>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.4.231219000" />
|
||||
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.22621.2428" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Update="StoragePickerService.cs">
|
||||
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,25 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0-windows10.0.19041.0</TargetFramework>
|
||||
<TargetPlatformMinVersion>10.0.17763.0</TargetPlatformMinVersion>
|
||||
<RootNamespace>VDownload.GUI.Services.WebView</RootNamespace>
|
||||
<RuntimeIdentifiers>win10-x86;win10-x64;win10-arm64</RuntimeIdentifiers>
|
||||
<UseWinUI>true</UseWinUI>
|
||||
<UseRidGraph>true</UseRidGraph>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<None Remove="WebViewWindow.xaml" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.2.2" />
|
||||
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.4.231219000" />
|
||||
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.22621.2428" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Page Update="WebViewWindow.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,29 @@
|
||||
using Microsoft.Web.WebView2.Core;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace VDownload.GUI.Services.WebView
|
||||
{
|
||||
public interface IWebViewService
|
||||
{
|
||||
Task<string> Show(Uri url, Predicate<string> closePredicate, string name);
|
||||
}
|
||||
|
||||
|
||||
|
||||
public class WebViewService : IWebViewService
|
||||
{
|
||||
#region METHODS
|
||||
|
||||
public async Task<string> Show(Uri url, Predicate<string> closePredicate, string name)
|
||||
{
|
||||
WebViewWindow window = new WebViewWindow(name);
|
||||
return await window.Show(url, closePredicate);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Window
|
||||
x:Class="VDownload.GUI.Services.WebView.WebViewWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="using:VDownload.GUI.Services.WebView"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d"
|
||||
Closed="Window_Closed">
|
||||
<WebView2 x:Name="WebView" NavigationCompleted="WebView_NavigationCompleted"/>
|
||||
</Window>
|
||||
@@ -0,0 +1,89 @@
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Controls.Primitives;
|
||||
using Microsoft.UI.Xaml.Data;
|
||||
using Microsoft.UI.Xaml.Input;
|
||||
using Microsoft.UI.Xaml.Media;
|
||||
using Microsoft.UI.Xaml.Navigation;
|
||||
using Microsoft.Web.WebView2.Core;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices.WindowsRuntime;
|
||||
using System.Threading.Tasks;
|
||||
using Windows.Foundation;
|
||||
using Windows.Foundation.Collections;
|
||||
|
||||
namespace VDownload.GUI.Services.WebView
|
||||
{
|
||||
public sealed partial class WebViewWindow : Window
|
||||
{
|
||||
#region FIEDLS
|
||||
|
||||
private readonly Predicate<string> _defaultClosePredicate = args => false;
|
||||
|
||||
private bool _isOpened;
|
||||
private Predicate<string> _closePredicate;
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region CONSTRUCTORS
|
||||
|
||||
public WebViewWindow(string name)
|
||||
{
|
||||
this.InitializeComponent();
|
||||
this.Title = name;
|
||||
|
||||
_isOpened = false;
|
||||
_closePredicate = _defaultClosePredicate;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region PUBLIC METHODS
|
||||
|
||||
internal async Task<string> Show(Uri url, Predicate<string> closePredicate)
|
||||
{
|
||||
this.WebView.Source = url;
|
||||
_closePredicate = closePredicate;
|
||||
|
||||
this.Activate();
|
||||
_isOpened = true;
|
||||
while (_isOpened)
|
||||
{
|
||||
await Task.Delay(10);
|
||||
}
|
||||
|
||||
_closePredicate = _defaultClosePredicate;
|
||||
|
||||
return this.WebView.Source.ToString();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region EVENT HANDLER
|
||||
|
||||
|
||||
private void WebView_NavigationCompleted(WebView2 sender, CoreWebView2NavigationCompletedEventArgs args)
|
||||
{
|
||||
if (_closePredicate.Invoke(this.WebView.Source.ToString()))
|
||||
{
|
||||
this.Close();
|
||||
}
|
||||
}
|
||||
|
||||
private void Window_Closed(object sender, WindowEventArgs args)
|
||||
{
|
||||
_isOpened = false;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection.Metadata.Ecma335;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using VDownload.GUI.Services.Dialog;
|
||||
using VDownload.GUI.Services.WebView;
|
||||
using VDownload.Sources.Twitch;
|
||||
using VDownload.Sources.Twitch.Authentication;
|
||||
|
||||
namespace VDownload.GUI.ViewModels
|
||||
{
|
||||
public partial class AuthenticationViewModel : ObservableObject
|
||||
{
|
||||
#region ENUMS
|
||||
|
||||
public enum AuthenticationButton
|
||||
{
|
||||
SignIn,
|
||||
SignOut,
|
||||
Loading
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region SERVICES
|
||||
|
||||
private IDialogService _dialogService;
|
||||
private IWebViewService _webViewService;
|
||||
private ITwitchAuthenticationService _twitchAuthenticationService;
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region PROPERTIES
|
||||
|
||||
[ObservableProperty]
|
||||
private string _twitchDescription;
|
||||
|
||||
[ObservableProperty]
|
||||
private AuthenticationButton _twitchButtonState;
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region CONSTRUCTORS
|
||||
|
||||
public AuthenticationViewModel(IDialogService dialogService, IWebViewService webViewService, ITwitchAuthenticationService twitchAuthenticationService)
|
||||
{
|
||||
_dialogService = dialogService;
|
||||
_webViewService = webViewService;
|
||||
_twitchAuthenticationService = twitchAuthenticationService;
|
||||
|
||||
TwitchButtonState = AuthenticationButton.Loading;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region PUBLIC METHODS
|
||||
|
||||
[RelayCommand]
|
||||
public async Task TwitchAuthentication()
|
||||
{
|
||||
AuthenticationButton state = TwitchButtonState;
|
||||
TwitchButtonState = AuthenticationButton.Loading;
|
||||
|
||||
if (state == AuthenticationButton.SignOut)
|
||||
{
|
||||
await _twitchAuthenticationService.DeleteToken();
|
||||
}
|
||||
else
|
||||
{
|
||||
string url = await _webViewService.Show(new Uri(_twitchAuthenticationService.AuthenticationPageUrl), _twitchAuthenticationService.AuthenticationPageClosePredicate, "Twitch authentication");
|
||||
|
||||
Match match = _twitchAuthenticationService.AuthenticationPageRedirectUrlRegex.Match(url);
|
||||
|
||||
if (match.Success)
|
||||
{
|
||||
string token = match.Groups[1].Value;
|
||||
await _twitchAuthenticationService.SetToken(Encoding.UTF8.GetBytes(token));
|
||||
}
|
||||
else
|
||||
{
|
||||
await _dialogService.ShowOk("Twitch authentication error", "An error occured");
|
||||
}
|
||||
}
|
||||
await TwitchAuthenticationRefresh();
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
public async Task Navigation()
|
||||
{
|
||||
List<Task> refreshTasks = new List<Task>
|
||||
{
|
||||
TwitchAuthenticationRefresh()
|
||||
};
|
||||
await Task.WhenAll(refreshTasks);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region PRIVATE METHODS
|
||||
|
||||
private async Task TwitchAuthenticationRefresh()
|
||||
{
|
||||
TwitchButtonState = AuthenticationButton.Loading;
|
||||
|
||||
byte[]? token = await _twitchAuthenticationService.GetToken();
|
||||
|
||||
if (token is null)
|
||||
{
|
||||
TwitchDescription = "You are not authenticated. Please sign in";
|
||||
TwitchButtonState = AuthenticationButton.SignIn;
|
||||
}
|
||||
else
|
||||
{
|
||||
TwitchValidationResult validationResult = await _twitchAuthenticationService.ValidateToken(token);
|
||||
if (validationResult.Success)
|
||||
{
|
||||
TwitchDescription = $"Signed in as {validationResult.TokenData.Login}. Expiration date: {validationResult.TokenData.ExpirationDate:dd.MM.yyyy HH:mm}";
|
||||
TwitchButtonState = AuthenticationButton.SignOut;
|
||||
}
|
||||
else
|
||||
{
|
||||
await _twitchAuthenticationService.DeleteToken();
|
||||
TwitchDescription = "Token expired or is invalid. Please log in again";
|
||||
TwitchButtonState = AuthenticationButton.SignIn;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
315
VDownload.GUI/VDownload.GUI.ViewModels/HomeViewModel.cs
Normal file
315
VDownload.GUI/VDownload.GUI.ViewModels/HomeViewModel.cs
Normal file
@@ -0,0 +1,315 @@
|
||||
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.Common;
|
||||
using VDownload.Common.Exceptions;
|
||||
using VDownload.Common.Models;
|
||||
using VDownload.GUI.Services.StoragePicker;
|
||||
using VDownload.Services.Search;
|
||||
using VDownload.Tasks;
|
||||
|
||||
namespace VDownload.GUI.ViewModels
|
||||
{
|
||||
public partial class HomeViewModel : ObservableObject
|
||||
{
|
||||
#region ENUMS
|
||||
|
||||
public enum OptionBarContentType
|
||||
{
|
||||
None,
|
||||
VideoSearch,
|
||||
PlaylistSearch
|
||||
}
|
||||
|
||||
public enum MainContentType
|
||||
{
|
||||
Downloads,
|
||||
Video
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region SERVICES
|
||||
|
||||
private IStoragePickerService _storagePickerService;
|
||||
private ISearchService _searchService;
|
||||
private IDownloadTasksManager _tasksService;
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region PROPERTIES
|
||||
|
||||
// MAIN
|
||||
|
||||
[ObservableProperty]
|
||||
private MainContentType _mainContent;
|
||||
|
||||
|
||||
// DOWNLOADS
|
||||
public ObservableCollection<DownloadTask> Tasks => _tasksService.Tasks;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _taskListIsEmpty;
|
||||
|
||||
|
||||
// VIDEO
|
||||
|
||||
[ObservableProperty]
|
||||
private DownloadTask _task;
|
||||
|
||||
|
||||
// OPTION BAR
|
||||
|
||||
[ObservableProperty]
|
||||
private OptionBarContentType _optionBarContent;
|
||||
|
||||
[ObservableProperty]
|
||||
private string _optionBarMessage;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _optionBarLoading;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _optionBarVideoSearchButtonChecked;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _optionBarPlaylistSearchButtonChecked;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _optionBarSearchNotPending;
|
||||
|
||||
[ObservableProperty]
|
||||
private string _optionBarVideoSearchTBValue;
|
||||
|
||||
[ObservableProperty]
|
||||
private string _optionBarPlaylistSearchTBValue;
|
||||
|
||||
[ObservableProperty]
|
||||
private int _optionBarPlaylistSearchNBValue;
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region CONSTRUCTORS
|
||||
|
||||
public HomeViewModel(IStoragePickerService storagePickerService, ISearchService searchService, IDownloadTasksManager tasksService)
|
||||
{
|
||||
_storagePickerService = storagePickerService;
|
||||
_searchService = searchService;
|
||||
_tasksService = tasksService;
|
||||
_tasksService.Tasks.CollectionChanged += Tasks_CollectionChanged;
|
||||
|
||||
_taskListIsEmpty = _tasksService.Tasks.Count == 0;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region COMMANDS
|
||||
|
||||
[RelayCommand]
|
||||
public void Navigation()
|
||||
{
|
||||
MainContent = MainContentType.Downloads;
|
||||
|
||||
OptionBarContent = OptionBarContentType.None;
|
||||
OptionBarMessage = null;
|
||||
OptionBarVideoSearchButtonChecked = false;
|
||||
OptionBarPlaylistSearchButtonChecked = false;
|
||||
OptionBarSearchNotPending = true;
|
||||
OptionBarVideoSearchTBValue = string.Empty;
|
||||
OptionBarPlaylistSearchNBValue = 1; // TODO: load from settings
|
||||
OptionBarPlaylistSearchTBValue = string.Empty;
|
||||
}
|
||||
|
||||
|
||||
// DOWNLOADS
|
||||
|
||||
[RelayCommand]
|
||||
public void StartCancelTask(DownloadTask task)
|
||||
{
|
||||
DownloadTaskStatus[] idleStatuses =
|
||||
[
|
||||
DownloadTaskStatus.Idle,
|
||||
DownloadTaskStatus.EndedUnsuccessfully,
|
||||
DownloadTaskStatus.EndedSuccessfully,
|
||||
DownloadTaskStatus.EndedCancelled
|
||||
];
|
||||
if (idleStatuses.Contains(task.Status))
|
||||
{
|
||||
task.Enqueue();
|
||||
}
|
||||
else
|
||||
{
|
||||
task.Cancel();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// VIDEO
|
||||
|
||||
[RelayCommand]
|
||||
public async Task Browse()
|
||||
{
|
||||
string? newDirectory = await _storagePickerService.OpenDirectory();
|
||||
if (newDirectory is not null)
|
||||
{
|
||||
Task.DirectoryPath = newDirectory;
|
||||
}
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
public async Task CreateTask()
|
||||
{
|
||||
string extension = Task.MediaType switch
|
||||
{
|
||||
MediaType.OnlyAudio => Task.AudioExtension.ToString(),
|
||||
_ => Task.VideoExtension.ToString()
|
||||
};
|
||||
|
||||
Task.Filename = string.Join("_", Task.Filename.Split(Path.GetInvalidFileNameChars()));
|
||||
string file = $"{Task.Filename}.{extension}";
|
||||
string path = Path.Combine(Task.DirectoryPath, file);
|
||||
|
||||
await File.WriteAllBytesAsync(path, [0x00]);
|
||||
File.Delete(path);
|
||||
|
||||
_tasksService.AddTask(Task);
|
||||
|
||||
Navigation();
|
||||
}
|
||||
|
||||
|
||||
// OPTION BAR
|
||||
|
||||
[RelayCommand]
|
||||
public void LoadFromSubscription()
|
||||
{
|
||||
MainContent = MainContentType.Downloads;
|
||||
|
||||
OptionBarContent = OptionBarContentType.None;
|
||||
OptionBarVideoSearchButtonChecked = false;
|
||||
OptionBarPlaylistSearchButtonChecked = false;
|
||||
OptionBarSearchNotPending = false;
|
||||
|
||||
//TODO: Load videos
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
public void VideoSearchShow()
|
||||
{
|
||||
MainContent = MainContentType.Downloads;
|
||||
|
||||
if (OptionBarContent != OptionBarContentType.VideoSearch)
|
||||
{
|
||||
OptionBarContent = OptionBarContentType.VideoSearch;
|
||||
OptionBarPlaylistSearchButtonChecked = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
OptionBarContent = OptionBarContentType.None;
|
||||
}
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
public void PlaylistSearchShow()
|
||||
{
|
||||
MainContent = MainContentType.Downloads;
|
||||
|
||||
if (OptionBarContent != OptionBarContentType.PlaylistSearch)
|
||||
{
|
||||
OptionBarContent = OptionBarContentType.PlaylistSearch;
|
||||
OptionBarVideoSearchButtonChecked = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
OptionBarContent = OptionBarContentType.None;
|
||||
}
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
public async Task VideoSearchStart()
|
||||
{
|
||||
OptionBarSearchNotPending = false;
|
||||
OptionBarLoading = true;
|
||||
OptionBarMessage = "Loading...";
|
||||
|
||||
Video video;
|
||||
try
|
||||
{
|
||||
video = await _searchService.SearchVideo(OptionBarVideoSearchTBValue);
|
||||
}
|
||||
catch (MediaSearchException ex)
|
||||
{
|
||||
OptionBarLoading = false;
|
||||
OptionBarMessage = ex.Message;
|
||||
OptionBarSearchNotPending = true;
|
||||
return;
|
||||
}
|
||||
|
||||
Task = new DownloadTask(video);
|
||||
|
||||
MainContent = MainContentType.Video;
|
||||
|
||||
OptionBarSearchNotPending = true;
|
||||
OptionBarLoading = false;
|
||||
OptionBarMessage = null;
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
public async Task PlaylistSearchStart()
|
||||
{
|
||||
OptionBarSearchNotPending = false;
|
||||
OptionBarLoading = true;
|
||||
OptionBarMessage = "Loading...";
|
||||
|
||||
Playlist playlist;
|
||||
try
|
||||
{
|
||||
playlist = await _searchService.SearchPlaylist(OptionBarPlaylistSearchTBValue, OptionBarPlaylistSearchNBValue);
|
||||
}
|
||||
catch (MediaSearchException ex)
|
||||
{
|
||||
OptionBarLoading = false;
|
||||
OptionBarMessage = ex.Message;
|
||||
OptionBarSearchNotPending = true;
|
||||
return;
|
||||
}
|
||||
|
||||
OptionBarSearchNotPending = true;
|
||||
OptionBarLoading = false;
|
||||
OptionBarMessage = null;
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
public void Download()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region EVENT HANDLERS
|
||||
|
||||
private void Tasks_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
TaskListIsEmpty = Tasks.Count == 0;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
19
VDownload.GUI/VDownload.GUI.ViewModels/SettingsViewModel.cs
Normal file
19
VDownload.GUI/VDownload.GUI.ViewModels/SettingsViewModel.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace VDownload.GUI.ViewModels
|
||||
{
|
||||
public class SettingsViewModel
|
||||
{
|
||||
#region CONSTRUCTORS
|
||||
|
||||
public SettingsViewModel()
|
||||
{
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0-windows10.0.19041.0</TargetFramework>
|
||||
<TargetPlatformMinVersion>10.0.17763.0</TargetPlatformMinVersion>
|
||||
<RootNamespace>VDownload.GUI.ViewModels</RootNamespace>
|
||||
<RuntimeIdentifiers>win10-x86;win10-x64;win10-arm64</RuntimeIdentifiers>
|
||||
<UseWinUI>true</UseWinUI>
|
||||
<UseRidGraph>true</UseRidGraph>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.2.2" />
|
||||
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.4.231219000" />
|
||||
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.22621.2428" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\VDownload.Common\VDownload.Common.csproj" />
|
||||
<ProjectReference Include="..\..\VDownload.Services\VDownload.Services.Search\VDownload.Services.Search.csproj" />
|
||||
<ProjectReference Include="..\..\VDownload.Sources\VDownload.Sources.Twitch\VDownload.Sources.Twitch.Authentication\VDownload.Sources.Twitch.Authentication.csproj" />
|
||||
<ProjectReference Include="..\..\VDownload.Sources\VDownload.Sources.Twitch\VDownload.Sources.Twitch\VDownload.Sources.Twitch.csproj" />
|
||||
<ProjectReference Include="..\..\VDownload.Tasks\VDownload.Tasks.csproj" />
|
||||
<ProjectReference Include="..\VDownload.GUI.Services\VDownload.GUI.Services.Dialog\VDownload.GUI.Services.Dialog.csproj" />
|
||||
<ProjectReference Include="..\VDownload.GUI.Services\VDownload.GUI.Services.StoragePicker\VDownload.GUI.Services.StoragePicker.csproj" />
|
||||
<ProjectReference Include="..\VDownload.GUI.Services\VDownload.GUI.Services.WebView\VDownload.GUI.Services.WebView.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
80
VDownload.GUI/VDownload.GUI.Views/AuthenticationView.xaml
Normal file
80
VDownload.GUI/VDownload.GUI.Views/AuthenticationView.xaml
Normal file
@@ -0,0 +1,80 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Page
|
||||
x:Class="VDownload.GUI.Views.AuthenticationView"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="using:VDownload.GUI.Views"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:ctuc="using:CommunityToolkit.WinUI.UI.Controls"
|
||||
xmlns:ctc="using:CommunityToolkit.WinUI.Controls"
|
||||
xmlns:ct="using:CommunityToolkit.WinUI"
|
||||
xmlns:i="using:Microsoft.Xaml.Interactivity"
|
||||
xmlns:ic="using:Microsoft.Xaml.Interactions.Core"
|
||||
mc:Ignorable="d"
|
||||
Background="{ThemeResource ViewBackgroundColor}">
|
||||
<i:Interaction.Behaviors>
|
||||
<ic:EventTriggerBehavior EventName="Loaded">
|
||||
<ic:InvokeCommandAction Command="{Binding NavigationCommand}"/>
|
||||
</ic:EventTriggerBehavior>
|
||||
</i:Interaction.Behaviors>
|
||||
<Grid Padding="20" RowSpacing="20">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition/>
|
||||
</Grid.RowDefinitions>
|
||||
<TextBlock Grid.Row="0"
|
||||
FontSize="28"
|
||||
FontWeight="SemiBold"
|
||||
Text="Authentication"/>
|
||||
<StackPanel Grid.Row="1"
|
||||
Spacing="10">
|
||||
<ctc:SettingsCard Header="Twitch">
|
||||
<i:Interaction.Behaviors>
|
||||
<ic:DataTriggerBehavior Binding="{Binding TwitchButtonState, Converter={StaticResource EnumToStringConverter}}"
|
||||
ComparisonCondition="NotEqual"
|
||||
Value="Loading">
|
||||
<ic:ChangePropertyAction PropertyName="Description"
|
||||
Value="{Binding TwitchDescription}"/>
|
||||
<ic:ChangePropertyAction PropertyName="Content">
|
||||
<ic:ChangePropertyAction.Value>
|
||||
<Button Command="{Binding TwitchAuthenticationCommand}">
|
||||
<i:Interaction.Behaviors>
|
||||
<ic:DataTriggerBehavior Binding="{Binding TwitchButtonState, Converter={StaticResource EnumToStringConverter}}"
|
||||
ComparisonCondition="Equal"
|
||||
Value="SignIn">
|
||||
<ic:ChangePropertyAction PropertyName="Content"
|
||||
Value="Sign in"/>
|
||||
</ic:DataTriggerBehavior>
|
||||
<ic:DataTriggerBehavior Binding="{Binding TwitchButtonState, Converter={StaticResource EnumToStringConverter}}"
|
||||
ComparisonCondition="Equal"
|
||||
Value="SignOut">
|
||||
<ic:ChangePropertyAction PropertyName="Content"
|
||||
Value="Sign out"/>
|
||||
</ic:DataTriggerBehavior>
|
||||
</i:Interaction.Behaviors>
|
||||
</Button>
|
||||
</ic:ChangePropertyAction.Value>
|
||||
</ic:ChangePropertyAction>
|
||||
</ic:DataTriggerBehavior>
|
||||
<ic:DataTriggerBehavior Binding="{Binding TwitchButtonState, Converter={StaticResource EnumToStringConverter}}"
|
||||
ComparisonCondition="Equal"
|
||||
Value="Loading">
|
||||
<ic:ChangePropertyAction PropertyName="Description"
|
||||
Value="Loading..."/>
|
||||
<ic:ChangePropertyAction PropertyName="Content">
|
||||
<ic:ChangePropertyAction.Value>
|
||||
<ProgressRing Width="20"
|
||||
Height="20"/>
|
||||
</ic:ChangePropertyAction.Value>
|
||||
</ic:ChangePropertyAction>
|
||||
</ic:DataTriggerBehavior>
|
||||
</i:Interaction.Behaviors>
|
||||
<ctc:SettingsCard.HeaderIcon>
|
||||
<BitmapIcon ShowAsMonochrome="False"
|
||||
UriSource="{StaticResource ImageSourcesTwitch}"/>
|
||||
</ctc:SettingsCard.HeaderIcon>
|
||||
</ctc:SettingsCard>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Page>
|
||||
27
VDownload.GUI/VDownload.GUI.Views/AuthenticationView.xaml.cs
Normal file
27
VDownload.GUI/VDownload.GUI.Views/AuthenticationView.xaml.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices.WindowsRuntime;
|
||||
using Windows.Foundation;
|
||||
using Windows.Foundation.Collections;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Controls.Primitives;
|
||||
using Microsoft.UI.Xaml.Data;
|
||||
using Microsoft.UI.Xaml.Input;
|
||||
using Microsoft.UI.Xaml.Media;
|
||||
using Microsoft.UI.Xaml.Navigation;
|
||||
using VDownload.GUI.ViewModels;
|
||||
|
||||
namespace VDownload.GUI.Views
|
||||
{
|
||||
public sealed partial class AuthenticationView : Page
|
||||
{
|
||||
public AuthenticationView(AuthenticationViewModel viewModel)
|
||||
{
|
||||
this.InitializeComponent();
|
||||
this.DataContext = viewModel;
|
||||
}
|
||||
}
|
||||
}
|
||||
578
VDownload.GUI/VDownload.GUI.Views/HomeView.xaml
Normal file
578
VDownload.GUI/VDownload.GUI.Views/HomeView.xaml
Normal file
@@ -0,0 +1,578 @@
|
||||
<Page
|
||||
x:Class="VDownload.GUI.Views.HomeView"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="using:VDownload.GUI.Views"
|
||||
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:cc="using:VDownload.GUI.Controls"
|
||||
xmlns:cmn="using:VDownload.Common"
|
||||
xmlns:ct="using:CommunityToolkit.WinUI"
|
||||
xmlns:ctc="using:CommunityToolkit.WinUI.Controls"
|
||||
xmlns:ctuc="using:CommunityToolkit.WinUI.UI.Controls"
|
||||
xmlns:ctb="using:CommunityToolkit.WinUI.Behaviors"
|
||||
mc:Ignorable="d"
|
||||
Background="Transparent"
|
||||
x:Name="Root">
|
||||
<i:Interaction.Behaviors>
|
||||
<ic:EventTriggerBehavior EventName="Loaded">
|
||||
<ic:InvokeCommandAction Command="{Binding NavigationCommand}"/>
|
||||
</ic:EventTriggerBehavior>
|
||||
</i:Interaction.Behaviors>
|
||||
<Grid RowSpacing="10"
|
||||
Margin="10">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<ctuc:SwitchPresenter Grid.Row="0"
|
||||
Value="{Binding MainContent, Converter={StaticResource EnumToStringConverter}}"
|
||||
CornerRadius="10">
|
||||
<ctuc:Case Value="Downloads">
|
||||
<ctuc:SwitchPresenter Value="{Binding TaskListIsEmpty, Converter={StaticResource BooleanToStringConverter}}">
|
||||
<ctuc:Case Value="True">
|
||||
<StackPanel HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center">
|
||||
<Image Source="{StaticResource ImageDownloadsNoTasks}"
|
||||
Width="100"/>
|
||||
<TextBlock Text="Click Video/Playlist search button to add new tasks"
|
||||
Foreground="{StaticResource GreyText}"/>
|
||||
</StackPanel>
|
||||
</ctuc:Case>
|
||||
<ctuc:Case Value="False">
|
||||
<ScrollViewer>
|
||||
<ItemsControl ItemsSource="{Binding Tasks}">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Grid Background="{ThemeResource ViewBackgroundColor}"
|
||||
CornerRadius="10"
|
||||
Height="150">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Image Grid.Column="0"
|
||||
Source="{Binding Video.ThumbnailUrl}"
|
||||
VerticalAlignment="Stretch"/>
|
||||
<Grid Grid.Column="1"
|
||||
Margin="10"
|
||||
RowSpacing="10">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition/>
|
||||
</Grid.RowDefinitions>
|
||||
<Grid Grid.Row="0"
|
||||
ColumnSpacing="10"
|
||||
HorizontalAlignment="Left">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Grid.Column="0"
|
||||
FontWeight="SemiBold"
|
||||
FontSize="18"
|
||||
Text="{Binding Video.Title}"
|
||||
TextTrimming="CharacterEllipsis"/>
|
||||
<TextBlock Grid.Column="1"
|
||||
VerticalAlignment="Center"
|
||||
FontWeight="Light"
|
||||
FontSize="12"
|
||||
Text="{Binding Video.Author}"/>
|
||||
</Grid>
|
||||
<Grid Grid.Row="1"
|
||||
RowSpacing="10"
|
||||
ColumnSpacing="10">
|
||||
<Grid.Resources>
|
||||
<x:Double x:Key="TextSize">12</x:Double>
|
||||
</Grid.Resources>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition/>
|
||||
<RowDefinition/>
|
||||
<RowDefinition/>
|
||||
<RowDefinition/>
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Image Grid.Row="0"
|
||||
Grid.Column="0"
|
||||
Source="{ThemeResource ImageDownloadsQuality}"/>
|
||||
<TextBlock Grid.Row="0"
|
||||
Grid.Column="1"
|
||||
FontSize="{StaticResource TextSize}"
|
||||
VerticalAlignment="Center">
|
||||
<Run Text="{Binding MediaType, Converter={StaticResource EnumToDescriptionConverter}}"/> (<Run Text="{Binding VideoStream.StreamIdentifier}"/>)
|
||||
</TextBlock>
|
||||
<Image Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
Source="{ThemeResource ImageDownloadsTime}"/>
|
||||
<StackPanel Grid.Row="1"
|
||||
Grid.Column="1"
|
||||
Orientation="Horizontal"
|
||||
VerticalAlignment="Center">
|
||||
<TextBlock Text="{Binding DurationAfterTrim}"
|
||||
FontSize="{StaticResource TextSize}"/>
|
||||
<TextBlock Visibility="{Binding IsTrimmed, Converter={StaticResource BooleanToVisibilityConverter}}"
|
||||
FontSize="{StaticResource TextSize}">
|
||||
<Run Text=" "/>(<Run Text="{Binding TrimStart}"/> - <Run Text="{Binding TrimEnd}"/>)
|
||||
</TextBlock>
|
||||
</StackPanel>
|
||||
<Image Grid.Row="2"
|
||||
Grid.Column="0"
|
||||
Source="{ThemeResource ImageDownloadsFile}"/>
|
||||
<TextBlock Grid.Row="2"
|
||||
Grid.Column="1"
|
||||
FontSize="{StaticResource TextSize}"
|
||||
VerticalAlignment="Center"
|
||||
Text="{Binding FilePath}"/>
|
||||
<Image Grid.Row="3"
|
||||
Grid.Column="0">
|
||||
<i:Interaction.Behaviors>
|
||||
<ic:DataTriggerBehavior Binding="{Binding Status, Converter={StaticResource EnumToStringConverter}}"
|
||||
ComparisonCondition="Equal"
|
||||
Value="Idle">
|
||||
<ic:ChangePropertyAction PropertyName="Source"
|
||||
Value="{ThemeResource ImageDownloadsIdle}"/>
|
||||
</ic:DataTriggerBehavior>
|
||||
<ic:DataTriggerBehavior Binding="{Binding Status, Converter={StaticResource EnumToStringConverter}}"
|
||||
ComparisonCondition="Equal"
|
||||
Value="Queued">
|
||||
<ic:ChangePropertyAction PropertyName="Source"
|
||||
Value="{ThemeResource ImageDownloadsQueued}"/>
|
||||
</ic:DataTriggerBehavior>
|
||||
<ic:DataTriggerBehavior Binding="{Binding Status, Converter={StaticResource EnumToStringConverter}}"
|
||||
ComparisonCondition="Equal"
|
||||
Value="Initializing">
|
||||
<ic:ChangePropertyAction PropertyName="Source"
|
||||
Value="{ThemeResource ImageDownloadsInitializing}"/>
|
||||
</ic:DataTriggerBehavior>
|
||||
</i:Interaction.Behaviors>
|
||||
</Image>
|
||||
<TextBlock Grid.Row="3"
|
||||
Grid.Column="1"
|
||||
FontSize="{StaticResource TextSize}"
|
||||
VerticalAlignment="Center">
|
||||
<i:Interaction.Behaviors>
|
||||
<ic:DataTriggerBehavior Binding="{Binding Status, Converter={StaticResource EnumToStringConverter}}"
|
||||
ComparisonCondition="Equal"
|
||||
Value="Idle">
|
||||
<ic:ChangePropertyAction PropertyName="Text"
|
||||
Value="Idle"/>
|
||||
</ic:DataTriggerBehavior>
|
||||
<ic:DataTriggerBehavior Binding="{Binding Status, Converter={StaticResource EnumToStringConverter}}"
|
||||
ComparisonCondition="Equal"
|
||||
Value="Queued">
|
||||
<ic:ChangePropertyAction PropertyName="Text"
|
||||
Value="Queued"/>
|
||||
</ic:DataTriggerBehavior>
|
||||
<ic:DataTriggerBehavior Binding="{Binding Status, Converter={StaticResource EnumToStringConverter}}"
|
||||
ComparisonCondition="Equal"
|
||||
Value="Initializing">
|
||||
<ic:ChangePropertyAction PropertyName="Text"
|
||||
Value="Initializing"/>
|
||||
</ic:DataTriggerBehavior>
|
||||
</i:Interaction.Behaviors>
|
||||
</TextBlock>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid Grid.Column="2"
|
||||
Margin="0,0,5,0">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition/>
|
||||
<RowDefinition/>
|
||||
<RowDefinition/>
|
||||
</Grid.RowDefinitions>
|
||||
<AppBarButton Grid.Row="0"
|
||||
Width="40"
|
||||
Height="48">
|
||||
<i:Interaction.Behaviors>
|
||||
<ic:EventTriggerBehavior EventName="Click">
|
||||
<ctb:NavigateToUriAction NavigateUri="{Binding Video.Url}"/>
|
||||
</ic:EventTriggerBehavior>
|
||||
<ic:DataTriggerBehavior Binding="{Binding Video.Source, Converter={StaticResource EnumToStringConverter}}"
|
||||
ComparisonCondition="Equal"
|
||||
Value="Twitch">
|
||||
<ic:ChangePropertyAction PropertyName="Icon">
|
||||
<ic:ChangePropertyAction.Value>
|
||||
<BitmapIcon ShowAsMonochrome="False"
|
||||
UriSource="{StaticResource ImageSourcesTwitch}"/>
|
||||
</ic:ChangePropertyAction.Value>
|
||||
</ic:ChangePropertyAction>
|
||||
</ic:DataTriggerBehavior>
|
||||
</i:Interaction.Behaviors>
|
||||
</AppBarButton>
|
||||
<AppBarButton Grid.Row="1"
|
||||
Width="40"
|
||||
Height="48"
|
||||
Command="{Binding ElementName=Root, Path=DataContext.StartCancelTaskCommand}"
|
||||
CommandParameter="{Binding}">
|
||||
<i:Interaction.Behaviors>
|
||||
<ic:DataTriggerBehavior Binding="{Binding Status, Converter={StaticResource EnumToIntConverter}}"
|
||||
ComparisonCondition="LessThan"
|
||||
Value="4">
|
||||
<ic:ChangePropertyAction PropertyName="Icon">
|
||||
<ic:ChangePropertyAction.Value>
|
||||
<SymbolIcon Symbol="Download"/>
|
||||
</ic:ChangePropertyAction.Value>
|
||||
</ic:ChangePropertyAction>
|
||||
</ic:DataTriggerBehavior>
|
||||
<ic:DataTriggerBehavior Binding="{Binding Status, Converter={StaticResource EnumToIntConverter}}"
|
||||
ComparisonCondition="GreaterThanOrEqual"
|
||||
Value="4">
|
||||
<ic:ChangePropertyAction PropertyName="Icon">
|
||||
<ic:ChangePropertyAction.Value>
|
||||
<SymbolIcon Symbol="Cancel"/>
|
||||
</ic:ChangePropertyAction.Value>
|
||||
</ic:ChangePropertyAction>
|
||||
</ic:DataTriggerBehavior>
|
||||
</i:Interaction.Behaviors>
|
||||
</AppBarButton>
|
||||
<AppBarButton Grid.Row="2"
|
||||
Icon="Delete"
|
||||
Width="40"
|
||||
Height="48"/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<ProgressBar Grid.Row="1"
|
||||
Value="{Binding Progress}">
|
||||
<i:Interaction.Behaviors>
|
||||
<ic:DataTriggerBehavior Binding="{Binding Status, Converter={StaticResource EnumToIntConverter}}"
|
||||
ComparisonCondition="LessThan"
|
||||
Value="5">
|
||||
<ic:ChangePropertyAction PropertyName="Visibility" Value="Collapsed"/>
|
||||
</ic:DataTriggerBehavior>
|
||||
<ic:DataTriggerBehavior Binding="{Binding Status, Converter={StaticResource EnumToIntConverter}}"
|
||||
ComparisonCondition="GreaterThanOrEqual"
|
||||
Value="5">
|
||||
<ic:ChangePropertyAction PropertyName="Visibility" Value="Visible"/>
|
||||
</ic:DataTriggerBehavior>
|
||||
<ic:DataTriggerBehavior Binding="{Binding Status, Converter={StaticResource EnumToIntConverter}}"
|
||||
ComparisonCondition="LessThan"
|
||||
Value="7">
|
||||
<ic:ChangePropertyAction PropertyName="IsIndeterminate" Value="True"/>
|
||||
</ic:DataTriggerBehavior>
|
||||
<ic:DataTriggerBehavior Binding="{Binding Status, Converter={StaticResource EnumToIntConverter}}"
|
||||
ComparisonCondition="GreaterThanOrEqual"
|
||||
Value="7">
|
||||
<ic:ChangePropertyAction PropertyName="IsIndeterminate" Value="False"/>
|
||||
</ic:DataTriggerBehavior>
|
||||
</i:Interaction.Behaviors>
|
||||
</ProgressBar>
|
||||
</Grid>
|
||||
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</ScrollViewer>
|
||||
</ctuc:Case>
|
||||
</ctuc:SwitchPresenter>
|
||||
</ctuc:Case>
|
||||
<ctuc:Case Value="Video">
|
||||
<Grid Padding="15"
|
||||
RowSpacing="20"
|
||||
Background="{ThemeResource ViewBackgroundColor}">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="150"/>
|
||||
<RowDefinition/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<Grid Grid.Row="0"
|
||||
ColumnSpacing="10">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Border Grid.Column="0"
|
||||
CornerRadius="{ThemeResource ControlCornerRadius}">
|
||||
<Image Source="{Binding Task.Video.ThumbnailUrl}"
|
||||
VerticalAlignment="Stretch"/>
|
||||
</Border>
|
||||
<StackPanel Grid.Column="1"
|
||||
Spacing="15">
|
||||
<TextBlock Text="{Binding Task.Video.Title}"
|
||||
FontWeight="Bold"
|
||||
FontSize="20"
|
||||
TextWrapping="WrapWholeWords"/>
|
||||
<Grid ColumnSpacing="10"
|
||||
RowSpacing="10">
|
||||
<Grid.Resources>
|
||||
<x:Double x:Key="IconSize">18</x:Double>
|
||||
</Grid.Resources>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<Image Grid.Column="0"
|
||||
Grid.Row="0"
|
||||
Source="{ThemeResource ImageVideoAuthor}"
|
||||
Width="{StaticResource IconSize}"/>
|
||||
<TextBlock Grid.Column="1"
|
||||
Grid.Row="0"
|
||||
Text="{Binding Task.Video.Author}"/>
|
||||
<Image Grid.Column="0"
|
||||
Grid.Row="1"
|
||||
Source="{ThemeResource ImageVideoDate}"
|
||||
Width="{StaticResource IconSize}"/>
|
||||
<TextBlock Grid.Column="1"
|
||||
Grid.Row="1"
|
||||
Text="{Binding Task.Video.PublishDate}"/>
|
||||
<Image Grid.Column="0"
|
||||
Grid.Row="2"
|
||||
Source="{ThemeResource ImageVideoTime}"
|
||||
Width="{StaticResource IconSize}"/>
|
||||
<TextBlock Grid.Column="1"
|
||||
Grid.Row="2"
|
||||
Text="{Binding Task.Video.Duration}"/>
|
||||
<Image Grid.Column="0"
|
||||
Grid.Row="3"
|
||||
Source="{ThemeResource ImageVideoView}"
|
||||
Width="{StaticResource IconSize}"/>
|
||||
<TextBlock Grid.Column="1"
|
||||
Grid.Row="3"
|
||||
Text="{Binding Task.Video.ViewCount}"/>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
<ScrollViewer Grid.Row="1">
|
||||
<StackPanel Spacing="20">
|
||||
<StackPanel Spacing="5">
|
||||
<TextBlock Text="Media options"
|
||||
FontWeight="Bold"
|
||||
FontSize="15"/>
|
||||
<ctc:SettingsCard Header="Quality">
|
||||
<ctc:SettingsCard.HeaderIcon>
|
||||
<BitmapIcon ShowAsMonochrome="False"
|
||||
UriSource="{ThemeResource ImageVideoQuality}"/>
|
||||
</ctc:SettingsCard.HeaderIcon>
|
||||
<ComboBox ItemsSource="{Binding Task.Video.Streams}"
|
||||
SelectedItem="{Binding Task.VideoStream, Mode=TwoWay}">
|
||||
<ComboBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding StreamIdentifier}"/>
|
||||
</DataTemplate>
|
||||
</ComboBox.ItemTemplate>
|
||||
</ComboBox>
|
||||
</ctc:SettingsCard>
|
||||
<ctc:SettingsCard Header="Media type">
|
||||
<ctc:SettingsCard.HeaderIcon>
|
||||
<BitmapIcon ShowAsMonochrome="False"
|
||||
UriSource="{ThemeResource ImageVideoMedia}"/>
|
||||
</ctc:SettingsCard.HeaderIcon>
|
||||
<ComboBox ItemsSource="{ct:EnumValues Type=cmn:MediaType}"
|
||||
SelectedItem="{Binding Task.MediaType, Mode=TwoWay}">
|
||||
<ComboBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding Converter={StaticResource EnumToDescriptionConverter}}"/>
|
||||
</DataTemplate>
|
||||
</ComboBox.ItemTemplate>
|
||||
</ComboBox>
|
||||
</ctc:SettingsCard>
|
||||
<ctc:SettingsExpander Header="Trim">
|
||||
<ctc:SettingsExpander.HeaderIcon>
|
||||
<BitmapIcon ShowAsMonochrome="False"
|
||||
UriSource="{ThemeResource ImageVideoTrim}"/>
|
||||
</ctc:SettingsExpander.HeaderIcon>
|
||||
<ctc:SettingsExpander.Items>
|
||||
<ctc:SettingsCard Header="Start at">
|
||||
<cc:TimeSpanControl Value="{Binding Task.TrimStart, Mode=TwoWay}"
|
||||
Maximum="{Binding Task.TrimEnd, Mode=OneWay}"/>
|
||||
</ctc:SettingsCard>
|
||||
<ctc:SettingsCard Header="End at">
|
||||
<cc:TimeSpanControl Minimum="{Binding Task.TrimStart, Mode=OneWay}"
|
||||
Value="{Binding Task.TrimEnd, Mode=TwoWay}"
|
||||
Maximum="{Binding Task.Video.Duration, Mode=OneWay}"/>
|
||||
</ctc:SettingsCard>
|
||||
</ctc:SettingsExpander.Items>
|
||||
</ctc:SettingsExpander>
|
||||
</StackPanel>
|
||||
<StackPanel Spacing="5">
|
||||
<TextBlock Text="File options"
|
||||
FontWeight="Bold"
|
||||
FontSize="15"/>
|
||||
<ctc:SettingsCard Header="Directory"
|
||||
Description="{Binding Task.DirectoryPath}">
|
||||
<ctc:SettingsCard.HeaderIcon>
|
||||
<BitmapIcon ShowAsMonochrome="False" UriSource="{ThemeResource ImageVideoDirectory}"/>
|
||||
</ctc:SettingsCard.HeaderIcon>
|
||||
<Button Content="Browse"
|
||||
Command="{Binding BrowseCommand}"/>
|
||||
</ctc:SettingsCard>
|
||||
<ctc:SettingsCard Header="Filename">
|
||||
<ctc:SettingsCard.HeaderIcon>
|
||||
<BitmapIcon ShowAsMonochrome="False" UriSource="{ThemeResource ImageVideoFilename}"/>
|
||||
</ctc:SettingsCard.HeaderIcon>
|
||||
<TextBox Text="{Binding Task.Filename, Mode=TwoWay}"/>
|
||||
</ctc:SettingsCard>
|
||||
<ctc:SettingsCard Header="File type"
|
||||
Description="If original video is not in selected type, it will be converted">
|
||||
<ctc:SettingsCard.HeaderIcon>
|
||||
<BitmapIcon ShowAsMonochrome="False" UriSource="{ThemeResource ImageVideoExtension}"/>
|
||||
</ctc:SettingsCard.HeaderIcon>
|
||||
<ComboBox>
|
||||
<i:Interaction.Behaviors>
|
||||
<ic:DataTriggerBehavior Binding="{Binding Task.MediaType, Converter={StaticResource EnumToStringConverter}}"
|
||||
ComparisonCondition="Equal"
|
||||
Value="OnlyAudio">
|
||||
<ic:ChangePropertyAction PropertyName="ItemsSource"
|
||||
Value="{ct:EnumValues Type=cmn:AudioExtension}"/>
|
||||
<ic:ChangePropertyAction PropertyName="SelectedItem"
|
||||
Value="{Binding Task.AudioExtension}"/>
|
||||
</ic:DataTriggerBehavior>
|
||||
<ic:DataTriggerBehavior Binding="{Binding Task.MediaType, Converter={StaticResource EnumToStringConverter}}"
|
||||
ComparisonCondition="NotEqual"
|
||||
Value="OnlyAudio">
|
||||
<ic:ChangePropertyAction PropertyName="ItemsSource"
|
||||
Value="{ct:EnumValues Type=cmn:VideoExtension}"/>
|
||||
<ic:ChangePropertyAction PropertyName="SelectedItem"
|
||||
Value="{Binding Task.VideoExtension}"/>
|
||||
</ic:DataTriggerBehavior>
|
||||
</i:Interaction.Behaviors>
|
||||
<ComboBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding}"/>
|
||||
</DataTemplate>
|
||||
</ComboBox.ItemTemplate>
|
||||
</ComboBox>
|
||||
</ctc:SettingsCard>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
<Button Grid.Row="2"
|
||||
HorizontalAlignment="Right"
|
||||
Style="{StaticResource AccentButtonStyle}"
|
||||
Content="Create download task"
|
||||
Command="{Binding CreateTaskCommand}"/>
|
||||
</Grid>
|
||||
</ctuc:Case>
|
||||
</ctuc:SwitchPresenter>
|
||||
<Grid Grid.Row="1"
|
||||
Background="{ThemeResource OptionBarBackgroundColor}"
|
||||
CornerRadius="10">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition/>
|
||||
<ColumnDefinition Width="50"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<ctuc:UniformGrid Grid.Column="0"
|
||||
Rows="1"
|
||||
Margin="15,0,0,0">
|
||||
<ctuc:UniformGrid.RowDefinitions>
|
||||
<RowDefinition/>
|
||||
</ctuc:UniformGrid.RowDefinitions>
|
||||
<ctuc:UniformGrid.ColumnDefinitions>
|
||||
<ColumnDefinition/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</ctuc:UniformGrid.ColumnDefinitions>
|
||||
<ctuc:SwitchPresenter Grid.Row="0"
|
||||
VerticalAlignment="Stretch"
|
||||
Margin="0,0,15,0"
|
||||
Value="{Binding OptionBarContent, Converter={StaticResource EnumToStringConverter}}">
|
||||
<i:Interaction.Behaviors>
|
||||
<ic:DataTriggerBehavior Binding="{Binding OptionBarContent, Converter={StaticResource EnumToStringConverter}}"
|
||||
ComparisonCondition="Equal"
|
||||
Value="None">
|
||||
<ic:ChangePropertyAction PropertyName="Visibility"
|
||||
Value="Collapsed"/>
|
||||
</ic:DataTriggerBehavior>
|
||||
<ic:DataTriggerBehavior Binding="{Binding OptionBarContent, Converter={StaticResource EnumToStringConverter}}"
|
||||
ComparisonCondition="NotEqual"
|
||||
Value="None">
|
||||
<ic:ChangePropertyAction PropertyName="Visibility"
|
||||
Value="Visible"/>
|
||||
</ic:DataTriggerBehavior>
|
||||
</i:Interaction.Behaviors>
|
||||
<ctuc:Case Value="VideoSearch">
|
||||
<Grid ColumnSpacing="10"
|
||||
VerticalAlignment="Stretch">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBox Grid.Column="0"
|
||||
PlaceholderText="Video URL"
|
||||
VerticalAlignment="Center"
|
||||
Text="{Binding OptionBarVideoSearchTBValue, Mode=TwoWay}"/>
|
||||
<Button Grid.Column="1"
|
||||
Content="Search"
|
||||
IsEnabled="{Binding OptionBarSearchNotPending}"
|
||||
Command="{Binding VideoSearchStartCommand}"/>
|
||||
</Grid>
|
||||
</ctuc:Case>
|
||||
<ctuc:Case Value="PlaylistSearch">
|
||||
<Grid ColumnSpacing="10">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBox Grid.Column="0"
|
||||
VerticalAlignment="Center"
|
||||
PlaceholderText="Playlist URL"
|
||||
Text="{Binding OptionBarPlaylistSearchTBValue, Mode=TwoWay}"/>
|
||||
<NumberBox Grid.Column="1"
|
||||
VerticalAlignment="Center"
|
||||
SpinButtonPlacementMode="Compact"
|
||||
SmallChange="1"
|
||||
LargeChange="10"
|
||||
Value="{Binding OptionBarPlaylistSearchNBValue, Mode=TwoWay}"
|
||||
Minimum="1"
|
||||
ToolTipService.ToolTip="Number of videos to get from playlist"/>
|
||||
<Button Grid.Column="2"
|
||||
Content="Search"
|
||||
IsEnabled="{Binding OptionBarSearchNotPending}"
|
||||
Command="{Binding PlaylistSearchStartCommand}"/>
|
||||
</Grid>
|
||||
</ctuc:Case>
|
||||
</ctuc:SwitchPresenter>
|
||||
<StackPanel VerticalAlignment="Center"
|
||||
Orientation="Horizontal">
|
||||
<ProgressRing Width="20"
|
||||
Height="20"
|
||||
Margin="0,0,10,0"
|
||||
Visibility="{Binding OptionBarLoading, Converter={StaticResource BooleanToVisibilityConverter}}"/>
|
||||
<TextBlock Text="{Binding OptionBarMessage}"/>
|
||||
</StackPanel>
|
||||
</ctuc:UniformGrid>
|
||||
<StackPanel Grid.Column="2"
|
||||
Orientation="Horizontal">
|
||||
<AppBarButton Width="150"
|
||||
Label="Load from subscriptions"
|
||||
Icon="Favorite"
|
||||
IsEnabled="{Binding OptionBarSearchNotPending}"
|
||||
Command="{Binding LoadFromSubscriptionCommand}"/>
|
||||
<AppBarToggleButton Label="Video search"
|
||||
Width="100"
|
||||
Icon="Video"
|
||||
IsEnabled="{Binding OptionBarSearchNotPending}"
|
||||
IsChecked="{Binding OptionBarVideoSearchButtonChecked, Mode=TwoWay}"
|
||||
Command="{Binding VideoSearchShowCommand}"/>
|
||||
<AppBarToggleButton Label="Playlist search"
|
||||
Width="100"
|
||||
Icon="List"
|
||||
IsEnabled="{Binding OptionBarSearchNotPending}"
|
||||
IsChecked="{Binding OptionBarPlaylistSearchButtonChecked, Mode=TwoWay}"
|
||||
Command="{Binding PlaylistSearchShowCommand}"/>
|
||||
<AppBarSeparator/>
|
||||
<AppBarButton Width="100"
|
||||
Label="Download all"
|
||||
Icon="Download"
|
||||
Command="{Binding DownloadCommand}"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Page>
|
||||
31
VDownload.GUI/VDownload.GUI.Views/HomeView.xaml.cs
Normal file
31
VDownload.GUI/VDownload.GUI.Views/HomeView.xaml.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Controls.Primitives;
|
||||
using Microsoft.UI.Xaml.Data;
|
||||
using Microsoft.UI.Xaml.Input;
|
||||
using Microsoft.UI.Xaml.Media;
|
||||
using Microsoft.UI.Xaml.Navigation;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices.WindowsRuntime;
|
||||
using VDownload.GUI.ViewModels;
|
||||
using Windows.Foundation;
|
||||
using Windows.Foundation.Collections;
|
||||
|
||||
namespace VDownload.GUI.Views
|
||||
{
|
||||
public sealed partial class HomeView : Page
|
||||
{
|
||||
#region CONSTRUCTORS
|
||||
|
||||
public HomeView(HomeViewModel viewModel)
|
||||
{
|
||||
this.InitializeComponent();
|
||||
this.DataContext = viewModel;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,15 @@
|
||||
<UserControl
|
||||
x:Class="VDownload.Controls.PlaceholderableStackPanel"
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Page
|
||||
x:Class="VDownload.GUI.Views.SettingsView"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="using:VDownload.Controls"
|
||||
xmlns:local="using:VDownload.GUI.Views"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="300"
|
||||
d:DesignWidth="400">
|
||||
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
|
||||
|
||||
<StackPanel x:Name="StackPanel"/>
|
||||
</UserControl>
|
||||
<Grid>
|
||||
<TextBlock Text="Settings"/>
|
||||
</Grid>
|
||||
</Page>
|
||||
31
VDownload.GUI/VDownload.GUI.Views/SettingsView.xaml.cs
Normal file
31
VDownload.GUI/VDownload.GUI.Views/SettingsView.xaml.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Controls.Primitives;
|
||||
using Microsoft.UI.Xaml.Data;
|
||||
using Microsoft.UI.Xaml.Input;
|
||||
using Microsoft.UI.Xaml.Media;
|
||||
using Microsoft.UI.Xaml.Navigation;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices.WindowsRuntime;
|
||||
using VDownload.GUI.ViewModels;
|
||||
using Windows.Foundation;
|
||||
using Windows.Foundation.Collections;
|
||||
|
||||
namespace VDownload.GUI.Views
|
||||
{
|
||||
public sealed partial class SettingsView : Page
|
||||
{
|
||||
#region CONSTRUCTORS
|
||||
|
||||
public SettingsView(SettingsViewModel viewModel)
|
||||
{
|
||||
this.InitializeComponent();
|
||||
this.DataContext = viewModel;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
50
VDownload.GUI/VDownload.GUI.Views/VDownload.GUI.Views.csproj
Normal file
50
VDownload.GUI/VDownload.GUI.Views/VDownload.GUI.Views.csproj
Normal file
@@ -0,0 +1,50 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0-windows10.0.19041.0</TargetFramework>
|
||||
<TargetPlatformMinVersion>10.0.17763.0</TargetPlatformMinVersion>
|
||||
<RootNamespace>VDownload.GUI.Views</RootNamespace>
|
||||
<RuntimeIdentifiers>win10-x86;win10-x64;win10-arm64</RuntimeIdentifiers>
|
||||
<UseWinUI>true</UseWinUI>
|
||||
<UseRidGraph>true</UseRidGraph>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<None Remove="AuthenticationView.xaml" />
|
||||
<None Remove="HomeView.xaml" />
|
||||
<None Remove="SettingsView.xaml" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.2.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.231219000" />
|
||||
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.22621.2428" />
|
||||
<PackageReference Include="Microsoft.Xaml.Behaviors.WinUI.Managed" Version="2.0.9" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\VDownload.GUI.Controls\VDownload.GUI.Controls.csproj" />
|
||||
<ProjectReference Include="..\VDownload.GUI.Customs\VDownload.GUI.Customs.csproj" />
|
||||
<ProjectReference Include="..\VDownload.GUI.ViewModels\VDownload.GUI.ViewModels.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Page Update="AuthenticationView.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Page Update="SettingsView.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Page Update="HomeView.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,30 @@
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace VDownload.Services.Authentication
|
||||
{
|
||||
public class AuthenticationConfiguration
|
||||
{
|
||||
#region PROPERTIES
|
||||
|
||||
public string FilePath { get; private set; }
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region CONSTRUCTORS
|
||||
|
||||
public AuthenticationConfiguration(IConfiguration configuration)
|
||||
{
|
||||
IConfigurationSection section = configuration.GetSection("authentication");
|
||||
FilePath = section["file_path"];
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user