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 is universal video downloader written in .NET/C# and Universal Windows Platform.
![VDownload Home Page](.github/Images/Home.png)
## 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 string Phrase { get; set; }
public int Count { get; set; }
public string Url { 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 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.Tasks;
using VDownload.Core.Enums;
using VDownload.Core.Structs;
using Windows.Storage;
namespace VDownload.Core.Interfaces
@@ -14,13 +15,8 @@ namespace VDownload.Core.Interfaces
// VIDEO PROPERTIES
string ID { get; }
Uri VideoUrl { get; }
string Title { get; }
string Author { get; }
DateTime Date { get; }
TimeSpan Duration { get; }
long Views { get; }
Uri Thumbnail { get; }
IBaseStream[] BaseStreams { get; }
Metadata Metadata { get; }
BaseStream[] BaseStreams { get; }
#endregion
@@ -35,7 +31,7 @@ namespace VDownload.Core.Interfaces
Task GetStreamsAsync(CancellationToken cancellationToken = default);
// 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
@@ -43,12 +39,8 @@ namespace VDownload.Core.Interfaces
#region EVENT HANDLERS
event EventHandler DownloadingStarted;
event EventHandler<ProgressChangedEventArgs> DownloadingProgressChanged;
event EventHandler DownloadingCompleted;
event EventHandler ProcessingStarted;
event EventHandler<ProgressChangedEventArgs> ProcessingProgressChanged;
event EventHandler ProcessingCompleted;
event EventHandler<EventArgs.ProgressChangedEventArgs> DownloadingProgressChanged;
event EventHandler<EventArgs.ProgressChangedEventArgs> ProcessingProgressChanged;
#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 VDownload.Core.Enums;
using Windows.Media.Editing;
using Windows.Media.Transcoding;
using Windows.Storage;
namespace VDownload.Core.Services
@@ -21,7 +22,7 @@ namespace VDownload.Core.Services
{ "twitch_vod_downloading_chunk_max_retries", 10 },
{ "twitch_vod_downloading_chunk_retries_delay", 5000 },
{ "media_transcoding_use_hardware_acceleration", true },
{ "media_transcoding_use_mrfcrf444_algorithm", true },
{ "media_transcoding_algorithm", (int)MediaVideoProcessingAlgorithm.MrfCrf444 },
{ "media_editing_algorithm", (int)MediaTrimmingPreference.Fast },
{ "default_max_playlist_videos", 0 },
{ "default_media_type", (int)MediaType.AudioVideo },

View File

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

View File

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

View File

@@ -12,21 +12,14 @@ using System.Web;
using VDownload.Core.Enums;
using VDownload.Core.Exceptions;
using VDownload.Core.Interfaces;
using VDownload.Core.Objects;
using VDownload.Core.Services.Sources.Twitch.Helpers;
using VDownload.Core.Structs;
using Windows.Storage;
namespace VDownload.Core.Services.Sources.Twitch
{
public class Clip : IVideoService
{
#region CONSTANTS
#endregion
#region CONSTRUCTORS
public Clip(string id)
@@ -42,13 +35,8 @@ namespace VDownload.Core.Services.Sources.Twitch
public string ID { get; private set; }
public Uri VideoUrl { get; private set; }
public string Title { get; private set; }
public string Author { get; private set; }
public DateTime Date { get; private set; }
public TimeSpan Duration { get; private set; }
public long Views { get; private set; }
public Uri Thumbnail { get; private set; }
public IBaseStream[] BaseStreams { get; private set; }
public Metadata Metadata { get; private set; }
public BaseStream[] BaseStreams { get; private set; }
#endregion
@@ -59,92 +47,83 @@ namespace VDownload.Core.Services.Sources.Twitch
// GET CLIP METADATA
public async Task GetMetadataAsync(CancellationToken cancellationToken = default)
{
// Get access token
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
client.QueryString.Add("id", ID);
JToken response = JObject.Parse(await client.DownloadStringTaskAsync("https://api.twitch.tv/helix/clips")).GetValue("data")[0];
JToken response = null;
using (WebClient client = await Client.Helix())
{
client.QueryString.Add("id", ID);
response = JObject.Parse(await client.DownloadStringTaskAsync("https://api.twitch.tv/helix/clips")).GetValue("data")[0];
}
// Create unified video url
VideoUrl = new Uri($"https://clips.twitch.tv/{ID}");
// Set parameters
Title = (string)response["title"];
Author = (string)response["broadcaster_name"];
Date = Convert.ToDateTime(response["created_at"]);
Duration = TimeSpan.FromSeconds((double)response["duration"]);
Views = (long)response["view_count"];
Thumbnail = new Uri((string)response["thumbnail_url"]);
// Set metadata
Metadata = new Metadata()
{
Title = (string)response["title"],
Author = (string)response["broadcaster_name"],
Date = Convert.ToDateTime(response["created_at"]),
Duration = TimeSpan.FromSeconds((double)response["duration"]),
Views = (long)response["view_count"],
Thumbnail = new Uri((string)response["thumbnail_url"]),
};
}
public async Task GetStreamsAsync(CancellationToken cancellationToken = default)
{
// Create client
WebClient client = new WebClient { Encoding = Encoding.UTF8 };
client.Headers.Add("Client-ID", Auth.GQLApiClientID);
// Get video streams
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
List<Stream> streams = new List<Stream>();
List<BaseStream> streams = new List<BaseStream>();
// Parse 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
Stream stream = new Stream(url, false, StreamType.AudioVideo)
BaseStream stream = new BaseStream()
{
Height = height,
FrameRate = frameRate
Url = new Uri((string)streamData["sourceURL"]),
Height = int.Parse((string)streamData["quality"]),
FrameRate = (int)streamData["frameRate"],
};
// Add stream
streams.Add(stream);
}
// Set Streams parameter
// Set streams
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
DownloadingStarted?.Invoke(this, System.EventArgs.Empty);
// Create client
WebClient client = new WebClient();
client.Headers.Add("Client-Id", Auth.GQLApiClientID);
cancellationToken.ThrowIfCancellationRequested();
DownloadingProgressChanged(this, new EventArgs.ProgressChangedEventArgs(0));
// Get video GQL access token
cancellationToken.ThrowIfCancellationRequested();
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"];
JToken videoAccessToken = null;
using (WebClient client = Client.GQL())
{
videoAccessToken = JArray.Parse(await client.UploadStringTaskAsync("https://gql.twitch.tv/gql", "[{\"operationName\":\"VideoAccessToken_Clip\",\"variables\":{\"slug\":\"" + ID + "\"},\"extensions\":{\"persistedQuery\":{\"version\":1,\"sha256Hash\":\"36b89d2507fce29e5ca551df756d27c1cfe079e2609642b4390aa4c35796eb11\"}}}]"))[0]["data"]["clip"]["playbackAccessToken"];
}
// Download
cancellationToken.ThrowIfCancellationRequested();
StorageFile rawFile = await downloadingFolder.CreateFileAsync("raw.mp4");
using (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("token", HttpUtility.UrlEncode((string)videoAccessToken["value"]));
cancellationToken.ThrowIfCancellationRequested();
@@ -153,20 +132,24 @@ namespace VDownload.Core.Services.Sources.Twitch
await client.DownloadFileTaskAsync(baseStream.Url, rawFile.Path);
}
}
DownloadingCompleted?.Invoke(this, System.EventArgs.Empty);
DownloadingProgressChanged(this, new EventArgs.ProgressChangedEventArgs(100, true));
// Processing
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();
outputFile = await downloadingFolder.CreateFileAsync($"transcoded.{extension.ToString().ToLower()}");
MediaProcessor mediaProcessor = new MediaProcessor(outputFile, trimStart, trimEnd);
mediaProcessor.ProcessingStarted += ProcessingStarted;
mediaProcessor.ProcessingProgressChanged += ProcessingProgressChanged;
mediaProcessor.ProcessingCompleted += ProcessingCompleted;
cancellationToken.ThrowIfCancellationRequested();
await mediaProcessor.Run(rawFile, extension, mediaType, cancellationToken);
MediaProcessor mediaProcessor = new MediaProcessor();
mediaProcessor.ProgressChanged += ProcessingProgressChanged;
Task mediaProcessorTask;
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
@@ -179,12 +162,8 @@ namespace VDownload.Core.Services.Sources.Twitch
#region EVENT HANDLERS
public event EventHandler DownloadingStarted;
public event EventHandler<ProgressChangedEventArgs> DownloadingProgressChanged;
public event EventHandler DownloadingCompleted;
public event EventHandler ProcessingStarted;
public event EventHandler<ProgressChangedEventArgs> ProcessingProgressChanged;
public event EventHandler ProcessingCompleted;
public event EventHandler<EventArgs.ProgressChangedEventArgs> DownloadingProgressChanged;
public event EventHandler<EventArgs.ProgressChangedEventArgs> ProcessingProgressChanged;
#endregion
}

View File

@@ -1,16 +1,12 @@
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading.Tasks;
using Windows.Storage;
using Windows.Storage.Streams;
namespace VDownload.Core.Services.Sources.Twitch
namespace VDownload.Core.Services.Sources.Twitch.Helpers
{
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 System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Net;
@@ -9,27 +8,15 @@ using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using VDownload.Core.Enums;
using VDownload.Core.Exceptions;
using VDownload.Core.Interfaces;
using VDownload.Core.Objects;
using VDownload.Core.Services.Sources.Twitch.Helpers;
using VDownload.Core.Structs;
using Windows.Storage;
namespace VDownload.Core.Services.Sources.Twitch
{
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
public Vod(string id)
@@ -45,13 +32,8 @@ namespace VDownload.Core.Services.Sources.Twitch
public string ID { get; private set; }
public Uri VideoUrl { get; private set; }
public string Title { get; private set; }
public string Author { get; private set; }
public DateTime Date { get; private set; }
public TimeSpan Duration { get; private set; }
public long Views { get; private set; }
public Uri Thumbnail { get; private set; }
public IBaseStream[] BaseStreams { get; private set; }
public Metadata Metadata { get; private set; }
public BaseStream[] BaseStreams { get; private set; }
#endregion
@@ -62,25 +44,16 @@ namespace VDownload.Core.Services.Sources.Twitch
// GET VOD METADATA
public async Task GetMetadataAsync(CancellationToken cancellationToken = default)
{
// Get access token
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
client.QueryString.Add("id", ID);
cancellationToken.ThrowIfCancellationRequested();
JToken response = JObject.Parse(await client.DownloadStringTaskAsync("https://api.twitch.tv/helix/videos")).GetValue("data")[0];
JToken response = null;
using (WebClient client = await Client.Helix())
{
client.QueryString.Add("id", ID);
cancellationToken.ThrowIfCancellationRequested();
response = JObject.Parse(await client.DownloadStringTaskAsync("https://api.twitch.tv/helix/videos")).GetValue("data")[0];
}
// Set parameters
GetMetadataAsync(response);
@@ -90,83 +63,86 @@ namespace VDownload.Core.Services.Sources.Twitch
// Create unified video url
VideoUrl = new Uri($"https://www.twitch.tv/videos/{ID}");
// Set parameters
Title = ((string)response["title"]).Replace("\n", "");
Author = (string)response["user_name"];
Date = Convert.ToDateTime(response["created_at"]);
Duration = ParseDuration((string)response["duration"]);
Views = (long)response["view_count"];
Thumbnail = (string)response["thumbnail_url"] == string.Empty ? null : new Uri(((string)response["thumbnail_url"]).Replace("%{width}", "1920").Replace("%{height}", "1080"));
// Set metadata
Metadata = new Metadata()
{
Title = ((string)response["title"]).Replace("\n", ""),
Author = (string)response["user_name"],
Date = Convert.ToDateTime(response["created_at"]),
Duration = ParseDuration((string)response["duration"]),
Views = (long)response["view_count"],
Thumbnail = (string)response["thumbnail_url"] == string.Empty ? null : new Uri(((string)response["thumbnail_url"]).Replace("%{width}", "1920").Replace("%{height}", "1080")),
};
}
// GET VOD STREAMS
public async Task GetStreamsAsync(CancellationToken cancellationToken = default)
{
// Create client
WebClient client = new WebClient();
client.Headers.Add("Client-Id", Auth.GQLApiClientID);
// Get video GQL access token
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"];
// Get video streams
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");
// Get response
string[] response = null;
using (WebClient client = Client.GQL())
{
// Get video GQL access token
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"];
// Get video streams
cancellationToken.ThrowIfCancellationRequested();
response = (await client.DownloadStringTaskAsync($"http://usher.twitch.tv/vod/{ID}?nauth={videoAccessToken["value"]}&nauthsig={videoAccessToken["signature"]}&allow_source=true&player=twitchweb")).Split("\n");
}
// 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
for (int i = 2; i < response.Length; i += 3)
{
// Parse line 2
Match line2 = L2Regex.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;
Match line2 = streamDataL2Regex.Match(response[i + 1]);
// Create stream
Stream stream = new Stream(url, true, StreamType.AudioVideo)
BaseStream stream = new BaseStream()
{
Width = width,
Height = height,
FrameRate = frameRate,
VideoCodec = videoCodec,
AudioCodec = audioCodec,
Url = new Uri(response[i + 2]),
Height = int.Parse(line2.Groups["height"].Value),
FrameRate = line2.Groups["frame_rate"].Value != string.Empty ? (int)Math.Round(double.Parse(line2.Groups["frame_rate"].Value)) : 0,
};
// Add stream
streams.Add(stream);
}
// Set Streams parameter
// Set streams
BaseStreams = streams.ToArray();
}
// 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
DownloadingStarted?.Invoke(this, System.EventArgs.Empty);
cancellationToken.ThrowIfCancellationRequested();
DownloadingProgressChanged(this, new EventArgs.ProgressChangedEventArgs(0));
// Get video chunks
cancellationToken.ThrowIfCancellationRequested();
List<(Uri ChunkUrl, TimeSpan ChunkDuration)> chunksList = await ExtractChunksFromM3U8Async(baseStream.Url, cancellationToken);
// Changeable duration
TimeSpan duration = Metadata.Duration;
// 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
cancellationToken.ThrowIfCancellationRequested();
StorageFile rawFile = await downloadingFolder.CreateFileAsync("raw.ts");
float chunksDownloaded = 0;
double chunksDownloaded = 0;
Task<byte[]> downloadTask;
Task writeTask;
@@ -180,26 +156,25 @@ namespace VDownload.Core.Services.Sources.Twitch
writeTask = WriteChunkToFileAsync(rawFile, downloadTask.Result);
downloadTask = DownloadChunkAsync(chunksList[i].ChunkUrl);
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();
await WriteChunkToFileAsync(rawFile, downloadTask.Result);
DownloadingProgressChanged(this, new ProgressChangedEventArgs((int)Math.Round(++chunksDownloaded * 100 / chunksList.Count), null));
DownloadingCompleted?.Invoke(this, System.EventArgs.Empty);
DownloadingProgressChanged(this, new EventArgs.ProgressChangedEventArgs(100, true));
// Processing
cancellationToken.ThrowIfCancellationRequested();
StorageFile outputFile = await downloadingFolder.CreateFileAsync($"transcoded.{extension.ToString().ToLower()}");
MediaProcessor mediaProcessor = new MediaProcessor(outputFile, trimStart, trimEnd);
mediaProcessor.ProcessingStarted += ProcessingStarted;
mediaProcessor.ProcessingProgressChanged += ProcessingProgressChanged;
mediaProcessor.ProcessingCompleted += ProcessingCompleted;
cancellationToken.ThrowIfCancellationRequested();
await mediaProcessor.Run(rawFile, extension, mediaType, cancellationToken);
MediaProcessor mediaProcessor = new MediaProcessor();
mediaProcessor.ProgressChanged += ProcessingProgressChanged;
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 outputFile;
@@ -214,21 +189,28 @@ namespace VDownload.Core.Services.Sources.Twitch
// GET CHUNKS DATA FROM M3U8 PLAYLIST
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();
string response = await client.DownloadStringTaskAsync(streamUrl);
// Get response
string response = null;
using (WebClient client = Client.GQL())
{
response = await client.DownloadStringTaskAsync(streamUrl);
}
// Create dictionary
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
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));
chunks.Add((chunkUrl, chunkDuration));
}
@@ -238,7 +220,7 @@ namespace VDownload.Core.Services.Sources.Twitch
}
// 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
TimeSpan newDuration = duration;
@@ -260,7 +242,7 @@ namespace VDownload.Core.Services.Sources.Twitch
}
// Return data
return (trimStart, trimEnd);
return (trimStart, trimEnd, newDuration);
}
// DOWNLOAD CHUNK
@@ -322,12 +304,8 @@ namespace VDownload.Core.Services.Sources.Twitch
#region EVENT HANDLERS
public event EventHandler DownloadingStarted;
public event EventHandler<ProgressChangedEventArgs> DownloadingProgressChanged;
public event EventHandler DownloadingCompleted;
public event EventHandler ProcessingStarted;
public event EventHandler<ProgressChangedEventArgs> ProcessingProgressChanged;
public event EventHandler ProcessingCompleted;
public event EventHandler<EventArgs.ProgressChangedEventArgs> DownloadingProgressChanged;
public event EventHandler<EventArgs.ProgressChangedEventArgs> ProcessingProgressChanged;
#endregion
}

