1.0-dev15 (Core code cleaning)

This commit is contained in:
2022-03-07 14:59:11 +01:00
Unverified
parent d32c23f493
commit 51389c4df1
45 changed files with 802 additions and 1090 deletions

BIN
.github/Images/Home.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 217 KiB

View File

@@ -1,6 +1,7 @@
# VDownload # VDownload
VDownload is universal video downloader written in .NET/C# and Universal Windows Platform. VDownload is universal video downloader written in .NET/C# and Universal Windows Platform.
![VDownload Home Page](.github/Images/Home.png)
## Requirements ## Requirements

View File

@@ -1,9 +0,0 @@
namespace VDownload.Core.Enums
{
public enum StreamType
{
AudioVideo,
OnlyAudio,
OnlyVideo,
}
}

View File

@@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace VDownload.Core.Enums
{
public enum TaskAddingRequestSource
{
Video,
Playlist
}
}

View File

@@ -1,23 +0,0 @@
using System;
using VDownload.Core.Enums;
using VDownload.Core.Interfaces;
using VDownload.Core.Objects;
using Windows.Storage;
namespace VDownload.Core.EventArgs
{
public class PlaylistAddEventArgs : System.EventArgs
{
public (
IVideoService VideoService,
MediaType MediaType,
IBaseStream Stream,
TimeSpan TrimStart,
TimeSpan TrimEnd,
string Filename,
MediaFileExtension Extension,
StorageFolder Location,
double Schedule
)[] Videos { get; set; }
}
}

View File

@@ -2,7 +2,7 @@
{ {
public class PlaylistSearchEventArgs : System.EventArgs public class PlaylistSearchEventArgs : System.EventArgs
{ {
public string Phrase { get; set; } public string Url { get; set; }
public int Count { get; set; } public int VideosCount { get; set; }
} }
} }

View File

@@ -0,0 +1,24 @@
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; set; }
public bool IsCompleted { get; set; }
#endregion
}
}

View File

@@ -0,0 +1,11 @@
using VDownload.Core.Enums;
using VDownload.Core.Structs;
namespace VDownload.Core.EventArgs
{
public class TasksAddingRequestedEventArgs : System.EventArgs
{
public TaskData[] TaskData { get; set; }
public TaskAddingRequestSource RequestSource { get; set; }
}
}

View File

@@ -2,6 +2,6 @@
{ {
public class VideoSearchEventArgs : System.EventArgs public class VideoSearchEventArgs : System.EventArgs
{ {
public string Phrase { get; set; } public string Url { get; set; }
} }
} }

View File

@@ -1,22 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using VDownload.Core.Enums;
namespace VDownload.Core.Interfaces
{
public interface IBaseStream
{
#region PROPERTIES
Uri Url { get; }
bool IsChunked { get; }
StreamType StreamType { get; }
int Height { get; }
int FrameRate { get; }
#endregion
}
}

View File

@@ -3,6 +3,7 @@ using System.ComponentModel;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using VDownload.Core.Enums; using VDownload.Core.Enums;
using VDownload.Core.Structs;
using Windows.Storage; using Windows.Storage;
namespace VDownload.Core.Interfaces namespace VDownload.Core.Interfaces
@@ -14,13 +15,8 @@ namespace VDownload.Core.Interfaces
// VIDEO PROPERTIES // VIDEO PROPERTIES
string ID { get; } string ID { get; }
Uri VideoUrl { get; } Uri VideoUrl { get; }
string Title { get; } Metadata Metadata { get; }
string Author { get; } BaseStream[] BaseStreams { get; }
DateTime Date { get; }
TimeSpan Duration { get; }
long Views { get; }
Uri Thumbnail { get; }
IBaseStream[] BaseStreams { get; }
#endregion #endregion
@@ -35,7 +31,7 @@ namespace VDownload.Core.Interfaces
Task GetStreamsAsync(CancellationToken cancellationToken = default); Task GetStreamsAsync(CancellationToken cancellationToken = default);
// DOWNLOAD VIDEO // DOWNLOAD VIDEO
Task<StorageFile> DownloadAndTranscodeAsync(StorageFolder downloadingFolder, IBaseStream baseStream, MediaFileExtension extension, MediaType mediaType, TimeSpan trimStart, TimeSpan trimEnd, CancellationToken cancellationToken = default); Task<StorageFile> DownloadAndTranscodeAsync(StorageFolder downloadingFolder, BaseStream baseStream, MediaFileExtension extension, MediaType mediaType, TimeSpan trimStart, TimeSpan trimEnd, CancellationToken cancellationToken = default);
#endregion #endregion
@@ -43,12 +39,8 @@ namespace VDownload.Core.Interfaces
#region EVENT HANDLERS #region EVENT HANDLERS
event EventHandler DownloadingStarted; event EventHandler<EventArgs.ProgressChangedEventArgs> DownloadingProgressChanged;
event EventHandler<ProgressChangedEventArgs> DownloadingProgressChanged; event EventHandler<EventArgs.ProgressChangedEventArgs> ProcessingProgressChanged;
event EventHandler DownloadingCompleted;
event EventHandler ProcessingStarted;
event EventHandler<ProgressChangedEventArgs> ProcessingProgressChanged;
event EventHandler ProcessingCompleted;
#endregion #endregion
} }

View File

@@ -1,36 +0,0 @@
using System;
using VDownload.Core.Enums;
using VDownload.Core.Interfaces;
namespace VDownload.Core.Objects
{
public class Stream : IBaseStream
{
#region CONSTRUCTORS
public Stream(Uri url, bool isChunked, StreamType streamType)
{
Url = url;
IsChunked = isChunked;
StreamType = streamType;
}
#endregion
#region PROPERTIES
public Uri Url { get; private set; }
public bool IsChunked { get; private set; }
public StreamType StreamType { get; private set; }
public int Width { get; set; }
public int Height { get; set; }
public int FrameRate { get; set; }
public string VideoCodec { get; set; }
public int AudioBitrate { get; set; }
public string AudioCodec { get; set; }
#endregion
}
}

View File

@@ -1,6 +1,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using VDownload.Core.Enums; using VDownload.Core.Enums;
using Windows.Media.Editing; using Windows.Media.Editing;
using Windows.Media.Transcoding;
using Windows.Storage; using Windows.Storage;
namespace VDownload.Core.Services namespace VDownload.Core.Services
@@ -21,7 +22,7 @@ namespace VDownload.Core.Services
{ "twitch_vod_downloading_chunk_max_retries", 10 }, { "twitch_vod_downloading_chunk_max_retries", 10 },
{ "twitch_vod_downloading_chunk_retries_delay", 5000 }, { "twitch_vod_downloading_chunk_retries_delay", 5000 },
{ "media_transcoding_use_hardware_acceleration", true }, { "media_transcoding_use_hardware_acceleration", true },
{ "media_transcoding_use_mrfcrf444_algorithm", true }, { "media_transcoding_algorithm", (int)MediaVideoProcessingAlgorithm.MrfCrf444 },
{ "media_editing_algorithm", (int)MediaTrimmingPreference.Fast }, { "media_editing_algorithm", (int)MediaTrimmingPreference.Fast },
{ "default_max_playlist_videos", 0 }, { "default_max_playlist_videos", 0 },
{ "default_media_type", (int)MediaType.AudioVideo }, { "default_media_type", (int)MediaType.AudioVideo },

View File