View File

@@ -7,21 +7,12 @@ namespace VDownload.Core.Services
{
#region CONSTANTS
// RANDOM
private static readonly Random Random = new Random();
// ID SETTINGS
private static readonly char[] IDChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890".ToCharArray();
private static readonly int IDLength = 10;
#endregion
#region PROPERTIES
// USED IDS LIST
private static readonly List<string> UsedIDs = new List<string>();
// IDS LIST
private static readonly List<string> IDList = new List<string>();
#endregion
@@ -38,17 +29,17 @@ namespace VDownload.Core.Services
id = "";
while (id.Length < IDLength)
{
id += IDChars[Random.Next(0, IDChars.Length)];
id += IDChars[new Random().Next(0, IDChars.Length)];
}
} while (UsedIDs.Contains(id));
UsedIDs.Add(id);
} while (IDList.Contains(id));
IDList.Add(id);
return id;
}
// DISPOSE TASK ID
public static void Dispose(string id)
{
UsedIDs.Remove(id);
IDList.Remove(id);
}
#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 VDownload.Core.Enums;
using VDownload.Core.Interfaces;
using VDownload.Core.Objects;
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 MediaType MediaType { get; set; }
public IBaseStream Stream { get; set; }
public BaseStream Stream { get; set; }
public TimeSpan TrimStart { get; set; }
public TimeSpan TrimEnd { get; set; }
public string Filename { get; set; }

View File

@@ -124,24 +124,27 @@
<Compile Include="Enums\MediaFileExtension.cs" />
<Compile Include="Enums\MediaType.cs" />
<Compile Include="Enums\PlaylistSource.cs" />
<Compile Include="Enums\StreamType.cs" />
<Compile Include="Enums\TaskAddingRequestSource.cs" />
<Compile Include="Enums\VideoFileExtension.cs" />
<Compile Include="Enums\VideoSource.cs" />
<Compile Include="Enums\TaskStatus.cs" />
<Compile Include="EventArgs\PlaylistAddEventArgs.cs" />
<Compile Include="EventArgs\VideoAddEventArgs.cs" />
<Compile Include="EventArgs\ProgressChangedEventArgs.cs" />
<Compile Include="EventArgs\TasksAddingRequestedEventArgs.cs" />
<Compile Include="EventArgs\VideoSearchEventArgs.cs" />
<Compile Include="EventArgs\PlaylistSearchEventArgs.cs" />
<Compile Include="Exceptions\TwitchAccessTokenNotFoundException.cs" />
<Compile Include="Exceptions\TwitchAccessTokenNotValidException.cs" />
<Compile Include="Interfaces\IBaseStream.cs" />
<Compile Include="Interfaces\IPlaylistService.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\MediaProcessor.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\Clip.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>
</data>
<data name="HomeOptionsBarLoadSubscripionsButton.Label" xml:space="preserve">
<value>Load subscription</value>
<value>Load subscriptions</value>
</data>
<data name="HomeOptionsBarLoadSubscripionsButton.Width" xml:space="preserve">
<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>
</PackageReference>
</ItemGroup>
<ItemGroup>
<PRIResource Include="Strings\en-US\ResourcesOld.resw" />
</ItemGroup>
<ItemGroup>
<PRIResource Include="Strings\en-US\Resources.resw" />
</ItemGroup>