@@ -1,5 +1,4 @@
using System; using System;
using System.ComponentModel;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
@@ -15,66 +14,50 @@ namespace VDownload.Core.Services
{ {
public class MediaProcessor public class MediaProcessor
{ {
#region CONSTRUCTORS
public MediaProcessor(StorageFile outputFile, TimeSpan trimStart, TimeSpan trimEnd)
{
OutputFile = outputFile;
TrimStart = trimStart;
TrimEnd = trimEnd;
}
#endregion
#region PROPERTIES
public StorageFile OutputFile { get; private set; }
public TimeSpan TrimStart { get; private set; }
public TimeSpan TrimEnd { get; private set; }
#endregion
#region STANDARD METHODS #region STANDARD METHODS
// SINGLE AUDIO & VIDEO FILE PROCESSING // SINGLE AUDIO & VIDEO FILE PROCESSING
public async Task Run(StorageFile audioVideoInputFile, MediaFileExtension extension, MediaType mediaType, CancellationToken cancellationToken = default) public async Task Run(StorageFile mediaFile, MediaFileExtension extension, MediaType mediaType, StorageFile outputFile, TimeSpan? trimStart = null, TimeSpan? trimEnd = null, CancellationToken cancellationToken = default)
{ {
// Invoke ProcessingStarted event // Invoke event at start
ProcessingStarted?.Invoke(this, System.EventArgs.Empty); cancellationToken.ThrowIfCancellationRequested();
ProgressChanged(this, new EventArgs.ProgressChangedEventArgs(0));
// Init transcoder // Init transcoder
MediaTranscoder mediaTranscoder = new MediaTranscoder MediaTranscoder mediaTranscoder = new MediaTranscoder
{ {
HardwareAccelerationEnabled = (bool)Config.GetValue("media_transcoding_use_hardware_acceleration"), HardwareAccelerationEnabled = (bool)Config.GetValue("media_transcoding_use_hardware_acceleration"),
VideoProcessingAlgorithm = (bool)Config.GetValue("media_transcoding_use_mrfcrf444_algorithm") ? MediaVideoProcessingAlgorithm.MrfCrf444 : MediaVideoProcessingAlgorithm.Default, VideoProcessingAlgorithm = (MediaVideoProcessingAlgorithm)Config.GetValue("media_transcoding_algorithm"),
TrimStartTime = TrimStart,
TrimStopTime = TrimEnd,
}; };
if (trimStart != null) mediaTranscoder.TrimStartTime = (TimeSpan)trimStart;
if (trimEnd != null) mediaTranscoder.TrimStopTime = (TimeSpan)trimEnd;
// Start transcoding operation // Start transcoding operation
cancellationToken.ThrowIfCancellationRequested(); using (IRandomAccessStream openedOutputFile = await outputFile.OpenAsync(FileAccessMode.ReadWrite))
using (IRandomAccessStream outputFileOpened = await OutputFile.OpenAsync(FileAccessMode.ReadWrite))
{ {
PrepareTranscodeResult transcodingPreparated = await mediaTranscoder.PrepareStreamTranscodeAsync(await audioVideoInputFile.OpenAsync(FileAccessMode.Read), outputFileOpened, await GetMediaEncodingProfile(audioVideoInputFile, extension, mediaType)); // Prepare transcode task
PrepareTranscodeResult transcodingPreparated = await mediaTranscoder.PrepareStreamTranscodeAsync(await mediaFile.OpenAsync(FileAccessMode.Read), openedOutputFile, await GetMediaEncodingProfile(mediaFile, extension, mediaType));
// Start transcoding
IAsyncActionWithProgress<double> transcodingTask = transcodingPreparated.TranscodeAsync(); IAsyncActionWithProgress<double> transcodingTask = transcodingPreparated.TranscodeAsync();
await transcodingTask.AsTask(cancellationToken, new Progress<double>((percent) => { ProcessingProgressChanged(this, new ProgressChangedEventArgs((int)Math.Round(percent), null)); })); await transcodingTask.AsTask(cancellationToken, new Progress<double>((percent) => { ProgressChanged(this, new EventArgs.ProgressChangedEventArgs(percent)); }));
await outputFileOpened.FlushAsync(); cancellationToken.ThrowIfCancellationRequested();
// Finalizing
await openedOutputFile.FlushAsync();
transcodingTask.Close(); transcodingTask.Close();
} }
// Invoke ProcessingCompleted event // Invoke event at end
ProcessingCompleted?.Invoke(this, System.EventArgs.Empty); ProgressChanged(this, new EventArgs.ProgressChangedEventArgs(100, true));
} }
// SEPARATE AUDIO & VIDEO FILES PROCESSING // SEPARATE AUDIO & VIDEO FILES PROCESSING
public async Task Run(StorageFile audioFile, StorageFile videoFile, VideoFileExtension extension, CancellationToken cancellationToken = default) public async Task Run(StorageFile audioFile, StorageFile videoFile, VideoFileExtension extension, StorageFile outputFile, TimeSpan? trimStart = null, TimeSpan? trimEnd = null, CancellationToken cancellationToken = default)
{ {
// Invoke ProcessingStarted event // Invoke event at start
ProcessingStarted?.Invoke(this, System.EventArgs.Empty); cancellationToken.ThrowIfCancellationRequested();
ProgressChanged(this, new EventArgs.ProgressChangedEventArgs(0));
// Init editor // Init editor
MediaComposition mediaEditor = new MediaComposition(); MediaComposition mediaEditor = new MediaComposition();
@@ -86,28 +69,36 @@ namespace VDownload.Core.Services
await Task.WhenAll(getVideoFileTask, getAudioFileTask); await Task.WhenAll(getVideoFileTask, getAudioFileTask);
MediaClip videoElement = getVideoFileTask.Result; MediaClip videoElement = getVideoFileTask.Result;
videoElement.TrimTimeFromStart = TrimStart; if (trimStart != null) videoElement.TrimTimeFromStart = (TimeSpan)trimStart;
videoElement.TrimTimeFromEnd = TrimEnd; if (trimEnd != null) videoElement.TrimTimeFromEnd = (TimeSpan)trimEnd;
BackgroundAudioTrack audioElement = getAudioFileTask.Result; BackgroundAudioTrack audioElement = getAudioFileTask.Result;
audioElement.TrimTimeFromStart = TrimStart; if (trimStart != null) audioElement.TrimTimeFromStart = (TimeSpan)trimStart;
audioElement.TrimTimeFromEnd = TrimEnd; if (trimEnd != null) audioElement.TrimTimeFromEnd = (TimeSpan)trimEnd;
mediaEditor.Clips.Add(getVideoFileTask.Result); mediaEditor.Clips.Add(videoElement);
mediaEditor.BackgroundAudioTracks.Add(getAudioFileTask.Result); mediaEditor.BackgroundAudioTracks.Add(audioElement);
// Start rendering operation // Start rendering operation
var renderOperation = mediaEditor.RenderToFileAsync(OutputFile, (MediaTrimmingPreference)Config.GetValue("media_editing_algorithm"), await GetMediaEncodingProfile(videoFile, audioFile, (MediaFileExtension)extension, MediaType.AudioVideo)); var renderOperation = mediaEditor.RenderToFileAsync(outputFile, (MediaTrimmingPreference)Config.GetValue("media_editing_algorithm"), await GetMediaEncodingProfile(videoFile, audioFile, (MediaFileExtension)extension, MediaType.AudioVideo));
renderOperation.Progress += (info, progress) => { ProcessingProgressChanged(this, new ProgressChangedEventArgs((int)Math.Round(progress), null)); }; renderOperation.Progress += (info, progress) => { ProgressChanged(this, new EventArgs.ProgressChangedEventArgs(progress)); };
cancellationToken.ThrowIfCancellationRequested(); cancellationToken.ThrowIfCancellationRequested();
await renderOperation.AsTask(cancellationToken); await renderOperation.AsTask(cancellationToken);
// Invoke ProcessingCompleted event // Invoke event at end
ProcessingCompleted?.Invoke(this, System.EventArgs.Empty); ProgressChanged(this, new EventArgs.ProgressChangedEventArgs(100, true));
} }
// SINGLE AUDIO OR VIDEO FILES PROCESSING // AUDIO FILE PROCESSING
public async Task Run(StorageFile audioFile, AudioFileExtension extension, CancellationToken cancellationToken = default) { await Run(audioFile, (MediaFileExtension)extension, MediaType.OnlyAudio, cancellationToken); } public async Task Run(StorageFile audioFile, AudioFileExtension extension, StorageFile outputFile, TimeSpan? trimStart = null, TimeSpan? trimEnd = null, CancellationToken cancellationToken = default)
public async Task Run(StorageFile videoFile, VideoFileExtension extension, CancellationToken cancellationToken = default) { await Run(videoFile, (MediaFileExtension)extension, MediaType.OnlyVideo, cancellationToken); } {
await Run(audioFile, (MediaFileExtension)extension, MediaType.OnlyAudio, outputFile, trimStart, trimEnd, cancellationToken);
}
// VIDEO FILE PROCESSING
public async Task Run(StorageFile videoFile, VideoFileExtension extension, StorageFile outputFile, TimeSpan? trimStart = null, TimeSpan? trimEnd = null, CancellationToken cancellationToken = default)
{
await Run(videoFile, (MediaFileExtension)extension, MediaType.OnlyVideo, outputFile, trimStart, trimEnd, cancellationToken);
}
#endregion #endregion
@@ -116,7 +107,7 @@ namespace VDownload.Core.Services
#region LOCAL METHODS #region LOCAL METHODS
// GET ENCODING PROFILE // GET ENCODING PROFILE
public static async Task<MediaEncodingProfile> GetMediaEncodingProfile(StorageFile videoFile, StorageFile audioFile, MediaFileExtension extension, MediaType mediaType) private static async Task<MediaEncodingProfile> GetMediaEncodingProfile(StorageFile videoFile, StorageFile audioFile, MediaFileExtension extension, MediaType mediaType)
{ {
// Create profile object // Create profile object
MediaEncodingProfile profile; MediaEncodingProfile profile;
@@ -164,7 +155,10 @@ namespace VDownload.Core.Services
// Return profile // Return profile
return profile; return profile;
} }
public static async Task<MediaEncodingProfile> GetMediaEncodingProfile(StorageFile audioVideoFile, MediaFileExtension extension, MediaType mediaType) { return await GetMediaEncodingProfile(audioVideoFile, audioVideoFile, extension, mediaType); } private static async Task<MediaEncodingProfile> GetMediaEncodingProfile(StorageFile audioVideoFile, MediaFileExtension extension, MediaType mediaType)
{
return await GetMediaEncodingProfile(audioVideoFile, audioVideoFile, extension, mediaType);
}
#endregion #endregion
@@ -172,9 +166,7 @@ namespace VDownload.Core.Services
#region EVENT HANDLERS #region EVENT HANDLERS
public event EventHandler ProcessingStarted; public event EventHandler<EventArgs.ProgressChangedEventArgs> ProgressChanged;
public event EventHandler<ProgressChangedEventArgs> ProcessingProgressChanged;
public event EventHandler ProcessingCompleted;
#endregion #endregion
} }

View File

@@ -9,6 +9,7 @@ using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using VDownload.Core.Exceptions; using VDownload.Core.Exceptions;
using VDownload.Core.Interfaces; using VDownload.Core.Interfaces;
using VDownload.Core.Services.Sources.Twitch.Helpers;
namespace VDownload.Core.Services.Sources.Twitch namespace VDownload.Core.Services.Sources.Twitch
{ {
@@ -41,25 +42,15 @@ namespace VDownload.Core.Services.Sources.Twitch
// GET CHANNEL METADATA // GET CHANNEL METADATA
public async Task GetMetadataAsync(CancellationToken cancellationToken = default) public async Task GetMetadataAsync(CancellationToken cancellationToken = default)
{ {
// Get access token
cancellationToken.ThrowIfCancellationRequested(); cancellationToken.ThrowIfCancellationRequested();
string accessToken = await Auth.ReadAccessTokenAsync();
if (accessToken == null) throw new TwitchAccessTokenNotFoundException();
// Check access token
cancellationToken.ThrowIfCancellationRequested();
var twitchAccessTokenValidation = await Auth.ValidateAccessTokenAsync(accessToken);
if (!twitchAccessTokenValidation.IsValid) throw new TwitchAccessTokenNotValidException();
// Create client
WebClient client = new WebClient();
client.Headers.Add("Authorization", $"Bearer {accessToken}");
client.Headers.Add("Client-Id", Auth.ClientID);
// Get response // Get response
JToken response = null;
using (WebClient client = await Client.Helix())
{
client.QueryString.Add("login", ID); client.QueryString.Add("login", ID);
cancellationToken.ThrowIfCancellationRequested(); response = JObject.Parse(await client.DownloadStringTaskAsync("https://api.twitch.tv/helix/users"))["data"][0];
JToken response = JObject.Parse(await client.DownloadStringTaskAsync("https://api.twitch.tv/helix/users"))["data"][0]; }
// Create unified playlist url // Create unified playlist url
PlaylistUrl = new Uri($"https://twitch.tv/{ID}"); PlaylistUrl = new Uri($"https://twitch.tv/{ID}");
@@ -72,12 +63,9 @@ namespace VDownload.Core.Services.Sources.Twitch
// GET CHANNEL VIDEOS // GET CHANNEL VIDEOS
public async Task GetVideosAsync(int numberOfVideos, CancellationToken cancellationToken = default) public async Task GetVideosAsync(int numberOfVideos, CancellationToken cancellationToken = default)
{ {
// Get access token
cancellationToken.ThrowIfCancellationRequested(); cancellationToken.ThrowIfCancellationRequested();
string accessToken = await Auth.ReadAccessTokenAsync();
if (accessToken == null) throw new TwitchAccessTokenNotFoundException();
// Set pagination // Set page id
string pagination = ""; string pagination = "";
// Set array of videos // Set array of videos
@@ -92,30 +80,24 @@ namespace VDownload.Core.Services.Sources.Twitch
List<Task> getStreamsTasks = new List<Task>(); List<Task> getStreamsTasks = new List<Task>();
do do
{ {
// Check access token
cancellationToken.ThrowIfCancellationRequested();
var twitchAccessTokenValidation = await Auth.ValidateAccessTokenAsync(accessToken);
if (!twitchAccessTokenValidation.IsValid) throw new TwitchAccessTokenNotValidException();
// Create client
WebClient client = new WebClient();
client.Headers.Add("Authorization", $"Bearer {accessToken}");
client.Headers.Add("Client-Id", Auth.ClientID);
// Set number of videos to get in this iteration // Set number of videos to get in this iteration
count = numberOfVideos < 100 && !getAll ? numberOfVideos : 100; count = numberOfVideos < 100 && !getAll ? numberOfVideos : 100;
// Get response // Get response
JToken response = null;
using (WebClient client = await Client.Helix())
{
client.QueryString.Add("user_id", ID); client.QueryString.Add("user_id", ID);
client.QueryString.Add("first", count.ToString()); client.QueryString.Add("first", count.ToString());
client.QueryString.Add("after", pagination); client.QueryString.Add("after", pagination);
response = JObject.Parse(await client.DownloadStringTaskAsync("https://api.twitch.tv/helix/videos"));
}
cancellationToken.ThrowIfCancellationRequested(); // Set page id
JToken response = JObject.Parse(await client.DownloadStringTaskAsync("https://api.twitch.tv/helix/videos"));
pagination = (string)response["pagination"]["cursor"]; pagination = (string)response["pagination"]["cursor"];
videosData = response["data"].ToArray();
// Set videos data
videosData = response["data"].ToArray();
foreach (JToken videoData in videosData) foreach (JToken videoData in videosData)
{ {
Vod video = new Vod((string)videoData["id"]); Vod video = new Vod((string)videoData["id"]);
@@ -131,7 +113,7 @@ namespace VDownload.Core.Services.Sources.Twitch
// Wait for all getStreams tasks // Wait for all getStreams tasks
await Task.WhenAll(getStreamsTasks); await Task.WhenAll(getStreamsTasks);
// Set Videos parameter // Set videos
Videos = videos.ToArray(); Videos = videos.ToArray();
} }

View File

@@ -12,21 +12,14 @@ using System.Web;
using VDownload.Core.Enums; using VDownload.Core.Enums;
using VDownload.Core.Exceptions; using VDownload.Core.Exceptions;
using VDownload.Core.Interfaces; using VDownload.Core.Interfaces;
using VDownload.Core.Objects; using VDownload.Core.Services.Sources.Twitch.Helpers;
using VDownload.Core.Structs;
using Windows.Storage; using Windows.Storage;
namespace VDownload.Core.Services.Sources.Twitch namespace VDownload.Core.Services.Sources.Twitch
{ {
public class Clip : IVideoService public class Clip : IVideoService
{ {
#region CONSTANTS
#endregion
#region CONSTRUCTORS #region CONSTRUCTORS
public Clip(string id) public Clip(string id)
@@ -42,13 +35,8 @@ namespace VDownload.Core.Services.Sources.Twitch
public string ID { get; private set; } public string ID { get; private set; }
public Uri VideoUrl { get; private set; } public Uri VideoUrl { get; private set; }
public string Title { get; private set; } public Metadata Metadata { get; private set; }
public string Author { get; private set; } public BaseStream[] BaseStreams { 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 IBaseStream[] BaseStreams { get; private set; }
#endregion #endregion
@@ -59,92 +47,83 @@ namespace VDownload.Core.Services.Sources.Twitch
// GET CLIP METADATA // GET CLIP METADATA
public async Task GetMetadataAsync(CancellationToken cancellationToken = default) public async Task GetMetadataAsync(CancellationToken cancellationToken = default)
{ {
// Get access token
cancellationToken.ThrowIfCancellationRequested(); cancellationToken.ThrowIfCancellationRequested();
string accessToken = await Auth.ReadAccessTokenAsync();
if (accessToken == null) throw new TwitchAccessTokenNotFoundException();
// Check access token
cancellationToken.ThrowIfCancellationRequested();
var twitchAccessTokenValidation = await Auth.ValidateAccessTokenAsync(accessToken);
if (!twitchAccessTokenValidation.IsValid) throw new TwitchAccessTokenNotValidException();
// Create client
WebClient client = new WebClient();
client.Headers.Add("Authorization", $"Bearer {accessToken}");
client.Headers.Add("Client-Id", Auth.ClientID);
// Get response // Get response
JToken response = null;
using (WebClient client = await Client.Helix())
{
client.QueryString.Add("id", ID); client.QueryString.Add("id", ID);
JToken response = JObject.Parse(await client.DownloadStringTaskAsync("https://api.twitch.tv/helix/clips")).GetValue("data")[0]; response = JObject.Parse(await client.DownloadStringTaskAsync("https://api.twitch.tv/helix/clips")).GetValue("data")[0];
}
// Create unified video url // Create unified video url
VideoUrl = new Uri($"https://clips.twitch.tv/{ID}"); VideoUrl = new Uri($"https://clips.twitch.tv/{ID}");
// Set parameters // Set metadata
Title = (string)response["title"]; Metadata = new Metadata()
Author = (string)response["broadcaster_name"]; {
Date = Convert.ToDateTime(response["created_at"]); Title = (string)response["title"],
Duration = TimeSpan.FromSeconds((double)response["duration"]); Author = (string)response["broadcaster_name"],
Views = (long)response["view_count"]; Date = Convert.ToDateTime(response["created_at"]),
Thumbnail = new Uri((string)response["thumbnail_url"]); 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) public async Task GetStreamsAsync(CancellationToken cancellationToken = default)
{ {
// Create client
WebClient client = new WebClient { Encoding = Encoding.UTF8 };
client.Headers.Add("Client-ID", Auth.GQLApiClientID);
// Get video streams
cancellationToken.ThrowIfCancellationRequested(); cancellationToken.ThrowIfCancellationRequested();
JToken[] 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();
// Get response
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();
}
// Init streams list // Init streams list
List<Stream> streams = new List<Stream>(); List<BaseStream> streams = new List<BaseStream>();
// Parse response // Parse response
foreach (JToken streamData in response) foreach (JToken streamData in response)
{ {
// Get info
Uri url = new Uri((string)streamData["sourceURL"]);
int height = int.Parse((string)streamData["quality"]);
int frameRate = (int)streamData["frameRate"];
// Create stream // Create stream
Stream stream = new Stream(url, false, StreamType.AudioVideo) BaseStream stream = new BaseStream()
{ {
Height = height, Url = new Uri((string)streamData["sourceURL"]),
FrameRate = frameRate Height = int.Parse((string)streamData["quality"]),
FrameRate = (int)streamData["frameRate"],
}; };
// Add stream // Add stream
streams.Add(stream); streams.Add(stream);
} }
// Set Streams parameter // Set streams
BaseStreams = streams.ToArray(); BaseStreams = streams.ToArray();
} }
public async Task<StorageFile> DownloadAndTranscodeAsync(StorageFolder downloadingFolder, IBaseStream baseStream, MediaFileExtension extension, MediaType mediaType, TimeSpan trimStart, TimeSpan trimEnd, CancellationToken cancellationToken = default) public async Task<StorageFile> DownloadAndTranscodeAsync(StorageFolder downloadingFolder, BaseStream baseStream, MediaFileExtension extension, MediaType mediaType, TimeSpan trimStart, TimeSpan trimEnd, CancellationToken cancellationToken = default)
{ {
// Invoke DownloadingStarted event // Invoke DownloadingStarted event
DownloadingStarted?.Invoke(this, System.EventArgs.Empty); cancellationToken.ThrowIfCancellationRequested();
DownloadingProgressChanged(this, new EventArgs.ProgressChangedEventArgs(0));
// Create client
WebClient client = new WebClient();
client.Headers.Add("Client-Id", Auth.GQLApiClientID);
// Get video GQL access token // Get video GQL access token
cancellationToken.ThrowIfCancellationRequested(); JToken videoAccessToken = null;
JToken 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"]; 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 // Download
cancellationToken.ThrowIfCancellationRequested(); cancellationToken.ThrowIfCancellationRequested();
StorageFile rawFile = await downloadingFolder.CreateFileAsync("raw.mp4"); StorageFile rawFile = await downloadingFolder.CreateFileAsync("raw.mp4");
using (client = new WebClient()) using (WebClient client = new WebClient())
{ {
client.DownloadProgressChanged += (s, a) => { DownloadingProgressChanged(this, new ProgressChangedEventArgs(a.ProgressPercentage, null)); }; client.DownloadProgressChanged += (s, a) => { DownloadingProgressChanged(this, new EventArgs.ProgressChangedEventArgs(a.ProgressPercentage)); };
client.QueryString.Add("sig", (string)videoAccessToken["signature"]); client.QueryString.Add("sig", (string)videoAccessToken["signature"]);
client.QueryString.Add("token", HttpUtility.UrlEncode((string)videoAccessToken["value"])); client.QueryString.Add("token", HttpUtility.UrlEncode((string)videoAccessToken["value"]));
cancellationToken.ThrowIfCancellationRequested(); cancellationToken.ThrowIfCancellationRequested();
@@ -153,20 +132,24 @@ namespace VDownload.Core.Services.Sources.Twitch
await client.DownloadFileTaskAsync(baseStream.Url, rawFile.Path); await client.DownloadFileTaskAsync(baseStream.Url, rawFile.Path);
} }
} }
DownloadingCompleted?.Invoke(this, System.EventArgs.Empty); DownloadingProgressChanged(this, new EventArgs.ProgressChangedEventArgs(100, true));
// Processing // Processing
StorageFile outputFile = rawFile; StorageFile outputFile = rawFile;
if (extension != MediaFileExtension.MP4 || mediaType != MediaType.AudioVideo || trimStart > new TimeSpan(0) || trimEnd < Duration) if (extension != MediaFileExtension.MP4 || mediaType != MediaType.AudioVideo || trimStart != null || trimEnd != null)
{ {
cancellationToken.ThrowIfCancellationRequested(); cancellationToken.ThrowIfCancellationRequested();
outputFile = await downloadingFolder.CreateFileAsync($"transcoded.{extension.ToString().ToLower()}"); outputFile = await downloadingFolder.CreateFileAsync($"transcoded.{extension.ToString().ToLower()}");
MediaProcessor mediaProcessor = new MediaProcessor(outputFile, trimStart, trimEnd);
mediaProcessor.ProcessingStarted += ProcessingStarted; MediaProcessor mediaProcessor = new MediaProcessor();
mediaProcessor.ProcessingProgressChanged += ProcessingProgressChanged; mediaProcessor.ProgressChanged += ProcessingProgressChanged;
mediaProcessor.ProcessingCompleted += ProcessingCompleted;
cancellationToken.ThrowIfCancellationRequested(); Task mediaProcessorTask;
await mediaProcessor.Run(rawFile, extension, mediaType, cancellationToken); if (trimStart == TimeSpan.Zero && trimEnd == Metadata.Duration) mediaProcessorTask = mediaProcessor.Run(rawFile, extension, mediaType, outputFile, cancellationToken: cancellationToken);
else if (trimStart == TimeSpan.Zero) mediaProcessorTask = mediaProcessor.Run(rawFile, extension, mediaType, outputFile, trimStart: trimStart, cancellationToken: cancellationToken);
else if (trimEnd == Metadata.Duration) mediaProcessorTask = mediaProcessor.Run(rawFile, extension, mediaType, outputFile, trimEnd: trimEnd, cancellationToken: cancellationToken);
else mediaProcessorTask = mediaProcessor.Run(rawFile, extension, mediaType, outputFile, trimStart, trimEnd, cancellationToken);
await mediaProcessorTask;
} }
// Return output file // Return output file
@@ -179,12 +162,8 @@ namespace VDownload.Core.Services.Sources.Twitch
#region EVENT HANDLERS #region EVENT HANDLERS
public event EventHandler DownloadingStarted; public event EventHandler<EventArgs.ProgressChangedEventArgs> DownloadingProgressChanged;
public event EventHandler<ProgressChangedEventArgs> DownloadingProgressChanged; public event EventHandler<EventArgs.ProgressChangedEventArgs> ProcessingProgressChanged;
public event EventHandler DownloadingCompleted;
public event EventHandler ProcessingStarted;
public event EventHandler<ProgressChangedEventArgs> ProcessingProgressChanged;
public event EventHandler ProcessingCompleted;
#endregion #endregion
} }

View File

@@ -1,16 +1,12 @@
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using System; using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO; using System.IO;
using System.Linq;
using System.Net; using System.Net;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using Windows.Storage; using Windows.Storage;
using Windows.Storage.Streams;
namespace VDownload.Core.Services.Sources.Twitch namespace VDownload.Core.Services.Sources.Twitch.Helpers
{ {
public class Auth public class Auth
{ {

View File

@@ -0,0 +1,38 @@
using System.Net;
using System.Threading.Tasks;
using VDownload.Core.Exceptions;
namespace VDownload.Core.Services.Sources.Twitch.Helpers
{
internal class Client
{
internal static async Task<WebClient> Helix()
{
// Get access token
string accessToken = await Auth.ReadAccessTokenAsync();
if (accessToken == null) throw new TwitchAccessTokenNotFoundException();
// Check access token
var twitchAccessTokenValidation = await Auth.ValidateAccessTokenAsync(accessToken);
if (!twitchAccessTokenValidation.IsValid) throw new TwitchAccessTokenNotValidException();
// Create client
WebClient client = new WebClient();
client.Headers.Add("Authorization", $"Bearer {accessToken}");
client.Headers.Add("Client-Id", Auth.ClientID);
// Return client
return client;
}
internal static WebClient GQL()
{
// Create client
WebClient client = new WebClient();
client.Headers.Add("Client-Id", Auth.GQLApiClientID);
// Return client
return client;
}
}
}

View File

@@ -1,7 +1,6 @@
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics; using System.Diagnostics;
using System.Linq; using System.Linq;
using System.Net; using System.Net;
@@ -9,27 +8,15 @@ using System.Text.RegularExpressions;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using VDownload.Core.Enums; using VDownload.Core.Enums;
using VDownload.Core.Exceptions;
using VDownload.Core.Interfaces; using VDownload.Core.Interfaces;
using VDownload.Core.Objects; using VDownload.Core.Services.Sources.Twitch.Helpers;
using VDownload.Core.Structs;
using Windows.Storage; using Windows.Storage;
namespace VDownload.Core.Services.Sources.Twitch namespace VDownload.Core.Services.Sources.Twitch
{ {
public class Vod : IVideoService public class Vod : IVideoService
{ {
#region CONSTANTS
// STREAMS RESPONSE REGULAR EXPRESSIONS
private static readonly Regex L2Regex = new Regex(@"^#EXT-X-STREAM-INF:BANDWIDTH=\d+,CODECS=""(?<video_codec>\S+),(?<audio_codec>\S+)"",RESOLUTION=(?<width>\d+)x(?<height>\d+),VIDEO=""\w+""(,FRAME-RATE=(?<frame_rate>\d+.\d+))?");
// CHUNK RESPONSE REGULAR EXPRESSION
private static readonly Regex ChunkRegex = new Regex(@"#EXTINF:(?<duration>\d+.\d+),\n(?<filename>\S+.ts)");
#endregion
#region CONSTRUCTORS #region CONSTRUCTORS
public Vod(string id) public Vod(string id)
@@ -45,13 +32,8 @@ namespace VDownload.Core.Services.Sources.Twitch
public string ID { get; private set; } public string ID { get; private set; }
public Uri VideoUrl { get; private set; } public Uri VideoUrl { get; private set; }
public string Title { get; private set; } public Metadata Metadata { get; private set; }
public string Author { get; private set; } public BaseStream[] BaseStreams { 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 IBaseStream[] BaseStreams { get; private set; }
#endregion #endregion
@@ -62,25 +44,16 @@ namespace VDownload.Core.Services.Sources.Twitch
// GET VOD METADATA // GET VOD METADATA
public async Task GetMetadataAsync(CancellationToken cancellationToken = default) public async Task GetMetadataAsync(CancellationToken cancellationToken = default)
{ {
// Get access token
cancellationToken.ThrowIfCancellationRequested(); cancellationToken.ThrowIfCancellationRequested();
string accessToken = await Auth.ReadAccessTokenAsync();
if (accessToken == null) throw new TwitchAccessTokenNotFoundException();
// Check access token
cancellationToken.ThrowIfCancellationRequested();
var twitchAccessTokenValidation = await Auth.ValidateAccessTokenAsync(accessToken);
if (!twitchAccessTokenValidation.IsValid) throw new TwitchAccessTokenNotValidException();
// Create client
WebClient client = new WebClient();
client.Headers.Add("Authorization", $"Bearer {accessToken}");
client.Headers.Add("Client-Id", Auth.ClientID);
// Get response // Get response
JToken response = null;
using (WebClient client = await Client.Helix())
{
client.QueryString.Add("id", ID); client.QueryString.Add("id", ID);
cancellationToken.ThrowIfCancellationRequested(); cancellationToken.ThrowIfCancellationRequested();
JToken response = JObject.Parse(await client.DownloadStringTaskAsync("https://api.twitch.tv/helix/videos")).GetValue("data")[0]; response = JObject.Parse(await client.DownloadStringTaskAsync("https://api.twitch.tv/helix/videos")).GetValue("data")[0];
}
// Set parameters // Set parameters
GetMetadataAsync(response); GetMetadataAsync(response);
@@ -90,83 +63,86 @@ namespace VDownload.Core.Services.Sources.Twitch
// Create unified video url // Create unified video url
VideoUrl = new Uri($"https://www.twitch.tv/videos/{ID}"); VideoUrl = new Uri($"https://www.twitch.tv/videos/{ID}");
// Set parameters // Set metadata
Title = ((string)response["title"]).Replace("\n", ""); Metadata = new Metadata()
Author = (string)response["user_name"]; {
Date = Convert.ToDateTime(response["created_at"]); Title = ((string)response["title"]).Replace("\n", ""),
Duration = ParseDuration((string)response["duration"]); Author = (string)response["user_name"],
Views = (long)response["view_count"]; Date = Convert.ToDateTime(response["created_at"]),
Thumbnail = (string)response["thumbnail_url"] == string.Empty ? null : new Uri(((string)response["thumbnail_url"]).Replace("%{width}", "1920").Replace("%{height}", "1080")); 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")),
};
} }
// GET VOD STREAMS // GET VOD STREAMS
public async Task GetStreamsAsync(CancellationToken cancellationToken = default) public async Task GetStreamsAsync(CancellationToken cancellationToken = default)
{ {
// Create client cancellationToken.ThrowIfCancellationRequested();
WebClient client = new WebClient();
client.Headers.Add("Client-Id", Auth.GQLApiClientID);
// Get response
string[] response = null;
using (WebClient client = Client.GQL())
{
// Get video GQL access token // Get video GQL access token
cancellationToken.ThrowIfCancellationRequested(); 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"]; 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"];
// Get video streams // Get video streams
cancellationToken.ThrowIfCancellationRequested(); cancellationToken.ThrowIfCancellationRequested();
string[] response = (await client.DownloadStringTaskAsync($"http://usher.twitch.tv/vod/{ID}?nauth={videoAccessToken["value"]}&nauthsig={videoAccessToken["signature"]}&allow_source=true&player=twitchweb")).Split("\n"); response = (await client.DownloadStringTaskAsync($"http://usher.twitch.tv/vod/{ID}?nauth={videoAccessToken["value"]}&nauthsig={videoAccessToken["signature"]}&allow_source=true&player=twitchweb")).Split("\n");
}
// Init streams list // Init streams list
List<Stream> streams = new List<Stream>(); List<BaseStream> streams = new List<BaseStream>();
// Stream data line2 regular expression
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+))?");
// Parse response // Parse response
for (int i = 2; i < response.Length; i += 3) for (int i = 2; i < response.Length; i += 3)
{ {
// Parse line 2 // Parse line 2
Match line2 = L2Regex.Match(response[i + 1]); Match line2 = streamDataL2Regex.Match(response[i + 1]);
// Get info
Uri url = new Uri(response[i + 2]);
int width = int.Parse(line2.Groups["width"].Value);
int height = int.Parse(line2.Groups["height"].Value);
int frameRate = line2.Groups["frame_rate"].Value != string.Empty ? (int)Math.Round(double.Parse(line2.Groups["frame_rate"].Value)) : 0;
string videoCodec = line2.Groups["video_codec"].Value;
string audioCodec = line2.Groups["audio_codec"].Value;
// Create stream // Create stream
Stream stream = new Stream(url, true, StreamType.AudioVideo) BaseStream stream = new BaseStream()
{ {
Width = width, Url = new Uri(response[i + 2]),
Height = height, Height = int.Parse(line2.Groups["height"].Value),
FrameRate = frameRate, FrameRate = line2.Groups["frame_rate"].Value != string.Empty ? (int)Math.Round(double.Parse(line2.Groups["frame_rate"].Value)) : 0,
VideoCodec = videoCodec,
AudioCodec = audioCodec,
}; };
// Add stream // Add stream
streams.Add(stream); streams.Add(stream);
} }
// Set Streams parameter // Set streams
BaseStreams = streams.ToArray(); BaseStreams = streams.ToArray();
} }
// DOWNLOAD AND TRANSCODE VOD // DOWNLOAD AND TRANSCODE VOD
public async Task<StorageFile> DownloadAndTranscodeAsync(StorageFolder downloadingFolder, IBaseStream baseStream, MediaFileExtension extension, MediaType mediaType, TimeSpan trimStart, TimeSpan trimEnd, CancellationToken cancellationToken = default) public async Task<StorageFile> DownloadAndTranscodeAsync(StorageFolder downloadingFolder, BaseStream baseStream, MediaFileExtension extension, MediaType mediaType, TimeSpan trimStart, TimeSpan trimEnd, CancellationToken cancellationToken = default)
{ {
// Invoke DownloadingStarted event // Invoke DownloadingStarted event
DownloadingStarted?.Invoke(this, System.EventArgs.Empty); cancellationToken.ThrowIfCancellationRequested();
DownloadingProgressChanged(this, new EventArgs.ProgressChangedEventArgs(0));
// Get video chunks // Get video chunks
cancellationToken.ThrowIfCancellationRequested(); cancellationToken.ThrowIfCancellationRequested();
List<(Uri ChunkUrl, TimeSpan ChunkDuration)> chunksList = await ExtractChunksFromM3U8Async(baseStream.Url, cancellationToken); List<(Uri ChunkUrl, TimeSpan ChunkDuration)> chunksList = await ExtractChunksFromM3U8Async(baseStream.Url, cancellationToken);
// Changeable duration
TimeSpan duration = Metadata.Duration;
// Passive trim // Passive trim
if ((bool)Config.GetValue("twitch_vod_passive_trim")) (trimStart, trimEnd) = PassiveVideoTrim(chunksList, trimStart, trimEnd, Duration); if ((bool)Config.GetValue("twitch_vod_passive_trim") && trimStart != TimeSpan.Zero && trimEnd != duration) (trimStart, trimEnd, duration) = PassiveVideoTrim(chunksList, trimStart, trimEnd, Metadata.Duration);
// Download // Download
cancellationToken.ThrowIfCancellationRequested(); cancellationToken.ThrowIfCancellationRequested();
StorageFile rawFile = await downloadingFolder.CreateFileAsync("raw.ts"); StorageFile rawFile = await downloadingFolder.CreateFileAsync("raw.ts");
float chunksDownloaded = 0; double chunksDownloaded = 0;
Task<byte[]> downloadTask; Task<byte[]> downloadTask;
Task writeTask; Task writeTask;
@@ -180,26 +156,25 @@ namespace VDownload.Core.Services.Sources.Twitch
writeTask = WriteChunkToFileAsync(rawFile, downloadTask.Result); writeTask = WriteChunkToFileAsync(rawFile, downloadTask.Result);
downloadTask = DownloadChunkAsync(chunksList[i].ChunkUrl); downloadTask = DownloadChunkAsync(chunksList[i].ChunkUrl);
await Task.WhenAll(writeTask, downloadTask); await Task.WhenAll(writeTask, downloadTask);
DownloadingProgressChanged(this, new ProgressChangedEventArgs((int)Math.Round(++chunksDownloaded * 100 / chunksList.Count), null)); DownloadingProgressChanged(this, new EventArgs.ProgressChangedEventArgs(++chunksDownloaded * 100 / chunksList.Count));
} }
cancellationToken.ThrowIfCancellationRequested(); cancellationToken.ThrowIfCancellationRequested();
await WriteChunkToFileAsync(rawFile, downloadTask.Result); await WriteChunkToFileAsync(rawFile, downloadTask.Result);
DownloadingProgressChanged(this, new ProgressChangedEventArgs((int)Math.Round(++chunksDownloaded * 100 / chunksList.Count), null)); DownloadingProgressChanged(this, new EventArgs.ProgressChangedEventArgs(100, true));
DownloadingCompleted?.Invoke(this, System.EventArgs.Empty);
// Processing // Processing
cancellationToken.ThrowIfCancellationRequested(); cancellationToken.ThrowIfCancellationRequested();
StorageFile outputFile = await downloadingFolder.CreateFileAsync($"transcoded.{extension.ToString().ToLower()}"); StorageFile outputFile = await downloadingFolder.CreateFileAsync($"transcoded.{extension.ToString().ToLower()}");
MediaProcessor mediaProcessor = new MediaProcessor(outputFile, trimStart, trimEnd); MediaProcessor mediaProcessor = new MediaProcessor();
mediaProcessor.ProcessingStarted += ProcessingStarted; mediaProcessor.ProgressChanged += ProcessingProgressChanged;
mediaProcessor.ProcessingProgressChanged += ProcessingProgressChanged;
mediaProcessor.ProcessingCompleted += ProcessingCompleted;
cancellationToken.ThrowIfCancellationRequested();
await mediaProcessor.Run(rawFile, extension, mediaType, cancellationToken);
Task mediaProcessorTask;
if (trimStart == TimeSpan.Zero && trimEnd == duration) mediaProcessorTask = mediaProcessor.Run(rawFile, extension, mediaType, outputFile, cancellationToken: cancellationToken);
else if (trimStart == TimeSpan.Zero) mediaProcessorTask = mediaProcessor.Run(rawFile, extension, mediaType, outputFile, trimStart: trimStart, cancellationToken: cancellationToken);
else if (trimEnd == duration) mediaProcessorTask = mediaProcessor.Run(rawFile, extension, mediaType, outputFile, trimEnd: trimEnd, cancellationToken: cancellationToken);
else mediaProcessorTask = mediaProcessor.Run(rawFile, extension, mediaType, outputFile, trimStart, trimEnd, cancellationToken);
await mediaProcessorTask;
// Return output file // Return output file
return outputFile; return outputFile;
@@ -214,21 +189,28 @@ namespace VDownload.Core.Services.Sources.Twitch
// GET CHUNKS DATA FROM M3U8 PLAYLIST // GET CHUNKS DATA FROM M3U8 PLAYLIST
private static async Task<List<(Uri ChunkUrl, TimeSpan ChunkDuration)>> ExtractChunksFromM3U8Async(Uri streamUrl, CancellationToken cancellationToken = default) private static async Task<List<(Uri ChunkUrl, TimeSpan ChunkDuration)>> ExtractChunksFromM3U8Async(Uri streamUrl, CancellationToken cancellationToken = default)
{ {
// Create client
WebClient client = new WebClient();
client.Headers.Add("Client-Id", Auth.GQLApiClientID);
// Get playlist
cancellationToken.ThrowIfCancellationRequested(); cancellationToken.ThrowIfCancellationRequested();
string response = await client.DownloadStringTaskAsync(streamUrl);
// Get response
string response = null;
using (WebClient client = Client.GQL())
{
response = await client.DownloadStringTaskAsync(streamUrl);
}
// Create dictionary // Create dictionary
List<(Uri ChunkUrl, TimeSpan ChunkDuration)> chunks = new List<(Uri ChunkUrl, TimeSpan ChunkDuration)>(); List<(Uri ChunkUrl, TimeSpan ChunkDuration)> chunks = new List<(Uri ChunkUrl, TimeSpan ChunkDuration)>();
// Chunk data regular expression
Regex chunkDataRegex = new Regex(@"#EXTINF:(?<duration>\d+.\d+),\n(?<filename>\S+.ts)");
// Chunks location
string chunkLocationPath = streamUrl.AbsoluteUri.Replace(System.IO.Path.GetFileName(streamUrl.AbsoluteUri), "");
// Pack data into dictionary // Pack data into dictionary
foreach (Match chunk in ChunkRegex.Matches(response)) foreach (Match chunk in chunkDataRegex.Matches(response))
{ {
Uri chunkUrl = new Uri($"{streamUrl.AbsoluteUri.Replace(System.IO.Path.GetFileName(streamUrl.AbsoluteUri), "")}{chunk.Groups["filename"].Value}"); Uri chunkUrl = new Uri($"{chunkLocationPath}{chunk.Groups["filename"].Value}");
TimeSpan chunkDuration = TimeSpan.FromSeconds(double.Parse(chunk.Groups["duration"].Value)); TimeSpan chunkDuration = TimeSpan.FromSeconds(double.Parse(chunk.Groups["duration"].Value));
chunks.Add((chunkUrl, chunkDuration)); chunks.Add((chunkUrl, chunkDuration));
} }
@@ -238,7 +220,7 @@ namespace VDownload.Core.Services.Sources.Twitch
} }
// PASSIVE TRIM // PASSIVE TRIM
private static (TimeSpan TrimStart, TimeSpan TrimEnd) PassiveVideoTrim(List<(Uri ChunkUrl, TimeSpan ChunkDuration)> chunksList, TimeSpan trimStart, TimeSpan trimEnd, TimeSpan duration) private static (TimeSpan NewTrimStart, TimeSpan NewTrimEnd, TimeSpan NewDuration) PassiveVideoTrim(List<(Uri ChunkUrl, TimeSpan ChunkDuration)> chunksList, TimeSpan trimStart, TimeSpan trimEnd, TimeSpan duration)
{ {
// Copy duration // Copy duration
TimeSpan newDuration = duration; TimeSpan newDuration = duration;
@@ -260,7 +242,7 @@ namespace VDownload.Core.Services.Sources.Twitch
} }
// Return data // Return data
return (trimStart, trimEnd); return (trimStart, trimEnd, newDuration);
} }
// DOWNLOAD CHUNK // DOWNLOAD CHUNK
@@ -322,12 +304,8 @@ namespace VDownload.Core.Services.Sources.Twitch
#region EVENT HANDLERS #region EVENT HANDLERS
public event EventHandler DownloadingStarted; public event EventHandler<EventArgs.ProgressChangedEventArgs> DownloadingProgressChanged;
public event EventHandler<ProgressChangedEventArgs> DownloadingProgressChanged; public event EventHandler<EventArgs.ProgressChangedEventArgs> ProcessingProgressChanged;
public event EventHandler DownloadingCompleted;
public event EventHandler ProcessingStarted;
public event EventHandler<ProgressChangedEventArgs> ProcessingProgressChanged;
public event EventHandler ProcessingCompleted;
#endregion #endregion
} }

View File

@@ -7,21 +7,12 @@ namespace VDownload.Core.Services
{ {
#region CONSTANTS #region CONSTANTS
// RANDOM
private static readonly Random Random = new Random();
// ID SETTINGS // ID SETTINGS
private static readonly char[] IDChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890".ToCharArray(); private static readonly char[] IDChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890".ToCharArray();
private static readonly int IDLength = 10; private static readonly int IDLength = 10;
#endregion // IDS LIST
private static readonly List<string> IDList = new List<string>();
#region PROPERTIES
// USED IDS LIST
private static readonly List<string> UsedIDs = new List<string>();
#endregion #endregion
@@ -38,17 +29,17 @@ namespace VDownload.Core.Services
id = ""; id = "";
while (id.Length < IDLength) while (id.Length < IDLength)
{ {
id += IDChars[Random.Next(0, IDChars.Length)]; id += IDChars[new Random().Next(0, IDChars.Length)];
} }
} while (UsedIDs.Contains(id)); } while (IDList.Contains(id));
UsedIDs.Add(id); IDList.Add(id);
return id; return id;
} }
// DISPOSE TASK ID // DISPOSE TASK ID
public static void Dispose(string id) public static void Dispose(string id)
{ {
UsedIDs.Remove(id); IDList.Remove(id);
} }
#endregion #endregion

View File

@@ -0,0 +1,61 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace VDownload.Core.Services
{
public class TimeSpanCustomFormat
{
// (TH:)MM:SS
public static string ToOptTHBaseMMSS(TimeSpan timeSpan, params TimeSpan[] formatBase)
{
string formattedTimeSpan = string.Empty;
int maxTHLength = 0;
if (Math.Floor(timeSpan.TotalHours) > 0)
{
maxTHLength = Math.Floor(timeSpan.TotalHours).ToString().Length;
foreach (TimeSpan format in formatBase)
{
int THLength = Math.Floor(format.TotalHours) > 0 ? Math.Floor(timeSpan.TotalHours).ToString().Length : 0;
if (THLength > maxTHLength) maxTHLength = THLength;
}
formattedTimeSpan += $"{((int)Math.Floor(timeSpan.TotalHours)).ToString($"D{maxTHLength}")}:";
}
formattedTimeSpan += maxTHLength == 0 ? $"{timeSpan.Minutes}:" : $"{timeSpan.Minutes:00}:";
formattedTimeSpan += $"{timeSpan.Seconds:00}";
return formattedTimeSpan;
}
// ((TH:)MM:)SS
public static string ToOptTHMMBaseSS(TimeSpan timeSpan, params TimeSpan[] formatBase)
{
string formattedTimeSpan = string.Empty;
int maxTHLength = 0;
if (Math.Floor(timeSpan.TotalHours) > 0)
{
maxTHLength = Math.Floor(timeSpan.TotalHours).ToString().Length;
foreach (TimeSpan format in formatBase)
{
int THLength = Math.Floor(format.TotalHours) > 0 ? Math.Floor(timeSpan.TotalHours).ToString().Length : 0;
if (THLength > maxTHLength) maxTHLength = THLength;
}
formattedTimeSpan += $"{((int)Math.Floor(timeSpan.TotalHours)).ToString($"D{maxTHLength}")}:";
}
bool MM = false;
if (Math.Floor(timeSpan.TotalMinutes) > 0)
{
formattedTimeSpan += maxTHLength > 0 ? $"{timeSpan.Minutes:00}:" : $"{timeSpan.Minutes}:";
MM = true;
}
formattedTimeSpan += MM ? $"{timeSpan.Seconds:00}:" : $"{timeSpan.Seconds}:";
return formattedTimeSpan;
}
}
}

View File

@@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace VDownload.Core.Structs
{
public struct BaseStream
{
public Uri Url { get; set; }
public int Height { get; set; }
public int FrameRate { get; set; }
}
}

View File

@@ -0,0 +1,18 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace VDownload.Core.Structs
{
public struct Metadata
{
public string Title { get; set; }
public string Author { get; set; }
public DateTime Date { get; set; }
public TimeSpan Duration { get; set; }
public long Views { get; set; }
public Uri Thumbnail { get; set; }
}
}

View File

@@ -1,16 +1,15 @@
using System; using System;
using VDownload.Core.Enums; using VDownload.Core.Enums;
using VDownload.Core.Interfaces; using VDownload.Core.Interfaces;
using VDownload.Core.Objects;
using Windows.Storage; using Windows.Storage;
namespace VDownload.Core.EventArgs namespace VDownload.Core.Structs
{ {
public class VideoAddEventArgs : System.EventArgs public struct TaskData
{ {
public IVideoService VideoService { get; set; } public IVideoService VideoService { get; set; }
public MediaType MediaType { get; set; } public MediaType MediaType { get; set; }
public IBaseStream Stream { get; set; } public BaseStream Stream { get; set; }
public TimeSpan TrimStart { get; set; } public TimeSpan TrimStart { get; set; }
public TimeSpan TrimEnd { get; set; } public TimeSpan TrimEnd { get; set; }
public string Filename { get; set; } public string Filename { get; set; }

View File

@@ -124,24 +124,27 @@
<Compile Include="Enums\MediaFileExtension.cs" /> <Compile Include="Enums\MediaFileExtension.cs" />
<Compile Include="Enums\MediaType.cs" /> <Compile Include="Enums\MediaType.cs" />
<Compile Include="Enums\PlaylistSource.cs" /> <Compile Include="Enums\PlaylistSource.cs" />
<Compile Include="Enums\StreamType.cs" /> <Compile Include="Enums\TaskAddingRequestSource.cs" />
<Compile Include="Enums\VideoFileExtension.cs" /> <Compile Include="Enums\VideoFileExtension.cs" />
<Compile Include="Enums\VideoSource.cs" /> <Compile Include="Enums\VideoSource.cs" />
<Compile Include="Enums\TaskStatus.cs" /> <Compile Include="Enums\TaskStatus.cs" />
<Compile Include="EventArgs\PlaylistAddEventArgs.cs" /> <Compile Include="EventArgs\ProgressChangedEventArgs.cs" />
<Compile Include="EventArgs\VideoAddEventArgs.cs" /> <Compile Include="EventArgs\TasksAddingRequestedEventArgs.cs" />
<Compile Include="EventArgs\VideoSearchEventArgs.cs" /> <Compile Include="EventArgs\VideoSearchEventArgs.cs" />
<Compile Include="EventArgs\PlaylistSearchEventArgs.cs" /> <Compile Include="EventArgs\PlaylistSearchEventArgs.cs" />
<Compile Include="Exceptions\TwitchAccessTokenNotFoundException.cs" /> <Compile Include="Exceptions\TwitchAccessTokenNotFoundException.cs" />
<Compile Include="Exceptions\TwitchAccessTokenNotValidException.cs" /> <Compile Include="Exceptions\TwitchAccessTokenNotValidException.cs" />
<Compile Include="Interfaces\IBaseStream.cs" />
<Compile Include="Interfaces\IPlaylistService.cs" /> <Compile Include="Interfaces\IPlaylistService.cs" />
<Compile Include="Interfaces\IVideoService.cs" /> <Compile Include="Interfaces\IVideoService.cs" />
<Compile Include="Objects\Stream.cs" /> <Compile Include="Services\Sources\Twitch\Helpers\Client.cs" />
<Compile Include="Services\TimeSpanCustomFormat.cs" />
<Compile Include="Structs\BaseStream.cs" />
<Compile Include="Structs\Metadata.cs" />
<Compile Include="Structs\TaskData.cs" />
<Compile Include="Services\Config.cs" /> <Compile Include="Services\Config.cs" />
<Compile Include="Services\MediaProcessor.cs" /> <Compile Include="Services\MediaProcessor.cs" />
<Compile Include="Services\Source.cs" /> <Compile Include="Services\Source.cs" />
<Compile Include="Services\Sources\Twitch\Auth.cs" /> <Compile Include="Services\Sources\Twitch\Helpers\Auth.cs" />
<Compile Include="Services\Sources\Twitch\Channel.cs" /> <Compile Include="Services\Sources\Twitch\Channel.cs" />
<Compile Include="Services\Sources\Twitch\Clip.cs" /> <Compile Include="Services\Sources\Twitch\Clip.cs" />
<Compile Include="Services\Sources\Twitch\Vod.cs" /> <Compile Include="Services\Sources\Twitch\Vod.cs" />

View File

@@ -182,7 +182,7 @@ The number in the numberbox indicades how many videos will be got from playlist.
<value>90</value> <value>90</value>
</data> </data>
<data name="HomeOptionsBarLoadSubscripionsButton.Label" xml:space="preserve"> <data name="HomeOptionsBarLoadSubscripionsButton.Label" xml:space="preserve">
<value>Load subscription</value> <value>Load subscriptions</value>
</data> </data>
<data name="HomeOptionsBarLoadSubscripionsButton.Width" xml:space="preserve"> <data name="HomeOptionsBarLoadSubscripionsButton.Width" xml:space="preserve">
<value>120</value> <value>120</value>

View File

@@ -1,348 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="AddPlaylistBase.CloseButtonText" xml:space="preserve">
<value>Cancel</value>
</data>
<data name="AddPlaylistBase.PrimaryButtonText" xml:space="preserve">
<value>Add</value>
</data>
<data name="AddPlaylistBase.Title" xml:space="preserve">
<value>ADD PLAYLIST</value>
</data>
<data name="AddPlaylistDeletedVideosPanelButton" xml:space="preserve">
<value>Restore</value>
</data>
<data name="AddPlaylistDeletedVideosPanelTextBlock" xml:space="preserve">
<value>{x} videos removed</value>
</data>
<data name="AddPlaylistLocationSelectionApplyLocationButton.Content" xml:space="preserve">
<value>Apply</value>
</data>
<data name="AddPlaylistLocationSelectionTextBlock.Text" xml:space="preserve">
<value>Location</value>
</data>
<data name="AddPlaylistNotFoundText" xml:space="preserve">
<value>Playlist not found. Try again.</value>
</data>
<data name="AddPlaylistSearchButton.Content" xml:space="preserve">
<value>Search</value>
</data>
<data name="AddPlaylistStartText.Text" xml:space="preserve">
<value>Paste URL and click "Search" button</value>
</data>
<data name="AddPlaylistUrlPathText.Text" xml:space="preserve">
<value>URL</value>
</data>
<data name="AddPlaylistVideoDownloadOptionsMediaTypeRadiobuttonA" xml:space="preserve">
<value>Only audio</value>
</data>
<data name="AddPlaylistVideoDownloadOptionsMediaTypeRadiobuttonAV" xml:space="preserve">
<value>Normal</value>
</data>
<data name="AddPlaylistVideoDownloadOptionsMediaTypeRadiobuttonV" xml:space="preserve">
<value>Only video</value>
</data>
<data name="AddPlaylistVideoDownloadOptionsMediaTypeTextBlock" xml:space="preserve">
<value>Media type</value>
</data>
<data name="AddPlaylistVideoDownloadOptionsQualityTextBlock" xml:space="preserve">
<value>Quality</value>
</data>
<data name="AddPlaylistVideoDownloadOptionsTrimTextBlock" xml:space="preserve">
<value>Trim</value>
</data>
<data name="AddPlaylistVideoFileDataTextBlock" xml:space="preserve">
<value>File</value>
</data>
<data name="AddPlaylistVideoLocationDataTextBlock" xml:space="preserve">
<value>Location</value>
</data>
<data name="AddPlaylistVideoQualityNoVideoStream" xml:space="preserve">
<value>Only audio</value>
</data>
<data name="AddVideoBase.CloseButtonText" xml:space="preserve">
<value>Cancel</value>
</data>
<data name="AddVideoBase.PrimaryButtonText" xml:space="preserve">
<value>Add</value>
</data>
<data name="AddVideoBase.Title" xml:space="preserve">
<value>ADD VIDEO</value>
</data>
<data name="AddVideoNotFoundText.Text" xml:space="preserve">
<value>Video not found. Try again.</value>
</data>
<data name="AddVideoStartText.Text" xml:space="preserve">
<value>Paste URL and click "Search" button</value>
</data>
<data name="MainPageNavigationPanelStartItem.Content" xml:space="preserve">
<value>Start</value>
</data>
<data name="MainPageNavigationPanelSubscriptionsItem.Content" xml:space="preserve">
<value>Subscriptions</value>
</data>
<data name="StartAddingBarDownloadOptionsMediaTypeTextBlockText" xml:space="preserve">
<value>Media type</value>
</data>
<data name="StartAddingBarDownloadOptionsQualityTextBlockText" xml:space="preserve">
<value>Quality</value>
</data>
<data name="StartAddingBarDownloadOptionsTextBlockText" xml:space="preserve">
<value>Download options</value>
</data>
<data name="StartAddingBarDownloadOptionsTrimTextBlockText" xml:space="preserve">
<value>Trim</value>
</data>
<data name="StartAddingBarDownloadQualityNoVideoStream" xml:space="preserve">
<value>Only audio</value>
</data>
<data name="StartAddingBarPlaylistLocationApplyButtonText" xml:space="preserve">
<value>Apply</value>
</data>
<data name="StartAddingBarSaveOptionsFileDescriptionTextBlockText" xml:space="preserve">
<value>Filename and extension</value>
</data>
<data name="StartAddingBarSaveOptionsFileTextBlockText" xml:space="preserve">
<value>File</value>
</data>
<data name="StartAddingBarSaveOptionsLocationTextBlockText" xml:space="preserve">
<value>Location</value>
</data>
<data name="StartAddingBarSaveOptionsTextBlockText" xml:space="preserve">
<value>Save options</value>
</data>
<data name="StartAddingBarVideoOptionsFileTextBlockText" xml:space="preserve">
<value>File</value>
</data>
<data name="StartAddingBarVideoOptionsLocationButtonText" xml:space="preserve">
<value>Browse</value>
</data>
<data name="StartAddingBarVideoOptionsLocationTextBlockText" xml:space="preserve">
<value>Location</value>
</data>
<data name="StartAddingBarVideoOptionsMediaTypeComboBoxAText" xml:space="preserve">
<value>Only audio</value>
</data>
<data name="StartAddingBarVideoOptionsMediaTypeComboBoxAVText" xml:space="preserve">
<value>Normal</value>
</data>
<data name="StartAddingBarVideoOptionsMediaTypeComboBoxVText" xml:space="preserve">
<value>Only video</value>
</data>
<data name="StartAddPlaylistInputInfoTeachingTipSubtitleNumberBox" xml:space="preserve">
<value>If numberbox is set to 0, app will load all videos from the playlist. Otherwise, the program will load the specified number of videos.</value>
</data>
<data name="StartAddPlaylistInputInfoTeachingTipSubtitleTwitch" xml:space="preserve">
<value>- Twitch (Channels)</value>
</data>
<data name="StartAddPlaylistInputInfoTeachingTipSubtitleYoutube" xml:space="preserve">
<value>- Youtube (Playlists, Channels)</value>
</data>
<data name="StartAddPlaylistInputInfoTeachingTipTitle" xml:space="preserve">
<value>Supported websites, types of playlist and numberbox instruction</value>
</data>
<data name="StartAddPlaylistInputSearchButtonText" xml:space="preserve">
<value>Search</value>
</data>
<data name="StartAddPlaylistInputTextBoxPlaceholderText" xml:space="preserve">
<value>Playlist URL</value>
</data>
<data name="StartAddVideoInputInfoTeachingTipSubtitleTwitch" xml:space="preserve">
<value>- Twitch (VODs, Clips)</value>
</data>
<data name="StartAddVideoInputInfoTeachingTipSubtitleYoutube" xml:space="preserve">
<value>- Youtube (Videos)</value>
</data>
<data name="StartAddVideoInputInfoTeachingTipTitle" xml:space="preserve">
<value>Supported websites and types of videos</value>
</data>
<data name="StartAddVideoInputSearchButtonText" xml:space="preserve">
<value>Search</value>
</data>
<data name="StartAddVideoInputTextBoxPlaceholderText" xml:space="preserve">
<value>Video URL</value>
</data>
<data name="StartOptionsBarAddPlaylistButton.Label" xml:space="preserve">
<value>Add playlist</value>
</data>
<data name="StartOptionsBarAddPlaylistButton.Width" xml:space="preserve">
<value>85</value>
</data>
<data name="StartOptionsBarAddVideoButton.Label" xml:space="preserve">
<value>Add video</value>
</data>
<data name="StartOptionsBarAddVideoButton.Width" xml:space="preserve">
<value>75</value>
</data>
<data name="StartOptionsBarDownloadAllButton.Label" xml:space="preserve">
<value>Download All</value>
</data>
<data name="StartOptionsBarDownloadAllButton.Width" xml:space="preserve">
<value>90</value>
</data>
<data name="StartOptionsBarLoadSubscripionsButton.Label" xml:space="preserve">
<value>Load subscriptions</value>
</data>
<data name="StartOptionsBarLoadSubscripionsButton.Width" xml:space="preserve">
<value>120</value>
</data>
<data name="VideoPanelMediaTypeDataA" xml:space="preserve">
<value>Only audio</value>
</data>
<data name="VideoPanelMediaTypeDataAV" xml:space="preserve">
<value>Audio &amp; Video</value>
</data>
<data name="VideoPanelMediaTypeDataV" xml:space="preserve">
<value>Only video</value>
</data>
<data name="VideoPanelProgressLabelCancelled" xml:space="preserve">
<value>Cancelled</value>
</data>
<data name="VideoPanelProgressLabelDone1" xml:space="preserve">
<value>Done in</value>
</data>
<data name="VideoPanelProgressLabelDone2" xml:space="preserve">
<value>seconds</value>
</data>
<data name="VideoPanelProgressLabelDownloading" xml:space="preserve">
<value>Downloading</value>
</data>
<data name="VideoPanelProgressLabelError" xml:space="preserve">
<value>An error occured!</value>
</data>
<data name="VideoPanelProgressLabelErrorInternetConnection" xml:space="preserve">
<value>Internet connection error</value>
</data>
<data name="VideoPanelProgressLabelFinalizing" xml:space="preserve">
<value>Finalizing</value>
</data>
<data name="VideoPanelProgressLabelIdle" xml:space="preserve">
<value>Idle</value>
</data>
<data name="VideoPanelProgressLabelTranscoding" xml:space="preserve">
<value>Transcoding</value>
</data>
<data name="VideoPanelProgressLabelWaiting" xml:space="preserve">
<value>Queued</value>
</data>
<data name="VideoPanelQualityNoVideoStream" xml:space="preserve">
<value>Only audio</value>
</data>
</root>

View File

@@ -365,9 +365,6 @@
<Version>2.8.0-prerelease.220118001</Version> <Version>2.8.0-prerelease.220118001</Version>
</PackageReference> </PackageReference>
</ItemGroup> </ItemGroup>
<ItemGroup>
<PRIResource Include="Strings\en-US\ResourcesOld.resw" />
</ItemGroup>
<ItemGroup> <ItemGroup>
<PRIResource Include="Strings\en-US\Resources.resw" /> <PRIResource Include="Strings\en-US\Resources.resw" />
</ItemGroup> </ItemGroup>

View File

@@ -60,7 +60,7 @@
<!-- VIDEOS LIST --> <!-- VIDEOS LIST -->
<ScrollViewer Margin="0,0,0,10" CornerRadius="{ThemeResource ControlCornerRadius}"> <ScrollViewer Margin="0,0,0,10" CornerRadius="{ThemeResource ControlCornerRadius}">
<ContentControl x:Name="HomeTasksListPlace" HorizontalContentAlignment="Stretch" VerticalContentAlignment="Stretch"/> <ContentControl x:Name="HomeTasksListParent" HorizontalContentAlignment="Stretch" VerticalContentAlignment="Stretch"/>
</ScrollViewer> </ScrollViewer>
<!-- OPTIONS BAR AND ADDING PANEL --> <!-- OPTIONS BAR AND ADDING PANEL -->

View File

@@ -11,6 +11,7 @@ using VDownload.Core.EventArgs;
using VDownload.Core.Exceptions; using VDownload.Core.Exceptions;
using VDownload.Core.Interfaces; using VDownload.Core.Interfaces;
using VDownload.Core.Services; using VDownload.Core.Services;
using VDownload.Core.Structs;
using Windows.ApplicationModel.Resources; using Windows.ApplicationModel.Resources;
using Windows.UI.Xaml; using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls; using Windows.UI.Xaml.Controls;
@@ -21,11 +22,29 @@ namespace VDownload.Views.Home
{ {
public sealed partial class HomeMain : Page public sealed partial class HomeMain : Page
{ {
#region CONSTANTS
// RESOURCES
private static readonly ResourceDictionary ImageRes = new ResourceDictionary { Source = new Uri("ms-appx:///Resources/Icons.xaml") };
// SEARCHING STATUS CONTROLS
private static readonly Microsoft.UI.Xaml.Controls.ProgressRing HomeOptionsBarSearchingStatusProgressRing = new Microsoft.UI.Xaml.Controls.ProgressRing { Width = 15, Height = 15, Margin = new Thickness(5), IsActive = true };
private static readonly Image HomeOptionsBarSearchingStatusErrorImage = new Image { Width = 15, Height = 15, Margin = new Thickness(5), Source = (SvgImageSource)ImageRes["ErrorIcon"] };
// TASKS LIST PLACEHOLDER
private static readonly HomeTasksListPlaceholder HomeTasksListPlaceholder = new HomeTasksListPlaceholder();
#endregion
#region CONSTRUCTORS #region CONSTRUCTORS
public HomeMain() public HomeMain()
{ {
this.InitializeComponent(); this.InitializeComponent();
// Set cancellation token
SearchingCancellationToken = new CancellationTokenSource();
} }
#endregion #endregion
@@ -34,20 +53,13 @@ namespace VDownload.Views.Home
#region PROPERTIES #region PROPERTIES
// SEARCHING STATUS CONTROLS
private readonly Microsoft.UI.Xaml.Controls.ProgressRing HomeOptionsBarSearchingStatusProgressRing = new Microsoft.UI.Xaml.Controls.ProgressRing { Width = 15, Height = 15, Margin = new Thickness(5), IsActive = true };
private readonly Image HomeOptionsBarSearchingStatusErrorImage = new Image { Width = 15, Height = 15, Margin = new Thickness(5), Source = (SvgImageSource)new ResourceDictionary { Source = new Uri("ms-appx:///Resources/Icons.xaml") }["ErrorIcon"] };
// CANCELLATON TOKEN // CANCELLATON TOKEN
private CancellationTokenSource SearchingCancellationToken = new CancellationTokenSource(); private CancellationTokenSource SearchingCancellationToken { get; set; }
// HOME TASKS LIST PLACEHOLDER // HOME TASKS LIST
private readonly HomeTasksListPlaceholder HomeTasksListPlaceholder = new HomeTasksListPlaceholder(); private static ContentControl HomeTasksListCurrentParent = null;
// HOME VIDEOS LIST
private static ContentControl HomeTasksListPlaceCurrent { get; set; }
private static StackPanel HomeTasksList = null; private static StackPanel HomeTasksList = null;
public static List<HomeTaskPanel> TaskPanelsList = new List<HomeTaskPanel>(); public static List<HomeTaskPanel> TasksList = new List<HomeTaskPanel>();
#endregion #endregion
@@ -58,20 +70,28 @@ namespace VDownload.Views.Home
// ON NAVIGATED TO // ON NAVIGATED TO
protected override void OnNavigatedTo(NavigationEventArgs e) protected override void OnNavigatedTo(NavigationEventArgs e)
{ {
HomeTasksListPlaceCurrent = HomeTasksListPlace; // Set current panel
HomeTasksListCurrentParent = HomeTasksListParent;
// Detach task panels from old task list
if (HomeTasksList != null) HomeTasksList.Children.Clear(); if (HomeTasksList != null) HomeTasksList.Children.Clear();
// Create new task list
HomeTasksList = new StackPanel { Spacing = 10 }; HomeTasksList = new StackPanel { Spacing = 10 };
if (TaskPanelsList.Count > 0)
// Attach task panels to new task list
if (TasksList.Count > 0)
{ {
foreach (HomeTaskPanel homeVideoPanel in TaskPanelsList) HomeTasksList.Children.Add(homeVideoPanel); foreach (HomeTaskPanel homeVideoPanel in TasksList) HomeTasksList.Children.Add(homeVideoPanel);
HomeTasksListPlaceCurrent.Content = HomeTasksList; HomeTasksListCurrentParent.Content = HomeTasksList;
} }
else else
{ {
HomeTasksListPlaceCurrent.Content = HomeTasksListPlaceholder; HomeTasksListCurrentParent.Content = HomeTasksListPlaceholder;
} }
} }
// ADD VIDEO BUTTON CHECKED // ADD VIDEO BUTTON CHECKED
private void HomeOptionsBarAddVideoButton_Checked(object sender, RoutedEventArgs e) private void HomeOptionsBarAddVideoButton_Checked(object sender, RoutedEventArgs e)
{ {
@@ -100,7 +120,7 @@ namespace VDownload.Views.Home
HomeOptionsBarSearchingStatusControl.Content = HomeOptionsBarSearchingStatusProgressRing; HomeOptionsBarSearchingStatusControl.Content = HomeOptionsBarSearchingStatusProgressRing;
// Parse url // Parse url
(VideoSource Type, string ID) source = Source.GetVideoSource(e.Phrase); (VideoSource Type, string ID) source = Source.GetVideoSource(e.Url);
// Check url // Check url
if (source.Type == VideoSource.Null) if (source.Type == VideoSource.Null)
@@ -176,37 +196,13 @@ namespace VDownload.Views.Home
HomeOptionBarAndAddingPanelRow.Height = new GridLength(1, GridUnitType.Star); HomeOptionBarAndAddingPanelRow.Height = new GridLength(1, GridUnitType.Star);
HomeTasksListRow.Height = new GridLength(0); HomeTasksListRow.Height = new GridLength(0);
// Open adding panel
HomeVideoAddingPanel addingPanel = new HomeVideoAddingPanel(videoService); HomeVideoAddingPanel addingPanel = new HomeVideoAddingPanel(videoService);
addingPanel.VideoAddRequest += HomeVideoAddingPanel_VideoAddRequest; addingPanel.TasksAddingRequested += HomeTasksAddingRequest;
HomeAddingPanel.Content = addingPanel; HomeAddingPanel.Content = addingPanel;
} }
} }
// ADD VIDEO REQUEST FROM VIDEO ADDING PANEL
private void HomeVideoAddingPanel_VideoAddRequest(object sender, VideoAddEventArgs e)
{
// Replace placeholder
HomeTasksListPlaceCurrent.Content = HomeTasksList;
// Uncheck video button
HomeOptionsBarAddVideoButton.IsChecked = false;
// Create video task
HomeTaskPanel taskPanel = new HomeTaskPanel(e.VideoService, e.MediaType, e.Stream, e.TrimStart, e.TrimEnd, e.Filename, e.Extension, e.Location, e.Schedule);
taskPanel.TaskRemovingRequested += (s, a) =>
{
// Remove task from tasks lists
TaskPanelsList.Remove(taskPanel);
HomeTasksList.Children.Remove(taskPanel);
if (TaskPanelsList.Count <= 0) HomeTasksListPlaceCurrent.Content = HomeTasksListPlaceholder;
};
// Add task to tasks lists
HomeTasksList.Children.Add(taskPanel);
TaskPanelsList.Add(taskPanel);
}
// ADD PLAYLIST BUTTON CHECKED // ADD PLAYLIST BUTTON CHECKED
private void HomeOptionsBarAddPlaylistButton_Checked(object sender, RoutedEventArgs e) private void HomeOptionsBarAddPlaylistButton_Checked(object sender, RoutedEventArgs e)
@@ -236,7 +232,7 @@ namespace VDownload.Views.Home
HomeOptionsBarSearchingStatusControl.Content = HomeOptionsBarSearchingStatusProgressRing; HomeOptionsBarSearchingStatusControl.Content = HomeOptionsBarSearchingStatusProgressRing;
// Parse url // Parse url
(PlaylistSource Type, string ID) source = Source.GetPlaylistSource(e.Phrase); (PlaylistSource Type, string ID) source = Source.GetPlaylistSource(e.Url);
// Check url // Check url
if (source.Type == PlaylistSource.Null) if (source.Type == PlaylistSource.Null)
@@ -256,7 +252,7 @@ namespace VDownload.Views.Home
try try
{ {
await playlistService.GetMetadataAsync(SearchingCancellationToken.Token); await playlistService.GetMetadataAsync(SearchingCancellationToken.Token);
await playlistService.GetVideosAsync(e.Count, SearchingCancellationToken.Token); await playlistService.GetVideosAsync(e.VideosCount, SearchingCancellationToken.Token);
} }
catch (OperationCanceledException) catch (OperationCanceledException)
{ {
@@ -290,7 +286,7 @@ namespace VDownload.Views.Home
catch (WebException wex) catch (WebException wex)
{ {
HomeOptionsBarSearchingStatusControl.Content = HomeOptionsBarSearchingStatusErrorImage; HomeOptionsBarSearchingStatusControl.Content = HomeOptionsBarSearchingStatusErrorImage;
if (wex.Response == null) if (!NetworkHelper.Instance.ConnectionInformation.IsInternetAvailable)
{ {
ContentDialog internetAccessErrorDialog = new ContentDialog ContentDialog internetAccessErrorDialog = new ContentDialog
{ {
@@ -311,64 +307,81 @@ namespace VDownload.Views.Home
HomeOptionBarAndAddingPanelRow.Height = new GridLength(1, GridUnitType.Star); HomeOptionBarAndAddingPanelRow.Height = new GridLength(1, GridUnitType.Star);
HomeTasksListRow.Height = new GridLength(0); HomeTasksListRow.Height = new GridLength(0);
// Open adding panel
HomePlaylistAddingPanel addingPanel = new HomePlaylistAddingPanel(playlistService); HomePlaylistAddingPanel addingPanel = new HomePlaylistAddingPanel(playlistService);
addingPanel.PlaylistAddRequest += HomeVideoAddingPanel_PlayListAddRequest; addingPanel.TasksAddingRequested += HomeTasksAddingRequest;
HomeAddingPanel.Content = addingPanel; HomeAddingPanel.Content = addingPanel;
} }
} }
// ADD PLAYLIST REQUEST FROM PLAYLIST ADDING PANEL
private void HomeVideoAddingPanel_PlayListAddRequest(object sender, PlaylistAddEventArgs e) // TASK ADDING REQUEST
private void HomeTasksAddingRequest(object sender, TasksAddingRequestedEventArgs e)
{ {
// Replace placeholder // Replace placeholder
HomeTasksListPlaceCurrent.Content = HomeTasksList; HomeTasksListCurrentParent.Content = HomeTasksList;
// Uncheck video button // Uncheck button
HomeOptionsBarAddPlaylistButton.IsChecked = false; switch (e.RequestSource)
{
case TaskAddingRequestSource.Video: HomeOptionsBarAddVideoButton.IsChecked = false; break;
case TaskAddingRequestSource.Playlist: HomeOptionsBarAddPlaylistButton.IsChecked = false; break;
}
// Create video tasks // Create video tasks
foreach (var video in e.Videos) foreach (TaskData taskData in e.TaskData)
{ {
HomeTaskPanel taskPanel = new HomeTaskPanel(video.VideoService, video.MediaType, video.Stream, video.TrimStart, video.TrimEnd, video.Filename, video.Extension, video.Location, video.Schedule); HomeTaskPanel taskPanel = new HomeTaskPanel(taskData);
taskPanel.TaskRemovingRequested += (s, a) => taskPanel.TaskRemovingRequested += (s, a) =>
{ {
// Remove task from tasks lists // Remove task from tasks lists
TaskPanelsList.Remove(taskPanel); TasksList.Remove(taskPanel);
HomeTasksList.Children.Remove(taskPanel); HomeTasksList.Children.Remove(taskPanel);
if (TaskPanelsList.Count <= 0) HomeTasksListPlaceCurrent.Content = HomeTasksListPlaceholder; if (TasksList.Count <= 0) HomeTasksListCurrentParent.Content = HomeTasksListPlaceholder;
}; };
// Add task to tasks lists // Add task to tasks lists
HomeTasksList.Children.Add(taskPanel); HomeTasksList.Children.Add(taskPanel);
TaskPanelsList.Add(taskPanel); TasksList.Add(taskPanel);
} }
} }
// ADDING BUTTONS UNCHECKED // TASK ADDING CANCELLED
private void HomeOptionsBarAddingButtons_Unchecked(object sender, RoutedEventArgs e) private void HomeSearchingCancelled()
{ {
// Cancel searching operations // Cancel searching operations
SearchingCancellationToken.Cancel(); SearchingCancellationToken.Cancel();
SearchingCancellationToken = new CancellationTokenSource(); SearchingCancellationToken = new CancellationTokenSource();
// Set grid dimensions
HomeOptionBarAndAddingPanelRow.Height = GridLength.Auto; HomeOptionBarAndAddingPanelRow.Height = GridLength.Auto;
HomeTasksListRow.Height = new GridLength(1, GridUnitType.Star); HomeTasksListRow.Height = new GridLength(1, GridUnitType.Star);
// Clear panels
HomeAddingPanel.Content = null; HomeAddingPanel.Content = null;
HomeOptionsBarAddingControl.Content = null; HomeOptionsBarAddingControl.Content = null;
HomeOptionsBarSearchingStatusControl.Content = null; HomeOptionsBarSearchingStatusControl.Content = null;
} }
// ADDING BUTTONS UNCHECKED
private void HomeOptionsBarAddingButtons_Unchecked(object sender, RoutedEventArgs e)
{
HomeSearchingCancelled();
}
// DOWNLOAD ALL BUTTON CLICKED // DOWNLOAD ALL BUTTON CLICKED
private async void HomeOptionsBarDownloadAllButton_Click(object sender, RoutedEventArgs e) private async void HomeOptionsBarDownloadAllButton_Click(object sender, RoutedEventArgs e)
{ {
HomeTaskPanel[] idleTasks = TaskPanelsList.Where((HomeTaskPanel video) => video.TaskStatus == Core.Enums.TaskStatus.Idle).ToArray(); HomeTaskPanel[] idleTasks = TasksList.Where((HomeTaskPanel video) => video.Status == Core.Enums.TaskStatus.Idle).ToArray();
if (idleTasks.Count() > 0) if (idleTasks.Count() > 0)
{ {
bool delay = (bool)Config.GetValue("delay_task_when_queued_task_starts_on_metered_network"); bool delay = (bool)Config.GetValue("delay_task_when_queued_task_starts_on_metered_network");
if (NetworkHelper.Instance.ConnectionInformation.IsInternetOnMeteredConnection)
{
ContentDialogResult dialogResult = await new ContentDialog ContentDialogResult dialogResult = await new ContentDialog
{ {
Title = ResourceLoader.GetForCurrentView().GetString("HomeDownloadAllButtonMeteredConnectionDialogTitle"), Title = ResourceLoader.GetForCurrentView().GetString("HomeDownloadAllButtonMeteredConnectionDialogTitle"),
@@ -383,10 +396,11 @@ namespace VDownload.Views.Home
case ContentDialogResult.Secondary: delay = false; break; case ContentDialogResult.Secondary: delay = false; break;
case ContentDialogResult.None: return; case ContentDialogResult.None: return;
} }
}
foreach (HomeTaskPanel videoPanel in idleTasks) foreach (HomeTaskPanel videoPanel in idleTasks)
{ {
await Task.Delay(50); await Task.Delay(10);
#pragma warning disable CS4014 #pragma warning disable CS4014
videoPanel.Start(delay); videoPanel.Start(delay);
@@ -404,7 +418,7 @@ namespace VDownload.Views.Home
// WAIT IN QUEUE // WAIT IN QUEUE
public static async Task WaitInQueue(bool delayWhenOnMeteredConnection, CancellationToken token) public static async Task WaitInQueue(bool delayWhenOnMeteredConnection, CancellationToken token)
{ {
while ((TaskPanelsList.Where((HomeTaskPanel video) => video.TaskStatus == Core.Enums.TaskStatus.InProgress).Count() >= (int)Config.GetValue("max_active_video_task") || (delayWhenOnMeteredConnection && NetworkHelper.Instance.ConnectionInformation.IsInternetOnMeteredConnection)) && !token.IsCancellationRequested) while ((TasksList.Where((HomeTaskPanel task) => task.Status == Core.Enums.TaskStatus.InProgress).Count() >= (int)Config.GetValue("max_active_video_task") || (delayWhenOnMeteredConnection && NetworkHelper.Instance.ConnectionInformation.IsInternetOnMeteredConnection)) && !token.IsCancellationRequested)
{ {
await Task.Delay(100); await Task.Delay(100);
} }

View File

@@ -23,7 +23,7 @@
<TextBox x:Name="HomeOptionsBarAddPlaylistControlUrlTextBox" x:Uid="HomeOptionsBarAddPlaylistControlUrlTextBox" Grid.Column="0" VerticalAlignment="Center"/> <TextBox x:Name="HomeOptionsBarAddPlaylistControlUrlTextBox" x:Uid="HomeOptionsBarAddPlaylistControlUrlTextBox" Grid.Column="0" VerticalAlignment="Center"/>
<!-- MAX VIDEOS NUMBERBOX --> <!-- MAX VIDEOS NUMBERBOX -->
<muxc:NumberBox x:Name="HomeOptionsBarAddPlaylistControlMaxVideosNumberBox" Grid.Column="1" VerticalAlignment="Center" SpinButtonPlacementMode="Compact" Minimum="0" Value="{x:Bind DefaultMaxPlaylistVideos}"/> <muxc:NumberBox x:Name="HomeOptionsBarAddPlaylistControlMaxVideosNumberBox" Grid.Column="1" VerticalAlignment="Center" SpinButtonPlacementMode="Compact" Minimum="0" Value="{x:Bind DefaultMaxPlaylistVideos}" LostFocus="HomeOptionsBarAddPlaylistControlMaxVideosNumberBox_LostFocus"/>
<!-- SEARCH BUTTON--> <!-- SEARCH BUTTON-->
<Button x:Name="HomeOptionsBarAddPlaylistControlSearchButton" x:Uid="HomeOptionsBarAddPlaylistControlSearchButton" Grid.Column="2" Click="HomeOptionsBarAddPlaylistControlSearchButton_Click"/> <Button x:Name="HomeOptionsBarAddPlaylistControlSearchButton" x:Uid="HomeOptionsBarAddPlaylistControlSearchButton" Grid.Column="2" Click="HomeOptionsBarAddPlaylistControlSearchButton_Click"/>

View File

@@ -30,6 +30,12 @@ namespace VDownload.Views.Home
#region EVENT HANDLERS #region EVENT HANDLERS
// NUMBERBOX FOCUS LOST
private void HomeOptionsBarAddPlaylistControlMaxVideosNumberBox_LostFocus(object sender, RoutedEventArgs e)
{
if (double.IsNaN(HomeOptionsBarAddPlaylistControlMaxVideosNumberBox.Value)) HomeOptionsBarAddPlaylistControlMaxVideosNumberBox.Value = DefaultMaxPlaylistVideos;
}
// SEARCH BUTTON CLICKED // SEARCH BUTTON CLICKED
private void HomeOptionsBarAddPlaylistControlSearchButton_Click(object sender, RoutedEventArgs e) private void HomeOptionsBarAddPlaylistControlSearchButton_Click(object sender, RoutedEventArgs e)
{ {
@@ -39,8 +45,8 @@ namespace VDownload.Views.Home
// Invoke search button event handlers // Invoke search button event handlers
PlaylistSearchEventArgs args = new PlaylistSearchEventArgs PlaylistSearchEventArgs args = new PlaylistSearchEventArgs
{ {
Phrase = HomeOptionsBarAddPlaylistControlUrlTextBox.Text, Url = HomeOptionsBarAddPlaylistControlUrlTextBox.Text,
Count = int.Parse(HomeOptionsBarAddPlaylistControlMaxVideosNumberBox.Text), VideosCount = int.Parse(HomeOptionsBarAddPlaylistControlMaxVideosNumberBox.Text),
}; };
SearchButtonClicked?.Invoke(this, args); SearchButtonClicked?.Invoke(this, args);
} }

View File

@@ -29,7 +29,7 @@ namespace VDownload.Views.Home
// Invoke search button event handlers // Invoke search button event handlers
VideoSearchEventArgs args = new VideoSearchEventArgs VideoSearchEventArgs args = new VideoSearchEventArgs
{ {
Phrase = HomeOptionsBarAddVideoControlUrlTextBox.Text Url = HomeOptionsBarAddVideoControlUrlTextBox.Text
}; };
SearchButtonClicked?.Invoke(this, args); SearchButtonClicked?.Invoke(this, args);
} }

View File

@@ -12,6 +12,7 @@ using VDownload.Core.Enums;
using VDownload.Core.EventArgs; using VDownload.Core.EventArgs;
using VDownload.Core.Interfaces; using VDownload.Core.Interfaces;
using VDownload.Core.Services; using VDownload.Core.Services;
using VDownload.Core.Structs;
using Windows.ApplicationModel.Resources; using Windows.ApplicationModel.Resources;
using Windows.Foundation; using Windows.Foundation;
using Windows.Foundation.Collections; using Windows.Foundation.Collections;
@@ -44,20 +45,20 @@ namespace VDownload.Views.Home
Name = PlaylistService.Name; Name = PlaylistService.Name;
// Add videos to list and search mins and maxes // Add videos to list and search mins and maxes
MinViews = PlaylistService.Videos[0].Views; MinViews = PlaylistService.Videos[0].Metadata.Views;
MaxViews = PlaylistService.Videos[0].Views; MaxViews = PlaylistService.Videos[0].Metadata.Views;
MinDate = PlaylistService.Videos[0].Date; MinDate = PlaylistService.Videos[0].Metadata.Date;
MaxDate = PlaylistService.Videos[0].Date; MaxDate = PlaylistService.Videos[0].Metadata.Date;
MinDuration = PlaylistService.Videos[0].Duration; MinDuration = PlaylistService.Videos[0].Metadata.Duration;
MaxDuration = PlaylistService.Videos[0].Duration; MaxDuration = PlaylistService.Videos[0].Metadata.Duration;
foreach (IVideoService video in PlaylistService.Videos) foreach (IVideoService video in PlaylistService.Videos)
{ {
if (video.Views < MinViews) MinViews = video.Views; if (video.Metadata.Views < MinViews) MinViews = video.Metadata.Views;
if (video.Views > MaxViews) MaxViews = video.Views; if (video.Metadata.Views > MaxViews) MaxViews = video.Metadata.Views;
if (video.Date < MinDate) MinDate = video.Date; if (video.Metadata.Date < MinDate) MinDate = video.Metadata.Date;
if (video.Date > MaxDate) MaxDate = video.Date; if (video.Metadata.Date > MaxDate) MaxDate = video.Metadata.Date;
if (video.Duration < MinDuration) MinDuration = video.Duration; if (video.Metadata.Duration < MinDuration) MinDuration = video.Metadata.Duration;
if (video.Duration > MaxDuration) MaxDuration = video.Duration; if (video.Metadata.Duration > MaxDuration) MaxDuration = video.Metadata.Duration;
HomePlaylistAddingPanelVideoPanel videoPanel = new HomePlaylistAddingPanelVideoPanel(video); HomePlaylistAddingPanelVideoPanel videoPanel = new HomePlaylistAddingPanelVideoPanel(video);
videoPanel.DeleteRequested += (s, a) => videoPanel.DeleteRequested += (s, a) =>
{ {
@@ -190,14 +191,7 @@ namespace VDownload.Views.Home
int.TryParse(maxSegments.ElementAtOrDefault(2), out int maxHours); int.TryParse(maxSegments.ElementAtOrDefault(2), out int maxHours);
TimeSpan maxDuration = new TimeSpan(maxHours, maxMinutes, maxSeconds); TimeSpan maxDuration = new TimeSpan(maxHours, maxMinutes, maxSeconds);
List<HomePlaylistAddingPanelVideoPanel> allVideos = new List<HomePlaylistAddingPanelVideoPanel>(); // Title and author regex
foreach (HomePlaylistAddingPanelVideoPanel videoPanel in HomePlaylistAddingPanelVideosList.Children) allVideos.Add(videoPanel);
foreach (HomePlaylistAddingPanelVideoPanel videoPanel in HiddenVideos) allVideos.Add(videoPanel);
HomePlaylistAddingPanelVideosList.Children.Clear();
HiddenVideos.Clear();
foreach (HomePlaylistAddingPanelVideoPanel videoPanel in allVideos)
{
Regex titleRegex = new Regex(""); Regex titleRegex = new Regex("");
Regex authorRegex = new Regex(""); Regex authorRegex = new Regex("");
try try
@@ -206,16 +200,28 @@ namespace VDownload.Views.Home
authorRegex = new Regex(HomePlaylistAddingPanelFilterAuthorTextBox.Text); authorRegex = new Regex(HomePlaylistAddingPanelFilterAuthorTextBox.Text);
} }
catch (ArgumentException) { } catch (ArgumentException) { }
if (!titleRegex.IsMatch(videoPanel.VideoService.Title)) HiddenVideos.Add(videoPanel);
else if (!authorRegex.IsMatch(videoPanel.VideoService.Author)) HiddenVideos.Add(videoPanel); List<HomePlaylistAddingPanelVideoPanel> allVideos = new List<HomePlaylistAddingPanelVideoPanel>();
else if (HomePlaylistAddingPanelFilterMinViewsNumberBox.Value > videoPanel.VideoService.Views) HiddenVideos.Add(videoPanel); foreach (HomePlaylistAddingPanelVideoPanel videoPanel in HomePlaylistAddingPanelVideosList.Children) allVideos.Add(videoPanel);
else if (HomePlaylistAddingPanelFilterMaxViewsNumberBox.Value < videoPanel.VideoService.Views) HiddenVideos.Add(videoPanel); foreach (HomePlaylistAddingPanelVideoPanel videoPanel in HiddenVideos) allVideos.Add(videoPanel);
else if (HomePlaylistAddingPanelFilterMinDateDatePicker.Date > videoPanel.VideoService.Date) HiddenVideos.Add(videoPanel); HomePlaylistAddingPanelVideosList.Children.Clear();
else if (HomePlaylistAddingPanelFilterMaxDateDatePicker.Date < videoPanel.VideoService.Date) HiddenVideos.Add(videoPanel); HiddenVideos.Clear();
else if (minDuration > videoPanel.VideoService.Duration) HiddenVideos.Add(videoPanel);
else if (maxDuration < videoPanel.VideoService.Duration) HiddenVideos.Add(videoPanel); foreach (HomePlaylistAddingPanelVideoPanel videoPanel in allVideos)
{
if (
!titleRegex.IsMatch(videoPanel.VideoService.Metadata.Title) ||
!authorRegex.IsMatch(videoPanel.VideoService.Metadata.Author) ||
HomePlaylistAddingPanelFilterMinViewsNumberBox.Value > videoPanel.VideoService.Metadata.Views ||
HomePlaylistAddingPanelFilterMaxViewsNumberBox.Value < videoPanel.VideoService.Metadata.Views ||
HomePlaylistAddingPanelFilterMinDateDatePicker.Date > videoPanel.VideoService.Metadata.Date ||
HomePlaylistAddingPanelFilterMaxDateDatePicker.Date < videoPanel.VideoService.Metadata.Date ||
minDuration > videoPanel.VideoService.Metadata.Duration ||
maxDuration < videoPanel.VideoService.Metadata.Duration
) HiddenVideos.Add(videoPanel);
else HomePlaylistAddingPanelVideosList.Children.Add(videoPanel); else HomePlaylistAddingPanelVideosList.Children.Add(videoPanel);
} }
HomePlaylistAddingPanelFilterHeaderCountTextBlock.Text = HiddenVideos.Count + DeletedVideos.Count > 0 ? $"{ResourceLoader.GetForCurrentView().GetString("HomePlaylistAddingPanelFilterHeaderCountTextBlockPrefix")}: {HiddenVideos.Count + DeletedVideos.Count}" : ""; HomePlaylistAddingPanelFilterHeaderCountTextBlock.Text = HiddenVideos.Count + DeletedVideos.Count > 0 ? $"{ResourceLoader.GetForCurrentView().GetString("HomePlaylistAddingPanelFilterHeaderCountTextBlockPrefix")}: {HiddenVideos.Count + DeletedVideos.Count}" : "";
} }
@@ -327,18 +333,22 @@ namespace VDownload.Views.Home
TimeSpan parsedTimeSpan = new TimeSpan(hours, minutes, seconds); TimeSpan parsedTimeSpan = new TimeSpan(hours, minutes, seconds);
if (parsedTimeSpan < MinDuration && parsedTimeSpan > MaxDuration) if (parsedTimeSpan < MinDuration || parsedTimeSpan > MaxDuration)
{ {
if (Math.Floor(MaxDuration.TotalHours) > 0) HomePlaylistAddingPanelFilterMinDurationTextBox.Text += $"{Math.Floor(MinDuration.TotalHours)}:"; string newMinDuration = "";
if (Math.Floor(MaxDuration.TotalMinutes) > 0) HomePlaylistAddingPanelFilterMinDurationTextBox.Text += Math.Floor(MaxDuration.TotalHours) > 0 ? $"{MinDuration.Minutes:00}:" : $"{MinDuration.Minutes}:"; if (Math.Floor(MaxDuration.TotalHours) > 0) newMinDuration += $"{Math.Floor(MinDuration.TotalHours)}:";
HomePlaylistAddingPanelFilterMinDurationTextBox.Text += Math.Floor(MaxDuration.TotalMinutes) > 0 ? $"{MinDuration.Seconds:00}" : $"{MinDuration.Seconds}"; if (Math.Floor(MaxDuration.TotalMinutes) > 0) newMinDuration += Math.Floor(MaxDuration.TotalHours) > 0 ? $"{MinDuration.Minutes:00}:" : $"{MinDuration.Minutes}:";
newMinDuration += Math.Floor(MaxDuration.TotalMinutes) > 0 ? $"{MinDuration.Seconds:00}" : $"{MinDuration.Seconds}";
HomePlaylistAddingPanelFilterMinDurationTextBox.Text = newMinDuration;
} }
} }
else else
{ {
if (Math.Floor(MaxDuration.TotalHours) > 0) HomePlaylistAddingPanelFilterMinDurationTextBox.Text += $"{Math.Floor(MinDuration.TotalHours)}:"; string newMinDuration = "";
if (Math.Floor(MaxDuration.TotalMinutes) > 0) HomePlaylistAddingPanelFilterMinDurationTextBox.Text += Math.Floor(MaxDuration.TotalHours) > 0 ? $"{MinDuration.Minutes:00}:" : $"{MinDuration.Minutes}:"; if (Math.Floor(MaxDuration.TotalHours) > 0) newMinDuration += $"{Math.Floor(MinDuration.TotalHours)}:";
HomePlaylistAddingPanelFilterMinDurationTextBox.Text += Math.Floor(MaxDuration.TotalMinutes) > 0 ? $"{MinDuration.Seconds:00}" : $"{MinDuration.Seconds}"; if (Math.Floor(MaxDuration.TotalMinutes) > 0) newMinDuration += Math.Floor(MaxDuration.TotalHours) > 0 ? $"{MinDuration.Minutes:00}:" : $"{MinDuration.Minutes}:";
newMinDuration += Math.Floor(MaxDuration.TotalMinutes) > 0 ? $"{MinDuration.Seconds:00}" : $"{MinDuration.Seconds}";
HomePlaylistAddingPanelFilterMinDurationTextBox.Text = newMinDuration;
} }
FilterChanged(); FilterChanged();
} }
@@ -355,18 +365,22 @@ namespace VDownload.Views.Home
TimeSpan parsedTimeSpan = new TimeSpan(hours, minutes, seconds); TimeSpan parsedTimeSpan = new TimeSpan(hours, minutes, seconds);
if (parsedTimeSpan < MinDuration && parsedTimeSpan > MaxDuration) if (parsedTimeSpan < MinDuration || parsedTimeSpan > MaxDuration)
{ {
if (Math.Floor(MaxDuration.TotalHours) > 0) HomePlaylistAddingPanelFilterMaxDurationTextBox.Text += $"{Math.Floor(MaxDuration.TotalHours)}:"; string newMaxDuration = "";
if (Math.Floor(MaxDuration.TotalMinutes) > 0) HomePlaylistAddingPanelFilterMaxDurationTextBox.Text += Math.Floor(MaxDuration.TotalHours) > 0 ? $"{MaxDuration.Minutes:00}:" : $"{MaxDuration.Minutes}:"; if (Math.Floor(MaxDuration.TotalHours) > 0) newMaxDuration += $"{Math.Floor(MaxDuration.TotalHours)}:";
HomePlaylistAddingPanelFilterMaxDurationTextBox.Text += Math.Floor(MaxDuration.TotalMinutes) > 0 ? $"{MaxDuration.Seconds:00}" : $"{MaxDuration.Seconds}"; if (Math.Floor(MaxDuration.TotalMinutes) > 0) newMaxDuration += Math.Floor(MaxDuration.TotalHours) > 0 ? $"{MaxDuration.Minutes:00}:" : $"{MaxDuration.Minutes}:";
newMaxDuration += Math.Floor(MaxDuration.TotalMinutes) > 0 ? $"{MaxDuration.Seconds:00}" : $"{MaxDuration.Seconds}";
HomePlaylistAddingPanelFilterMaxDurationTextBox.Text = newMaxDuration;
} }
} }
else else
{ {
if (Math.Floor(MaxDuration.TotalHours) > 0) HomePlaylistAddingPanelFilterMaxDurationTextBox.Text += $"{Math.Floor(MaxDuration.TotalHours)}:"; string newMaxDuration = "";
if (Math.Floor(MaxDuration.TotalMinutes) > 0) HomePlaylistAddingPanelFilterMaxDurationTextBox.Text += Math.Floor(MaxDuration.TotalHours) > 0 ? $"{MaxDuration.Minutes:00}:" : $"{MaxDuration.Minutes}:"; if (Math.Floor(MaxDuration.TotalHours) > 0) newMaxDuration += $"{Math.Floor(MaxDuration.TotalHours)}:";
HomePlaylistAddingPanelFilterMaxDurationTextBox.Text += Math.Floor(MaxDuration.TotalMinutes) > 0 ? $"{MaxDuration.Seconds:00}" : $"{MaxDuration.Seconds}"; if (Math.Floor(MaxDuration.TotalMinutes) > 0) newMaxDuration += Math.Floor(MaxDuration.TotalHours) > 0 ? $"{MaxDuration.Minutes:00}:" : $"{MaxDuration.Minutes}:";
newMaxDuration += Math.Floor(MaxDuration.TotalMinutes) > 0 ? $"{MaxDuration.Seconds:00}" : $"{MaxDuration.Seconds}";
HomePlaylistAddingPanelFilterMaxDurationTextBox.Text = newMaxDuration;
} }
FilterChanged(); FilterChanged();
} }
@@ -385,6 +399,7 @@ namespace VDownload.Views.Home
DeletedVideos.Clear(); DeletedVideos.Clear();
} }
// SOURCE BUTTON CLICKED // SOURCE BUTTON CLICKED
private async void HomePlaylistAddingPanelSourceButton_Click(object sender, RoutedEventArgs e) private async void HomePlaylistAddingPanelSourceButton_Click(object sender, RoutedEventArgs e)
{ {
@@ -395,15 +410,33 @@ namespace VDownload.Views.Home
// ADD BUTTON CLICKED // ADD BUTTON CLICKED
private void HomePlaylistAddingPanelAddButton_Click(object sender, RoutedEventArgs e) private void HomePlaylistAddingPanelAddButton_Click(object sender, RoutedEventArgs e)
{ {
var videos = new List<(IVideoService VideoService, MediaType MediaType, IBaseStream Stream, TimeSpan TrimStart, TimeSpan TrimEnd, string Filename, MediaFileExtension Extension, StorageFolder Location, double Schedule)>(); // Pack tasks data
List<TaskData> taskDataList = new List<TaskData>();
foreach (HomePlaylistAddingPanelVideoPanel videoPanel in HomePlaylistAddingPanelVideosList.Children) foreach (HomePlaylistAddingPanelVideoPanel videoPanel in HomePlaylistAddingPanelVideosList.Children)
{ {
videos.Add((videoPanel.VideoService, videoPanel.MediaType, videoPanel.Stream, videoPanel.TrimStart, videoPanel.TrimEnd, videoPanel.Filename, videoPanel.Extension, videoPanel.Location, videoPanel.Schedule)); TaskData taskData = new TaskData
} {
PlaylistAddEventArgs eventArgs = new PlaylistAddEventArgs { Videos = videos.ToArray() }; VideoService = videoPanel.VideoService,
PlaylistAddRequest?.Invoke(this, eventArgs); MediaType = videoPanel.MediaType,
Stream = videoPanel.Stream,
TrimStart = videoPanel.TrimStart,
TrimEnd = videoPanel.TrimEnd,
Filename = videoPanel.Filename,
Extension = videoPanel.Extension,
Location = videoPanel.Location,
Schedule = videoPanel.Schedule,
};
taskDataList.Add(taskData);
} }
// Request tasks adding
TasksAddingRequestedEventArgs eventArgs = new TasksAddingRequestedEventArgs
{
TaskData = taskDataList.ToArray(),
RequestSource = TaskAddingRequestSource.Playlist
};
TasksAddingRequested?.Invoke(this, eventArgs);
}
#endregion #endregion
@@ -411,7 +444,7 @@ namespace VDownload.Views.Home
#region EVENT HANDLERS #region EVENT HANDLERS
public event EventHandler<PlaylistAddEventArgs> PlaylistAddRequest; public event EventHandler<TasksAddingRequestedEventArgs> TasksAddingRequested;
#endregion #endregion
} }

View File

@@ -87,9 +87,9 @@
<cc:SettingControl x:Uid="HomePlaylistAddingVideoPanelTrimSettingControl" Icon="{ThemeResource TrimIcon}"> <cc:SettingControl x:Uid="HomePlaylistAddingVideoPanelTrimSettingControl" Icon="{ThemeResource TrimIcon}">
<cc:SettingControl.SettingContent> <cc:SettingControl.SettingContent>
<StackPanel Orientation="Horizontal" Spacing="5"> <StackPanel Orientation="Horizontal" Spacing="5">
<TextBox x:Name="HomePlaylistAddingVideoPanelTrimStartTextBox" ex:TextBoxExtensions.CustomMask="{x:Bind VideoService.Duration, Converter={StaticResource TimeSpanToTextBoxMaskElementsConverter}}" ex:TextBoxExtensions.Mask="{x:Bind VideoService.Duration, Converter={StaticResource TimeSpanToTextBoxMaskConverter}}" TextChanged="HomePlaylistAddingVideoPanelTrimStartTextBox_TextChanged"/> <TextBox x:Name="HomePlaylistAddingVideoPanelTrimStartTextBox" ex:TextBoxExtensions.CustomMask="{x:Bind VideoService.Metadata.Duration, Converter={StaticResource TimeSpanToTextBoxMaskElementsConverter}}" ex:TextBoxExtensions.Mask="{x:Bind VideoService.Metadata.Duration, Converter={StaticResource TimeSpanToTextBoxMaskConverter}}" TextChanged="HomePlaylistAddingVideoPanelTrimStartTextBox_TextChanged"/>
<TextBlock VerticalAlignment="Center" Text="-"/> <TextBlock VerticalAlignment="Center" Text="-"/>
<TextBox x:Name="HomePlaylistAddingVideoPanelTrimEndTextBox" ex:TextBoxExtensions.CustomMask="{x:Bind VideoService.Duration, Converter={StaticResource TimeSpanToTextBoxMaskElementsConverter}}" ex:TextBoxExtensions.Mask="{x:Bind VideoService.Duration, Converter={StaticResource TimeSpanToTextBoxMaskConverter}}" TextChanged="HomePlaylistAddingVideoPanelTrimEndTextBox_TextChanged"/> <TextBox x:Name="HomePlaylistAddingVideoPanelTrimEndTextBox" ex:TextBoxExtensions.CustomMask="{x:Bind VideoService.Metadata.Duration, Converter={StaticResource TimeSpanToTextBoxMaskElementsConverter}}" ex:TextBoxExtensions.Mask="{x:Bind VideoService.Metadata.Duration, Converter={StaticResource TimeSpanToTextBoxMaskConverter}}" TextChanged="HomePlaylistAddingVideoPanelTrimEndTextBox_TextChanged"/>
</StackPanel> </StackPanel>
</cc:SettingControl.SettingContent> </cc:SettingControl.SettingContent>
</cc:SettingControl> </cc:SettingControl>

View File

@@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization; using System.Globalization;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
@@ -9,6 +10,7 @@ using System.Threading.Tasks;
using VDownload.Core.Enums; using VDownload.Core.Enums;
using VDownload.Core.Interfaces; using VDownload.Core.Interfaces;
using VDownload.Core.Services; using VDownload.Core.Services;
using VDownload.Core.Structs;
using Windows.ApplicationModel.Resources; using Windows.ApplicationModel.Resources;
using Windows.Foundation; using Windows.Foundation;
using Windows.Foundation.Collections; using Windows.Foundation.Collections;
@@ -46,13 +48,13 @@ namespace VDownload.Views.Home
VideoService = videoService; VideoService = videoService;
// Set metadata // Set metadata
ThumbnailImage = VideoService.Thumbnail != null ? new BitmapImage { UriSource = VideoService.Thumbnail } : (BitmapImage)ImagesRes["UnknownThumbnailImage"]; ThumbnailImage = VideoService.Metadata.Thumbnail != null ? new BitmapImage { UriSource = VideoService.Metadata.Thumbnail } : (BitmapImage)ImagesRes["UnknownThumbnailImage"];
SourceImage = new BitmapIcon { UriSource = new Uri($"ms-appx:///Assets/Sources/{VideoService.GetType().Namespace.Split(".").Last()}.png"), ShowAsMonochrome = false }; SourceImage = new BitmapIcon { UriSource = new Uri($"ms-appx:///Assets/Sources/{VideoService.GetType().Namespace.Split(".").Last()}.png"), ShowAsMonochrome = false };
Title = VideoService.Title; Title = VideoService.Metadata.Title;
Author = VideoService.Author; Author = VideoService.Metadata.Author;
Views = VideoService.Views.ToString(); Views = VideoService.Metadata.Views.ToString();
Date = VideoService.Date.ToString(CultureInfo.InstalledUICulture.DateTimeFormat.ShortDatePattern); Date = VideoService.Metadata.Date.ToString(CultureInfo.InstalledUICulture.DateTimeFormat.ShortDatePattern);
Duration = $"{(Math.Floor(VideoService.Duration.TotalHours) > 0 ? $"{Math.Floor(VideoService.Duration.TotalHours):0}:" : "")}{VideoService.Duration.Minutes:00}:{VideoService.Duration.Seconds:00}"; Duration = $"{(Math.Floor(VideoService.Metadata.Duration.TotalHours) > 0 ? $"{Math.Floor(VideoService.Metadata.Duration.TotalHours):0}:" : "")}{VideoService.Metadata.Duration.Minutes:00}:{VideoService.Metadata.Duration.Seconds:00}";
// Set media type // Set media type
foreach (string mediaType in Enum.GetNames(typeof(MediaType))) foreach (string mediaType in Enum.GetNames(typeof(MediaType)))
@@ -62,37 +64,39 @@ namespace VDownload.Views.Home
HomePlaylistAddingVideoPanelMediaTypeSettingControlComboBox.SelectedIndex = (int)Config.GetValue("default_media_type"); HomePlaylistAddingVideoPanelMediaTypeSettingControlComboBox.SelectedIndex = (int)Config.GetValue("default_media_type");
// Set quality // Set quality
foreach (IBaseStream stream in VideoService.BaseStreams) foreach (BaseStream stream in VideoService.BaseStreams)
{ {
HomePlaylistAddingVideoPanelQualitySettingControlComboBox.Items.Add($"{stream.Height}p{(stream.FrameRate > 0 ? stream.FrameRate.ToString() : "N/A")}"); HomePlaylistAddingVideoPanelQualitySettingControlComboBox.Items.Add($"{stream.Height}p{(stream.FrameRate > 0 ? stream.FrameRate.ToString() : "N/A")}");
} }
HomePlaylistAddingVideoPanelQualitySettingControlComboBox.SelectedIndex = 0; HomePlaylistAddingVideoPanelQualitySettingControlComboBox.SelectedIndex = 0;
// Set trim start // Set trim start
if (Math.Floor(VideoService.Duration.TotalHours) > 0) HomePlaylistAddingVideoPanelTrimStartTextBox.Text += $"{new string('0', Math.Floor(VideoService.Duration.TotalHours).ToString().Length)}:"; TrimStart = new TimeSpan(0);
if (Math.Floor(VideoService.Duration.TotalMinutes) > 0) HomePlaylistAddingVideoPanelTrimStartTextBox.Text += Math.Floor(VideoService.Duration.TotalHours) > 0 ? "00:" : $"{new string('0', VideoService.Duration.Minutes.ToString().Length)}:"; if (Math.Floor(VideoService.Metadata.Duration.TotalHours) > 0) HomePlaylistAddingVideoPanelTrimStartTextBox.Text += $"{new string('0', Math.Floor(VideoService.Metadata.Duration.TotalHours).ToString().Length)}:";
HomePlaylistAddingVideoPanelTrimStartTextBox.Text += Math.Floor(VideoService.Duration.TotalMinutes) > 0 ? "00" : $"{new string('0', VideoService.Duration.Seconds.ToString().Length)}"; if (Math.Floor(VideoService.Metadata.Duration.TotalMinutes) > 0) HomePlaylistAddingVideoPanelTrimStartTextBox.Text += Math.Floor(VideoService.Metadata.Duration.TotalHours) > 0 ? "00:" : $"{new string('0', VideoService.Metadata.Duration.Minutes.ToString().Length)}:";
HomePlaylistAddingVideoPanelTrimStartTextBox.Text += Math.Floor(VideoService.Metadata.Duration.TotalMinutes) > 0 ? "00" : $"{new string('0', VideoService.Metadata.Duration.Seconds.ToString().Length)}";
// Set trim end // Set trim end
if (Math.Floor(VideoService.Duration.TotalHours) > 0) HomePlaylistAddingVideoPanelTrimEndTextBox.Text += $"{Math.Floor(VideoService.Duration.TotalHours)}:"; TrimEnd = VideoService.Metadata.Duration;
if (Math.Floor(VideoService.Duration.TotalMinutes) > 0) HomePlaylistAddingVideoPanelTrimEndTextBox.Text += Math.Floor(VideoService.Duration.TotalHours) > 0 ? $"{VideoService.Duration.Minutes:00}:" : $"{VideoService.Duration.Minutes}:"; if (Math.Floor(VideoService.Metadata.Duration.TotalHours) > 0) HomePlaylistAddingVideoPanelTrimEndTextBox.Text += $"{Math.Floor(VideoService.Metadata.Duration.TotalHours)}:";
HomePlaylistAddingVideoPanelTrimEndTextBox.Text += Math.Floor(VideoService.Duration.TotalMinutes) > 0 ? $"{VideoService.Duration.Seconds:00}" : $"{VideoService.Duration.Seconds}"; if (Math.Floor(VideoService.Metadata.Duration.TotalMinutes) > 0) HomePlaylistAddingVideoPanelTrimEndTextBox.Text += Math.Floor(VideoService.Metadata.Duration.TotalHours) > 0 ? $"{VideoService.Metadata.Duration.Minutes:00}:" : $"{VideoService.Metadata.Duration.Minutes}:";
HomePlaylistAddingVideoPanelTrimEndTextBox.Text += Math.Floor(VideoService.Metadata.Duration.TotalMinutes) > 0 ? $"{VideoService.Metadata.Duration.Seconds:00}" : $"{VideoService.Metadata.Duration.Seconds}";
// Set filename // Set filename
string temporaryFilename = (string)Config.GetValue("default_filename"); string temporaryFilename = (string)Config.GetValue("default_filename");
Dictionary<string, string> filenameStandardTemplates = new Dictionary<string, string>() Dictionary<string, string> filenameStandardTemplates = new Dictionary<string, string>()
{ {
{ "<title>", VideoService.Title }, { "<title>", VideoService.Metadata.Title },
{ "<author>", VideoService.Author }, { "<author>", VideoService.Metadata.Author },
{ "<views>", VideoService.Views.ToString() }, { "<views>", VideoService.Metadata.Views.ToString() },
{ "<id>", VideoService.ID }, { "<id>", VideoService.ID },
}; };
foreach (KeyValuePair<string, string> template in filenameStandardTemplates) temporaryFilename = temporaryFilename.Replace(template.Key, template.Value); foreach (KeyValuePair<string, string> template in filenameStandardTemplates) temporaryFilename = temporaryFilename.Replace(template.Key, template.Value);
Dictionary<Regex, IFormattable> filenameFormatTemplates = new Dictionary<Regex, IFormattable>() Dictionary<Regex, IFormattable> filenameFormatTemplates = new Dictionary<Regex, IFormattable>()
{ {
{ new Regex(@"<date_pub:(?<format>.*)>"), VideoService.Date }, { new Regex(@"<date_pub:(?<format>.*)>"), VideoService.Metadata.Date },
{ new Regex(@"<date_now:(?<format>.*)>"), DateTime.Now }, { new Regex(@"<date_now:(?<format>.*)>"), DateTime.Now },
{ new Regex(@"<duration:(?<format>.*)>"), VideoService.Duration }, { new Regex(@"<duration:(?<format>.*)>"), VideoService.Metadata.Duration },
}; };
foreach (KeyValuePair<Regex, IFormattable> template in filenameFormatTemplates) foreach (Match templateMatch in template.Key.Matches(temporaryFilename)) temporaryFilename = temporaryFilename.Replace(templateMatch.Value, template.Value.ToString(templateMatch.Groups["format"].Value, null)); foreach (KeyValuePair<Regex, IFormattable> template in filenameFormatTemplates) foreach (Match templateMatch in template.Key.Matches(temporaryFilename)) temporaryFilename = temporaryFilename.Replace(templateMatch.Value, template.Value.ToString(templateMatch.Groups["format"].Value, null));
foreach (char c in System.IO.Path.GetInvalidFileNameChars()) temporaryFilename = temporaryFilename.Replace(c, ' '); foreach (char c in System.IO.Path.GetInvalidFileNameChars()) temporaryFilename = temporaryFilename.Replace(c, ' ');
@@ -142,7 +146,7 @@ namespace VDownload.Views.Home
// VIDEO OPTIONS // VIDEO OPTIONS
public MediaType MediaType { get; set; } public MediaType MediaType { get; set; }
public IBaseStream Stream { get; set; } public BaseStream Stream { get; set; }
public TimeSpan TrimStart { get; set; } public TimeSpan TrimStart { get; set; }
public TimeSpan TrimEnd { get; set; } public TimeSpan TrimEnd { get; set; }
public string Filename { get; set; } public string Filename { get; set; }
@@ -204,15 +208,15 @@ namespace VDownload.Views.Home
TimeSpan parsedTimeSpan = new TimeSpan(hours, minutes, seconds); TimeSpan parsedTimeSpan = new TimeSpan(hours, minutes, seconds);
if (parsedTimeSpan < VideoService.Duration && parsedTimeSpan > new TimeSpan(0)) TrimStart = parsedTimeSpan; if (parsedTimeSpan < VideoService.Metadata.Duration && parsedTimeSpan > new TimeSpan(0)) TrimStart = parsedTimeSpan;
else else
{ {
TrimStart = new TimeSpan(0); TrimStart = new TimeSpan(0);
string newText = string.Empty; string newText = string.Empty;
if (Math.Floor(VideoService.Duration.TotalHours) > 0) newText += $"{new string('0', Math.Floor(VideoService.Duration.TotalHours).ToString().Length)}:"; if (Math.Floor(VideoService.Metadata.Duration.TotalHours) > 0) newText += $"{new string('0', Math.Floor(VideoService.Metadata.Duration.TotalHours).ToString().Length)}:";
if (Math.Floor(VideoService.Duration.TotalMinutes) > 0) newText += Math.Floor(VideoService.Duration.TotalHours) > 0 ? "00:" : $"{new string('0', VideoService.Duration.Minutes.ToString().Length)}:"; if (Math.Floor(VideoService.Metadata.Duration.TotalMinutes) > 0) newText += Math.Floor(VideoService.Metadata.Duration.TotalHours) > 0 ? "00:" : $"{new string('0', VideoService.Metadata.Duration.Minutes.ToString().Length)}:";
newText += Math.Floor(VideoService.Duration.TotalMinutes) > 0 ? "00" : $"{new string('0', VideoService.Duration.Seconds.ToString().Length)}"; newText += Math.Floor(VideoService.Metadata.Duration.TotalMinutes) > 0 ? "00" : $"{new string('0', VideoService.Metadata.Duration.Seconds.ToString().Length)}";
if (newText != HomePlaylistAddingVideoPanelTrimStartTextBox.Text) HomePlaylistAddingVideoPanelTrimStartTextBox.Text = newText; if (newText != HomePlaylistAddingVideoPanelTrimStartTextBox.Text) HomePlaylistAddingVideoPanelTrimStartTextBox.Text = newText;
} }
@@ -231,15 +235,15 @@ namespace VDownload.Views.Home
TimeSpan parsedTimeSpan = new TimeSpan(hours, minutes, seconds); TimeSpan parsedTimeSpan = new TimeSpan(hours, minutes, seconds);
if (parsedTimeSpan < VideoService.Duration && parsedTimeSpan > new TimeSpan(0)) TrimEnd = parsedTimeSpan; if (parsedTimeSpan < VideoService.Metadata.Duration && parsedTimeSpan > new TimeSpan(0)) TrimEnd = parsedTimeSpan;
else else
{ {
TrimEnd = VideoService.Duration; TrimEnd = VideoService.Metadata.Duration;
string newText = string.Empty; string newText = string.Empty;
if (Math.Floor(VideoService.Duration.TotalHours) > 0) newText += $"{Math.Floor(VideoService.Duration.TotalHours)}:"; if (Math.Floor(VideoService.Metadata.Duration.TotalHours) > 0) newText += $"{Math.Floor(VideoService.Metadata.Duration.TotalHours)}:";
if (Math.Floor(VideoService.Duration.TotalMinutes) > 0) newText += Math.Floor(VideoService.Duration.TotalHours) > 0 ? $"{TrimEnd.Minutes:00}:" : $"{TrimEnd.Minutes}:"; if (Math.Floor(VideoService.Metadata.Duration.TotalMinutes) > 0) newText += Math.Floor(VideoService.Metadata.Duration.TotalHours) > 0 ? $"{TrimEnd.Minutes:00}:" : $"{TrimEnd.Minutes}:";
newText += Math.Floor(VideoService.Duration.TotalMinutes) > 0 ? $"{TrimEnd.Seconds:00}" : $"{TrimEnd.Seconds}"; newText += Math.Floor(VideoService.Metadata.Duration.TotalMinutes) > 0 ? $"{TrimEnd.Seconds:00}" : $"{TrimEnd.Seconds}";
if (newText != HomePlaylistAddingVideoPanelTrimEndTextBox.Text) HomePlaylistAddingVideoPanelTrimEndTextBox.Text = newText; if (newText != HomePlaylistAddingVideoPanelTrimEndTextBox.Text) HomePlaylistAddingVideoPanelTrimEndTextBox.Text = newText;
} }

View File

@@ -39,8 +39,8 @@
<ColumnDefinition Width="*"/> <ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/> <ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" FontSize="18" VerticalAlignment="Center" FontWeight="SemiBold" Text="{x:Bind Title}"/> <TextBlock Grid.Column="0" FontSize="18" VerticalAlignment="Center" FontWeight="SemiBold" Text="{x:Bind Data.VideoService.Metadata.Title}"/>
<TextBlock Grid.Column="1" FontSize="12" VerticalAlignment="Bottom" FontWeight="Light" Text="{x:Bind Author}" Margin="0,0,0,2"/> <TextBlock Grid.Column="1" FontSize="12" VerticalAlignment="Bottom" FontWeight="Light" Text="{x:Bind Data.VideoService.Metadata.Author}" Margin="0,0,0,2"/>
</Grid> </Grid>
<Grid Grid.Row="1" Grid.RowSpan="2" Grid.Column="1" ColumnSpacing="5"> <Grid Grid.Row="1" Grid.RowSpan="2" Grid.Column="1" ColumnSpacing="5">
<Grid.RowDefinitions> <Grid.RowDefinitions>

View File

@@ -1,4 +1,5 @@
using Microsoft.Toolkit.Uwp.Notifications; using Microsoft.Toolkit.Uwp.Connectivity;
using Microsoft.Toolkit.Uwp.Notifications;
using System; using System;
using System.Diagnostics; using System.Diagnostics;
using System.Globalization; using System.Globalization;
@@ -7,8 +8,8 @@ using System.Net;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using VDownload.Core.Enums; using VDownload.Core.Enums;
using VDownload.Core.Interfaces;
using VDownload.Core.Services; using VDownload.Core.Services;
using VDownload.Core.Structs;
using Windows.ApplicationModel.ExtendedExecution; using Windows.ApplicationModel.ExtendedExecution;
using Windows.ApplicationModel.Resources; using Windows.ApplicationModel.Resources;
using Windows.Storage; using Windows.Storage;
@@ -24,8 +25,8 @@ namespace VDownload.Views.Home
{ {
#region CONSTANTS #region CONSTANTS
private readonly ResourceDictionary IconsRes = new ResourceDictionary { Source = new Uri("ms-appx:///Resources/Icons.xaml") }; private static readonly ResourceDictionary IconsRes = new ResourceDictionary { Source = new Uri("ms-appx:///Resources/Icons.xaml") };
private readonly ResourceDictionary ImagesRes = new ResourceDictionary { Source = new Uri("ms-appx:///Resources/Images.xaml") }; private static readonly ResourceDictionary ImagesRes = new ResourceDictionary { Source = new Uri("ms-appx:///Resources/Images.xaml") };
#endregion #endregion
@@ -33,40 +34,36 @@ namespace VDownload.Views.Home
#region CONSTRUCTORS #region CONSTRUCTORS
public HomeTaskPanel(IVideoService videoService, MediaType mediaType, IBaseStream stream, TimeSpan trimStart, TimeSpan trimEnd, string filename, MediaFileExtension extension, StorageFolder location, double schedule) public HomeTaskPanel(TaskData taskData)
{ {
this.InitializeComponent(); this.InitializeComponent();
// Set video status // Set task data
TaskStatus = Core.Enums.TaskStatus.Idle; Data = taskData;
// Set video service object // Set video status
VideoService = videoService; Status = Core.Enums.TaskStatus.Idle;
// Create video cancellation token // Create video cancellation token
CancellationTokenSource = new CancellationTokenSource(); CancellationTokenSource = new CancellationTokenSource();
// Set video options // Set thumbnail image
MediaType = mediaType; ThumbnailImage = Data.VideoService.Metadata.Thumbnail != null ? new BitmapImage { UriSource = Data.VideoService.Metadata.Thumbnail } : (BitmapImage)ImagesRes["UnknownThumbnailImage"];
Stream = stream;
TrimStart = trimStart;
TrimEnd = trimEnd;
Filename = filename;
Extension = extension;
Location = location;
Schedule = schedule;
// Set metadata // Set source icon
ThumbnailImage = VideoService.Thumbnail != null ? new BitmapImage { UriSource = VideoService.Thumbnail } : (BitmapImage)ImagesRes["UnknownThumbnailImage"]; SourceImage = new BitmapIcon { UriSource = new Uri($"ms-appx:///Assets/Sources/{Data.VideoService.GetType().Namespace.Split(".").Last()}.png"), ShowAsMonochrome = false };
SourceImage = new BitmapIcon { UriSource = new Uri($"ms-appx:///Assets/Sources/{VideoService.GetType().Namespace.Split(".").Last()}.png"), ShowAsMonochrome = false };
Title = VideoService.Title; // Set duration
Author = VideoService.Author; TimeSpan newDuration = Data.TrimEnd.Subtract(Data.TrimStart);
TimeSpan newDuration = TrimEnd.Subtract(TrimStart); Duration = TimeSpanCustomFormat.ToOptTHBaseMMSS(newDuration);
Duration += $"{(Math.Floor(newDuration.TotalHours) > 0 ? $"{Math.Floor(newDuration.TotalHours):0}:" : "")}{newDuration.Minutes:00}:{newDuration.Seconds:00}"; if (Data.VideoService.Metadata.Duration > newDuration) Duration += $" ({TimeSpanCustomFormat.ToOptTHBaseMMSS(Data.TrimStart, Data.TrimEnd)} - {TimeSpanCustomFormat.ToOptTHBaseMMSS(Data.TrimEnd, Data.TrimStart)})";
if (VideoService.Duration > newDuration) Duration += $" ({(Math.Floor(TrimStart.TotalHours) > 0 || Math.Floor(TrimEnd.TotalHours) > 0 ? $"{Math.Floor(TrimStart.TotalHours):0}:" : "")}{TrimStart.Minutes:00}:{TrimStart.Seconds:00} - {(Math.Floor(TrimStart.TotalHours) > 0 || Math.Floor(TrimEnd.TotalHours) > 0 ? $"{Math.Floor(TrimEnd.TotalHours):0}:" : "")}{TrimEnd.Minutes:00}:{TrimEnd.Seconds:00})";
MediaTypeQuality += ResourceLoader.GetForCurrentView().GetString($"MediaType{MediaType}Text"); // Set media type
if (MediaType != MediaType.OnlyAudio) MediaTypeQuality += $" ({Stream.Height}p{(Stream.FrameRate > 0 ? Stream.FrameRate.ToString() : "N/A")})"; MediaTypeQuality += ResourceLoader.GetForCurrentView().GetString($"MediaType{Data.MediaType}Text");
File += $@"{(Location != null ? Location.Path : $@"{UserDataPaths.GetDefault().Downloads}\VDownload")}\{Filename}.{Extension.ToString().ToLower()}"; if (Data.MediaType != MediaType.OnlyAudio) MediaTypeQuality += $" ({Data.Stream.Height}p{(Data.Stream.FrameRate > 0 ? Data.Stream.FrameRate.ToString() : "N/A")})";
// Set file
File += $@"{(Data.Location != null ? Data.Location.Path : $@"{UserDataPaths.GetDefault().Downloads}\VDownload")}\{Data.Filename}.{Data.Extension.ToString().ToLower()}";
// Set state controls // Set state controls
HomeTaskPanelStateIcon.Source = (SvgImageSource)IconsRes["StateIdleIcon"]; HomeTaskPanelStateIcon.Source = (SvgImageSource)IconsRes["StateIdleIcon"];
@@ -80,26 +77,16 @@ namespace VDownload.Views.Home
#region PROPERTIES #region PROPERTIES
// VIDEO STATUS // TASK STATUS
public Core.Enums.TaskStatus TaskStatus { get; set; } public Core.Enums.TaskStatus Status { get; set; }
// TASK CANCELLATION TOKEN // TASK CANCELLATION TOKEN
public CancellationTokenSource CancellationTokenSource { get; set; } public CancellationTokenSource CancellationTokenSource { get; set; }
// VIDEO SERVICE // TASK DATA
private IVideoService VideoService { get; set; } private TaskData Data { get; set; }
// VIDEO OPTIONS // TASK PANEL DATA
private MediaType MediaType { get; set; }
private IBaseStream Stream { get; set; }
private TimeSpan TrimStart { get; set; }
private TimeSpan TrimEnd { get; set; }
private string Filename { get; set; }
private MediaFileExtension Extension { get; set; }
private StorageFolder Location { get; set; }
private double Schedule { get; set; }
// VIDEO PANEL DATA
private ImageSource ThumbnailImage { get; set; } private ImageSource ThumbnailImage { get; set; }
private IconElement SourceImage { get; set; } private IconElement SourceImage { get; set; }
private string Title { get; set; } private string Title { get; set; }
@@ -123,12 +110,12 @@ namespace VDownload.Views.Home
CancellationTokenSource = new CancellationTokenSource(); CancellationTokenSource = new CancellationTokenSource();
// Scheduling // Scheduling
if (Schedule > 0) if (Data.Schedule > 0)
{ {
DateTime ScheduledDateTime = DateTime.Now.AddMinutes(Schedule); DateTime ScheduledDateTime = DateTime.Now.AddMinutes(Data.Schedule);
// Set task status // Set task status
TaskStatus = Core.Enums.TaskStatus.Scheduled; Status = Core.Enums.TaskStatus.Scheduled;
// Set state controls // Set state controls
HomeTaskPanelStateIcon.Source = (SvgImageSource)IconsRes["StateScheduledIcon"]; HomeTaskPanelStateIcon.Source = (SvgImageSource)IconsRes["StateScheduledIcon"];
@@ -139,20 +126,19 @@ namespace VDownload.Views.Home
} }
// Set task status // Set task status
TaskStatus = Core.Enums.TaskStatus.Waiting; Status = Core.Enums.TaskStatus.Waiting;
// Set state controls // Set state controls
HomeTaskPanelStateIcon.Source = (SvgImageSource)IconsRes["StateWaitingIcon"]; HomeTaskPanelStateIcon.Source = (SvgImageSource)IconsRes["StateWaitingIcon"];
HomeTaskPanelStateText.Text = ResourceLoader.GetForCurrentView().GetString("HomeTaskPanelStateTextWaiting"); HomeTaskPanelStateText.Text = ResourceLoader.GetForCurrentView().GetString("HomeTaskPanelStateTextWaiting");
HomeTaskPanelStateProgressBar.Visibility = Visibility.Visible; HomeTaskPanelStateProgressBar.Visibility = Visibility.Visible;
HomeTaskPanelStateProgressBar.IsIndeterminate = true;
// Wait in queue // Wait in queue
await HomeMain.WaitInQueue(delayWhenOnMeteredConnection, CancellationTokenSource.Token); await HomeMain.WaitInQueue(delayWhenOnMeteredConnection, CancellationTokenSource.Token);
if (!CancellationTokenSource.IsCancellationRequested) if (!CancellationTokenSource.IsCancellationRequested)
{ {
// Set task status // Set task status
TaskStatus = Core.Enums.TaskStatus.InProgress; Status = Core.Enums.TaskStatus.InProgress;
// Get task unique ID // Get task unique ID
string uniqueID = TaskId.Get(); string uniqueID = TaskId.Get();
@@ -176,30 +162,17 @@ namespace VDownload.Views.Home
Stopwatch taskStopwatch = Stopwatch.StartNew(); Stopwatch taskStopwatch = Stopwatch.StartNew();
// Set progress event handlers // Set progress event handlers
VideoService.DownloadingStarted += (s, a) => Data.VideoService.DownloadingProgressChanged += (s, a) =>
{ {
HomeTaskPanelStateIcon.Source = (SvgImageSource)IconsRes["StateDownloadingIcon"]; HomeTaskPanelStateIcon.Source = (SvgImageSource)IconsRes["StateDownloadingIcon"];
HomeTaskPanelStateText.Text = $"{ResourceLoader.GetForCurrentView().GetString("HomeTaskPanelStateTextDownloading")} (0%)"; HomeTaskPanelStateText.Text = $"{ResourceLoader.GetForCurrentView().GetString("HomeTaskPanelStateTextDownloading")} ({Math.Round(a.Progress)}%)";
HomeTaskPanelStateProgressBar.IsIndeterminate = false; HomeTaskPanelStateProgressBar.Value = a.Progress;
HomeTaskPanelStateProgressBar.Value = 0;
}; };
VideoService.DownloadingProgressChanged += (s, a) => Data.VideoService.ProcessingProgressChanged += (s, a) =>
{
HomeTaskPanelStateIcon.Source = (SvgImageSource)IconsRes["StateDownloadingIcon"];
HomeTaskPanelStateText.Text = $"{ResourceLoader.GetForCurrentView().GetString("HomeTaskPanelStateTextDownloading")} ({a.ProgressPercentage}%)";
HomeTaskPanelStateProgressBar.Value = a.ProgressPercentage;
};
VideoService.ProcessingStarted += (s, a) =>
{ {
HomeTaskPanelStateIcon.Source = (SvgImageSource)IconsRes["StateProcessingIcon"]; HomeTaskPanelStateIcon.Source = (SvgImageSource)IconsRes["StateProcessingIcon"];
HomeTaskPanelStateText.Text = $"{ResourceLoader.GetForCurrentView().GetString("HomeTaskPanelStateTextProcessing")} (0%)"; HomeTaskPanelStateText.Text = $"{ResourceLoader.GetForCurrentView().GetString("HomeTaskPanelStateTextProcessing")} ({Math.Round(a.Progress)}%)";
HomeTaskPanelStateProgressBar.Value = 0; HomeTaskPanelStateProgressBar.Value = a.Progress;
};
VideoService.ProcessingProgressChanged += (s, a) =>
{
HomeTaskPanelStateIcon.Source = (SvgImageSource)IconsRes["StateProcessingIcon"];
HomeTaskPanelStateText.Text = $"{ResourceLoader.GetForCurrentView().GetString("HomeTaskPanelStateTextProcessing")} ({a.ProgressPercentage}%)";
HomeTaskPanelStateProgressBar.Value = a.ProgressPercentage;
}; };
// Request extended session // Request extended session
@@ -208,7 +181,7 @@ namespace VDownload.Views.Home
// Start task // Start task
CancellationTokenSource.Token.ThrowIfCancellationRequested(); CancellationTokenSource.Token.ThrowIfCancellationRequested();
StorageFile tempOutputFile = await VideoService.DownloadAndTranscodeAsync(tempFolder, Stream, Extension, MediaType, TrimStart, TrimEnd, CancellationTokenSource.Token); StorageFile tempOutputFile = await Data.VideoService.DownloadAndTranscodeAsync(tempFolder, Data.Stream, Data.Extension, Data.MediaType, Data.TrimStart, Data.TrimEnd, CancellationTokenSource.Token);
// Dispose session // Dispose session
session.Dispose(); session.Dispose();
@@ -222,9 +195,9 @@ namespace VDownload.Views.Home
HomeTaskPanelStateProgressBar.IsIndeterminate = true; HomeTaskPanelStateProgressBar.IsIndeterminate = true;
// Move to output location // Move to output location
StorageFile outputFile; string filename = $"{Data.Filename}.{Data.Extension.ToString().ToLower()}";
if (Location != null) outputFile = await Location.CreateFileAsync($"{Filename}.{Extension.ToString().ToLower()}", (bool)Config.GetValue("replace_output_file_if_exists") ? CreationCollisionOption.ReplaceExisting : CreationCollisionOption.GenerateUniqueName); CreationCollisionOption collisionOption = (bool)Config.GetValue("replace_output_file_if_exists") ? CreationCollisionOption.ReplaceExisting : CreationCollisionOption.GenerateUniqueName;
else outputFile = await DownloadsFolder.CreateFileAsync($"{Filename}.{Extension.ToString().ToLower()}", (bool)Config.GetValue("replace_output_file_if_exists") ? CreationCollisionOption.ReplaceExisting : CreationCollisionOption.GenerateUniqueName); StorageFile outputFile = await (Data.Location != null ? Data.Location.CreateFileAsync(filename, collisionOption): DownloadsFolder.CreateFileAsync(filename, collisionOption));
await tempOutputFile.MoveAndReplaceAsync(outputFile); await tempOutputFile.MoveAndReplaceAsync(outputFile);
// Stop stopwatch // Stop stopwatch
@@ -270,7 +243,7 @@ namespace VDownload.Views.Home
finally finally
{ {
// Set video status // Set video status
TaskStatus = Core.Enums.TaskStatus.Idle; Status = Core.Enums.TaskStatus.Idle;
// Change icon // Change icon
HomeTaskPanelStartStopButton.Icon = new SymbolIcon(Symbol.Download); HomeTaskPanelStartStopButton.Icon = new SymbolIcon(Symbol.Download);
@@ -313,16 +286,18 @@ namespace VDownload.Views.Home
private async void HomeTaskPanelSourceButton_Click(object sender, RoutedEventArgs e) private async void HomeTaskPanelSourceButton_Click(object sender, RoutedEventArgs e)
{ {
// Launch the website // Launch the website
await Windows.System.Launcher.LaunchUriAsync(VideoService.VideoUrl); await Windows.System.Launcher.LaunchUriAsync(Data.VideoService.VideoUrl);
} }
// START STOP BUTTON CLICKED // START STOP BUTTON CLICKED
private async void HomeTaskPanelStartStopButton_Click(object sender, RoutedEventArgs e) private async void HomeTaskPanelStartStopButton_Click(object sender, RoutedEventArgs e)
{ {
if (TaskStatus == Core.Enums.TaskStatus.InProgress || TaskStatus == Core.Enums.TaskStatus.Waiting || TaskStatus == Core.Enums.TaskStatus.Scheduled) CancellationTokenSource.Cancel(); if (Status == Core.Enums.TaskStatus.InProgress || Status == Core.Enums.TaskStatus.Waiting || Status == Core.Enums.TaskStatus.Scheduled) CancellationTokenSource.Cancel();
else else
{ {
bool delay = (bool)Config.GetValue("delay_task_when_queued_task_starts_on_metered_network"); bool delay = (bool)Config.GetValue("delay_task_when_queued_task_starts_on_metered_network");
if (NetworkHelper.Instance.ConnectionInformation.IsInternetOnMeteredConnection)
{
ContentDialogResult dialogResult = await new ContentDialog ContentDialogResult dialogResult = await new ContentDialog
{ {
Title = ResourceLoader.GetForCurrentView().GetString("HomeTaskPanelTaskStartMeteredConnectionDialogTitle"), Title = ResourceLoader.GetForCurrentView().GetString("HomeTaskPanelTaskStartMeteredConnectionDialogTitle"),
@@ -337,6 +312,7 @@ namespace VDownload.Views.Home
case ContentDialogResult.Secondary: delay = false; break; case ContentDialogResult.Secondary: delay = false; break;
case ContentDialogResult.None: return; case ContentDialogResult.None: return;
} }
}
await Start(delay); await Start(delay);
} }
} }
@@ -344,7 +320,7 @@ namespace VDownload.Views.Home
// REMOVE BUTTON CLICKED // REMOVE BUTTON CLICKED
private void HomeTaskPanelRemoveButton_Click(object sender, RoutedEventArgs e) private void HomeTaskPanelRemoveButton_Click(object sender, RoutedEventArgs e)
{ {
if (TaskStatus == Core.Enums.TaskStatus.InProgress || TaskStatus == Core.Enums.TaskStatus.Waiting || TaskStatus == Core.Enums.TaskStatus.Scheduled) CancellationTokenSource.Cancel(); if (Status == Core.Enums.TaskStatus.InProgress || Status == Core.Enums.TaskStatus.Waiting || Status == Core.Enums.TaskStatus.Scheduled) CancellationTokenSource.Cancel();
TaskRemovingRequested?.Invoke(this, EventArgs.Empty); TaskRemovingRequested?.Invoke(this, EventArgs.Empty);
} }

View File

@@ -217,9 +217,9 @@
<cc:SettingControl x:Uid="HomeVideoAddingTrimSettingControl" Icon="{ThemeResource TrimIcon}"> <cc:SettingControl x:Uid="HomeVideoAddingTrimSettingControl" Icon="{ThemeResource TrimIcon}">
<cc:SettingControl.SettingContent> <cc:SettingControl.SettingContent>
<StackPanel Orientation="Horizontal" Spacing="5"> <StackPanel Orientation="Horizontal" Spacing="5">
<TextBox x:Name="HomeVideoAddingTrimStartTextBox" ex:TextBoxExtensions.CustomMask="{x:Bind VideoService.Duration, Converter={StaticResource TimeSpanToTextBoxMaskElementsConverter}}" ex:TextBoxExtensions.Mask="{x:Bind VideoService.Duration, Converter={StaticResource TimeSpanToTextBoxMaskConverter}}" TextChanged="HomeVideoAddingTrimStartTextBox_TextChanged"/> <TextBox x:Name="HomeVideoAddingTrimStartTextBox" ex:TextBoxExtensions.CustomMask="{x:Bind VideoService.Metadata.Duration, Converter={StaticResource TimeSpanToTextBoxMaskElementsConverter}}" ex:TextBoxExtensions.Mask="{x:Bind VideoService.Metadata.Duration, Converter={StaticResource TimeSpanToTextBoxMaskConverter}}" TextChanged="HomeVideoAddingTrimStartTextBox_TextChanged"/>
<TextBlock VerticalAlignment="Center" Text="-"/> <TextBlock VerticalAlignment="Center" Text="-"/>
<TextBox x:Name="HomeVideoAddingTrimEndTextBox" ex:TextBoxExtensions.CustomMask="{x:Bind VideoService.Duration, Converter={StaticResource TimeSpanToTextBoxMaskElementsConverter}}" ex:TextBoxExtensions.Mask="{x:Bind VideoService.Duration, Converter={StaticResource TimeSpanToTextBoxMaskConverter}}" TextChanged="HomeVideoAddingTrimEndTextBox_TextChanged"/> <TextBox x:Name="HomeVideoAddingTrimEndTextBox" ex:TextBoxExtensions.CustomMask="{x:Bind VideoService.Metadata.Duration, Converter={StaticResource TimeSpanToTextBoxMaskElementsConverter}}" ex:TextBoxExtensions.Mask="{x:Bind VideoService.Metadata.Duration, Converter={StaticResource TimeSpanToTextBoxMaskConverter}}" TextChanged="HomeVideoAddingTrimEndTextBox_TextChanged"/>
</StackPanel> </StackPanel>
</cc:SettingControl.SettingContent> </cc:SettingControl.SettingContent>
</cc:SettingControl> </cc:SettingControl>

View File

@@ -8,6 +8,7 @@ using VDownload.Core.Enums;
using VDownload.Core.EventArgs; using VDownload.Core.EventArgs;
using VDownload.Core.Interfaces; using VDownload.Core.Interfaces;
using VDownload.Core.Services; using VDownload.Core.Services;
using VDownload.Core.Structs;
using Windows.ApplicationModel.Resources; using Windows.ApplicationModel.Resources;
using Windows.Storage; using Windows.Storage;
using Windows.Storage.AccessCache; using Windows.Storage.AccessCache;
@@ -39,13 +40,13 @@ namespace VDownload.Views.Home
VideoService = videoService; VideoService = videoService;
// Set metadata // Set metadata
ThumbnailImage = VideoService.Thumbnail != null ? new BitmapImage { UriSource = VideoService.Thumbnail } : (BitmapImage)ImagesRes["UnknownThumbnailImage"]; ThumbnailImage = VideoService.Metadata.Thumbnail != null ? new BitmapImage { UriSource = VideoService.Metadata.Thumbnail } : (BitmapImage)ImagesRes["UnknownThumbnailImage"];
SourceImage = new BitmapIcon { UriSource = new Uri($"ms-appx:///Assets/Sources/{VideoService.GetType().Namespace.Split(".").Last()}.png"), ShowAsMonochrome = false }; SourceImage = new BitmapIcon { UriSource = new Uri($"ms-appx:///Assets/Sources/{VideoService.GetType().Namespace.Split(".").Last()}.png"), ShowAsMonochrome = false };
Title = VideoService.Title; Title = VideoService.Metadata.Title;
Author = VideoService.Author; Author = VideoService.Metadata.Author;
Views = VideoService.Views.ToString(); Views = VideoService.Metadata.Views.ToString();
Date = VideoService.Date.ToString(CultureInfo.InstalledUICulture.DateTimeFormat.ShortDatePattern); Date = VideoService.Metadata.Date.ToString(CultureInfo.InstalledUICulture.DateTimeFormat.ShortDatePattern);
Duration = $"{(Math.Floor(VideoService.Duration.TotalHours) > 0 ? $"{Math.Floor(VideoService.Duration.TotalHours):0}:" : "")}{VideoService.Duration.Minutes:00}:{VideoService.Duration.Seconds:00}"; Duration = TimeSpanCustomFormat.ToOptTHBaseMMSS(VideoService.Metadata.Duration);
// Set media type // Set media type
foreach (string mediaType in Enum.GetNames(typeof(MediaType))) foreach (string mediaType in Enum.GetNames(typeof(MediaType)))
@@ -55,42 +56,40 @@ namespace VDownload.Views.Home
HomeVideoAddingMediaTypeSettingControlComboBox.SelectedIndex = (int)Config.GetValue("default_media_type"); HomeVideoAddingMediaTypeSettingControlComboBox.SelectedIndex = (int)Config.GetValue("default_media_type");
// Set quality // Set quality
foreach (IBaseStream stream in VideoService.BaseStreams) foreach (BaseStream stream in VideoService.BaseStreams)
{ {
HomeVideoAddingQualitySettingControlComboBox.Items.Add($"{stream.Height}p{(stream.FrameRate > 0 ? stream.FrameRate.ToString() : "N/A")}"); HomeVideoAddingQualitySettingControlComboBox.Items.Add($"{stream.Height}p{(stream.FrameRate > 0 ? stream.FrameRate.ToString() : "N/A")}");
} }
HomeVideoAddingQualitySettingControlComboBox.SelectedIndex = 0; HomeVideoAddingQualitySettingControlComboBox.SelectedIndex = 0;
// Set trim start // Set trim start
if (Math.Floor(VideoService.Duration.TotalHours) > 0) HomeVideoAddingTrimStartTextBox.Text += $"{new string('0', Math.Floor(VideoService.Duration.TotalHours).ToString().Length)}:"; TrimStart = TimeSpan.Zero;
if (Math.Floor(VideoService.Duration.TotalMinutes) > 0) HomeVideoAddingTrimStartTextBox.Text += Math.Floor(VideoService.Duration.TotalHours) > 0 ? "00:" : $"{new string('0', VideoService.Duration.Minutes.ToString().Length)}:"; HomeVideoAddingTrimStartTextBox.Text = TimeSpanCustomFormat.ToOptTHMMBaseSS(TrimStart, VideoService.Metadata.Duration);
HomeVideoAddingTrimStartTextBox.Text += Math.Floor(VideoService.Duration.TotalMinutes) > 0 ? "00" : $"{new string('0', VideoService.Duration.Seconds.ToString().Length)}";
// Set trim end // Set trim end
if (Math.Floor(VideoService.Duration.TotalHours) > 0) HomeVideoAddingTrimEndTextBox.Text += $"{Math.Floor(VideoService.Duration.TotalHours)}:"; TrimEnd = VideoService.Metadata.Duration;
if (Math.Floor(VideoService.Duration.TotalMinutes) > 0) HomeVideoAddingTrimEndTextBox.Text += Math.Floor(VideoService.Duration.TotalHours) > 0 ? $"{VideoService.Duration.Minutes:00}:" : $"{VideoService.Duration.Minutes}:"; HomeVideoAddingTrimStartTextBox.Text = TimeSpanCustomFormat.ToOptTHMMBaseSS(TrimEnd);
HomeVideoAddingTrimEndTextBox.Text += Math.Floor(VideoService.Duration.TotalMinutes) > 0 ? $"{VideoService.Duration.Seconds:00}" : $"{VideoService.Duration.Seconds}";
// Set filename // Set filename
string temporaryFilename = (string)Config.GetValue("default_filename"); string temporaryFilename = (string)Config.GetValue("default_filename");
Dictionary<string, string> filenameStandardTemplates = new Dictionary<string, string>() Dictionary<string, string> filenameStandardTemplates = new Dictionary<string, string>()
{ {
{ "<title>", VideoService.Title }, { "<title>", VideoService.Metadata.Title },
{ "<author>", VideoService.Author }, { "<author>", VideoService.Metadata.Author },
{ "<views>", VideoService.Views.ToString() }, { "<views>", VideoService.Metadata.Views.ToString() },
{ "<id>", VideoService.ID }, { "<id>", VideoService.ID },
}; };
foreach (KeyValuePair<string, string> template in filenameStandardTemplates) temporaryFilename = temporaryFilename.Replace(template.Key, template.Value); foreach (KeyValuePair<string, string> template in filenameStandardTemplates) temporaryFilename = temporaryFilename.Replace(template.Key, template.Value);
Dictionary<Regex, IFormattable> filenameFormatTemplates = new Dictionary<Regex, IFormattable>() Dictionary<Regex, IFormattable> filenameFormatTemplates = new Dictionary<Regex, IFormattable>()
{ {
{ new Regex(@"<date_pub:(?<format>.*)>"), VideoService.Date }, { new Regex(@"<date_pub:(?<format>.*)>"), VideoService.Metadata.Date },
{ new Regex(@"<date_now:(?<format>.*)>"), DateTime.Now }, { new Regex(@"<date_now:(?<format>.*)>"), DateTime.Now },
{ new Regex(@"<duration:(?<format>.*)>"), VideoService.Duration }, { new Regex(@"<duration:(?<format>.*)>"), VideoService.Metadata.Duration },
}; };
foreach (KeyValuePair<Regex, IFormattable> template in filenameFormatTemplates) foreach (Match templateMatch in template.Key.Matches(temporaryFilename)) temporaryFilename = temporaryFilename.Replace(templateMatch.Value, template.Value.ToString(templateMatch.Groups["format"].Value, null)); foreach (KeyValuePair<Regex, IFormattable> template in filenameFormatTemplates) foreach (Match templateMatch in template.Key.Matches(temporaryFilename)) temporaryFilename = temporaryFilename.Replace(templateMatch.Value, template.Value.ToString(templateMatch.Groups["format"].Value, null));
foreach (char c in System.IO.Path.GetInvalidFileNameChars()) temporaryFilename = temporaryFilename.Replace(c, ' '); foreach (char c in System.IO.Path.GetInvalidFileNameChars()) temporaryFilename = temporaryFilename.Replace(c, ' ');
HomeVideoAddingFilenameTextBox.Text = temporaryFilename;
Filename = temporaryFilename; Filename = temporaryFilename;
HomeVideoAddingFilenameTextBox.Text = Filename;
// Set location // Set location
if (!(bool)Config.GetValue("custom_media_location") && StorageApplicationPermissions.FutureAccessList.ContainsItem("last_media_location")) if (!(bool)Config.GetValue("custom_media_location") && StorageApplicationPermissions.FutureAccessList.ContainsItem("last_media_location"))
@@ -135,7 +134,7 @@ namespace VDownload.Views.Home
// VIDEO OPTIONS // VIDEO OPTIONS
private MediaType MediaType { get; set; } private MediaType MediaType { get; set; }
private IBaseStream Stream { get; set; } private BaseStream Stream { get; set; }
private TimeSpan TrimStart { get; set; } private TimeSpan TrimStart { get; set; }
private TimeSpan TrimEnd { get; set; } private TimeSpan TrimEnd { get; set; }
private string Filename { get; set; } private string Filename { get; set; }
@@ -197,15 +196,15 @@ namespace VDownload.Views.Home
TimeSpan parsedTimeSpan = new TimeSpan(hours, minutes, seconds); TimeSpan parsedTimeSpan = new TimeSpan(hours, minutes, seconds);
if (parsedTimeSpan < VideoService.Duration && parsedTimeSpan > new TimeSpan(0)) TrimStart = parsedTimeSpan; if (parsedTimeSpan < VideoService.Metadata.Duration && parsedTimeSpan > new TimeSpan(0)) TrimStart = parsedTimeSpan;
else else
{ {
TrimStart = new TimeSpan(0); TrimStart = new TimeSpan(0);
string newText = string.Empty; string newText = string.Empty;
if (Math.Floor(VideoService.Duration.TotalHours) > 0) newText += $"{new string('0', Math.Floor(VideoService.Duration.TotalHours).ToString().Length)}:"; if (Math.Floor(VideoService.Metadata.Duration.TotalHours) > 0) newText += $"{new string('0', Math.Floor(VideoService.Metadata.Duration.TotalHours).ToString().Length)}:";
if (Math.Floor(VideoService.Duration.TotalMinutes) > 0) newText += Math.Floor(VideoService.Duration.TotalHours) > 0 ? "00:" : $"{new string('0', VideoService.Duration.Minutes.ToString().Length)}:"; if (Math.Floor(VideoService.Metadata.Duration.TotalMinutes) > 0) newText += Math.Floor(VideoService.Metadata.Duration.TotalHours) > 0 ? "00:" : $"{new string('0', VideoService.Metadata.Duration.Minutes.ToString().Length)}:";
newText += Math.Floor(VideoService.Duration.TotalMinutes) > 0 ? "00" : $"{new string('0', VideoService.Duration.Seconds.ToString().Length)}"; newText += Math.Floor(VideoService.Metadata.Duration.TotalMinutes) > 0 ? "00" : $"{new string('0', VideoService.Metadata.Duration.Seconds.ToString().Length)}";
if (newText != HomeVideoAddingTrimStartTextBox.Text) HomeVideoAddingTrimStartTextBox.Text = newText; if (newText != HomeVideoAddingTrimStartTextBox.Text) HomeVideoAddingTrimStartTextBox.Text = newText;
} }
@@ -224,15 +223,15 @@ namespace VDownload.Views.Home
TimeSpan parsedTimeSpan = new TimeSpan(hours, minutes, seconds); TimeSpan parsedTimeSpan = new TimeSpan(hours, minutes, seconds);
if (parsedTimeSpan < VideoService.Duration && parsedTimeSpan > new TimeSpan(0)) TrimEnd = parsedTimeSpan; if (parsedTimeSpan < VideoService.Metadata.Duration && parsedTimeSpan > new TimeSpan(0)) TrimEnd = parsedTimeSpan;
else else
{ {
TrimEnd = VideoService.Duration; TrimEnd = VideoService.Metadata.Duration;
string newText = string.Empty; string newText = string.Empty;
if (Math.Floor(VideoService.Duration.TotalHours) > 0) newText += $"{Math.Floor(VideoService.Duration.TotalHours)}:"; if (Math.Floor(VideoService.Metadata.Duration.TotalHours) > 0) newText += $"{Math.Floor(VideoService.Metadata.Duration.TotalHours)}:";
if (Math.Floor(VideoService.Duration.TotalMinutes) > 0) newText += Math.Floor(VideoService.Duration.TotalHours) > 0 ? $"{TrimEnd.Minutes:00}:" : $"{TrimEnd.Minutes}:"; if (Math.Floor(VideoService.Metadata.Duration.TotalMinutes) > 0) newText += Math.Floor(VideoService.Metadata.Duration.TotalHours) > 0 ? $"{TrimEnd.Minutes:00}:" : $"{TrimEnd.Minutes}:";
newText += Math.Floor(VideoService.Duration.TotalMinutes) > 0 ? $"{TrimEnd.Seconds:00}" : $"{TrimEnd.Seconds}"; newText += Math.Floor(VideoService.Metadata.Duration.TotalMinutes) > 0 ? $"{TrimEnd.Seconds:00}" : $"{TrimEnd.Seconds}";
if (newText != HomeVideoAddingTrimEndTextBox.Text) HomeVideoAddingTrimEndTextBox.Text = newText; if (newText != HomeVideoAddingTrimEndTextBox.Text) HomeVideoAddingTrimEndTextBox.Text = newText;
} }
@@ -267,12 +266,14 @@ namespace VDownload.Views.Home
// LOCATION BROWSE BUTTON CLICKED // LOCATION BROWSE BUTTON CLICKED
private async void HomeVideoAddingLocationBrowseButton_Click(object sender, RoutedEventArgs e) private async void HomeVideoAddingLocationBrowseButton_Click(object sender, RoutedEventArgs e)
{ {
// Create location picker
FolderPicker picker = new FolderPicker FolderPicker picker = new FolderPicker
{ {
SuggestedStartLocation = PickerLocationId.Downloads SuggestedStartLocation = PickerLocationId.Downloads
}; };
picker.FileTypeFilter.Add("*"); picker.FileTypeFilter.Add("*");
// Select location
StorageFolder selectedFolder = await picker.PickSingleFolderAsync(); StorageFolder selectedFolder = await picker.PickSingleFolderAsync();
if (selectedFolder != null) if (selectedFolder != null)
@@ -288,6 +289,7 @@ namespace VDownload.Views.Home
} }
} }
// SOURCE BUTTON CLICKED // SOURCE BUTTON CLICKED
public async void HomeVideoAddingPanelSourceButton_Click(object sender, RoutedEventArgs e) public async void HomeVideoAddingPanelSourceButton_Click(object sender, RoutedEventArgs e)
{ {
@@ -298,7 +300,8 @@ namespace VDownload.Views.Home
// ADD BUTTON CLICKED // ADD BUTTON CLICKED
public void HomeVideoAddingPanelAddButton_Click(object sender, RoutedEventArgs e) public void HomeVideoAddingPanelAddButton_Click(object sender, RoutedEventArgs e)
{ {
VideoAddEventArgs args = new VideoAddEventArgs // Pack task data
TaskData taskData = new TaskData
{ {
VideoService = VideoService, VideoService = VideoService,
MediaType = MediaType, MediaType = MediaType,
@@ -310,7 +313,14 @@ namespace VDownload.Views.Home
Location = Location, Location = Location,
Schedule = Schedule, Schedule = Schedule,
}; };
VideoAddRequest?.Invoke(this, args);
// Request task adding
TasksAddingRequestedEventArgs eventArgs = new TasksAddingRequestedEventArgs
{
TaskData = new TaskData[] { taskData },
RequestSource = TaskAddingRequestSource.Video
};
TasksAddingRequested?.Invoke(this, eventArgs);
} }
#endregion #endregion
@@ -319,7 +329,7 @@ namespace VDownload.Views.Home
#region EVENT HANDLERS #region EVENT HANDLERS
public event EventHandler<VideoAddEventArgs> VideoAddRequest; public event EventHandler<TasksAddingRequestedEventArgs> TasksAddingRequested;
#endregion #endregion
} }

View File

@@ -13,13 +13,8 @@ using Windows.UI.Xaml.Input;
using Windows.UI.Xaml.Media; using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Navigation; using Windows.UI.Xaml.Navigation;
// The Blank Page item template is documented at https://go.microsoft.com/fwlink/?LinkId=234238
namespace VDownload.Views.Settings namespace VDownload.Views.Settings
{ {
/// <summary>
/// An empty page that can be used on its own or navigated to within a Frame.
/// </summary>
public sealed partial class SettingsMain : Page public sealed partial class SettingsMain : Page
{ {
public SettingsMain() public SettingsMain()

View File

@@ -15,10 +15,9 @@
</Page.Resources> </Page.Resources>
<Grid Padding="20"> <Grid Padding="20" RowSpacing="20">
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/>
<RowDefinition Height="20"/>
<RowDefinition/> <RowDefinition/>
</Grid.RowDefinitions> </Grid.RowDefinitions>
@@ -26,7 +25,7 @@
<TextBlock x:Name="SourcesMainPageHeaderText" x:Uid="SourcesMainPageHeaderText" Grid.Row="0" FontSize="28" FontWeight="SemiBold"/> <TextBlock x:Name="SourcesMainPageHeaderText" x:Uid="SourcesMainPageHeaderText" Grid.Row="0" FontSize="28" FontWeight="SemiBold"/>
<!-- CONTENT --> <!-- CONTENT -->
<StackPanel Grid.Row="2" Spacing="10"> <StackPanel Grid.Row="1" Spacing="10">
<cc:SettingControl x:Name="SourcesTwitchSettingControl" x:Uid="SourcesTwitchSettingControl" Grid.Row="0" Icon="{StaticResource TwitchIcon}" Title="Twitch"> <!-- Twitch --> <cc:SettingControl x:Name="SourcesTwitchSettingControl" x:Uid="SourcesTwitchSettingControl" Grid.Row="0" Icon="{StaticResource TwitchIcon}" Title="Twitch"> <!-- Twitch -->
<cc:SettingControl.SettingContent> <cc:SettingControl.SettingContent>
<Button x:Name="SourcesTwitchLoginButton" IsEnabled="False" Click="SourcesTwitchLoginButton_Click"/> <Button x:Name="SourcesTwitchLoginButton" IsEnabled="False" Click="SourcesTwitchLoginButton_Click"/>

View File

@@ -1,8 +1,10 @@
using Microsoft.UI.Xaml.Controls; using Microsoft.Toolkit.Uwp.Connectivity;
using Microsoft.UI.Xaml.Controls;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Net; using System.Net;
using System.Threading.Tasks;
using Windows.ApplicationModel.Resources; using Windows.ApplicationModel.Resources;
using Windows.UI.WindowManagement; using Windows.UI.WindowManagement;
using Windows.UI.Xaml; using Windows.UI.Xaml;
@@ -25,36 +27,54 @@ namespace VDownload.Views.Sources
#region EVENT HANDLERS #region MAIN EVENT HANDLERS VOIDS
// NAVIGATED TO THIS PAGE (Check all services) // ON NAVIGATED TO THIS PAGE (Check all services)
protected override async void OnNavigatedTo(NavigationEventArgs e) protected override async void OnNavigatedTo(NavigationEventArgs e)
{ {
// Check Twitch Task[] checkingTasks = new Task[1];
checkingTasks[0] = CheckTwitch();
await Task.WhenAll(checkingTasks);
}
#endregion
#region TWITCH
// CHECK TWITCH LOGIN AT START
private async Task CheckTwitch()
{
try try
{ {
string twitchAccessToken = await Core.Services.Sources.Twitch.Auth.ReadAccessTokenAsync(); string twitchAccessToken = await Core.Services.Sources.Twitch.Helpers.Auth.ReadAccessTokenAsync();
(bool IsValid, string Login, DateTime? ExpirationDate) twitchAccessTokenValidation = await Core.Services.Sources.Twitch.Auth.ValidateAccessTokenAsync(twitchAccessToken); #pragma warning disable IDE0042 // Deconstruct variable declaration
if (twitchAccessToken != null && twitchAccessTokenValidation.IsValid) (bool IsValid, string Login, DateTime? ExpirationDate) twitchAccessTokenValidation = await Core.Services.Sources.Twitch.Helpers.Auth.ValidateAccessTokenAsync(twitchAccessToken);
#pragma warning restore IDE0042 // Deconstruct variable declaration
if (twitchAccessTokenValidation.IsValid)
{ {
SourcesTwitchSettingControl.Description = $"{ResourceLoader.GetForCurrentView().GetString("SourcesTwitchSettingControlDescriptionLoggedIn")} {twitchAccessTokenValidation.Login}"; SourcesTwitchSettingControl.Description = $"{ResourceLoader.GetForCurrentView().GetString("SourcesTwitchSettingControlDescriptionLoggedIn")} {twitchAccessTokenValidation.Login}";
SourcesTwitchLoginButton.Content = ResourceLoader.GetForCurrentView().GetString("SourcesTwitchLoginButtonTextLoggedIn"); SourcesTwitchLoginButton.Content = ResourceLoader.GetForCurrentView().GetString("SourcesTwitchLoginButtonTextLoggedIn");
SourcesTwitchLoginButton.IsEnabled = true; SourcesTwitchLoginButton.IsEnabled = true;
} }
else if (twitchAccessToken == null || !twitchAccessTokenValidation.IsValid) else
{ {
if (twitchAccessToken != null) await Core.Services.Sources.Twitch.Auth.DeleteAccessTokenAsync(); if (twitchAccessToken != null) await Core.Services.Sources.Twitch.Helpers.Auth.DeleteAccessTokenAsync();
SourcesTwitchSettingControl.Description = ResourceLoader.GetForCurrentView().GetString("SourcesTwitchSettingControlDescriptionNotLoggedIn"); SourcesTwitchSettingControl.Description = ResourceLoader.GetForCurrentView().GetString("SourcesTwitchSettingControlDescriptionNotLoggedIn");
SourcesTwitchLoginButton.Content = ResourceLoader.GetForCurrentView().GetString("SourcesTwitchLoginButtonTextNotLoggedIn"); SourcesTwitchLoginButton.Content = ResourceLoader.GetForCurrentView().GetString("SourcesTwitchLoginButtonTextNotLoggedIn");
SourcesTwitchLoginButton.IsEnabled = true; SourcesTwitchLoginButton.IsEnabled = true;
} }
} }
catch (WebException wex) catch (WebException)
{ {
if (wex.Response == null) if (!NetworkHelper.Instance.ConnectionInformation.IsInternetAvailable)
{ {
SourcesTwitchSettingControl.Description = ResourceLoader.GetForCurrentView().GetString("SourcesTwitchSettingControlDescriptionInternetConnectionError"); SourcesTwitchSettingControl.Description = ResourceLoader.GetForCurrentView().GetString("SourcesTwitchSettingControlDescriptionInternetConnectionError");
SourcesTwitchLoginButton.Content = ResourceLoader.GetForCurrentView().GetString("SourcesTwitchLoginButtonTextNotLoggedIn"); SourcesTwitchLoginButton.Content = ResourceLoader.GetForCurrentView().GetString("SourcesTwitchLoginButtonTextNotLoggedIn");
SourcesTwitchLoginButton.IsEnabled = false;
} }
else throw; else throw;
} }
@@ -65,15 +85,15 @@ namespace VDownload.Views.Sources
{ {
try try
{ {
string accessToken = await Core.Services.Sources.Twitch.Auth.ReadAccessTokenAsync(); string accessToken = await Core.Services.Sources.Twitch.Helpers.Auth.ReadAccessTokenAsync();
var accessTokenValidation = await Core.Services.Sources.Twitch.Auth.ValidateAccessTokenAsync(accessToken); var accessTokenValidation = await Core.Services.Sources.Twitch.Helpers.Auth.ValidateAccessTokenAsync(accessToken);
if (accessToken != null && accessTokenValidation.IsValid) if (accessTokenValidation.IsValid)
{ {
// Revoke access token // Revoke access token
await Core.Services.Sources.Twitch.Auth.RevokeAccessTokenAsync(accessToken); await Core.Services.Sources.Twitch.Helpers.Auth.RevokeAccessTokenAsync(accessToken);
// Delete access token // Delete access token
await Core.Services.Sources.Twitch.Auth.DeleteAccessTokenAsync(); await Core.Services.Sources.Twitch.Helpers.Auth.DeleteAccessTokenAsync();
// Update Twitch SettingControl // Update Twitch SettingControl
SourcesTwitchSettingControl.Description = ResourceLoader.GetForCurrentView().GetString("SourcesTwitchSettingControlDescriptionNotLoggedIn"); SourcesTwitchSettingControl.Description = ResourceLoader.GetForCurrentView().GetString("SourcesTwitchSettingControlDescriptionNotLoggedIn");
@@ -85,21 +105,22 @@ namespace VDownload.Views.Sources
AppWindow TwitchAuthWindow = await AppWindow.TryCreateAsync(); AppWindow TwitchAuthWindow = await AppWindow.TryCreateAsync();
TwitchAuthWindow.Title = "Twitch Authentication"; TwitchAuthWindow.Title = "Twitch Authentication";
#pragma warning disable CS8305 // Type is for evaluation purposes only and is subject to change or removal in future updates.
WebView2 TwitchAuthWebView = new WebView2(); WebView2 TwitchAuthWebView = new WebView2();
await TwitchAuthWebView.EnsureCoreWebView2Async(); await TwitchAuthWebView.EnsureCoreWebView2Async();
TwitchAuthWebView.Source = Core.Services.Sources.Twitch.Auth.AuthorizationUrl; TwitchAuthWebView.Source = Core.Services.Sources.Twitch.Helpers.Auth.AuthorizationUrl;
ElementCompositionPreview.SetAppWindowContent(TwitchAuthWindow, TwitchAuthWebView); ElementCompositionPreview.SetAppWindowContent(TwitchAuthWindow, TwitchAuthWebView);
// NavigationStarting event (only when redirected) // NavigationStarting event (only when redirected)
TwitchAuthWebView.NavigationStarting += async (s, a) => TwitchAuthWebView.NavigationStarting += async (s, a) =>
{ {
if (new Uri(a.Uri).Host == Core.Services.Sources.Twitch.Auth.RedirectUrl.Host) if (new Uri(a.Uri).Host == Core.Services.Sources.Twitch.Helpers.Auth.RedirectUrl.Host)
{ {
// Close window // Close window
await TwitchAuthWindow.CloseAsync(); await TwitchAuthWindow.CloseAsync();
// Get response // Get response
string response = a.Uri.Replace(Core.Services.Sources.Twitch.Auth.RedirectUrl.OriginalString, ""); string response = a.Uri.Replace(Core.Services.Sources.Twitch.Helpers.Auth.RedirectUrl.OriginalString, "");
if (response[1] == '#') if (response[1] == '#')
{ {
@@ -107,10 +128,10 @@ namespace VDownload.Views.Sources
accessToken = response.Split('&')[0].Replace("/#access_token=", ""); accessToken = response.Split('&')[0].Replace("/#access_token=", "");
// Check token // Check token
accessTokenValidation = await Core.Services.Sources.Twitch.Auth.ValidateAccessTokenAsync(accessToken); accessTokenValidation = await Core.Services.Sources.Twitch.Helpers.Auth.ValidateAccessTokenAsync(accessToken);
// Save token // Save token
await Core.Services.Sources.Twitch.Auth.SaveAccessTokenAsync(accessToken); await Core.Services.Sources.Twitch.Helpers.Auth.SaveAccessTokenAsync(accessToken);
// Update Twitch SettingControl // Update Twitch SettingControl
SourcesTwitchSettingControl.Description = $"{ResourceLoader.GetForCurrentView().GetString("SourcesTwitchSettingControlDescriptionLoggedIn")} {accessTokenValidation.Login}"; SourcesTwitchSettingControl.Description = $"{ResourceLoader.GetForCurrentView().GetString("SourcesTwitchSettingControlDescriptionLoggedIn")} {accessTokenValidation.Login}";
@@ -151,11 +172,12 @@ namespace VDownload.Views.Sources
TwitchAuthWebView.CoreWebView2.CookieManager.DeleteAllCookies(); TwitchAuthWebView.CoreWebView2.CookieManager.DeleteAllCookies();
} }
}; };
#pragma warning restore CS8305 // Type is for evaluation purposes only and is subject to change or removal in future updates.
} }
} }
catch (WebException wex) catch (WebException wex)
{ {
if (wex.Response == null) if (!NetworkHelper.Instance.ConnectionInformation.IsInternetAvailable)
{ {
SourcesTwitchSettingControl.Description = ResourceLoader.GetForCurrentView().GetString("SourcesTwitchSettingControlDescriptionInternetConnectionError"); SourcesTwitchSettingControl.Description = ResourceLoader.GetForCurrentView().GetString("SourcesTwitchSettingControlDescriptionInternetConnectionError");
SourcesTwitchLoginButton.Content = ResourceLoader.GetForCurrentView().GetString("SourcesTwitchLoginButtonTextNotLoggedIn"); SourcesTwitchLoginButton.Content = ResourceLoader.GetForCurrentView().GetString("SourcesTwitchLoginButtonTextNotLoggedIn");

View File

@@ -8,7 +8,11 @@
mc:Ignorable="d" mc:Ignorable="d"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"> Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid> <Grid Padding="20" RowSpacing="20">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
</Grid> </Grid>
</Page> </Page>

View File

@@ -13,13 +13,8 @@ using Windows.UI.Xaml.Input;
using Windows.UI.Xaml.Media; using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Navigation; using Windows.UI.Xaml.Navigation;
// The Blank Page item template is documented at https://go.microsoft.com/fwlink/?LinkId=234238
namespace VDownload.Views.Subscriptions namespace VDownload.Views.Subscriptions
{ {
/// <summary>
/// An empty page that can be used on its own or navigated to within a Frame.
/// </summary>
public sealed partial class SubscriptionsMain : Page public sealed partial class SubscriptionsMain : Page
{ {
public SubscriptionsMain() public SubscriptionsMain()