View File

@@ -60,7 +60,7 @@
<!-- VIDEOS LIST -->
<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>
<!-- OPTIONS BAR AND ADDING PANEL -->

View File

@@ -11,6 +11,7 @@ using VDownload.Core.EventArgs;
using VDownload.Core.Exceptions;
using VDownload.Core.Interfaces;
using VDownload.Core.Services;
using VDownload.Core.Structs;
using Windows.ApplicationModel.Resources;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
@@ -21,11 +22,29 @@ namespace VDownload.Views.Home
{
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
public HomeMain()
{
this.InitializeComponent();
// Set cancellation token
SearchingCancellationToken = new CancellationTokenSource();
}
#endregion
@@ -34,20 +53,13 @@ namespace VDownload.Views.Home
#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
private CancellationTokenSource SearchingCancellationToken = new CancellationTokenSource();
private CancellationTokenSource SearchingCancellationToken { get; set; }
// HOME TASKS LIST PLACEHOLDER
private readonly HomeTasksListPlaceholder HomeTasksListPlaceholder = new HomeTasksListPlaceholder();
// HOME VIDEOS LIST
private static ContentControl HomeTasksListPlaceCurrent { get; set; }
// HOME TASKS LIST
private static ContentControl HomeTasksListCurrentParent = null;
private static StackPanel HomeTasksList = null;
public static List<HomeTaskPanel> TaskPanelsList = new List<HomeTaskPanel>();
public static List<HomeTaskPanel> TasksList = new List<HomeTaskPanel>();
#endregion
@@ -58,20 +70,28 @@ namespace VDownload.Views.Home
// ON NAVIGATED TO
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();
// Create new task list
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);
HomeTasksListPlaceCurrent.Content = HomeTasksList;
foreach (HomeTaskPanel homeVideoPanel in TasksList) HomeTasksList.Children.Add(homeVideoPanel);
HomeTasksListCurrentParent.Content = HomeTasksList;
}
else
{
HomeTasksListPlaceCurrent.Content = HomeTasksListPlaceholder;
HomeTasksListCurrentParent.Content = HomeTasksListPlaceholder;
}
}
// ADD VIDEO BUTTON CHECKED
private void HomeOptionsBarAddVideoButton_Checked(object sender, RoutedEventArgs e)
{
@@ -100,7 +120,7 @@ namespace VDownload.Views.Home
HomeOptionsBarSearchingStatusControl.Content = HomeOptionsBarSearchingStatusProgressRing;
// Parse url
(VideoSource Type, string ID) source = Source.GetVideoSource(e.Phrase);
(VideoSource Type, string ID) source = Source.GetVideoSource(e.Url);
// Check url
if (source.Type == VideoSource.Null)
@@ -176,37 +196,13 @@ namespace VDownload.Views.Home
HomeOptionBarAndAddingPanelRow.Height = new GridLength(1, GridUnitType.Star);
HomeTasksListRow.Height = new GridLength(0);
// Open adding panel
HomeVideoAddingPanel addingPanel = new HomeVideoAddingPanel(videoService);
addingPanel.VideoAddRequest += HomeVideoAddingPanel_VideoAddRequest;
addingPanel.TasksAddingRequested += HomeTasksAddingRequest;
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
private void HomeOptionsBarAddPlaylistButton_Checked(object sender, RoutedEventArgs e)
@@ -236,7 +232,7 @@ namespace VDownload.Views.Home
HomeOptionsBarSearchingStatusControl.Content = HomeOptionsBarSearchingStatusProgressRing;
// Parse url
(PlaylistSource Type, string ID) source = Source.GetPlaylistSource(e.Phrase);
(PlaylistSource Type, string ID) source = Source.GetPlaylistSource(e.Url);
// Check url
if (source.Type == PlaylistSource.Null)
@@ -256,7 +252,7 @@ namespace VDownload.Views.Home
try
{
await playlistService.GetMetadataAsync(SearchingCancellationToken.Token);
await playlistService.GetVideosAsync(e.Count, SearchingCancellationToken.Token);
await playlistService.GetVideosAsync(e.VideosCount, SearchingCancellationToken.Token);
}
catch (OperationCanceledException)
{
@@ -290,7 +286,7 @@ namespace VDownload.Views.Home
catch (WebException wex)
{
HomeOptionsBarSearchingStatusControl.Content = HomeOptionsBarSearchingStatusErrorImage;
if (wex.Response == null)
if (!NetworkHelper.Instance.ConnectionInformation.IsInternetAvailable)
{
ContentDialog internetAccessErrorDialog = new ContentDialog
{
@@ -310,83 +306,101 @@ namespace VDownload.Views.Home
// Set UI
HomeOptionBarAndAddingPanelRow.Height = new GridLength(1, GridUnitType.Star);
HomeTasksListRow.Height = new GridLength(0);
// Open adding panel
HomePlaylistAddingPanel addingPanel = new HomePlaylistAddingPanel(playlistService);
addingPanel.PlaylistAddRequest += HomeVideoAddingPanel_PlayListAddRequest;
addingPanel.TasksAddingRequested += HomeTasksAddingRequest;
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
HomeTasksListPlaceCurrent.Content = HomeTasksList;
HomeTasksListCurrentParent.Content = HomeTasksList;
// Uncheck video button
HomeOptionsBarAddPlaylistButton.IsChecked = false;
// Create video tasks
foreach (var video in e.Videos)
// Uncheck button
switch (e.RequestSource)
{
HomeTaskPanel taskPanel = new HomeTaskPanel(video.VideoService, video.MediaType, video.Stream, video.TrimStart, video.TrimEnd, video.Filename, video.Extension, video.Location, video.Schedule);
case TaskAddingRequestSource.Video: HomeOptionsBarAddVideoButton.IsChecked = false; break;
case TaskAddingRequestSource.Playlist: HomeOptionsBarAddPlaylistButton.IsChecked = false; break;
}
// Create video tasks
foreach (TaskData taskData in e.TaskData)
{
HomeTaskPanel taskPanel = new HomeTaskPanel(taskData);
taskPanel.TaskRemovingRequested += (s, a) =>
{
// Remove task from tasks lists
TaskPanelsList.Remove(taskPanel);
TasksList.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
HomeTasksList.Children.Add(taskPanel);
TaskPanelsList.Add(taskPanel);
TasksList.Add(taskPanel);
}
}
// ADDING BUTTONS UNCHECKED
private void HomeOptionsBarAddingButtons_Unchecked(object sender, RoutedEventArgs e)
// TASK ADDING CANCELLED
private void HomeSearchingCancelled()
{
// Cancel searching operations
SearchingCancellationToken.Cancel();
SearchingCancellationToken = new CancellationTokenSource();
// Set grid dimensions
HomeOptionBarAndAddingPanelRow.Height = GridLength.Auto;
HomeTasksListRow.Height = new GridLength(1, GridUnitType.Star);
// Clear panels
HomeAddingPanel.Content = null;
HomeOptionsBarAddingControl.Content = null;
HomeOptionsBarSearchingStatusControl.Content = null;
}
// ADDING BUTTONS UNCHECKED
private void HomeOptionsBarAddingButtons_Unchecked(object sender, RoutedEventArgs e)
{
HomeSearchingCancelled();
}
// DOWNLOAD ALL BUTTON CLICKED
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)
{
bool delay = (bool)Config.GetValue("delay_task_when_queued_task_starts_on_metered_network");
ContentDialogResult dialogResult = await new ContentDialog
if (NetworkHelper.Instance.ConnectionInformation.IsInternetOnMeteredConnection)
{
Title = ResourceLoader.GetForCurrentView().GetString("HomeDownloadAllButtonMeteredConnectionDialogTitle"),
Content = ResourceLoader.GetForCurrentView().GetString("HomeDownloadAllButtonMeteredConnectionDialogDescription"),
PrimaryButtonText = ResourceLoader.GetForCurrentView().GetString("HomeDownloadAllButtonMeteredConnectionDialogStartAndDelayText"),
SecondaryButtonText = ResourceLoader.GetForCurrentView().GetString("HomeDownloadAllButtonMeteredConnectionDialogStartWithoutDelayText"),
CloseButtonText = ResourceLoader.GetForCurrentView().GetString("HomeDownloadAllButtonMeteredConnectionDialogCancel"),
}.ShowAsync();
switch (dialogResult)
{
case ContentDialogResult.Primary: delay = true; break;
case ContentDialogResult.Secondary: delay = false; break;
case ContentDialogResult.None: return;
ContentDialogResult dialogResult = await new ContentDialog
{
Title = ResourceLoader.GetForCurrentView().GetString("HomeDownloadAllButtonMeteredConnectionDialogTitle"),
Content = ResourceLoader.GetForCurrentView().GetString("HomeDownloadAllButtonMeteredConnectionDialogDescription"),
PrimaryButtonText = ResourceLoader.GetForCurrentView().GetString("HomeDownloadAllButtonMeteredConnectionDialogStartAndDelayText"),
SecondaryButtonText = ResourceLoader.GetForCurrentView().GetString("HomeDownloadAllButtonMeteredConnectionDialogStartWithoutDelayText"),
CloseButtonText = ResourceLoader.GetForCurrentView().GetString("HomeDownloadAllButtonMeteredConnectionDialogCancel"),
}.ShowAsync();
switch (dialogResult)
{
case ContentDialogResult.Primary: delay = true; break;
case ContentDialogResult.Secondary: delay = false; break;
case ContentDialogResult.None: return;
}
}
foreach (HomeTaskPanel videoPanel in idleTasks)
{
await Task.Delay(50);
await Task.Delay(10);
#pragma warning disable CS4014
videoPanel.Start(delay);
@@ -404,7 +418,7 @@ namespace VDownload.Views.Home
// WAIT IN QUEUE
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);
}

View File

@@ -23,7 +23,7 @@
<TextBox x:Name="HomeOptionsBarAddPlaylistControlUrlTextBox" x:Uid="HomeOptionsBarAddPlaylistControlUrlTextBox" Grid.Column="0" VerticalAlignment="Center"/>
<!-- 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-->
<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
// NUMBERBOX FOCUS LOST
private void HomeOptionsBarAddPlaylistControlMaxVideosNumberBox_LostFocus(object sender, RoutedEventArgs e)
{
if (double.IsNaN(HomeOptionsBarAddPlaylistControlMaxVideosNumberBox.Value)) HomeOptionsBarAddPlaylistControlMaxVideosNumberBox.Value = DefaultMaxPlaylistVideos;
}
// SEARCH BUTTON CLICKED
private void HomeOptionsBarAddPlaylistControlSearchButton_Click(object sender, RoutedEventArgs e)
{
@@ -39,8 +45,8 @@ namespace VDownload.Views.Home
// Invoke search button event handlers
PlaylistSearchEventArgs args = new PlaylistSearchEventArgs
{
Phrase = HomeOptionsBarAddPlaylistControlUrlTextBox.Text,
Count = int.Parse(HomeOptionsBarAddPlaylistControlMaxVideosNumberBox.Text),
Url = HomeOptionsBarAddPlaylistControlUrlTextBox.Text,
VideosCount = int.Parse(HomeOptionsBarAddPlaylistControlMaxVideosNumberBox.Text),
};
SearchButtonClicked?.Invoke(this, args);
}

View File

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

View File

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

View File

@@ -87,9 +87,9 @@
<cc:SettingControl x:Uid="HomePlaylistAddingVideoPanelTrimSettingControl" Icon="{ThemeResource TrimIcon}">
<cc:SettingControl.SettingContent>
<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="-"/>
<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>
</cc:SettingControl.SettingContent>
</cc:SettingControl>

View File

@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
@@ -9,6 +10,7 @@ using System.Threading.Tasks;
using VDownload.Core.Enums;
using VDownload.Core.Interfaces;
using VDownload.Core.Services;
using VDownload.Core.Structs;
using Windows.ApplicationModel.Resources;
using Windows.Foundation;
using Windows.Foundation.Collections;
@@ -46,13 +48,13 @@ namespace VDownload.Views.Home
VideoService = videoService;
// 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 };
Title = VideoService.Title;
Author = VideoService.Author;
Views = VideoService.Views.ToString();
Date = VideoService.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}";
Title = VideoService.Metadata.Title;
Author = VideoService.Metadata.Author;
Views = VideoService.Metadata.Views.ToString();
Date = VideoService.Metadata.Date.ToString(CultureInfo.InstalledUICulture.DateTimeFormat.ShortDatePattern);
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
foreach (string mediaType in Enum.GetNames(typeof(MediaType)))
@@ -62,37 +64,39 @@ namespace VDownload.Views.Home
HomePlaylistAddingVideoPanelMediaTypeSettingControlComboBox.SelectedIndex = (int)Config.GetValue("default_media_type");
// 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.SelectedIndex = 0;
// Set trim start
if (Math.Floor(VideoService.Duration.TotalHours) > 0) HomePlaylistAddingVideoPanelTrimStartTextBox.Text += $"{new string('0', Math.Floor(VideoService.Duration.TotalHours).ToString().Length)}:";
if (Math.Floor(VideoService.Duration.TotalMinutes) > 0) HomePlaylistAddingVideoPanelTrimStartTextBox.Text += Math.Floor(VideoService.Duration.TotalHours) > 0 ? "00:" : $"{new string('0', VideoService.Duration.Minutes.ToString().Length)}:";
HomePlaylistAddingVideoPanelTrimStartTextBox.Text += Math.Floor(VideoService.Duration.TotalMinutes) > 0 ? "00" : $"{new string('0', VideoService.Duration.Seconds.ToString().Length)}";
TrimStart = new TimeSpan(0);
if (Math.Floor(VideoService.Metadata.Duration.TotalHours) > 0) HomePlaylistAddingVideoPanelTrimStartTextBox.Text += $"{new string('0', Math.Floor(VideoService.Metadata.Duration.TotalHours).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
if (Math.Floor(VideoService.Duration.TotalHours) > 0) HomePlaylistAddingVideoPanelTrimEndTextBox.Text += $"{Math.Floor(VideoService.Duration.TotalHours)}:";
if (Math.Floor(VideoService.Duration.TotalMinutes) > 0) HomePlaylistAddingVideoPanelTrimEndTextBox.Text += Math.Floor(VideoService.Duration.TotalHours) > 0 ? $"{VideoService.Duration.Minutes:00}:" : $"{VideoService.Duration.Minutes}:";
HomePlaylistAddingVideoPanelTrimEndTextBox.Text += Math.Floor(VideoService.Duration.TotalMinutes) > 0 ? $"{VideoService.Duration.Seconds:00}" : $"{VideoService.Duration.Seconds}";
TrimEnd = VideoService.Metadata.Duration;
if (Math.Floor(VideoService.Metadata.Duration.TotalHours) > 0) HomePlaylistAddingVideoPanelTrimEndTextBox.Text += $"{Math.Floor(VideoService.Metadata.Duration.TotalHours)}:";
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
string temporaryFilename = (string)Config.GetValue("default_filename");
Dictionary<string, string> filenameStandardTemplates = new Dictionary<string, string>()
{
{ "<title>", VideoService.Title },
{ "<author>", VideoService.Author },
{ "<views>", VideoService.Views.ToString() },
{ "<title>", VideoService.Metadata.Title },
{ "<author>", VideoService.Metadata.Author },
{ "<views>", VideoService.Metadata.Views.ToString() },
{ "<id>", VideoService.ID },
};
foreach (KeyValuePair<string, string> template in filenameStandardTemplates) temporaryFilename = temporaryFilename.Replace(template.Key, template.Value);
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(@"<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 (char c in System.IO.Path.GetInvalidFileNameChars()) temporaryFilename = temporaryFilename.Replace(c, ' ');
@@ -142,7 +146,7 @@ namespace VDownload.Views.Home
// VIDEO OPTIONS
public MediaType MediaType { get; set; }
public IBaseStream Stream { get; set; }
public BaseStream Stream { get; set; }
public TimeSpan TrimStart { get; set; }
public TimeSpan TrimEnd { get; set; }
public string Filename { get; set; }
@@ -204,15 +208,15 @@ namespace VDownload.Views.Home
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
{
TrimStart = new TimeSpan(0);
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.Duration.TotalMinutes) > 0) newText += Math.Floor(VideoService.Duration.TotalHours) > 0 ? "00:" : $"{new string('0', VideoService.Duration.Minutes.ToString().Length)}:";
newText += Math.Floor(VideoService.Duration.TotalMinutes) > 0 ? "00" : $"{new string('0', VideoService.Duration.Seconds.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.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.Metadata.Duration.TotalMinutes) > 0 ? "00" : $"{new string('0', VideoService.Metadata.Duration.Seconds.ToString().Length)}";
if (newText != HomePlaylistAddingVideoPanelTrimStartTextBox.Text) HomePlaylistAddingVideoPanelTrimStartTextBox.Text = newText;
}
@@ -231,15 +235,15 @@ namespace VDownload.Views.Home
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
{
TrimEnd = VideoService.Duration;
TrimEnd = VideoService.Metadata.Duration;
string newText = string.Empty;
if (Math.Floor(VideoService.Duration.TotalHours) > 0) newText += $"{Math.Floor(VideoService.Duration.TotalHours)}:";
if (Math.Floor(VideoService.Duration.TotalMinutes) > 0) newText += Math.Floor(VideoService.Duration.TotalHours) > 0 ? $"{TrimEnd.Minutes:00}:" : $"{TrimEnd.Minutes}:";
newText += Math.Floor(VideoService.Duration.TotalMinutes) > 0 ? $"{TrimEnd.Seconds:00}" : $"{TrimEnd.Seconds}";
if (Math.Floor(VideoService.Metadata.Duration.TotalHours) > 0) newText += $"{Math.Floor(VideoService.Metadata.Duration.TotalHours)}:";
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.Metadata.Duration.TotalMinutes) > 0 ? $"{TrimEnd.Seconds:00}" : $"{TrimEnd.Seconds}";
if (newText != HomePlaylistAddingVideoPanelTrimEndTextBox.Text) HomePlaylistAddingVideoPanelTrimEndTextBox.Text = newText;
}

View File

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

View File

@@ -217,9 +217,9 @@
<cc:SettingControl x:Uid="HomeVideoAddingTrimSettingControl" Icon="{ThemeResource TrimIcon}">
<cc:SettingControl.SettingContent>
<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="-"/>
<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>
</cc:SettingControl.SettingContent>
</cc:SettingControl>

View File

@@ -8,6 +8,7 @@ using VDownload.Core.Enums;
using VDownload.Core.EventArgs;
using VDownload.Core.Interfaces;
using VDownload.Core.Services;
using VDownload.Core.Structs;
using Windows.ApplicationModel.Resources;
using Windows.Storage;
using Windows.Storage.AccessCache;
@@ -39,13 +40,13 @@ namespace VDownload.Views.Home
VideoService = videoService;
// 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 };
Title = VideoService.Title;
Author = VideoService.Author;
Views = VideoService.Views.ToString();
Date = VideoService.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}";
Title = VideoService.Metadata.Title;
Author = VideoService.Metadata.Author;
Views = VideoService.Metadata.Views.ToString();
Date = VideoService.Metadata.Date.ToString(CultureInfo.InstalledUICulture.DateTimeFormat.ShortDatePattern);
Duration = TimeSpanCustomFormat.ToOptTHBaseMMSS(VideoService.Metadata.Duration);
// Set media type
foreach (string mediaType in Enum.GetNames(typeof(MediaType)))
@@ -55,42 +56,40 @@ namespace VDownload.Views.Home
HomeVideoAddingMediaTypeSettingControlComboBox.SelectedIndex = (int)Config.GetValue("default_media_type");
// 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.SelectedIndex = 0;
// Set trim start
if (Math.Floor(VideoService.Duration.TotalHours) > 0) HomeVideoAddingTrimStartTextBox.Text += $"{new string('0', Math.Floor(VideoService.Duration.TotalHours).ToString().Length)}:";
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 += Math.Floor(VideoService.Duration.TotalMinutes) > 0 ? "00" : $"{new string('0', VideoService.Duration.Seconds.ToString().Length)}";
TrimStart = TimeSpan.Zero;
HomeVideoAddingTrimStartTextBox.Text = TimeSpanCustomFormat.ToOptTHMMBaseSS(TrimStart, VideoService.Metadata.Duration);
// Set trim end
if (Math.Floor(VideoService.Duration.TotalHours) > 0) HomeVideoAddingTrimEndTextBox.Text += $"{Math.Floor(VideoService.Duration.TotalHours)}:";
if (Math.Floor(VideoService.Duration.TotalMinutes) > 0) HomeVideoAddingTrimEndTextBox.Text += Math.Floor(VideoService.Duration.TotalHours) > 0 ? $"{VideoService.Duration.Minutes:00}:" : $"{VideoService.Duration.Minutes}:";
HomeVideoAddingTrimEndTextBox.Text += Math.Floor(VideoService.Duration.TotalMinutes) > 0 ? $"{VideoService.Duration.Seconds:00}" : $"{VideoService.Duration.Seconds}";
TrimEnd = VideoService.Metadata.Duration;
HomeVideoAddingTrimStartTextBox.Text = TimeSpanCustomFormat.ToOptTHMMBaseSS(TrimEnd);
// Set filename
string temporaryFilename = (string)Config.GetValue("default_filename");
Dictionary<string, string> filenameStandardTemplates = new Dictionary<string, string>()
{
{ "<title>", VideoService.Title },
{ "<author>", VideoService.Author },
{ "<views>", VideoService.Views.ToString() },
{ "<title>", VideoService.Metadata.Title },
{ "<author>", VideoService.Metadata.Author },
{ "<views>", VideoService.Metadata.Views.ToString() },
{ "<id>", VideoService.ID },
};
foreach (KeyValuePair<string, string> template in filenameStandardTemplates) temporaryFilename = temporaryFilename.Replace(template.Key, template.Value);
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(@"<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 (char c in System.IO.Path.GetInvalidFileNameChars()) temporaryFilename = temporaryFilename.Replace(c, ' ');
HomeVideoAddingFilenameTextBox.Text = temporaryFilename;
Filename = temporaryFilename;
HomeVideoAddingFilenameTextBox.Text = Filename;
// Set location
if (!(bool)Config.GetValue("custom_media_location") && StorageApplicationPermissions.FutureAccessList.ContainsItem("last_media_location"))
@@ -135,7 +134,7 @@ namespace VDownload.Views.Home
// VIDEO OPTIONS
private MediaType MediaType { get; set; }
private IBaseStream Stream { get; set; }
private BaseStream Stream { get; set; }
private TimeSpan TrimStart { get; set; }
private TimeSpan TrimEnd { get; set; }
private string Filename { get; set; }
@@ -197,15 +196,15 @@ namespace VDownload.Views.Home
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
{
TrimStart = new TimeSpan(0);
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.Duration.TotalMinutes) > 0) newText += Math.Floor(VideoService.Duration.TotalHours) > 0 ? "00:" : $"{new string('0', VideoService.Duration.Minutes.ToString().Length)}:";
newText += Math.Floor(VideoService.Duration.TotalMinutes) > 0 ? "00" : $"{new string('0', VideoService.Duration.Seconds.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.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.Metadata.Duration.TotalMinutes) > 0 ? "00" : $"{new string('0', VideoService.Metadata.Duration.Seconds.ToString().Length)}";
if (newText != HomeVideoAddingTrimStartTextBox.Text) HomeVideoAddingTrimStartTextBox.Text = newText;
}
@@ -224,15 +223,15 @@ namespace VDownload.Views.Home
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
{
TrimEnd = VideoService.Duration;
TrimEnd = VideoService.Metadata.Duration;
string newText = string.Empty;
if (Math.Floor(VideoService.Duration.TotalHours) > 0) newText += $"{Math.Floor(VideoService.Duration.TotalHours)}:";
if (Math.Floor(VideoService.Duration.TotalMinutes) > 0) newText += Math.Floor(VideoService.Duration.TotalHours) > 0 ? $"{TrimEnd.Minutes:00}:" : $"{TrimEnd.Minutes}:";
newText += Math.Floor(VideoService.Duration.TotalMinutes) > 0 ? $"{TrimEnd.Seconds:00}" : $"{TrimEnd.Seconds}";
if (Math.Floor(VideoService.Metadata.Duration.TotalHours) > 0) newText += $"{Math.Floor(VideoService.Metadata.Duration.TotalHours)}:";
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.Metadata.Duration.TotalMinutes) > 0 ? $"{TrimEnd.Seconds:00}" : $"{TrimEnd.Seconds}";
if (newText != HomeVideoAddingTrimEndTextBox.Text) HomeVideoAddingTrimEndTextBox.Text = newText;
}
@@ -267,12 +266,14 @@ namespace VDownload.Views.Home
// LOCATION BROWSE BUTTON CLICKED
private async void HomeVideoAddingLocationBrowseButton_Click(object sender, RoutedEventArgs e)
{
// Create location picker
FolderPicker picker = new FolderPicker
{
SuggestedStartLocation = PickerLocationId.Downloads
};
picker.FileTypeFilter.Add("*");
// Select location
StorageFolder selectedFolder = await picker.PickSingleFolderAsync();
if (selectedFolder != null)
@@ -288,6 +289,7 @@ namespace VDownload.Views.Home
}
}
// SOURCE BUTTON CLICKED
public async void HomeVideoAddingPanelSourceButton_Click(object sender, RoutedEventArgs e)
{
@@ -298,7 +300,8 @@ namespace VDownload.Views.Home
// ADD BUTTON CLICKED
public void HomeVideoAddingPanelAddButton_Click(object sender, RoutedEventArgs e)
{
VideoAddEventArgs args = new VideoAddEventArgs
// Pack task data
TaskData taskData = new TaskData
{
VideoService = VideoService,
MediaType = MediaType,
@@ -310,7 +313,14 @@ namespace VDownload.Views.Home
Location = Location,
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
@@ -319,7 +329,7 @@ namespace VDownload.Views.Home
#region EVENT HANDLERS
public event EventHandler<VideoAddEventArgs> VideoAddRequest;
public event EventHandler<TasksAddingRequestedEventArgs> TasksAddingRequested;
#endregion
}

View File

@@ -13,13 +13,8 @@ using Windows.UI.Xaml.Input;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Navigation;
// The Blank Page item template is documented at https://go.microsoft.com/fwlink/?LinkId=234238
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 SettingsMain()

View File

@@ -15,10 +15,9 @@
</Page.Resources>
<Grid Padding="20">
<Grid Padding="20" RowSpacing="20">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="20"/>
<RowDefinition/>
</Grid.RowDefinitions>
@@ -26,7 +25,7 @@
<TextBlock x:Name="SourcesMainPageHeaderText" x:Uid="SourcesMainPageHeaderText" Grid.Row="0" FontSize="28" FontWeight="SemiBold"/>
<!-- 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.SettingContent>
<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.Collections.Generic;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
using Windows.ApplicationModel.Resources;
using Windows.UI.WindowManagement;
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)
{
// 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
{
string twitchAccessToken = await Core.Services.Sources.Twitch.Auth.ReadAccessTokenAsync();
(bool IsValid, string Login, DateTime? ExpirationDate) twitchAccessTokenValidation = await Core.Services.Sources.Twitch.Auth.ValidateAccessTokenAsync(twitchAccessToken);
if (twitchAccessToken != null && twitchAccessTokenValidation.IsValid)
string twitchAccessToken = await Core.Services.Sources.Twitch.Helpers.Auth.ReadAccessTokenAsync();
#pragma warning disable IDE0042 // Deconstruct variable declaration
(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}";
SourcesTwitchLoginButton.Content = ResourceLoader.GetForCurrentView().GetString("SourcesTwitchLoginButtonTextLoggedIn");
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");
SourcesTwitchLoginButton.Content = ResourceLoader.GetForCurrentView().GetString("SourcesTwitchLoginButtonTextNotLoggedIn");
SourcesTwitchLoginButton.IsEnabled = true;
}
}
catch (WebException wex)
catch (WebException)
{
if (wex.Response == null)
if (!NetworkHelper.Instance.ConnectionInformation.IsInternetAvailable)
{
SourcesTwitchSettingControl.Description = ResourceLoader.GetForCurrentView().GetString("SourcesTwitchSettingControlDescriptionInternetConnectionError");
SourcesTwitchLoginButton.Content = ResourceLoader.GetForCurrentView().GetString("SourcesTwitchLoginButtonTextNotLoggedIn");
SourcesTwitchLoginButton.IsEnabled = false;
}
else throw;
}
@@ -65,15 +85,15 @@ namespace VDownload.Views.Sources
{
try
{
string accessToken = await Core.Services.Sources.Twitch.Auth.ReadAccessTokenAsync();
var accessTokenValidation = await Core.Services.Sources.Twitch.Auth.ValidateAccessTokenAsync(accessToken);
if (accessToken != null && accessTokenValidation.IsValid)
string accessToken = await Core.Services.Sources.Twitch.Helpers.Auth.ReadAccessTokenAsync();
var accessTokenValidation = await Core.Services.Sources.Twitch.Helpers.Auth.ValidateAccessTokenAsync(accessToken);
if (accessTokenValidation.IsValid)
{
// Revoke access token
await Core.Services.Sources.Twitch.Auth.RevokeAccessTokenAsync(accessToken);
await Core.Services.Sources.Twitch.Helpers.Auth.RevokeAccessTokenAsync(accessToken);
// Delete access token
await Core.Services.Sources.Twitch.Auth.DeleteAccessTokenAsync();
await Core.Services.Sources.Twitch.Helpers.Auth.DeleteAccessTokenAsync();
// Update Twitch SettingControl
SourcesTwitchSettingControl.Description = ResourceLoader.GetForCurrentView().GetString("SourcesTwitchSettingControlDescriptionNotLoggedIn");
@@ -85,21 +105,22 @@ namespace VDownload.Views.Sources
AppWindow TwitchAuthWindow = await AppWindow.TryCreateAsync();
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();
await TwitchAuthWebView.EnsureCoreWebView2Async();
TwitchAuthWebView.Source = Core.Services.Sources.Twitch.Auth.AuthorizationUrl;
TwitchAuthWebView.Source = Core.Services.Sources.Twitch.Helpers.Auth.AuthorizationUrl;
ElementCompositionPreview.SetAppWindowContent(TwitchAuthWindow, TwitchAuthWebView);
// NavigationStarting event (only when redirected)
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
await TwitchAuthWindow.CloseAsync();
// 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] == '#')
{
@@ -107,10 +128,10 @@ namespace VDownload.Views.Sources
accessToken = response.Split('&')[0].Replace("/#access_token=", "");
// Check token
accessTokenValidation = await Core.Services.Sources.Twitch.Auth.ValidateAccessTokenAsync(accessToken);
accessTokenValidation = await Core.Services.Sources.Twitch.Helpers.Auth.ValidateAccessTokenAsync(accessToken);
// Save token
await Core.Services.Sources.Twitch.Auth.SaveAccessTokenAsync(accessToken);
await Core.Services.Sources.Twitch.Helpers.Auth.SaveAccessTokenAsync(accessToken);
// Update Twitch SettingControl
SourcesTwitchSettingControl.Description = $"{ResourceLoader.GetForCurrentView().GetString("SourcesTwitchSettingControlDescriptionLoggedIn")} {accessTokenValidation.Login}";
@@ -151,11 +172,12 @@ namespace VDownload.Views.Sources
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)
{
if (wex.Response == null)
if (!NetworkHelper.Instance.ConnectionInformation.IsInternetAvailable)
{
SourcesTwitchSettingControl.Description = ResourceLoader.GetForCurrentView().GetString("SourcesTwitchSettingControlDescriptionInternetConnectionError");
SourcesTwitchLoginButton.Content = ResourceLoader.GetForCurrentView().GetString("SourcesTwitchLoginButtonTextNotLoggedIn");

View File

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

View File

@@ -13,13 +13,8 @@ using Windows.UI.Xaml.Input;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Navigation;
// The Blank Page item template is documented at https://go.microsoft.com/fwlink/?LinkId=234238
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 SubscriptionsMain()