1.0-dev4 (New GUI and Twitch API change to Helix)

This commit is contained in:
2022-02-16 03:15:41 +01:00
Unverified
parent 6174d35416
commit 0c7e5ef099
103 changed files with 2384 additions and 3825 deletions

View File

@@ -1,5 +1,5 @@
// Internal
using VDownload.Services;
using VDownload.Core.Services;
// System
using System;
@@ -11,6 +11,7 @@ using Windows.Storage;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Navigation;
using System.Diagnostics;
namespace VDownload
{
@@ -24,16 +25,30 @@ namespace VDownload
protected override async void OnLaunched(LaunchActivatedEventArgs e)
{
Log.AddHeader("APP LAUNCHED");
Log.Break();
// Rebuild configuration file
Log.AddHeader("REBUILDING CONFIGURATION FILE");
Config.Rebuild();
Log.Add("Configuration file rebuilded successfully");
Log.Break();
// Delete temp on start
if (Config.GetValue("delete_temp_on_start") == "1")
// TODO
Debug.WriteLine(Config.GetValue("delete_temp_on_start"));
if ((bool)Config.GetValue("delete_temp_on_start"))
{
Log.AddHeader("DELETING TEMPORARY FILES");
IReadOnlyList<IStorageItem> tempItems = await ApplicationData.Current.TemporaryFolder.GetItemsAsync();
List<Task> tasks = new List<Task>();
foreach (IStorageItem item in tempItems) tasks.Add(item.DeleteAsync().AsTask());
await Task.WhenAll(tasks);
Log.Add("Temporary files deleted successfully");
Log.Break();
}
// Do not repeat app initialization when the Window already has content,
@@ -55,7 +70,7 @@ namespace VDownload
// When the navigation stack isn't restored navigate to the first page,
// configuring the new page by passing required information as a navigation
// parameter
rootFrame.Navigate(typeof(MainPage), e.Arguments);
rootFrame.Navigate(typeof(GUI.Views.MainPage), e.Arguments);
}
// Ensure the current window is active

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

View File

Before

Width:  |  Height:  |  Size: 43 KiB

After

Width:  |  Height:  |  Size: 43 KiB

View File

Before

Width:  |  Height:  |  Size: 54 KiB

After

Width:  |  Height:  |  Size: 54 KiB

View File

Before

Width:  |  Height:  |  Size: 9.8 KiB

After

Width:  |  Height:  |  Size: 9.8 KiB

View File

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 48 KiB

View File

Before

Width:  |  Height:  |  Size: 54 KiB

After

Width:  |  Height:  |  Size: 54 KiB

View File

Before

Width:  |  Height:  |  Size: 56 KiB

After

Width:  |  Height:  |  Size: 56 KiB

View File

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 49 KiB

View File

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

View File

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 46 KiB

View File

Before

Width:  |  Height:  |  Size: 57 KiB

After

Width:  |  Height:  |  Size: 57 KiB

View File

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

View File

Before

Width:  |  Height:  |  Size: 9.7 KiB

After

Width:  |  Height:  |  Size: 9.7 KiB

View File

Before

Width:  |  Height:  |  Size: 60 KiB

After

Width:  |  Height:  |  Size: 60 KiB

View File

Before

Width:  |  Height:  |  Size: 39 KiB

After

Width:  |  Height:  |  Size: 39 KiB

View File

Before

Width:  |  Height:  |  Size: 51 KiB

After

Width:  |  Height:  |  Size: 51 KiB

View File

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 49 KiB

View File

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 38 KiB

View File

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 48 KiB

View File

Before

Width:  |  Height:  |  Size: 9.6 KiB

After

Width:  |  Height:  |  Size: 9.6 KiB

View File

Before

Width:  |  Height:  |  Size: 43 KiB

After

Width:  |  Height:  |  Size: 43 KiB

View File

Before

Width:  |  Height:  |  Size: 47 KiB

After

Width:  |  Height:  |  Size: 47 KiB

View File

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 49 KiB

View File

Before

Width:  |  Height:  |  Size: 43 KiB

After

Width:  |  Height:  |  Size: 43 KiB

View File

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

View File

Before

Width:  |  Height:  |  Size: 39 KiB

After

Width:  |  Height:  |  Size: 39 KiB

View File

Before

Width:  |  Height:  |  Size: 50 KiB

After

Width:  |  Height:  |  Size: 50 KiB

View File

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

View File

Before

Width:  |  Height:  |  Size: 9.6 KiB

After

Width:  |  Height:  |  Size: 9.6 KiB

View File

Before

Width:  |  Height:  |  Size: 52 KiB

After

Width:  |  Height:  |  Size: 52 KiB

View File

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 35 KiB

View File

Before

Width:  |  Height:  |  Size: 45 KiB

After

Width:  |  Height:  |  Size: 45 KiB

View File

Before

Width:  |  Height:  |  Size: 43 KiB

After

Width:  |  Height:  |  Size: 43 KiB

View File

@@ -0,0 +1,12 @@
namespace VDownload.Core.Enums
{
public enum AudioFileExtension
{
MP3 = 4,
FLAC = 5,
WAV = 6,
M4A = 7,
ALAC = 8,
WMA = 9,
}
}

View File

@@ -0,0 +1,9 @@
namespace VDownload.Core.Enums
{
public enum LogMessageType
{
Header,
Normal,
Break,
}
}

View File

@@ -0,0 +1,15 @@
namespace VDownload.Core.Enums
{
public enum MediaFileExtension
{
MP4 = VideoFileExtension.MP4,
WMV = VideoFileExtension.WMV,
HEVC = VideoFileExtension.HEVC,
MP3 = AudioFileExtension.MP3,
FLAC = AudioFileExtension.FLAC,
WAV = AudioFileExtension.WAV,
M4A = AudioFileExtension.M4A,
ALAC = AudioFileExtension.ALAC,
WMA = AudioFileExtension.WMA,
}
}

View File

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

View File

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

View File

@@ -0,0 +1,9 @@
namespace VDownload.Core.Enums
{
public enum VideoFileExtension
{
MP4 = 1,
WMV = 2,
HEVC = 3,
}
}

View File

@@ -0,0 +1,11 @@
using System;
namespace VDownload.Core.Exceptions
{
public class TwitchAccessTokenNotFoundException : Exception
{
public TwitchAccessTokenNotFoundException() { }
public TwitchAccessTokenNotFoundException(string message) : base(message) { }
public TwitchAccessTokenNotFoundException(string message, Exception inner) :base(message, inner) { }
}
}

View File

@@ -0,0 +1,11 @@
using System;
namespace VDownload.Core.Exceptions
{
public class TwitchAccessTokenNotValidException : Exception
{
public TwitchAccessTokenNotValidException() { }
public TwitchAccessTokenNotValidException(string message) : base(message) { }
public TwitchAccessTokenNotValidException(string message, Exception inner) : base(message, inner) { }
}
}

View File

@@ -0,0 +1,13 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace VDownload.Core.Globals
{
public static class Assets
{
public static readonly Uri UnknownThumbnailImage = new Uri("ms-appx:///Assets/Other/UnknownThumbnail.png");
}
}

View File

@@ -0,0 +1,22 @@
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 IAStream
{
#region PARAMETERS
Uri Url { get; }
bool IsChunked { get; }
StreamType StreamType { get; }
int AudioBitrate { get; }
string AudioCodec { get; }
#endregion
}
}

View File

@@ -0,0 +1,28 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace VDownload.Core.Interfaces
{
internal interface IPlaylistService
{
#region PARAMETERS
string ID { get; }
string Name { get; }
#endregion
#region METHODS
Task GetMetadataAsync();
Task GetVideosAsync(int numberOfVideos);
#endregion
}
}

View File

@@ -0,0 +1,20 @@
using System;
using VDownload.Core.Enums;
namespace VDownload.Core.Interfaces
{
public interface IVStream
{
#region PARAMETERS
Uri Url { get; }
bool IsChunked { get; }
StreamType StreamType { get; }
int Width { get; }
int Height { get; }
int FrameRate { get; }
string VideoCodec { get; }
#endregion
}
}

View File

@@ -0,0 +1,65 @@
using System;
using System.ComponentModel;
using System.Threading;
using System.Threading.Tasks;
using VDownload.Core.Enums;
using VDownload.Core.Models;
using Windows.Storage;
namespace VDownload.Core.Interfaces
{
public interface IVideoService
{
#region PARAMETERS
string ID { get; }
string Title { get; }
string Author { get; }
DateTime Date { get; }
TimeSpan Duration { get; }
long Views { get; }
Uri Thumbnail { get; }
#endregion
#region METHODS
// GET VIDEO METADATA
Task GetMetadataAsync();
// GET VIDEO STREAMS
Task GetStreamsAsync();
// DOWNLOAD VIDEO
Task<StorageFile> DownloadAndTranscodeAsync(StorageFolder downloadingFolder, Stream audioVideoStream, MediaFileExtension extension, MediaType mediaType, TimeSpan trimStart, TimeSpan trimEnd, CancellationToken cancellationToken = default);
Task<StorageFile> DownloadAndTranscodeAsync(StorageFolder downloadingFolder, Stream audioVideoStream, MediaFileExtension extension, MediaType mediaType, CancellationToken cancellationToken = default);
Task<StorageFile> DownloadAndTranscodeAsync(StorageFolder downloadingFolder, IAStream audioStream, IVStream videoStream, VideoFileExtension extension, TimeSpan trimStart, TimeSpan trimEnd, CancellationToken cancellationToken = default);
Task<StorageFile> DownloadAndTranscodeAsync(StorageFolder downloadingFolder, IAStream audioStream, IVStream videoStream, VideoFileExtension extension, CancellationToken cancellationToken = default);
Task<StorageFile> DownloadAndTranscodeAsync(StorageFolder downloadingFolder, IAStream audioStream, AudioFileExtension extension, TimeSpan trimStart, TimeSpan trimEnd, CancellationToken cancellationToken = default);
Task<StorageFile> DownloadAndTranscodeAsync(StorageFolder downloadingFolder, IAStream audioStream, AudioFileExtension extension, CancellationToken cancellationToken = default);
Task<StorageFile> DownloadAndTranscodeAsync(StorageFolder downloadingFolder, IVStream videoStream, VideoFileExtension extension, TimeSpan trimStart, TimeSpan trimEnd, CancellationToken cancellationToken = default);
Task<StorageFile> DownloadAndTranscodeAsync(StorageFolder downloadingFolder, IVStream videoStream, VideoFileExtension extension, CancellationToken cancellationToken = default);
#endregion
#region EVENT HANDLERS
event EventHandler DownloadingStarted;
event EventHandler<ProgressChangedEventArgs> DownloadingProgressChanged;
event EventHandler DownloadingCompleted;
event EventHandler ProcessingStarted;
event EventHandler<ProgressChangedEventArgs> ProcessingProgressChanged;
event EventHandler ProcessingCompleted;
#endregion
}
}

View File

@@ -0,0 +1,36 @@
using System;
using VDownload.Core.Enums;
using VDownload.Core.Interfaces;
namespace VDownload.Core.Models
{
public class Stream : IVStream, IAStream
{
#region PARAMETERS
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
#region CONSTRUCTORS
public Stream(Uri url, bool isChunked, StreamType streamType)
{
Url = url;
IsChunked = isChunked;
StreamType = streamType;
}
#endregion
}
}

View File

@@ -0,0 +1,72 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Windows.Media.Editing;
using Windows.Storage;
namespace VDownload.Core.Services
{
internal class Config
{
#region CONSTANTS
// SETTINGS CONTAINER
private static readonly ApplicationDataContainer SettingsContainer = ApplicationData.Current.LocalSettings;
// DEFAULT SETTINGS
private static readonly Dictionary<string, object> DefaultSettings = new Dictionary<string, object>()
{
{ "delete_temp_on_start", true },
{ "twitch_vod_passive_trim", true },
{ "twitch_vod_downloading_chunk_retry_after_error", true },
{ "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_editing_algorithm", MediaTrimmingPreference.Fast }
};
#endregion
#region METHODS
// GET VALUE
public static object GetValue(string key)
{
return SettingsContainer.Values[key];
}
// SET VALUE
public static void SetValue(string key, object value)
{
SettingsContainer.Values[key] = value;
}
// SET DEFAULT
public static void SetDefault()
{
foreach (KeyValuePair<string, object> s in DefaultSettings)
{
SettingsContainer.Values[s.Key] = s.Value;
}
}
// REBUILD
public static void Rebuild()
{
foreach (KeyValuePair<string, object> s in DefaultSettings)
{
if (!SettingsContainer.Values.ContainsKey(s.Key))
{
SettingsContainer.Values[s.Key] = s.Value;
}
}
}
#endregion
}
}

View File

@@ -0,0 +1,35 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using VDownload.Core.Enums;
namespace VDownload.Core.Services
{
public class Log
{
private static List<(DateTime? Time, string Message, LogMessageType MessageType)> MessageList = new List<(DateTime? Time, string Message, LogMessageType MessageType)>();
public static void AddHeader(string message)
{
MessageList.Add((DateTime.Now, message, LogMessageType.Header));
Debug.WriteLine(message);
}
public static void Add(string message)
{
MessageList.Add((DateTime.Now, message, LogMessageType.Normal));
Debug.WriteLine(message);
}
public static void Break()
{
MessageList.Add((null, string.Empty, LogMessageType.Break));
}
public static void Clear()
{
MessageList.Clear();
}
}
}

View File

@@ -0,0 +1,177 @@
using System;
using System.ComponentModel;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using VDownload.Core.Enums;
using Windows.Foundation;
using Windows.Media.Editing;
using Windows.Media.MediaProperties;
using Windows.Media.Transcoding;
using Windows.Storage;
using Windows.Storage.Streams;
namespace VDownload.Core.Services
{
public class MediaProcessor
{
#region PARAMETERS
public StorageFile OutputFile { get; private set; }
public TimeSpan TrimStart { get; private set; }
public TimeSpan TrimEnd { get; private set; }
#endregion
#region CONSTRUCTOR
public MediaProcessor(StorageFile outputFile, TimeSpan trimStart, TimeSpan trimEnd)
{
OutputFile = outputFile;
TrimStart = trimStart;
TrimEnd = trimEnd;
}
#endregion
#region STANDARD METHODS
public async Task Run(StorageFile audioVideoInputFile, MediaFileExtension extension, MediaType mediaType, CancellationToken cancellationToken = default)
{
// Invoke ProcessingStarted event
ProcessingStarted?.Invoke(this, EventArgs.Empty);
// Init transcoder
MediaTranscoder mediaTranscoder = new MediaTranscoder
{
HardwareAccelerationEnabled = (bool)Config.GetValue("media_processor_use_hardware_acceleration"),
VideoProcessingAlgorithm = (bool)Config.GetValue("media_processor_use_mrfcrf444_algorithm") ? MediaVideoProcessingAlgorithm.MrfCrf444 : MediaVideoProcessingAlgorithm.Default,
TrimStartTime = TrimStart,
TrimStopTime = TrimEnd,
};
// Start transcoding operation
using (IRandomAccessStream outputFileOpened = await OutputFile.OpenAsync(FileAccessMode.ReadWrite))
{
PrepareTranscodeResult transcodingPreparated = await mediaTranscoder.PrepareStreamTranscodeAsync(await audioVideoInputFile.OpenAsync(FileAccessMode.Read), outputFileOpened, await GetMediaEncodingProfile(audioVideoInputFile, extension, mediaType));
IAsyncActionWithProgress<double> transcodingTask = transcodingPreparated.TranscodeAsync();
try
{
await transcodingTask.AsTask(cancellationToken, new Progress<double>((percent) => { ProcessingProgressChanged(this, new ProgressChangedEventArgs((int)Math.Round(percent), null)); }));
await outputFileOpened.FlushAsync();
}
catch (TaskCanceledException) { }
transcodingTask.Close();
}
// Invoke ProcessingCompleted event
ProcessingCompleted?.Invoke(this, EventArgs.Empty);
}
public async Task Run(StorageFile audioFile, StorageFile videoFile, VideoFileExtension extension, CancellationToken cancellationToken = default)
{
// Invoke ProcessingStarted event
ProcessingStarted?.Invoke(this, EventArgs.Empty);
// Init editor
MediaComposition mediaEditor = new MediaComposition();
// Add media files
Task<MediaClip> getVideoFileTask = MediaClip.CreateFromFileAsync(videoFile).AsTask();
Task<BackgroundAudioTrack> getAudioFileTask = BackgroundAudioTrack.CreateFromFileAsync(audioFile).AsTask();
await Task.WhenAll(getVideoFileTask, getAudioFileTask);
MediaClip videoElement = getVideoFileTask.Result;
videoElement.TrimTimeFromStart = TrimStart;
videoElement.TrimTimeFromEnd = TrimEnd;
BackgroundAudioTrack audioElement = getAudioFileTask.Result;
audioElement.TrimTimeFromStart = TrimStart;
audioElement.TrimTimeFromEnd = TrimEnd;
mediaEditor.Clips.Add(getVideoFileTask.Result);
mediaEditor.BackgroundAudioTracks.Add(getAudioFileTask.Result);
// 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)); };
await renderOperation.AsTask(cancellationToken);
// Invoke ProcessingCompleted event
ProcessingCompleted?.Invoke(this, EventArgs.Empty);
}
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); }
#endregion
#region LOCAL METHODS
// GET ENCODING PROFILE
public static async Task<MediaEncodingProfile> GetMediaEncodingProfile(StorageFile videoFile, StorageFile audioFile, MediaFileExtension extension, MediaType mediaType)
{
// Create profile object
MediaEncodingProfile profile;
// Set extension
switch (extension)
{
default:
case MediaFileExtension.MP4: profile = MediaEncodingProfile.CreateMp4(VideoEncodingQuality.HD1080p); break;
case MediaFileExtension.WMV: profile = MediaEncodingProfile.CreateWmv(VideoEncodingQuality.HD1080p); break;
case MediaFileExtension.HEVC: profile = MediaEncodingProfile.CreateHevc(VideoEncodingQuality.HD1080p); break;
case MediaFileExtension.MP3: profile = MediaEncodingProfile.CreateMp3(AudioEncodingQuality.High); break;
case MediaFileExtension.FLAC: profile = MediaEncodingProfile.CreateFlac(AudioEncodingQuality.High); break;
case MediaFileExtension.WAV: profile = MediaEncodingProfile.CreateWav(AudioEncodingQuality.High); break;
case MediaFileExtension.M4A: profile = MediaEncodingProfile.CreateM4a(AudioEncodingQuality.High); break;
case MediaFileExtension.ALAC: profile = MediaEncodingProfile.CreateAlac(AudioEncodingQuality.High); break;
case MediaFileExtension.WMA: profile = MediaEncodingProfile.CreateWma(AudioEncodingQuality.High); break;
}
// Set video parameters
if (mediaType != MediaType.OnlyAudio)
{
var videoData = await videoFile.Properties.GetVideoPropertiesAsync();
profile.Video.Height = videoData.Height;
profile.Video.Width = videoData.Width;
profile.Video.Bitrate = videoData.Bitrate;
}
// Set audio parameters
if (mediaType != MediaType.OnlyVideo)
{
var audioData = await audioFile.Properties.GetMusicPropertiesAsync();
profile.Audio.Bitrate = audioData.Bitrate;
if (mediaType == MediaType.AudioVideo) profile.Video.Bitrate -= audioData.Bitrate;
}
// Delete audio tracks
if (mediaType == MediaType.OnlyVideo)
{
var audioTracks = profile.GetAudioTracks();
audioTracks.Clear();
profile.SetAudioTracks(audioTracks.AsEnumerable());
}
// Return profile
return profile;
}
public static async Task<MediaEncodingProfile> GetMediaEncodingProfile(StorageFile audioVideoFile, MediaFileExtension extension, MediaType mediaType) { return await GetMediaEncodingProfile(audioVideoFile, audioVideoFile, extension, mediaType); }
#endregion
#region EVENT HANDLERS
public event EventHandler ProcessingStarted;
public event EventHandler<ProgressChangedEventArgs> ProcessingProgressChanged;
public event EventHandler ProcessingCompleted;
#endregion
}
}

View File

@@ -0,0 +1,122 @@
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
{
public class Auth
{
#region CONSTANTS
// CLIENT ID
public readonly static string ClientID = "yukkqkwp61wsv3u1pya17crpyaa98y";
// GQL API CLIENT ID
public readonly static string GQLApiClientID = "kimne78kx3ncx6brgo4mv6wki5h1ko";
// REDIRECT URL
public readonly static Uri RedirectUrl = new Uri("https://www.vd.com");
// AUTHORIZATION URL
private readonly static string ResponseType = "token";
private readonly static string[] Scopes = new[]
{
"user:read:subscriptions",
};
public readonly static Uri AuthorizationUrl = new Uri($"https://id.twitch.tv/oauth2/authorize?client_id={ClientID}&redirect_uri={RedirectUrl.OriginalString}&response_type={ResponseType}&scope={string.Join(" ", Scopes)}");
#endregion
#region METHODS
// READ ACCESS TOKEN
public static async Task<string> ReadAccessTokenAsync()
{
try
{
// Get file
StorageFolder authDataFolder = await ApplicationData.Current.LocalCacheFolder.GetFolderAsync("AuthData");
StorageFile authDataFile = await authDataFolder.GetFileAsync("Twitch.auth");
// Return data
return await FileIO.ReadTextAsync(authDataFile);
}
catch (FileNotFoundException)
{
return null;
}
}
// SAVE ACCESS TOKEN
public static async Task SaveAccessTokenAsync(string accessToken)
{
// Get file
StorageFolder authDataFolder = await ApplicationData.Current.LocalCacheFolder.CreateFolderAsync("AuthData", CreationCollisionOption.OpenIfExists);
StorageFile authDataFile = await authDataFolder.CreateFileAsync("Twitch.auth", CreationCollisionOption.ReplaceExisting);
// Save data
FileIO.WriteTextAsync(authDataFile, accessToken);
}
// DELETE ACCESS TOKEN
public static async Task DeleteAccessTokenAsync()
{
try
{
// Get file
StorageFolder authDataFolder = await ApplicationData.Current.LocalCacheFolder.GetFolderAsync("AuthData");
StorageFile authDataFile = await authDataFolder.GetFileAsync("Twitch.auth");
// Delete file
await authDataFile.DeleteAsync();
}
catch (FileNotFoundException) { }
}
// VALIDATE ACCESS TOKEN
public static async Task<(bool IsValid, string Login, DateTime? ExpirationDate)> ValidateAccessTokenAsync(string accessToken)
{
// Create client
WebClient client = new WebClient { Encoding = Encoding.UTF8 };
client.Headers.Add("Authorization", $"Bearer {accessToken}");
try
{
// Check access token
JObject response = JObject.Parse(await client.DownloadStringTaskAsync("https://id.twitch.tv/oauth2/validate"));
string login = response["login"].ToString();
DateTime? expirationDate = DateTime.Now.AddSeconds(long.Parse(response["expires_in"].ToString()));
return (true, login, expirationDate);
}
catch (WebException)
{
return (false, null, null);
}
}
// REVOKE ACCESS TOKEN
public static async Task RevokeAccessTokenAsync(string accessToken)
{
// Create client
WebClient client = new WebClient { Encoding = Encoding.UTF8 };
// Revoke access token
await client.UploadStringTaskAsync(new Uri("https://id.twitch.tv/oauth2/revoke"), $"client_id={ClientID}&token={accessToken}");
}
#endregion
}
}

View File

@@ -0,0 +1,126 @@
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading.Tasks;
using VDownload.Core.Exceptions;
using VDownload.Core.Interfaces;
namespace VDownload.Core.Services.Sources.Twitch
{
public class Channel : IPlaylistService
{
#region CONSTRUCTORS
public Channel(string id)
{
ID = id;
}
#endregion
#region PARAMETERS
public string ID { get; private set; }
public string Name { get; private set; }
public Vod[] Videos { get; private set; }
#endregion
#region STANDARD METHODS
// GET CHANNEL METADATA
public async Task GetMetadataAsync()
{
// 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);
// Get response
client.QueryString.Add("login", ID);
JToken response = JObject.Parse(await client.DownloadStringTaskAsync("https://api.twitch.tv/helix/users"))["data"][0];
// Set parameters
if (!ID.All(char.IsDigit)) ID = (string)response["id"];
Name = (string)response["display_name"];
}
// GET CHANNEL VIDEOS
public async Task GetVideosAsync(int numberOfVideos)
{
// Get access token
string accessToken = await Auth.ReadAccessTokenAsync();
if (accessToken == null) throw new TwitchAccessTokenNotFoundException();
// Set pagination
string pagination = "";
// Set array of videos
List<Vod> videos = new List<Vod>();
// Get videos
int count;
JToken[] videosData;
List<Task> getStreamsTasks = new List<Task>();
do
{
// 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);
// Set number of videos to get in this iteration
count = numberOfVideos < 100 ? numberOfVideos : 100;
// Get response
client.QueryString.Add("user_id", ID);
client.QueryString.Add("first", count.ToString());
client.QueryString.Add("after", pagination);
JToken response = JObject.Parse(await client.DownloadStringTaskAsync("https://api.twitch.tv/helix/videos"));
pagination = (string)response["pagination"]["cursor"];
videosData = response["data"].ToArray();
foreach (JToken videoData in videosData)
{
Vod video = new Vod((string)videoData["id"]);
video.GetMetadataAsync(videoData);
getStreamsTasks.Add(video.GetStreamsAsync());
videos.Add(video);
numberOfVideos--;
}
}
while (numberOfVideos > 0 && count == videosData.Length);
// Wait for all getStreams tasks
await Task.WhenAll(getStreamsTasks);
// Set Videos parameter
Videos = videos.ToArray();
}
#endregion
}
}

View File

@@ -0,0 +1,189 @@
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Web;
using VDownload.Core.Enums;
using VDownload.Core.Exceptions;
using VDownload.Core.Interfaces;
using VDownload.Core.Models;
using Windows.Storage;
namespace VDownload.Core.Services.Sources.Twitch
{
public class Clip : IVideoService
{
#region CONSTANTS
#endregion
#region CONSTRUCTORS
public Clip(string id)
{
ID = id;
}
#endregion
#region PARAMETERS
public string ID { 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 Stream[] Streams { get; private set; }
#endregion
#region STANDARD METHODS
// GET CLIP METADATA
public async Task GetMetadataAsync()
{
// 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);
// Get response
client.QueryString.Add("id", ID);
JToken response = JObject.Parse(await client.DownloadStringTaskAsync("https://api.twitch.tv/helix/clips")).GetValue("data")[0];
// 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"]);
}
public async Task GetStreamsAsync()
{
// Create client
WebClient client = new WebClient { Encoding = Encoding.UTF8 };
client.Headers.Add("Client-ID", Auth.GQLApiClientID);
// Get video streams
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();
// Init streams list
List<Stream> streams = new List<Stream>();
// 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)
{
Height = height,
FrameRate = frameRate
};
// Add stream
streams.Add(stream);
}
// Set Streams parameter
Streams = streams.ToArray();
}
public async Task<StorageFile> DownloadAndTranscodeAsync(StorageFolder downloadingFolder, Stream audioVideoStream, MediaFileExtension extension, MediaType mediaType, TimeSpan trimStart, TimeSpan trimEnd, CancellationToken cancellationToken = default)
{
// Set cancellation token
cancellationToken.ThrowIfCancellationRequested();
// Invoke DownloadingStarted event
DownloadingStarted?.Invoke(this, EventArgs.Empty);
// Create client
WebClient client = new WebClient();
client.Headers.Add("Client-Id", Auth.GQLApiClientID);
// Get video GQL access token
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"];
// Download
StorageFile rawFile = await downloadingFolder.CreateFileAsync("raw.mp4");
using (client = new WebClient())
{
client.DownloadProgressChanged += (s, a) => { DownloadingProgressChanged(this, new ProgressChangedEventArgs(a.ProgressPercentage, null)); };
client.QueryString.Add("sig", (string)videoAccessToken["signature"]);
client.QueryString.Add("token", HttpUtility.UrlEncode((string)videoAccessToken["value"]));
using (cancellationToken.Register(client.CancelAsync))
{
await client.DownloadFileTaskAsync(audioVideoStream.Url, rawFile.Path);
}
}
DownloadingCompleted?.Invoke(this, EventArgs.Empty);
// Processing
StorageFile outputFile = rawFile;
if (extension != MediaFileExtension.MP4 || mediaType != MediaType.AudioVideo || trimStart > new TimeSpan(0) || trimEnd < Duration)
{
outputFile = await downloadingFolder.CreateFileAsync($"transcoded.{extension.ToString().ToLower()}");
MediaProcessor mediaProcessor = new MediaProcessor(outputFile, trimStart, trimEnd);
mediaProcessor.ProcessingStarted += ProcessingStarted;
mediaProcessor.ProcessingProgressChanged += ProcessingProgressChanged;
mediaProcessor.ProcessingCompleted += ProcessingCompleted;
await mediaProcessor.Run(rawFile, extension, mediaType, cancellationToken);
}
// Return output file
return outputFile;
}
public async Task<StorageFile> DownloadAndTranscodeAsync(StorageFolder downloadingFolder, Stream audioVideoStream, MediaFileExtension extension, MediaType mediaType, CancellationToken cancellationToken = default) { return await DownloadAndTranscodeAsync(downloadingFolder, audioVideoStream, extension, mediaType, new TimeSpan(0), Duration, cancellationToken); }
public async Task<StorageFile> DownloadAndTranscodeAsync(StorageFolder downloadingFolder, IAStream audioStream, IVStream videoStream, VideoFileExtension extension, TimeSpan trimStart, TimeSpan trimEnd, CancellationToken cancellationToken = default) { throw new NotImplementedException("Twitch Clip download service doesn't support separate video and audio streams"); }
public async Task<StorageFile> DownloadAndTranscodeAsync(StorageFolder downloadingFolder, IAStream audioStream, IVStream videoStream, VideoFileExtension extension, CancellationToken cancellationToken = default) { return await DownloadAndTranscodeAsync(downloadingFolder, audioStream, videoStream, extension, new TimeSpan(0), Duration, cancellationToken); }
public async Task<StorageFile> DownloadAndTranscodeAsync(StorageFolder downloadingFolder, IAStream audioStream, AudioFileExtension extension, TimeSpan trimStart, TimeSpan trimEnd, CancellationToken cancellationToken = default) { throw new NotImplementedException("Twitch Clip download service doesn't support separate video and audio streams"); }
public async Task<StorageFile> DownloadAndTranscodeAsync(StorageFolder downloadingFolder, IAStream audioStream, AudioFileExtension extension, CancellationToken cancellationToken = default) { return await DownloadAndTranscodeAsync(downloadingFolder, audioStream, extension, new TimeSpan(0), Duration, cancellationToken); }
public async Task<StorageFile> DownloadAndTranscodeAsync(StorageFolder downloadingFolder, IVStream videoStream, VideoFileExtension extension, TimeSpan trimStart, TimeSpan trimEnd, CancellationToken cancellationToken = default) { throw new NotImplementedException("Twitch Clip download service doesn't support separate video and audio streams"); }
public async Task<StorageFile> DownloadAndTranscodeAsync(StorageFolder downloadingFolder, IVStream videoStream, VideoFileExtension extension, CancellationToken cancellationToken = default) { return await DownloadAndTranscodeAsync(downloadingFolder, videoStream, extension, new TimeSpan(0), Duration, cancellationToken); }
#endregion
#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;
#endregion
}
}

View File

@@ -0,0 +1,329 @@
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Net;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using VDownload.Core.Enums;
using VDownload.Core.Exceptions;
using VDownload.Core.Interfaces;
using VDownload.Core.Models;
using Windows.Storage;
namespace VDownload.Core.Services.Sources.Twitch
{
public class Vod : IVideoService
{
#region CONSTANTS
// METADATA TIME FORMATS
private static readonly string[] TimeFormats = new[]
{
@"h\hm\ms\s",
@"m\ms\s",
@"s\s",
};
// 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)
{
ID = id;
}
#endregion
#region PARAMETERS
public string ID { 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 Stream[] Streams { get; private set; }
#endregion
#region STANDARD METHODS
// GET VOD METADATA
public async Task GetMetadataAsync()
{
// 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);
// Get response
client.QueryString.Add("id", ID);
JToken response = JObject.Parse(await client.DownloadStringTaskAsync("https://api.twitch.tv/helix/videos")).GetValue("data")[0];
// Set parameters
Title = ((string)response["title"]).Replace("\n", "");
Author = (string)response["user_name"];
Date = Convert.ToDateTime(response["created_at"]);
Duration = TimeSpan.ParseExact((string)response["duration"], TimeFormats, null);
Views = (long)response["view_count"];
Thumbnail = (string)response["thumbnail_url"] == string.Empty ? Globals.Assets.UnknownThumbnailImage : new Uri((string)response["thumbnail_url"]);
}
public void GetMetadataAsync(JToken response)
{
// Set parameters
Title = ((string)response["title"]).Replace("\n", "");
Author = (string)response["user_name"];
Date = Convert.ToDateTime(response["created_at"]);
Duration = TimeSpan.ParseExact((string)response["duration"], TimeFormats, null);
Views = (long)response["view_count"];
Thumbnail = (string)response["thumbnail_url"] == string.Empty ? Globals.Assets.UnknownThumbnailImage : new Uri((string)response["thumbnail_url"]);
}
// GET VOD STREAMS
public async Task GetStreamsAsync()
{
// Create client
WebClient client = new WebClient();
client.Headers.Add("Client-Id", Auth.GQLApiClientID);
// Get video GQL access token
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
string[] 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>();
// 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 = (int)Math.Round(double.Parse(line2.Groups["frame_rate"].Value));
string videoCodec = line2.Groups["video_codec"].Value;
string audioCodec = line2.Groups["audio_codec"].Value;
// Create stream
Stream stream = new Stream(url, true, StreamType.AudioVideo)
{
Width = width,
Height = height,
FrameRate = frameRate,
VideoCodec = videoCodec,
AudioCodec = audioCodec,
};
// Add stream
streams.Add(stream);
}
// Set Streams parameter
Streams = streams.ToArray();
}
// DOWNLOAD AND TRANSCODE VOD
public async Task<StorageFile> DownloadAndTranscodeAsync(StorageFolder downloadingFolder, Stream audioVideoStream, MediaFileExtension extension, MediaType mediaType, TimeSpan trimStart, TimeSpan trimEnd, CancellationToken cancellationToken = default)
{
// Set cancellation token
cancellationToken.ThrowIfCancellationRequested();
// Invoke DownloadingStarted event
DownloadingStarted?.Invoke(this, EventArgs.Empty);
// Get video chunks
List<(Uri ChunkUrl, TimeSpan ChunkDuration)> chunksList = await ExtractChunksFromM3U8Async(audioVideoStream.Url);
// Passive trim
if ((bool)Config.GetValue("twitch_vod_passive_trim"))
{
var trimResult = PassiveVideoTrim(chunksList, trimStart, trimEnd, Duration);
trimStart = trimResult.TrimStart;
trimEnd = trimResult.TrimEnd;
}
// Download
StorageFile rawFile = await downloadingFolder.CreateFileAsync("raw.ts");
float chunksDownloaded = 0;
Task<byte[]> downloadTask;
Task writeTask;
downloadTask = DownloadChunkAsync(chunksList[0].ChunkUrl);
await downloadTask;
for (int i = 1; i < chunksList.Count; i++)
{
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));
}
await WriteChunkToFileAsync(rawFile, downloadTask.Result);
DownloadingProgressChanged(this, new ProgressChangedEventArgs((int)Math.Round(++chunksDownloaded * 100 / chunksList.Count), null));
DownloadingCompleted?.Invoke(this, EventArgs.Empty);
// Processing
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;
await mediaProcessor.Run(rawFile, extension, mediaType, cancellationToken);
// Return output file
return outputFile;
}
public async Task<StorageFile> DownloadAndTranscodeAsync(StorageFolder downloadingFolder, Stream audioVideoStream, MediaFileExtension extension, MediaType mediaType, CancellationToken cancellationToken = default) { return await DownloadAndTranscodeAsync(downloadingFolder, audioVideoStream, extension, mediaType, new TimeSpan(0), Duration, cancellationToken); }
public async Task<StorageFile> DownloadAndTranscodeAsync(StorageFolder downloadingFolder, IAStream audioStream, IVStream videoStream, VideoFileExtension extension, TimeSpan trimStart, TimeSpan trimEnd, CancellationToken cancellationToken = default) { throw new NotImplementedException("Twitch VOD download service doesn't support separate video and audio streams"); }
public async Task<StorageFile> DownloadAndTranscodeAsync(StorageFolder downloadingFolder, IAStream audioStream, IVStream videoStream, VideoFileExtension extension, CancellationToken cancellationToken = default) { return await DownloadAndTranscodeAsync(downloadingFolder, audioStream, videoStream, extension, new TimeSpan(0), Duration, cancellationToken); }
public async Task<StorageFile> DownloadAndTranscodeAsync(StorageFolder downloadingFolder, IAStream audioStream, AudioFileExtension extension, TimeSpan trimStart, TimeSpan trimEnd, CancellationToken cancellationToken = default) { throw new NotImplementedException("Twitch VOD download service doesn't support separate video and audio streams"); }
public async Task<StorageFile> DownloadAndTranscodeAsync(StorageFolder downloadingFolder, IAStream audioStream, AudioFileExtension extension, CancellationToken cancellationToken = default) { return await DownloadAndTranscodeAsync(downloadingFolder, audioStream, extension, new TimeSpan(0), Duration, cancellationToken); }
public async Task<StorageFile> DownloadAndTranscodeAsync(StorageFolder downloadingFolder, IVStream videoStream, VideoFileExtension extension, TimeSpan trimStart, TimeSpan trimEnd, CancellationToken cancellationToken = default) { throw new NotImplementedException("Twitch VOD download service doesn't support separate video and audio streams"); }
public async Task<StorageFile> DownloadAndTranscodeAsync(StorageFolder downloadingFolder, IVStream videoStream, VideoFileExtension extension, CancellationToken cancellationToken = default) { return await DownloadAndTranscodeAsync(downloadingFolder, videoStream, extension, new TimeSpan(0), Duration, cancellationToken); }
#endregion
#region LOCAL METHODS
// GET CHUNKS DATA FROM M3U8 PLAYLIST
private static async Task<List<(Uri ChunkUrl, TimeSpan ChunkDuration)>> ExtractChunksFromM3U8Async(Uri streamUrl)
{
// Create client
WebClient client = new WebClient();
client.Headers.Add("Client-Id", Auth.GQLApiClientID);
// Get playlist
string response = await client.DownloadStringTaskAsync(streamUrl);
Debug.WriteLine(response);
// Create dictionary
List<(Uri ChunkUrl, TimeSpan ChunkDuration)> chunks = new List<(Uri ChunkUrl, TimeSpan ChunkDuration)>();
// Pack data into dictionary
foreach (Match chunk in ChunkRegex.Matches(response))
{
Uri chunkUrl = new Uri($"{streamUrl.AbsoluteUri.Replace(System.IO.Path.GetFileName(streamUrl.AbsoluteUri), "")}{chunk.Groups["filename"].Value}");
TimeSpan chunkDuration = TimeSpan.FromSeconds(double.Parse(chunk.Groups["duration"].Value));
chunks.Add((chunkUrl, chunkDuration));
}
// Return chunks data
return chunks;
}
// PASSIVE TRIM
private static (TimeSpan TrimStart, TimeSpan TrimEnd) PassiveVideoTrim(List<(Uri ChunkUrl, TimeSpan ChunkDuration)> chunksList, TimeSpan trimStart, TimeSpan trimEnd, TimeSpan duration)
{
// Copy duration
TimeSpan newDuration = duration;
// Trim at start
while (chunksList[0].ChunkDuration <= trimStart)
{
trimStart = trimStart.Subtract(chunksList[0].ChunkDuration);
trimEnd = trimEnd.Subtract(chunksList[0].ChunkDuration);
newDuration = newDuration.Subtract(chunksList[0].ChunkDuration);
chunksList.RemoveAt(0);
}
// Trim at end
while (chunksList.Last().ChunkDuration <= newDuration.Subtract(trimEnd))
{
newDuration = newDuration.Subtract(chunksList.Last().ChunkDuration);
chunksList.RemoveAt(chunksList.Count - 1);
}
// Return data
return (trimStart, trimEnd);
}
// DOWNLOAD CHUNK
private static async Task<byte[]> DownloadChunkAsync(Uri chunkUrl)
{
int retriesCount = 0;
while ((bool)Config.GetValue("twitch_vod_downloading_chunk_retry_after_error") && retriesCount < (int)Config.GetValue("twitch_vod_downloading_chunk_max_retries"))
{
try
{
using (WebClient client = new WebClient())
{
return await client.DownloadDataTaskAsync(chunkUrl);
}
}
catch
{
retriesCount++;
await Task.Delay((int)Config.GetValue("twitch_vod_downloading_chunk_retries_delay"));
}
}
throw new WebException("An error occurs while downloading a Twitch VOD chunk");
}
// WRITE CHUNK TO FILE
private static Task WriteChunkToFileAsync(StorageFile file, byte[] chunk)
{
return Task.Factory.StartNew(() =>
{
using (var stream = new System.IO.FileStream(file.Path, System.IO.FileMode.Append))
{
stream.Write(chunk, 0, chunk.Length);
stream.Close();
}
});
}
#endregion
#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;
#endregion
}
}

View File

@@ -0,0 +1,21 @@
<UserControl
x:Class="VDownload.GUI.Controls.MainPageLayoutControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:VDownload.GUI.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignHeight="300"
d:DesignWidth="400">
<Grid Padding="20">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="20"/>
<RowDefinition/>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" FontSize="28" FontWeight="SemiBold" Text="{x:Bind Title, Mode=OneWay}"/>
<ContentPresenter Grid.Row="2" Content="{x:Bind PageContent, Mode=OneWay}"/>
</Grid>
</UserControl>

View File

@@ -0,0 +1,39 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using Windows.Foundation;
using Windows.Foundation.Collections;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
using Windows.UI.Xaml.Data;
using Windows.UI.Xaml.Input;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Navigation;
// The User Control item template is documented at https://go.microsoft.com/fwlink/?LinkId=234236
namespace VDownload.GUI.Controls
{
public sealed partial class MainPageLayoutControl : UserControl
{
// INIT
public MainPageLayoutControl()
{
this.InitializeComponent();
}
// PAGE CONTENT
public FrameworkElement PageContent { get; set; }
// TITLE
public static readonly DependencyProperty TitleProperty = DependencyProperty.Register("Title", typeof(string), typeof(MainPageLayoutControl), new PropertyMetadata(string.Empty));
public string Title
{
get => (string)GetValue(TitleProperty);
set => SetValue(TitleProperty, value);
}
}
}

View File

@@ -0,0 +1,48 @@
<UserControl
x:Class="VDownload.GUI.Controls.SettingControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:VDownload.GUI.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignHeight="300"
d:DesignWidth="400">
<UserControl.Resources>
<ResourceDictionary>
<ResourceDictionary.ThemeDictionaries>
<ResourceDictionary x:Key="Dark">
<SolidColorBrush x:Key="SettingControlBackgroundColor" Color="#2B2B2B"/>
</ResourceDictionary>
<ResourceDictionary x:Key="Light">
<SolidColorBrush x:Key="SettingControlBackgroundColor" Color="#F5F5F5"/>
</ResourceDictionary>
</ResourceDictionary.ThemeDictionaries>
</ResourceDictionary>
</UserControl.Resources>
<Grid Padding="18" CornerRadius="{ThemeResource ControlCornerRadius}" Background="{ThemeResource SettingControlBackgroundColor}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="18"/>
<ColumnDefinition/>
<ColumnDefinition Width="18"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<!-- ICON -->
<ContentPresenter VerticalAlignment="Center" Grid.Column="0" Width="20" Content="{x:Bind Icon, Mode=OneWay}"/>
<!-- TITLE & DECRIPTION -->
<Grid Grid.Column="2" VerticalAlignment="Center">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Text="{x:Bind Title, Mode=OneWay}"/>
<TextBlock Grid.Row="1" FontSize="12" Foreground="{x:Bind DescriptionColor, Mode=OneWay}" TextWrapping="Wrap" Text="{x:Bind Description, Mode=OneWay}"/>
</Grid>
<!-- CUSTOM CONTENT -->
<ContentPresenter Grid.Column="4" VerticalAlignment="Center" Content="{x:Bind SettingContent, Mode=OneWay}"/>
</Grid>
</UserControl>

View File

@@ -0,0 +1,65 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using Windows.Foundation;
using Windows.Foundation.Collections;
using Windows.UI;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Automation;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
using Windows.UI.Xaml.Data;
using Windows.UI.Xaml.Input;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Navigation;
// The User Control item template is documented at https://go.microsoft.com/fwlink/?LinkId=234236
namespace VDownload.GUI.Controls
{
public sealed partial class SettingControl : UserControl
{
// INIT
public SettingControl()
{
this.InitializeComponent();
}
// SETTING CONTENT
public FrameworkElement SettingContent { get; set; }
// ICON
public static readonly DependencyProperty IconProperty = DependencyProperty.Register("Icon", typeof(IconElement), typeof(SettingControl), new PropertyMetadata(null));
public IconElement Icon
{
get => (IconElement)GetValue(IconProperty);
set => SetValue(IconProperty, value);
}
// TITLE
public static readonly DependencyProperty TitleProperty = DependencyProperty.Register("Title", typeof(string), typeof(SettingControl), new PropertyMetadata(string.Empty));
public string Title
{
get => (string)GetValue(TitleProperty);
set => SetValue(TitleProperty, value);
}
// DESCRIPTION
public static readonly DependencyProperty DescriptionProperty = DependencyProperty.Register("Description", typeof(string), typeof(SettingControl), new PropertyMetadata(string.Empty));
public string Description
{
get => (string)GetValue(DescriptionProperty);
set => SetValue(DescriptionProperty, value);
}
// DESCRIPTION COLOR
public static readonly DependencyProperty DescriptionColorProperty = DependencyProperty.Register("DescriptionColor", typeof(Brush), typeof(SettingControl), new PropertyMetadata(new SolidColorBrush((Color)Application.Current.Resources["SystemBaseMediumColor"])));
public Brush DescriptionColor
{
get => (Brush)GetValue(DescriptionColorProperty);
set => SetValue(DescriptionColorProperty, value);
}
}
}

View File

@@ -1,17 +1,15 @@
<Page
x:Class="VDownload.Views.AddVideo.AddVideoLoading"
x:Class="VDownload.GUI.Views.Home.HomeMain"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:VDownload.Views.AddVideo"
xmlns:local="using:VDownload.GUI.Views.Home"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:muxc="using:Microsoft.UI.Xaml.Controls"
mc:Ignorable="d"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
Height="420"
Width="498">
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<!-- PROGRESS RING -->
<Grid>
<ProgressRing HorizontalAlignment="Center" VerticalAlignment="Center" IsActive="True" Width="50" Height="50"/>
<Button Content="aaaaa" Click="Button_Click"/>
</Grid>
</Page>

View File

@@ -0,0 +1,46 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using VDownload.Core.Enums;
using VDownload.Core.Interfaces;
using Windows.Foundation;
using Windows.Foundation.Collections;
using Windows.Storage;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
using Windows.UI.Xaml.Data;
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.GUI.Views.Home
{
/// <summary>
/// An empty page that can be used on its own or navigated to within a Frame.
/// </summary>
public sealed partial class HomeMain : Page
{
public HomeMain()
{
this.InitializeComponent();
}
private async void Button_Click(object sender, RoutedEventArgs e)
{
IPlaylistService videoServices = new Core.Services.Sources.Twitch.Channel("jacexdowozwideo");
await videoServices.GetMetadataAsync();
Stopwatch sw = Stopwatch.StartNew();
await videoServices.GetVideosAsync(500);
sw.Stop();
Debug.WriteLine(((Core.Services.Sources.Twitch.Channel)videoServices).Videos.Length);
Debug.WriteLine(sw.Elapsed.TotalSeconds);
}
}
}

View File

@@ -0,0 +1,56 @@
<Page
x:Class="VDownload.GUI.Views.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:muxc="using:Microsoft.UI.Xaml.Controls"
mc:Ignorable="d"
muxc:BackdropMaterial.ApplyToRootOrPageBackground="True">
<Page.Resources>
<Thickness x:Key="NavigationViewContentMargin">0,48,0,0</Thickness>
<Thickness x:Key="NavigationViewContentGridBorderThickness">0</Thickness>
<SolidColorBrush x:Key="NavigationViewContentBackground" Color="Transparent"></SolidColorBrush>
</Page.Resources>
<Grid>
<!-- PROGRAM NAME -->
<Border x:Name="AppTitleBar"
IsHitTestVisible="True"
VerticalAlignment="Top"
Background="Transparent"
Height="40"
Canvas.ZIndex="1"
Margin="55,4,0,0">
<StackPanel Orientation="Horizontal">
<Image x:Name="AppFontIcon"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Source="ms-appx:///Assets/Logo/Logo.png"
Width="16"
Height="16"/>
<TextBlock x:Name="AppTitle"
Text="VDownload"
VerticalAlignment="Center"
Margin="12,0,0,0"
Style="{StaticResource CaptionTextBlockStyle}" />
</StackPanel>
</Border>
<!-- NAVIGATION AND CONTENT -->
<muxc:NavigationView x:Name="MainPageNavigationPanel" Background="Transparent" IsTitleBarAutoPaddingEnabled="False" IsBackButtonVisible="Collapsed" PaneDisplayMode="LeftCompact" Canvas.ZIndex="0" ItemInvoked="MainPageNavigationPanel_ItemInvoked">
<muxc:NavigationView.MenuItems>
<muxc:NavigationViewItem x:Uid="MainPageNavigationPanelHomeItem" Tag="home" Icon="Video" Content="Home"/>
<muxc:NavigationViewItemSeparator/>
<muxc:NavigationViewItem x:Uid="MainPageNavigationPanelSubscriptionsItem" Tag="subscriptions" Icon="Favorite" Content="Subscriptions"/>
</muxc:NavigationView.MenuItems>
<muxc:NavigationView.FooterMenuItems>
<muxc:NavigationViewItem x:Uid="MainPageNavigationPanelSourcesItem" Tag="sources" Content="Sources">
<muxc:NavigationViewItem.Icon>
<BitmapIcon UriSource="ms-appx:///Assets/Icons/MainPage/Sources.png"/>
</muxc:NavigationViewItem.Icon>
</muxc:NavigationViewItem>
</muxc:NavigationView.FooterMenuItems>
<Frame x:Name="MainPageContentFrame" CornerRadius="5" Margin="10"/>
</muxc:NavigationView>
</Grid>
</Page>

View File

@@ -0,0 +1,60 @@
// Internal
// System
using System;
using Windows.ApplicationModel.Core;
using Windows.UI;
using Windows.UI.ViewManagement;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.Foundation;
using System.Collections.Generic;
namespace VDownload.GUI.Views
{
public sealed partial class MainPage : Page
{
#region INIT
// CONSTRUCTOR
public MainPage()
{
InitializeComponent();
// Hide default title bar.
ApplicationViewTitleBar titleBar = ApplicationView.GetForCurrentView().TitleBar;
CoreApplicationViewTitleBar coreTitleBar = CoreApplication.GetCurrentView().TitleBar;
coreTitleBar.ExtendViewIntoTitleBar = true;
Window.Current.SetTitleBar(AppTitleBar);
// Navigate to home page
MainPageContentFrame.Navigate(Pages["home"]);
MainPageNavigationPanel.SelectedItem = MainPageNavigationPanel.MenuItems[0];
}
#endregion
#region NAVIGATION PANEL
// PAGES DICTIONARY
private Dictionary<string, Type> Pages = new Dictionary<string, Type>()
{
{"home", typeof(Home.HomeMain)},
{"subscriptions", typeof(Subscriptions.SubscriptionsMain)},
{"sources", typeof(Sources.SourcesMain)},
{"settings", typeof(Settings.SettingsMain)}
};
// ON ITEM INVOKED
private void MainPageNavigationPanel_ItemInvoked(Microsoft.UI.Xaml.Controls.NavigationView sender, Microsoft.UI.Xaml.Controls.NavigationViewItemInvokedEventArgs args)
{
if (args.IsSettingsInvoked) MainPageContentFrame.Navigate(Pages["settings"], args.RecommendedNavigationTransitionInfo);
else if (args.InvokedItemContainer != null) MainPageContentFrame.Navigate(Pages[args.InvokedItemContainer.Tag.ToString()], args.RecommendedNavigationTransitionInfo);
}
#endregion
}
}

View File

@@ -1,17 +1,14 @@
<Page
x:Class="VDownload.Views.AddPlaylist.AddPlaylistLoading"
x:Class="VDownload.GUI.Views.Settings.SettingsMain"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:VDownload.Views.AddPlaylist"
xmlns:local="using:VDownload.Views.Settings"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
Height="500"
Width="498">
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<!-- PROGRESS RING -->
<Grid>
<ProgressRing HorizontalAlignment="Center" VerticalAlignment="Center" IsActive="True" Width="50" Height="50"/>
</Grid>
</Page>

View File

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

View File

@@ -0,0 +1,31 @@
<Page
x:Class="VDownload.GUI.Views.Sources.SourcesMain"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:VDownload.GUI.Views.Sources"
xmlns:cc="using:VDownload.GUI.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<cc:MainPageLayoutControl x:Uid="SourcesMainPage" Title="Sources">
<cc:MainPageLayoutControl.PageContent>
<Grid RowSpacing="10">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<cc:SettingControl x:Name="SourcesMainTwitchSettingControl" x:Uid="SourcesMainTwitchSettingControl" Grid.Row="0" Title="Twitch">
<cc:SettingControl.Icon>
<BitmapIcon UriSource="ms-appx:///Assets/Icons/Sources/Twitch.png" ShowAsMonochrome="False"/>
</cc:SettingControl.Icon>
<cc:SettingControl.SettingContent>
<Button x:Name="SourcesMainTwitchLoginButton" Click="SourcesMainTwitchLoginButton_Click"/>
</cc:SettingControl.SettingContent>
</cc:SettingControl>
</Grid>
</cc:MainPageLayoutControl.PageContent>
</cc:MainPageLayoutControl>
</Page>

View File

@@ -0,0 +1,191 @@
using Microsoft.UI.Xaml.Controls;
using Microsoft.Web.WebView2.Core;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net;
using System.Runtime.InteropServices.WindowsRuntime;
using System.Text;
using Windows.ApplicationModel.Resources;
using Windows.Foundation;
using Windows.Foundation.Collections;
using Windows.UI;
using Windows.UI.WindowManagement;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
using Windows.UI.Xaml.Data;
using Windows.UI.Xaml.Hosting;
using Windows.UI.Xaml.Input;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Navigation;
namespace VDownload.GUI.Views.Sources
{
public sealed partial class SourcesMain : Page
{
#region CONSTRUCTORS
public SourcesMain()
{
InitializeComponent();
// Twitch loading
SourcesMainTwitchSettingControl.Description = ResourceLoader.GetForCurrentView().GetString("SourcesMainTwitchSettingControlDescriptionLoading");
SourcesMainTwitchLoginButton.Content = ResourceLoader.GetForCurrentView().GetString("SourcesMainTwitchLoginButtonTextLoading");
}
#endregion
#region MAIN
// ONNAVIGATEDTO EVENT (CHECK SOURCES AUTHENTICATION)
protected override async void OnNavigatedTo(NavigationEventArgs e)
{
// Check Twitch
string twitchAccessToken = await Core.Services.Sources.Twitch.Auth.ReadAccessTokenAsync();
var twitchAccessTokenValidation = await Core.Services.Sources.Twitch.Auth.ValidateAccessTokenAsync(twitchAccessToken);
if (twitchAccessToken != null && twitchAccessTokenValidation.IsValid)
{
Debug.WriteLine("Twitch authentication status: LOGGED_IN");
Debug.WriteLine(twitchAccessTokenValidation.ExpirationDate.Value.ToString("dd.MM.yyyy"));
SourcesMainTwitchSettingControl.Description = $"{ResourceLoader.GetForCurrentView().GetString("SourcesMainTwitchSettingControlDescriptionLoggedIn")} {twitchAccessTokenValidation.Login}";
SourcesMainTwitchLoginButton.Content = ResourceLoader.GetForCurrentView().GetString("SourcesMainTwitchLoginButtonTextLoggedIn");
}
else if (twitchAccessToken == null || !twitchAccessTokenValidation.IsValid)
{
if (twitchAccessToken != null)
{
Debug.WriteLine("Twitch authentication status: ACCESS_TOKEN_READ_BUT_NOT_VALID");
await Core.Services.Sources.Twitch.Auth.DeleteAccessTokenAsync();
}
else
{
Debug.WriteLine("Twitch authentication status: ACCESS_TOKEN_NOT_FOUND");
}
SourcesMainTwitchSettingControl.Description = ResourceLoader.GetForCurrentView().GetString("SourcesMainTwitchSettingControlDescriptionNotLoggedIn");
SourcesMainTwitchSettingControl.DescriptionColor = new SolidColorBrush(Color.FromArgb(255, 225, 0, 0));
SourcesMainTwitchLoginButton.Content = ResourceLoader.GetForCurrentView().GetString("SourcesMainTwitchLoginButtonTextNotLoggedIn");
}
}
#endregion
#region LOGIN BUTTONS
// TWITCH LOGIN
private async void SourcesMainTwitchLoginButton_Click(object sender, RoutedEventArgs e)
{
string accessToken = await Core.Services.Sources.Twitch.Auth.ReadAccessTokenAsync();
var accessTokenValidation = await Core.Services.Sources.Twitch.Auth.ValidateAccessTokenAsync(accessToken);
if (accessToken != null && accessTokenValidation.IsValid)
{
Debug.WriteLine("Log out from Twitch (revoke and delete access token)");
// Revoke access token
await Core.Services.Sources.Twitch.Auth.RevokeAccessTokenAsync(accessToken);
Debug.WriteLine($"Twitch access token ({accessToken}) revoked successfully");
// Delete access token
await Core.Services.Sources.Twitch.Auth.DeleteAccessTokenAsync();
Debug.WriteLine($"Twitch access token ({accessToken}) deleted successfully");
// Update Twitch SettingControl
Debug.WriteLine("Twitch authentication status: ACCESS_TOKEN_NOT_FOUND");
SourcesMainTwitchSettingControl.Description = ResourceLoader.GetForCurrentView().GetString("SourcesMainTwitchSettingControlDescriptionNotLoggedIn");
SourcesMainTwitchSettingControl.DescriptionColor = new SolidColorBrush(Color.FromArgb(255, 225, 0, 0));
SourcesMainTwitchLoginButton.Content = ResourceLoader.GetForCurrentView().GetString("SourcesMainTwitchLoginButtonTextNotLoggedIn");
}
else
{
Debug.WriteLine("Log in to Twitch (get, validate and save access token)");
// Open new window
AppWindow TwitchAuthWindow = await AppWindow.TryCreateAsync();
TwitchAuthWindow.Title = "Twitch Authentication";
WebView2 TwitchAuthWebView = new WebView2();
await TwitchAuthWebView.EnsureCoreWebView2Async();
TwitchAuthWebView.Source = Core.Services.Sources.Twitch.Auth.AuthorizationUrl;
ElementCompositionPreview.SetAppWindowContent(TwitchAuthWindow, TwitchAuthWebView);
TwitchAuthWindow.TryShowAsync();
// NavigationStarting event (only when redirected)
TwitchAuthWebView.NavigationStarting += async (s, a) =>
{
Debug.WriteLine($"TwitchAuthWebView redirected to {a.Uri}");
if (new Uri(a.Uri).Host == Core.Services.Sources.Twitch.Auth.RedirectUrl.Host)
{
// Close window
await TwitchAuthWindow.CloseAsync();
// Get response
string response = a.Uri.Replace(Core.Services.Sources.Twitch.Auth.RedirectUrl.OriginalString, "");
if (response[1] == '#')
{
// Get access token
accessToken = response.Split('&')[0].Replace("/#access_token=", "");
Debug.WriteLine($"Twitch access token got successfully ({accessToken})");
// Check token
accessTokenValidation = await Core.Services.Sources.Twitch.Auth.ValidateAccessTokenAsync(accessToken);
Debug.WriteLine("Twitch access token validated successfully");
// Save token
await Core.Services.Sources.Twitch.Auth.SaveAccessTokenAsync(accessToken);
Debug.WriteLine("Twitch access token saved successfully");
// Update Twitch SettingControl
Debug.WriteLine("Twitch authentication status: LOGGED_IN");
SourcesMainTwitchSettingControl.Description = $"{ResourceLoader.GetForCurrentView().GetString("SourcesMainTwitchSettingControlDescriptionLoggedIn")} {accessTokenValidation.Login}";
SourcesMainTwitchSettingControl.DescriptionColor = new SolidColorBrush((Color)Application.Current.Resources["SystemBaseMediumColor"]);
SourcesMainTwitchLoginButton.Content = ResourceLoader.GetForCurrentView().GetString("SourcesMainTwitchLoginButtonTextLoggedIn");
}
else
{
// Ignored errors
string[] ignoredErrors = new[]
{
"The user denied you access",
};
// Errors translation
Dictionary<string, string> errorsTranslation = new Dictionary<string, string>
{
};
// Get error info
string errorInfo = (response.Split('&')[1].Replace("error_description=", "")).Replace('+', ' ');
if (!ignoredErrors.Contains(errorInfo))
{
// Error
ContentDialog loginErrorDialog = new ContentDialog
{
Title = ResourceLoader.GetForCurrentView().GetString("SourcesMainTwitchLoginErrorDialogTitle"),
Content = errorsTranslation.Keys.Contains(errorInfo) ? errorsTranslation[errorInfo] : $"{ResourceLoader.GetForCurrentView().GetString("SourcesMainTwitchLoginErrorDialogDescriptionUnknown")} ({errorInfo})",
CloseButtonText = "OK",
};
await loginErrorDialog.ShowAsync();
}
Debug.WriteLine($"Log in to Twitch failed ({errorInfo})");
}
// Clear cache
TwitchAuthWebView.CoreWebView2.CookieManager.DeleteAllCookies();
}
};
}
}
#endregion
}
}

View File

@@ -0,0 +1,14 @@
<Page
x:Class="VDownload.GUI.Views.Subscriptions.SubscriptionsMain"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:VDownload.Views.Subscriptions"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid>
</Grid>
</Page>

View File

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

View File

@@ -1,9 +0,0 @@
namespace VDownload.Objects.Enums
{
public enum PlaylistSource
{
TwitchChannel,
LocalFile,
Null,
}
}

View File

@@ -1,10 +0,0 @@
namespace VDownload.Objects.Enums
{
public enum VideoSource
{
TwitchVod,
TwitchClip,
YoutubeVideo,
Null
}
}

View File

@@ -1,10 +0,0 @@
namespace VDownload.Objects.Enums
{
public enum VideoStatus
{
Idle,
Waiting,
InProgress,
Removed
}
}

View File

@@ -1,102 +0,0 @@
// System
using System.Collections.Generic;
using Windows.Storage;
namespace VDownload.Services
{
internal class Config
{
#region CONSTANTS
// DATA VALUES LISTS
public static readonly string[] DateFormatList = new string[]
{
"yyyy.MM.dd",
"yyyy.dd.MM",
"dd.MM.yyyy",
"MM.dd.yyyy",
};
public static readonly string[] DefaultMediaTypeList = new string[]
{
"AV",
"A",
"V"
};
public static readonly string[] DefaultVideoExtensionList = new string[]
{
"MP4",
"WMV",
"HEVC"
};
public static readonly string[] DefaultAudioExtensionList = new string[]
{
"MP3",
"FLAC",
"WAV",
"M4A",
"ALAC",
"WMA"
};
// DEFAULT SETTINGS
private static readonly Dictionary<string, string> DefaultSettings = new Dictionary<string, string>()
{
{ "max_video_tasks" , "5" },
{ "default_video_extension", DefaultVideoExtensionList[0] },
{ "default_audio_extension", DefaultAudioExtensionList[0] },
{ "date_format", DateFormatList[0] },
{ "default_media_type", DefaultMediaTypeList[0] },
{ "default_output_filename", "[%date_pub%] %title%" },
{ "use_mrfcrf444", "1" },
{ "use_hardware_acceleration", "1" },
{ "delete_temp_on_start", "1" },
{ "delete_video_temp_after_error", "1" },
{ "max_playlist_videos", "0" },
};
// SETTINGS CONTAINER
private static readonly ApplicationDataContainer SettingsContainer = ApplicationData.Current.LocalSettings;
#endregion
#region MAIN
// GET VALUE
public static string GetValue(string key)
{
return SettingsContainer.Values[key].ToString();
}
// SET VALUE
public static void SetValue(string key, string value)
{
SettingsContainer.Values[key] = value;
}
// SET DEFAULT
public static void SetDefault()
{
foreach (KeyValuePair<string, string> s in DefaultSettings)
{
SettingsContainer.Values[s.Key] = s.Value;
}
}
// REBUILD
public static void Rebuild()
{
foreach (KeyValuePair<string, string> s in DefaultSettings)
{
if (!SettingsContainer.Values.ContainsKey(s.Key))
{
SettingsContainer.Values[s.Key] = s.Value;
}
}
}
#endregion
}
}

View File

@@ -1,119 +0,0 @@
// System
using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Windows.ApplicationModel.Resources;
using Windows.Foundation;
using Windows.Media.MediaProperties;
using Windows.Media.Transcoding;
using Windows.Storage;
using Windows.Storage.Streams;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
namespace VDownload.Services
{
internal class Media
{
#region VARIABLES
// PROGRESS UI ELEMENTS
private TextBlock ProgressLabelTextblock;
private ProgressBar ProgressBar;
#endregion
#region MAIN
// TRANSCODE MEDIA FILE
public async Task Transcode(StorageFile inputFile, StorageFile outputFile, string extension, string mediaType, TimeSpan duration, TimeSpan trimStart, TimeSpan trimEnd, CancellationToken token, TextBlock progressLabelTextblock, ProgressBar progressBar)
{
// Set progress UI elements
ProgressLabelTextblock = progressLabelTextblock;
ProgressBar = progressBar;
// Init transcoder
MediaTranscoder transcoder = new MediaTranscoder
{
HardwareAccelerationEnabled = Config.GetValue("use_hardware_acceleration") == "1" ? true : false,
VideoProcessingAlgorithm = Config.GetValue("use_mrfcrf444") == "1" ? MediaVideoProcessingAlgorithm.MrfCrf444 : MediaVideoProcessingAlgorithm.Default
};
if (0 < trimStart.TotalMilliseconds && trimStart.TotalMilliseconds < duration.TotalMilliseconds) // Set trimming at start
{
transcoder.TrimStartTime = trimStart;
}
if (0 < trimEnd.TotalMilliseconds && trimEnd.TotalMilliseconds < duration.TotalMilliseconds) // Set trimming at end
{
transcoder.TrimStopTime = trimEnd;
}
// Set video encoding profile
MediaEncodingProfile profile = MediaEncodingProfile.CreateMp4(VideoEncodingQuality.HD1080p);
switch (extension)
{
case "MP4": profile = MediaEncodingProfile.CreateMp4(VideoEncodingQuality.HD1080p); break;
case "WMV": profile = MediaEncodingProfile.CreateWmv(VideoEncodingQuality.HD1080p); break;
case "HEVC": profile = MediaEncodingProfile.CreateHevc(VideoEncodingQuality.HD1080p); break;
case "MP3": profile = MediaEncodingProfile.CreateMp3(AudioEncodingQuality.High); break;
case "FLAC": profile = MediaEncodingProfile.CreateFlac(AudioEncodingQuality.High); break;
case "WAV": profile = MediaEncodingProfile.CreateWav(AudioEncodingQuality.High); break;
case "M4A": profile = MediaEncodingProfile.CreateM4a(AudioEncodingQuality.High); break;
case "ALAC": profile = MediaEncodingProfile.CreateAlac(AudioEncodingQuality.High); break;
case "WMA": profile = MediaEncodingProfile.CreateWma(AudioEncodingQuality.High); break;
}
var videoData = await inputFile.Properties.GetVideoPropertiesAsync();
var audioData = await inputFile.Properties.GetMusicPropertiesAsync();
if (mediaType != "A")
{
profile.Video.Height = videoData.Height;
profile.Video.Width = videoData.Width;
profile.Video.Bitrate = videoData.Bitrate - audioData.Bitrate;
}
if (mediaType != "V")
{
profile.Audio.Bitrate = audioData.Bitrate;
}
if (mediaType == "V")
{
var audioTracks = profile.GetAudioTracks();
audioTracks.Clear();
profile.SetAudioTracks(audioTracks.AsEnumerable());
}
// Start transcoding operation
using (IRandomAccessStream outputFileOpened = await outputFile.OpenAsync(FileAccessMode.ReadWrite))
{
PrepareTranscodeResult transcodingPreparated = await transcoder.PrepareStreamTranscodeAsync(await inputFile.OpenAsync(FileAccessMode.Read), outputFileOpened, profile);
IAsyncActionWithProgress<double> transcodingTask = transcodingPreparated.TranscodeAsync();
try
{
await transcodingTask.AsTask(token, new Progress<double>(OnProgress));
await outputFileOpened.FlushAsync();
}
catch (TaskCanceledException) { }
transcodingTask.Close();
}
}
#endregion
#region EVENTS
// ON PROGRESS
private void OnProgress(double percent)
{
// Set progress
ProgressLabelTextblock.Text = $"{ResourceLoader.GetForCurrentView().GetString("VideoPanelProgressLabelTranscoding")} ({Math.Floor(percent)}%)";
ProgressBar.IsIndeterminate = false;
ProgressBar.Visibility = Visibility.Visible;
ProgressBar.Value = percent;
}
#endregion
}
}

View File

@@ -1,69 +0,0 @@
// Internal
using VDownload.Objects.Enums;
// System
using System;
using System.Linq;
using System.IO;
using Windows.Storage;
using System.Diagnostics;
namespace VDownload.Services
{
internal class Source
{
// VIDEO SOURCE
public static (VideoSource, string) GetVideoSourceData(Uri url)
{
// Twitch VOD
if (url.Host == "www.twitch.tv" && url.Segments.Contains("videos/"))
{
return (VideoSource.TwitchVod, url.Segments[url.Segments.Length - 1].Replace("/", ""));
}
// Twitch Clip
else if ((url.Host == "www.twitch.tv" && url.Segments.Contains("clip/")) || url.Host == "clips.twitch.tv")
{
return (VideoSource.TwitchClip, url.Segments[url.Segments.Length - 1].Replace("/", ""));
}
// Youtube Video
else if (url.Host == "www.youtube.com" && url.Segments.Contains("watch"))
{
return (VideoSource.YoutubeVideo, url.Query.Replace("?", "").Split('&')[0].Replace("v=", ""));
}
else if (url.Host == "youtu.be")
{
return (VideoSource.YoutubeVideo, url.Segments[url.Segments.Length - 1]);
}
// Unknown
else
{
return (VideoSource.Null, "");
}
}
// PLAYLIST SOURCE
public static (PlaylistSource, string) GetPlaylistSourceData(Uri url)
{
// Local file
if (url.IsFile)
{
return (PlaylistSource.LocalFile, url.AbsolutePath);
}
// Twitch Channel
else if (url.Host == "www.twitch.tv" && url.Segments.Length == 2)
{
return (PlaylistSource.TwitchChannel, url.Segments[url.Segments.Length - 1]);
}
// Unknown
else
{
return (PlaylistSource.Null, "");
}
}
}
}

View File

@@ -1,64 +0,0 @@
// Internal
using VDownload.Sources;
// System
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Threading;
namespace VDownload.Services
{
internal class Videos
{
#region MAIN
// VIDEO OBJECTS LIST
public static List<VObject> VideoObjectsList = new List<VObject>();
// ACTIVE VIDEO TASKS LIST
public static List<Task> VideoTasksList = new List<Task>();
// WAIT FOR FREE SPACE IN ACTIVE VIDEO TASKS LIST
public static async Task WaitForFreeSpace(CancellationToken token)
{
await Task.Run(async () =>
{
while (!(VideoTasksList.Count < int.Parse((string)Config.GetValue("max_video_tasks"))) && !token.IsCancellationRequested)
{
await Task.Delay(50);
}
});
}
#endregion
#region GET UNIQUE ID
// VARIABLES
private static readonly Random Random = new Random();
private static readonly char[] CharsID = "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890".ToCharArray();
private static readonly int LengthID = 10;
private static readonly List<string> UsedID = new List<string>();
// METHOD
public static string GetUniqueID()
{
string id;
do
{
id = "";
while (id.Length < LengthID)
{
id += CharsID[Random.Next(0, CharsID.Length)];
}
} while (UsedID.Contains(id));
UsedID.Add(id);
return id;
}
#endregion
}
}

View File

@@ -1,777 +0,0 @@
using Microsoft.Toolkit.Uwp.UI;
using Microsoft.UI.Xaml.Controls;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using VDownload.Objects.Enums;
using VDownload.Services;
using Windows.ApplicationModel.Resources;
using Windows.Storage;
using Windows.Storage.AccessCache;
using Windows.Storage.Pickers;
using Windows.UI;
using Windows.UI.Text;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Media.Imaging;
namespace VDownload.Sources
{
public class PObject
{
#region INIT
// PLAYLIST DATA
public PlaylistSource SourceType { get; private set; }
public string ID { get; private set; }
public Dictionary<VObject, TextBlock> VObjects { get; private set; }
private List<VObject> DeletedVObjects { get; set; }
// PLAYLIST PANEL OBJECTS
public StackPanel PlaylistPanel { get; set; }
private Grid DeletedVideosPanel { get; set; }
// CONSTRUCTOR
public PObject(Uri url)
{
DeletedVObjects = new List<VObject>();
VObjects = new Dictionary<VObject, TextBlock>();
(SourceType, ID) = Source.GetPlaylistSourceData(url);
if (SourceType == PlaylistSource.Null)
{
throw new ArgumentException();
}
}
#endregion
#region MAIN
// GET VIDEOS
public async Task GetVideos()
{
VObject[] videos = null;
switch (SourceType)
{
case PlaylistSource.TwitchChannel:
videos = await Twitch.Channel.GetVideos(ID);
break;
case PlaylistSource.Null:
throw new ArgumentException();
}
foreach(VObject video in videos)
{
VObjects.Add(video, null);
}
}
#endregion
#region VIDEO PANEL
// INIT PLAYLIST PANEL
public async Task InitPlaylistPanel()
{
// Add videos to panel
foreach (VObject video in VObjects.Keys.ToArray())
{
await VideoPanelHandler(video);
}
}
// VIDEO PANEL HANDLER
private async Task VideoPanelHandler(VObject Video)
{
// Video panel
Expander videoPanel = new Expander
{
Margin = new Thickness(0, 5, 0, 5),
HorizontalAlignment = HorizontalAlignment.Stretch,
HorizontalContentAlignment = HorizontalAlignment.Stretch,
};
// Header
Grid videoPanelHeader = new Grid
{
Margin = new Thickness(0, 15, 0, 15),
};
videoPanelHeader.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto });
videoPanelHeader.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(15) });
videoPanelHeader.ColumnDefinitions.Add(new ColumnDefinition());
videoPanelHeader.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(15) });
videoPanelHeader.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto });
videoPanel.Header = videoPanelHeader;
// Thumbnail
Image thumbnailImage = new Image
{
Source = new BitmapImage { UriSource = Video.Thumbnail },
Width = 120,
};
Grid.SetColumn(thumbnailImage, 0);
videoPanelHeader.Children.Add(thumbnailImage);
// Metadata grid
Grid metadataGrid = new Grid();
metadataGrid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });
metadataGrid.RowDefinitions.Add(new RowDefinition { Height = new GridLength(10) });
metadataGrid.RowDefinitions.Add(new RowDefinition());
Grid.SetColumn(metadataGrid, 2);
videoPanelHeader.Children.Add(metadataGrid);
// Title & Source icon grid
Grid titleSourceGrid = new Grid();
titleSourceGrid.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto });
titleSourceGrid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(10) });
titleSourceGrid.ColumnDefinitions.Add(new ColumnDefinition());
Grid.SetRow(titleSourceGrid, 0);
metadataGrid.Children.Add(titleSourceGrid);
// Title textblock
TextBlock titleTextBlock = new TextBlock
{
Text = Video.Title,
FontWeight = FontWeights.Bold,
FontSize = 15,
};
Grid.SetColumn(titleTextBlock, 2);
titleSourceGrid.Children.Add(titleTextBlock);
// Source icon image
Image sourceIcon = new Image
{
Source = new BitmapImage { UriSource = Video.SourceIcon },
Width = 15,
};
Grid.SetColumn(sourceIcon, 0);
titleSourceGrid.Children.Add(sourceIcon);
// Details grid
Grid detailedMetadataGrid = new Grid();
detailedMetadataGrid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });
detailedMetadataGrid.RowDefinitions.Add(new RowDefinition { Height = new GridLength(8) });
detailedMetadataGrid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });
detailedMetadataGrid.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto });
detailedMetadataGrid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(5) });
detailedMetadataGrid.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto });
detailedMetadataGrid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(20) });
detailedMetadataGrid.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto });
detailedMetadataGrid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(5) });
detailedMetadataGrid.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto });
Grid.SetRow(detailedMetadataGrid, 2);
metadataGrid.Children.Add(detailedMetadataGrid);
double iconSize = 12;
double textSize = 10;
// Author icon
Image authorIcon = new Image
{
Source = new BitmapImage { UriSource = new Uri($"ms-appx:///Assets/Icons/Universal/{(Application.Current.RequestedTheme == ApplicationTheme.Dark ? "Dark" : "Light")}/Author.png") },
Width = iconSize,
};
Grid.SetColumn(authorIcon, 0);
Grid.SetRow(authorIcon, 0);
detailedMetadataGrid.Children.Add(authorIcon);
// Author data textblock
TextBlock authorDataTextBlock = new TextBlock
{
Text = Video.Author,
VerticalAlignment = VerticalAlignment.Center,
FontSize = textSize,
};
Grid.SetColumn(authorDataTextBlock, 2);
Grid.SetRow(authorDataTextBlock, 0);
detailedMetadataGrid.Children.Add(authorDataTextBlock);
// Views icon
Image viewsIcon = new Image
{
Source = new BitmapImage { UriSource = new Uri($"ms-appx:///Assets/Icons/Universal/{(Application.Current.RequestedTheme == ApplicationTheme.Dark ? "Dark" : "Light")}/Views.png") },
Width = iconSize,
};
Grid.SetColumn(viewsIcon, 0);
Grid.SetRow(viewsIcon, 2);
detailedMetadataGrid.Children.Add(viewsIcon);
// Views data textblock
TextBlock viewsDataTextBlock = new TextBlock
{
Text = Video.Views.ToString(),
VerticalAlignment = VerticalAlignment.Center,
FontSize = textSize,
};
Grid.SetColumn(viewsDataTextBlock, 2);
Grid.SetRow(viewsDataTextBlock, 2);
detailedMetadataGrid.Children.Add(viewsDataTextBlock);
// Date icon
Image dateIcon = new Image
{
Source = new BitmapImage { UriSource = new Uri($"ms-appx:///Assets/Icons/Universal/{(Application.Current.RequestedTheme == ApplicationTheme.Dark ? "Dark" : "Light")}/Date.png") },
Width = iconSize,
};
Grid.SetColumn(dateIcon, 4);
Grid.SetRow(dateIcon, 0);
detailedMetadataGrid.Children.Add(dateIcon);
// Date data textblock
TextBlock dateDataTextBlock = new TextBlock
{
Text = Video.Date.ToString((string)Config.GetValue("date_format")),
VerticalAlignment = VerticalAlignment.Center,
FontSize = textSize,
};
Grid.SetColumn(dateDataTextBlock, 6);
Grid.SetRow(dateDataTextBlock, 0);
detailedMetadataGrid.Children.Add(dateDataTextBlock);
// Duration icon
Image durationIcon = new Image
{
Source = new BitmapImage { UriSource = new Uri($"ms-appx:///Assets/Icons/Universal/{(Application.Current.RequestedTheme == ApplicationTheme.Dark ? "Dark" : "Light")}/Duration.png") },
Width = iconSize,
};
Grid.SetColumn(durationIcon, 4);
Grid.SetRow(durationIcon, 2);
detailedMetadataGrid.Children.Add(durationIcon);
// Duration data textblock
TextBlock durationDataTextBlock = new TextBlock
{
Text = Video.Duration.ToString(),
VerticalAlignment = VerticalAlignment.Center,
FontSize = textSize,
};
Grid.SetColumn(durationDataTextBlock, 6);
Grid.SetRow(durationDataTextBlock, 2);
detailedMetadataGrid.Children.Add(durationDataTextBlock);
// Delete button
AppBarButton deleteButton = new AppBarButton
{
Icon = new SymbolIcon(Symbol.Clear),
Width = 40,
Height = 48,
VerticalAlignment = VerticalAlignment.Center,
};
deleteButton.Click += (object sender, RoutedEventArgs e) =>
{
PlaylistPanel.Children.Remove(videoPanel);
VObjects.Remove(Video);
DeletedVObjects.Add(Video);
DeletedVideosPanelHandler();
};
Grid.SetColumn(deleteButton, 4);
videoPanelHeader.Children.Add(deleteButton);
// Content
Grid content = new Grid
{
HorizontalAlignment = HorizontalAlignment.Stretch,
};
content.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });
content.RowDefinitions.Add(new RowDefinition { Height = new GridLength(50) });
content.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });
videoPanel.Content = content;
// Download options
Grid downloadOptionsGrid = new Grid
{
HorizontalAlignment = HorizontalAlignment.Stretch,
};
downloadOptionsGrid.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto });
downloadOptionsGrid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(30) });
downloadOptionsGrid.ColumnDefinitions.Add(new ColumnDefinition());
Grid.SetRow(downloadOptionsGrid, 0);
content.Children.Add(downloadOptionsGrid);
// Media type
Grid mediaTypeGrid = new Grid();
mediaTypeGrid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });
mediaTypeGrid.RowDefinitions.Add(new RowDefinition { Height = new GridLength(8) });
mediaTypeGrid.RowDefinitions.Add(new RowDefinition());
mediaTypeGrid.RowDefinitions.Add(new RowDefinition());
mediaTypeGrid.RowDefinitions.Add(new RowDefinition());
Grid.SetColumn(mediaTypeGrid, 0);
downloadOptionsGrid.Children.Add(mediaTypeGrid);
// Media type textblock
TextBlock AddPlaylistVideoDownloadOptionsMediaTypeTextBlock = new TextBlock
{
Text = ResourceLoader.GetForCurrentView().GetString("AddPlaylistVideoDownloadOptionsMediaTypeTextBlock"),
FontWeight = FontWeights.SemiBold,
HorizontalAlignment = HorizontalAlignment.Center,
};
Grid.SetRow(AddPlaylistVideoDownloadOptionsMediaTypeTextBlock, 0);
mediaTypeGrid.Children.Add(AddPlaylistVideoDownloadOptionsMediaTypeTextBlock);
// Media type radiobutton (AV)
RadioButton AddPlaylistVideoDownloadOptionsMediaTypeRadiobuttonAV = new RadioButton
{
Content = ResourceLoader.GetForCurrentView().GetString("AddPlaylistVideoDownloadOptionsMediaTypeRadiobuttonAV"),
HorizontalAlignment = HorizontalAlignment.Left,
VerticalAlignment = VerticalAlignment.Top,
GroupName = $"QualityRadiobuttons{Video.UniqueID}"
};
Grid.SetRow(AddPlaylistVideoDownloadOptionsMediaTypeRadiobuttonAV, 2);
mediaTypeGrid.Children.Add(AddPlaylistVideoDownloadOptionsMediaTypeRadiobuttonAV);
// Media type radiobutton (A)
RadioButton AddPlaylistVideoDownloadOptionsMediaTypeRadiobuttonA = new RadioButton
{
Content = ResourceLoader.GetForCurrentView().GetString("AddPlaylistVideoDownloadOptionsMediaTypeRadiobuttonA"),
HorizontalAlignment = HorizontalAlignment.Left,
VerticalAlignment = VerticalAlignment.Center,
GroupName = $"QualityRadiobuttons{Video.UniqueID}"
};
Grid.SetRow(AddPlaylistVideoDownloadOptionsMediaTypeRadiobuttonA, 3);
mediaTypeGrid.Children.Add(AddPlaylistVideoDownloadOptionsMediaTypeRadiobuttonA);
// Media type radiobutton (V)
RadioButton AddPlaylistVideoDownloadOptionsMediaTypeRadiobuttonV = new RadioButton
{
Content = ResourceLoader.GetForCurrentView().GetString("AddPlaylistVideoDownloadOptionsMediaTypeRadiobuttonV"),
HorizontalAlignment = HorizontalAlignment.Left,
VerticalAlignment = VerticalAlignment.Bottom,
GroupName = $"QualityRadiobuttons{Video.UniqueID}"
};
Grid.SetRow(AddPlaylistVideoDownloadOptionsMediaTypeRadiobuttonV, 4);
mediaTypeGrid.Children.Add(AddPlaylistVideoDownloadOptionsMediaTypeRadiobuttonV);
// Separator
AppBarSeparator AddPlaylistVideoDownloadOptionsSeparator = new AppBarSeparator
{
VerticalAlignment = VerticalAlignment.Stretch,
HorizontalAlignment = HorizontalAlignment.Center,
};
Grid.SetColumn(AddPlaylistVideoDownloadOptionsSeparator, 1);
downloadOptionsGrid.Children.Add(AddPlaylistVideoDownloadOptionsSeparator);
// Quality & Trim grid
Grid qualityTrimGrid = new Grid
{
HorizontalAlignment = HorizontalAlignment.Stretch,
};
qualityTrimGrid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });
qualityTrimGrid.RowDefinitions.Add(new RowDefinition { Height = new GridLength(20) });
qualityTrimGrid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });
Grid.SetColumn(qualityTrimGrid, 2);
downloadOptionsGrid.Children.Add(qualityTrimGrid);
// Quality grid
Grid qualityGrid = new Grid
{
HorizontalAlignment = HorizontalAlignment.Stretch,
};
qualityGrid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });
qualityGrid.RowDefinitions.Add(new RowDefinition { Height = new GridLength(8) });
qualityGrid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });
Grid.SetRow(qualityGrid, 0);
qualityTrimGrid.Children.Add(qualityGrid);
// Quality textblock
TextBlock AddPlaylistVideoDownloadOptionsQualityTextBlock = new TextBlock
{
Text = ResourceLoader.GetForCurrentView().GetString("AddPlaylistVideoDownloadOptionsQualityTextBlock"),
FontWeight = FontWeights.SemiBold,
HorizontalAlignment = HorizontalAlignment.Center,
};
Grid.SetRow(AddPlaylistVideoDownloadOptionsQualityTextBlock, 0);
qualityGrid.Children.Add(AddPlaylistVideoDownloadOptionsQualityTextBlock);
// Quality combobox
ComboBox AddPlaylistVideoDownloadOptionsQualityComboBox = new ComboBox
{
HorizontalAlignment = HorizontalAlignment.Stretch,
};
Grid.SetRow(AddPlaylistVideoDownloadOptionsQualityComboBox, 2);
qualityGrid.Children.Add(AddPlaylistVideoDownloadOptionsQualityComboBox);
// Trim grid
Grid trimGrid = new Grid();
trimGrid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });
trimGrid.RowDefinitions.Add(new RowDefinition { Height = new GridLength(8) });
trimGrid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });
Grid.SetRow(trimGrid, 2);
qualityTrimGrid.Children.Add(trimGrid);
// Trim textblock
TextBlock AddPlaylistVideoDownloadOptionsTrimTextBlock = new TextBlock
{
Text = ResourceLoader.GetForCurrentView().GetString("AddPlaylistVideoDownloadOptionsTrimTextBlock"),
FontWeight = FontWeights.SemiBold,
HorizontalAlignment = HorizontalAlignment.Center,
};
Grid.SetRow(AddPlaylistVideoDownloadOptionsTrimTextBlock, 0);
trimGrid.Children.Add(AddPlaylistVideoDownloadOptionsTrimTextBlock);
// Trim textbox grid
Grid trimTextBoxGrid = new Grid();
trimTextBoxGrid.ColumnDefinitions.Add(new ColumnDefinition());
trimTextBoxGrid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(8) });
trimTextBoxGrid.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto });
trimTextBoxGrid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(8) });
trimTextBoxGrid.ColumnDefinitions.Add(new ColumnDefinition());
Grid.SetRow(trimTextBoxGrid, 2);
trimGrid.Children.Add(trimTextBoxGrid);
// Trim start textbox
TextBox AddPlaylistVideoDownloadOptionsTrimStartTextBox = new TextBox();
TextBoxExtensions.SetCustomMask(AddPlaylistVideoDownloadOptionsTrimStartTextBox, "5:[0-5]");
TextBoxExtensions.SetMask(AddPlaylistVideoDownloadOptionsTrimStartTextBox, "99:59:59");
Grid.SetColumn(AddPlaylistVideoDownloadOptionsTrimStartTextBox, 0);
trimTextBoxGrid.Children.Add(AddPlaylistVideoDownloadOptionsTrimStartTextBox);
// Trim separator
TextBlock AddPlaylistVideoDownloadOptionsTrimSeparatorTextBlock = new TextBlock
{
Text = "-",
HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Center,
};
Grid.SetColumn(AddPlaylistVideoDownloadOptionsTrimSeparatorTextBlock, 2);
trimTextBoxGrid.Children.Add(AddPlaylistVideoDownloadOptionsTrimSeparatorTextBlock);
// Trim end textbox
TextBox AddPlaylistVideoDownloadOptionsTrimEndTextBox = new TextBox();
TextBoxExtensions.SetCustomMask(AddPlaylistVideoDownloadOptionsTrimEndTextBox, "5:[0-5]");
TextBoxExtensions.SetMask(AddPlaylistVideoDownloadOptionsTrimEndTextBox, "99:59:59");
Grid.SetColumn(AddPlaylistVideoDownloadOptionsTrimEndTextBox, 4);
trimTextBoxGrid.Children.Add(AddPlaylistVideoDownloadOptionsTrimEndTextBox);
// File & location
Grid fileLocationGrid = new Grid
{
HorizontalAlignment = HorizontalAlignment.Stretch,
};
fileLocationGrid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });
fileLocationGrid.RowDefinitions.Add(new RowDefinition { Height = new GridLength(10) });
fileLocationGrid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });
fileLocationGrid.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto });
fileLocationGrid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(15) });
fileLocationGrid.ColumnDefinitions.Add(new ColumnDefinition());
Grid.SetRow(fileLocationGrid, 4);
content.Children.Add(fileLocationGrid);
// File textblock
TextBlock AddPlaylistVideoFileDataTextBlock = new TextBlock
{
Text = ResourceLoader.GetForCurrentView().GetString("AddPlaylistVideoFileDataTextBlock"),
FontWeight = FontWeights.SemiBold,
VerticalAlignment = VerticalAlignment.Center,
};
Grid.SetRow(AddPlaylistVideoFileDataTextBlock, 0);
Grid.SetColumn(AddPlaylistVideoFileDataTextBlock, 0);
fileLocationGrid.Children.Add(AddPlaylistVideoFileDataTextBlock);
// File grid
Grid fileGrid = new Grid
{
HorizontalAlignment = HorizontalAlignment.Stretch,
};
fileGrid.ColumnDefinitions.Add(new ColumnDefinition());
fileGrid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(15) });
fileGrid.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto });
Grid.SetRow(fileGrid, 0);
Grid.SetColumn(fileGrid, 2);
fileLocationGrid.Children.Add(fileGrid);
// Filename textbox
TextBox AddPlaylistVideoFileDataFilenameTextBox = new TextBox();
Grid.SetColumn(AddPlaylistVideoFileDataFilenameTextBox, 0);
fileGrid.Children.Add(AddPlaylistVideoFileDataFilenameTextBox);
// Extension combobox
ComboBox AddPlaylistVideoFileDataExtensionComboBox = new ComboBox
{
Width = 80
};
Grid.SetColumn(AddPlaylistVideoFileDataExtensionComboBox, 2);
fileGrid.Children.Add(AddPlaylistVideoFileDataExtensionComboBox);
// Location textblock
TextBlock AddPlaylistVideoLocationDataTextBlock = new TextBlock
{
Text = ResourceLoader.GetForCurrentView().GetString("AddPlaylistVideoLocationDataTextBlock"),
FontWeight = FontWeights.SemiBold,
VerticalAlignment = VerticalAlignment.Center,
};
Grid.SetRow(AddPlaylistVideoLocationDataTextBlock, 2);
Grid.SetColumn(AddPlaylistVideoLocationDataTextBlock, 0);
fileLocationGrid.Children.Add(AddPlaylistVideoLocationDataTextBlock);
// Location grid
Grid locationGrid = new Grid
{
HorizontalAlignment = HorizontalAlignment.Stretch,
};
locationGrid.ColumnDefinitions.Add(new ColumnDefinition());
locationGrid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(0) });
locationGrid.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto });
Grid.SetRow(locationGrid, 2);
Grid.SetColumn(locationGrid, 2);
fileLocationGrid.Children.Add(locationGrid);
// Location data textblock
TextBlock AddPlaylistVideoLocationDataLocationTextBlock = new TextBlock
{
Text = "//Location",
FontSize = 11,
VerticalAlignment = VerticalAlignment.Center,
};
Grid.SetColumn(AddPlaylistVideoLocationDataLocationTextBlock, 0);
locationGrid.Children.Add(AddPlaylistVideoLocationDataLocationTextBlock);
// Location selection button
Button AddPlaylistVideoLocationDataChooseLocationButton = new Button
{
Content = "...",
};
Grid.SetColumn(AddPlaylistVideoLocationDataChooseLocationButton, 2);
locationGrid.Children.Add(AddPlaylistVideoLocationDataChooseLocationButton);
// Set items in quality combobox
foreach (string q in Video.Streams.Keys)
{
AddPlaylistVideoDownloadOptionsQualityComboBox.Items.Add(q);
}
// Set items in extension combobox
foreach (string x in Video.MediaType == "A" ? Config.DefaultAudioExtensionList : Config.DefaultVideoExtensionList)
{
AddPlaylistVideoFileDataExtensionComboBox.Items.Add(x);
}
// Set quality option
if (Video.MediaType == "A")
{
AddPlaylistVideoDownloadOptionsQualityComboBox.SelectedValue = ResourceLoader.GetForCurrentView().GetString("AddVideoQualityNoVideoStream");
AddPlaylistVideoDownloadOptionsQualityComboBox.IsEnabled = false;
}
else
{
AddPlaylistVideoDownloadOptionsQualityComboBox.SelectedValue = Video.SelectedQuality;
AddPlaylistVideoDownloadOptionsQualityComboBox.IsEnabled = true;
}
// Set trim options
AddPlaylistVideoDownloadOptionsTrimStartTextBox.Text = Video.TrimStart.ToString();
AddPlaylistVideoDownloadOptionsTrimEndTextBox.Text = Video.TrimEnd.ToString();
// Set media type option
switch (Video.MediaType)
{
case "AV": AddPlaylistVideoDownloadOptionsMediaTypeRadiobuttonAV.IsChecked = true; break;
case "V": AddPlaylistVideoDownloadOptionsMediaTypeRadiobuttonV.IsChecked = true; break;
case "A": AddPlaylistVideoDownloadOptionsMediaTypeRadiobuttonA.IsChecked = true; break;
default: AddPlaylistVideoDownloadOptionsMediaTypeRadiobuttonAV.IsChecked = true; break;
}
// Set filename option
AddPlaylistVideoFileDataFilenameTextBox.Text = Video.Filename;
// Set extension option
AddPlaylistVideoFileDataExtensionComboBox.SelectedItem = Video.Extension;
// Set location option
if (StorageApplicationPermissions.FutureAccessList.ContainsItem("save"))
{
Video.CustomSaveLocation = await StorageApplicationPermissions.FutureAccessList.GetFolderAsync("save");
AddPlaylistVideoLocationDataLocationTextBlock.Text = Video.CustomSaveLocation.Path;
Video.FilePath = $@"{(Video.CustomSaveLocation.Path[Video.CustomSaveLocation.Path.Length - 1] == '\\' ? Video.CustomSaveLocation.Path : Video.CustomSaveLocation.Path + '\\')}{Video.Filename}.{Video.Extension.ToLower()}";
}
else
{
AddPlaylistVideoLocationDataLocationTextBlock.Text = $@"{UserDataPaths.GetDefault().Downloads}\VDownload\";
Video.FilePath = $@"{UserDataPaths.GetDefault().Downloads}\VDownload\{Video.Filename}.{Video.Extension.ToLower()}";
}
// Set event handlers
AddPlaylistVideoDownloadOptionsMediaTypeRadiobuttonAV.Checked += (object sender, RoutedEventArgs e) =>
{
Video.MediaType = "AV";
AddPlaylistVideoDownloadOptionsQualityComboBox.IsEnabled = true;
AddPlaylistVideoDownloadOptionsQualityComboBox.SelectedValue = Video.Streams.Keys.ToArray()[0];
AddPlaylistVideoFileDataExtensionComboBox.Items.Clear();
foreach (string x in Config.DefaultVideoExtensionList)
{
AddPlaylistVideoFileDataExtensionComboBox.Items.Add(x);
}
AddPlaylistVideoFileDataExtensionComboBox.SelectedItem = Config.GetValue("default_video_extension");
};
AddPlaylistVideoDownloadOptionsMediaTypeRadiobuttonA.Checked += (object sender, RoutedEventArgs e) =>
{
Video.MediaType = "A";
AddPlaylistVideoDownloadOptionsQualityComboBox.IsEnabled = false;
AddPlaylistVideoDownloadOptionsQualityComboBox.SelectedValue = ResourceLoader.GetForCurrentView().GetString("AddPlaylistVideoQualityNoVideoStream");
AddPlaylistVideoFileDataExtensionComboBox.Items.Clear();
foreach (string x in Config.DefaultAudioExtensionList)
{
AddPlaylistVideoFileDataExtensionComboBox.Items.Add(x);
}
AddPlaylistVideoFileDataExtensionComboBox.SelectedItem = Config.GetValue("default_audio_extension");
};
AddPlaylistVideoDownloadOptionsMediaTypeRadiobuttonV.Checked += (object sender, RoutedEventArgs e) =>
{
Video.MediaType = "V";
AddPlaylistVideoDownloadOptionsQualityComboBox.IsEnabled = true;
AddPlaylistVideoDownloadOptionsQualityComboBox.SelectedValue = Video.Streams.Keys.ToArray()[0];
AddPlaylistVideoFileDataExtensionComboBox.Items.Clear();
foreach (string x in Config.DefaultVideoExtensionList)
{
AddPlaylistVideoFileDataExtensionComboBox.Items.Add(x);
}
AddPlaylistVideoFileDataExtensionComboBox.SelectedItem = Config.GetValue("default_video_extension");
};
AddPlaylistVideoDownloadOptionsQualityComboBox.SelectionChanged += (object sender, SelectionChangedEventArgs e) =>
{
if (Video.MediaType == "A")
Video.SelectedQuality = Video.Streams.Keys.ToArray()[0];
else
Video.SelectedQuality = (string)AddPlaylistVideoDownloadOptionsQualityComboBox.SelectedItem;
};
AddPlaylistVideoDownloadOptionsTrimStartTextBox.TextChanged += (object sender, TextChangedEventArgs e) =>
{
try
{
Video.TrimStart = TimeSpan.Parse(AddPlaylistVideoDownloadOptionsTrimStartTextBox.Text);
}
catch { }
};
AddPlaylistVideoDownloadOptionsTrimEndTextBox.TextChanged += (object sender, TextChangedEventArgs e) =>
{
try
{
Video.TrimEnd = TimeSpan.Parse(AddPlaylistVideoDownloadOptionsTrimEndTextBox.Text);
}
catch { }
};
AddPlaylistVideoFileDataFilenameTextBox.TextChanged += (object sender, TextChangedEventArgs e) =>
{
foreach (char c in Path.GetInvalidFileNameChars())
{
AddPlaylistVideoFileDataFilenameTextBox.Text = AddPlaylistVideoFileDataFilenameTextBox.Text.Replace(c, ' ');
}
Video.Filename = AddPlaylistVideoFileDataFilenameTextBox.Text;
if (Video.CustomSaveLocation != null)
Video.FilePath = $@"{(Video.CustomSaveLocation.Path[Video.CustomSaveLocation.Path.Length - 1] == '\\' ? Video.CustomSaveLocation.Path : Video.CustomSaveLocation.Path + '\\')}{Video.Filename}.{Video.Extension.ToLower()}";
else
Video.FilePath = $@"{UserDataPaths.GetDefault().Downloads}\VDownload\{Video.Filename}.{Video.Extension.ToLower()}";
};
AddPlaylistVideoFileDataExtensionComboBox.SelectionChanged += (object sender, SelectionChangedEventArgs e) =>
{
if (!(AddPlaylistVideoFileDataExtensionComboBox.SelectedItem == null))
{
Video.Extension = (string)AddPlaylistVideoFileDataExtensionComboBox.SelectedItem;
if (Video.CustomSaveLocation != null)
Video.FilePath = $@"{(Video.CustomSaveLocation.Path[Video.CustomSaveLocation.Path.Length - 1] == '\\' ? Video.CustomSaveLocation.Path : Video.CustomSaveLocation.Path + '\\')}{Video.Filename}.{Video.Extension.ToLower()}";
else
Video.FilePath = $@"{UserDataPaths.GetDefault().Downloads}\VDownload\{Video.Filename}.{Video.Extension.ToLower()}";
}
};
AddPlaylistVideoLocationDataChooseLocationButton.Click += async (object sender, RoutedEventArgs e) =>
{
FolderPicker picker = new FolderPicker();
picker.SuggestedStartLocation = PickerLocationId.ComputerFolder;
picker.FileTypeFilter.Add("*");
StorageFolder folder = await picker.PickSingleFolderAsync();
if (folder != null)
{
try
{
await (await folder.CreateFileAsync("VDownloadLocationAccessTest")).DeleteAsync();
Video.CustomSaveLocation = folder;
AddPlaylistVideoLocationDataLocationTextBlock.Text = Video.CustomSaveLocation.Path[Video.CustomSaveLocation.Path.Length - 1] == '\\' ? Video.CustomSaveLocation.Path : Video.CustomSaveLocation.Path + '\\';
Video.FilePath = $@"{(Video.CustomSaveLocation.Path[Video.CustomSaveLocation.Path.Length - 1] == '\\' ? Video.CustomSaveLocation.Path : Video.CustomSaveLocation.Path + '\\')}{Video.Filename}.{Video.Extension.ToLower()}";
}
catch { }
}
};
// Add panel
VObjects[Video] = AddPlaylistVideoLocationDataLocationTextBlock;
PlaylistPanel.Children.Add(videoPanel);
}
// DELETED VIDEOS PANEL HANDLER
private void DeletedVideosPanelHandler()
{
if (DeletedVideosPanel == null)
{
// Init panel
DeletedVideosPanel = new Grid
{
Background = new SolidColorBrush((Color)Application.Current.Resources["SystemChromeMediumHighColor"]),
BorderThickness = new Thickness(10),
BorderBrush = new SolidColorBrush((Color)Application.Current.Resources["SystemChromeMediumHighColor"]),
CornerRadius = new CornerRadius(1),
Margin = new Thickness(0, 5, 0, 5),
};
DeletedVideosPanel.ColumnDefinitions.Add(new ColumnDefinition());
DeletedVideosPanel.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(10) });
DeletedVideosPanel.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto });
// Textblock
TextBlock AddPlaylistDeletedVideosPanelTextBlock = new TextBlock
{
Text = ResourceLoader.GetForCurrentView().GetString("AddPlaylistDeletedVideosPanelTextBlock").Replace("{x}", DeletedVObjects.Count.ToString()),
HorizontalAlignment = HorizontalAlignment.Left,
VerticalAlignment = VerticalAlignment.Center,
};
Grid.SetColumn(AddPlaylistDeletedVideosPanelTextBlock, 0);
DeletedVideosPanel.Children.Add(AddPlaylistDeletedVideosPanelTextBlock);
// Button
Button AddPlaylistDeletedVideosPanelButton = new Button
{
Content = ResourceLoader.GetForCurrentView().GetString("AddPlaylistDeletedVideosPanelButton")
};
AddPlaylistDeletedVideosPanelButton.Click += async (object sender, RoutedEventArgs e) =>
{
foreach (VObject v in DeletedVObjects)
{
await VideoPanelHandler(v);
}
DeletedVObjects.Clear();
PlaylistPanel.Children.Remove(DeletedVideosPanel);
DeletedVideosPanel = null;
};
Grid.SetColumn(AddPlaylistDeletedVideosPanelButton, 2);
DeletedVideosPanel.Children.Add(AddPlaylistDeletedVideosPanelButton);
// Add panel
PlaylistPanel.Children.Add(DeletedVideosPanel);
}
else
{
// Update panel
((TextBlock)DeletedVideosPanel.Children[0]).Text = ResourceLoader.GetForCurrentView().GetString("AddPlaylistDeletedVideosPanelTextBlock").Replace("{x}", DeletedVObjects.Count.ToString());
}
}
#endregion
}
}

View File

@@ -1,83 +0,0 @@
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading.Tasks;
using VDownload.Services;
namespace VDownload.Sources.Twitch
{
internal class Channel
{
// GET VIDEOS
public static async Task<VObject[]> GetVideos(string ID)
{
// Client settings
WebClient Client = new WebClient();
Client.Headers.Add("Accept", "application/vnd.twitchtv.v5+json");
Client.Headers.Add("Client-ID", "v8kfhyc2980it9e7t5hhc7baukzuj2");
// Get channel id
Uri requestUri;
JObject response;
if (!ID.All(char.IsDigit))
{
requestUri = new Uri($"https://api.twitch.tv/kraken/users?login={ID}");
response = JObject.Parse(await Client.DownloadStringTaskAsync(requestUri));
response = (JObject)response["users"][0];
}
else
{
requestUri = new Uri($"https://api.twitch.tv/kraken/users/{ID}");
response = JObject.Parse(await Client.DownloadStringTaskAsync(requestUri));
}
string id = response["_id"].ToString();
// Get list
List<VObject> videos = new List<VObject>();
int offset = 0;
do
{
Client = new WebClient();
Client.Headers.Add("Accept", "application/vnd.twitchtv.v5+json");
Client.Headers.Add("Client-ID", "v8kfhyc2980it9e7t5hhc7baukzuj2");
requestUri = new Uri($"https://api.twitch.tv/kraken/channels/{id}/videos?limit=100&offset={offset}");
response = JObject.Parse(await Client.DownloadStringTaskAsync(requestUri));
foreach (var v in response["videos"])
{
if (int.Parse(Config.GetValue("max_playlist_videos")) == 0 || videos.Count < int.Parse(Config.GetValue("max_playlist_videos")))
{
try
{
VObject vid = new VObject(new Uri($"https://www.twitch.tv/videos/{v["_id"].ToString().Replace("v", "")}"));
await vid.GetMetadata();
videos.Add(vid);
}
catch { }
}
else
{
break;
}
}
if (response["videos"].ToArray().Length == 100 && !(int.Parse(Config.GetValue("max_playlist_videos")) == 0 || videos.Count < int.Parse(Config.GetValue("max_playlist_videos"))))
{
offset += 100;
}
else
{
break;
}
} while (true);
// Return videos
return videos.ToArray();
}
}
}

View File

@@ -1,196 +0,0 @@
// External
using Newtonsoft.Json.Linq;
// System
using System;
using System.Collections.Generic;
using System.Net;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Web;
using VDownload.Services;
using Windows.ApplicationModel.Resources;
using Windows.Storage;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Media.Imaging;
namespace VDownload.Sources.Twitch
{
internal class Clip
{
#region INIT
// ID
private string ID { get; set; }
// CONSTRUCTOR
public Clip(string id)
{
ID = id;
}
#endregion
#region MAIN
// GET METADATA
public async Task<Dictionary<string, object>> GetMetadata()
{
// Client settings
WebClient Client = new WebClient();
Client.Headers.Add("Accept", "application/vnd.twitchtv.v5+json");
Client.Headers.Add("Client-ID", "v8kfhyc2980it9e7t5hhc7baukzuj2");
// Get metadata
Uri requestUrl = new Uri($"https://api.twitch.tv/kraken/clips/{ID}");
JObject response = JObject.Parse(await Client.DownloadStringTaskAsync(requestUrl));
// Pack data into dictionary
Dictionary<string, object> metadata = new Dictionary<string, object>
{
["title"] = response["title"].ToString().Replace("\n", ""),
["author"] = response["broadcaster"]["display_name"].ToString(),
["date"] = Convert.ToDateTime(response["created_at"].ToString()),
["duration"] = TimeSpan.FromSeconds(Math.Ceiling(double.Parse(response["duration"].ToString()))),
["views"] = long.Parse(response["views"].ToString()),
["url"] = new Uri(response["url"].ToString()),
["thumbnail"] = new Uri(response["thumbnails"]["medium"].ToString()),
};
// Return metadata
return metadata;
}
// GET STREAMS
public async Task<Dictionary<string, Uri>> GetStreams()
{
// Client settings
WebClient Client = new WebClient();
Client.Headers.Add("Client-ID", "kimne78kx3ncx6brgo4mv6wki5h1ko");
Client.Encoding = Encoding.UTF8;
// Get streams
var response = JArray.Parse(await Client.UploadStringTaskAsync(new Uri("https://gql.twitch.tv/gql", UriKind.Absolute), "[{\"operationName\":\"VideoAccessToken_Clip\",\"variables\":{\"slug\":\"" + ID + "\"},\"extensions\":{\"persistedQuery\":{\"version\":1,\"sha256Hash\":\"36b89d2507fce29e5ca551df756d27c1cfe079e2609642b4390aa4c35796eb11\"}}}]"));
// Pack data into dictioanry
Dictionary<string, Uri> streams = new Dictionary<string, Uri>();
foreach (var s in response[0]["data"]["clip"]["videoQualities"])
{
string key = $"{s["quality"]}p{s["frameRate"]}";
Uri value = new Uri(s["sourceURL"].ToString());
streams[key] = value;
}
// Return streams
return streams;
}
// DOWNLOAD VIDEO
private TextBlock ProgressLabelTextblock;
private ProgressBar ProgressBar;
private Image ProgressIcon;
public async Task<StorageFile> Download(StorageFolder tempFolder, string quality, string extension, string mediaType, TimeSpan trimStart, TimeSpan trimEnd, TimeSpan duration, CancellationTokenSource token, TextBlock progressLabelTextblock, ProgressBar progressBar, Image progressIcon)
{
// Set variables
ProgressLabelTextblock = progressLabelTextblock;
ProgressBar = progressBar;
ProgressIcon = progressIcon;
// Set progress to downloading
if (!token.Token.IsCancellationRequested)
{
ProgressLabelTextblock.Text = $"{ResourceLoader.GetForCurrentView().GetString("VideoPanelProgressLabelDownloading")} (0%)";
ProgressBar.IsIndeterminate = false;
ProgressBar.Visibility = Visibility.Visible;
ProgressBar.Value = 0;
ProgressIcon.Source = new BitmapImage { UriSource = new Uri($"ms-appx:///Assets/Icons/Universal/{(Application.Current.RequestedTheme == ApplicationTheme.Dark ? "Dark" : "Light")}/Downloading.png") };
}
// Download
StorageFile rawFile = null;
if (!token.Token.IsCancellationRequested)
{
try
{
rawFile = await tempFolder.CreateFileAsync("raw.mp4");
if (!token.Token.IsCancellationRequested)
{
// Access token client settings
WebClient Client = new WebClient();
Client.Headers.Add("Client-ID", "kimne78kx3ncx6brgo4mv6wki5h1ko");
Client.Encoding = Encoding.UTF8;
// Get access token
var response = JArray.Parse(await Client.UploadStringTaskAsync(new Uri("https://gql.twitch.tv/gql", UriKind.Absolute), "[{\"operationName\":\"VideoAccessToken_Clip\",\"variables\":{\"slug\":\"" + ID + "\"},\"extensions\":{\"persistedQuery\":{\"version\":1,\"sha256Hash\":\"36b89d2507fce29e5ca551df756d27c1cfe079e2609642b4390aa4c35796eb11\"}}}]"));
string tokenVal = response[0]["data"]["clip"]["playbackAccessToken"]["value"].ToString();
string tokenSig = response[0]["data"]["clip"]["playbackAccessToken"]["signature"].ToString();
// Downloading client settings
Client = new WebClient();
Client.DownloadProgressChanged += OnProgress;
// Download
string downloadUrl = $"{(await GetStreams())[quality].OriginalString}?sig={tokenSig}&token={HttpUtility.UrlEncode(tokenVal)}";
token.Token.ThrowIfCancellationRequested();
using (token.Token.Register(Client.CancelAsync))
{
await Client.DownloadFileTaskAsync(downloadUrl, rawFile.Path);
}
}
}
catch (WebException)
{
throw new Exception(ResourceLoader.GetForCurrentView().GetString("VideoPanelProgressLabelErrorInternetConnection"));
}
}
// Rendering
StorageFile outputFile = null;
if (!token.Token.IsCancellationRequested)
{
if (extension != "MP4" && trimStart.TotalMilliseconds > 0 && trimEnd.TotalMilliseconds < duration.TotalMilliseconds)
{
// Set progress to transcoding
ProgressLabelTextblock.Text = $"{ResourceLoader.GetForCurrentView().GetString("VideoPanelProgressLabelTranscoding")} (0%)";
ProgressBar.IsIndeterminate = false;
ProgressBar.Visibility = Visibility.Visible;
ProgressBar.Value = 0;
ProgressIcon.Source = new BitmapImage { UriSource = new Uri($"ms-appx:///Assets/Icons/Universal/{(Application.Current.RequestedTheme == ApplicationTheme.Dark ? "Dark" : "Light")}/Transcoding.png") };
// Create transcoding output file
outputFile = await tempFolder.CreateFileAsync($"processed.{extension.ToLower()}");
// Run processing
await new Media().Transcode(rawFile, outputFile, extension, mediaType, duration, trimStart, trimEnd, token.Token, ProgressLabelTextblock, ProgressBar);
}
else
{
outputFile = rawFile;
}
}
// Return output file
return outputFile;
}
#endregion
#region EVENT HANDLERS
private void OnProgress(object sender, DownloadProgressChangedEventArgs e)
{
ProgressLabelTextblock.Text = $"{ResourceLoader.GetForCurrentView().GetString("VideoPanelProgressLabelDownloading")} ({e.ProgressPercentage}%)";
ProgressBar.IsIndeterminate = false;
ProgressBar.Visibility = Visibility.Visible;
ProgressBar.Value = e.ProgressPercentage;
}
#endregion
}
}

View File

@@ -1,266 +0,0 @@
// Internal
using VDownload.Services;
// External
using Newtonsoft.Json.Linq;
// System
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
using Windows.ApplicationModel.Resources;
using Windows.Storage;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Media.Imaging;
namespace VDownload.Sources.Twitch
{
internal class Vod
{
#region INIT
// ID
private string ID { get; set; }
// CONSTRUCTOR
public Vod(string id)
{
ID = id;
}
#endregion
#region MAIN
// GET METADATA
public async Task<Dictionary<string, object>> GetMetadata()
{
// Client settings
WebClient Client = new WebClient();
Client.Headers.Add("Accept", "application/vnd.twitchtv.v5+json");
Client.Headers.Add("Client-ID", "v8kfhyc2980it9e7t5hhc7baukzuj2");
// Send request and get response
Uri requestUrl = new Uri($"https://api.twitch.tv/kraken/videos/{ID}");
JObject response = JObject.Parse(await Client.DownloadStringTaskAsync(requestUrl));
// Pack data into dictionary
Dictionary<string, object> metadata = new Dictionary<string, object>()
{
["title"] = response["title"].ToString().Replace("\n", ""),
["author"] = response["channel"]["display_name"].ToString(),
["date"] = Convert.ToDateTime(response["created_at"].ToString()),
["duration"] = TimeSpan.FromSeconds(int.Parse(response["length"].ToString())),
["views"] = long.Parse(response["views"].ToString()),
["url"] = new Uri(response["url"].ToString())
};
try
{ metadata["thumbnail"] = new Uri(response["thumbnails"]["large"][0]["url"].ToString()); }
catch
{ metadata["thumbnail"] = new Uri("Assets/Icons/Unknown/Thumbnail.png", UriKind.Relative); }
// Return metadata
return metadata;
}
// GET STREAMS
public async Task<Dictionary<string, Uri>> GetStreams()
{
// Client settings
WebClient Client = new WebClient();
Client.Headers.Add("Client-ID", "kimne78kx3ncx6brgo4mv6wki5h1ko");
// Get access token
JObject accessToken = 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\"}}"));
string tokenVal = accessToken["data"]["videoPlaybackAccessToken"]["value"].ToString();
string tokenSig = accessToken["data"]["videoPlaybackAccessToken"]["signature"].ToString();
// Get streams
string[] response = Client.DownloadString($"http://usher.twitch.tv/vod/{ID}?nauth={tokenVal}&nauthsig={tokenSig}&allow_source=true&player=twitchweb").Split("\n");
// Pack data into dictionary
Dictionary<string, Uri> streams = new Dictionary<string, Uri>();
for (int i = 2; i < response.Length; i += 3)
{
string key = response[i].Replace("#EXT-X-MEDIA:", "").Split(',')[2].Replace("NAME=", "").Replace("\"", "");
Uri value = new Uri(response[i + 2]);
streams[key] = value;
}
// Return streams
return streams;
}
// DOWNLOAD VIDEO
private TextBlock ProgressLabelTextblock;
private ProgressBar ProgressBar;
private Image ProgressIcon;
public async Task<StorageFile> Download(StorageFolder tempFolder, string quality, string extension, string mediaType, TimeSpan trimStart, TimeSpan trimEnd, TimeSpan duration, CancellationTokenSource token, TextBlock progressLabelTextblock, ProgressBar progressBar, Image progressIcon)
{
// Set variables
ProgressLabelTextblock = progressLabelTextblock;
ProgressBar = progressBar;
ProgressIcon = progressIcon;
// Set progress to downloading
if (!token.Token.IsCancellationRequested)
{
ProgressLabelTextblock.Text = $"{ResourceLoader.GetForCurrentView().GetString("VideoPanelProgressLabelDownloading")} (0%)";
ProgressBar.IsIndeterminate = false;
ProgressBar.Visibility = Visibility.Visible;
ProgressBar.Value = 0;
ProgressIcon.Source = new BitmapImage { UriSource = new Uri($"ms-appx:///Assets/Icons/Universal/{(Application.Current.RequestedTheme == ApplicationTheme.Dark ? "Dark" : "Light")}/Downloading.png") };
}
// Get chunks
Uri[] chunks = new Uri[] { };
float chunksInTotal = 0;
float chunksDownloaded = 0;
if (!token.Token.IsCancellationRequested)
{
chunks = await ExtractChunksFromM3U8((await GetStreams())[quality]);
chunksInTotal = chunks.Length;
}
// Download
StorageFile rawFile = null;
if (!token.Token.IsCancellationRequested)
{
try
{
rawFile = await tempFolder.CreateFileAsync("raw.ts");
if (!token.Token.IsCancellationRequested)
{
Task writeTask = WriteChunkToFile(rawFile, await DownloadVodChunk(chunks[0]));
chunksDownloaded++;
ProgressLabelTextblock.Text = $"{ResourceLoader.GetForCurrentView().GetString("VideoPanelProgressLabelDownloading")} ({Math.Floor(chunksDownloaded / chunksInTotal * 100)}%)";
ProgressBar.IsIndeterminate = false;
ProgressBar.Visibility = Visibility.Visible;
ProgressBar.Value = chunksDownloaded / chunksInTotal * 100;
Task<byte[]> downloadTask = DownloadVodChunk(chunks[1]);
for (int i = 2; i < chunks.Length; i++)
{
if (!token.Token.IsCancellationRequested)
{
await Task.WhenAll(downloadTask, writeTask);
chunksDownloaded++;
ProgressLabelTextblock.Text = $"{ResourceLoader.GetForCurrentView().GetString("VideoPanelProgressLabelDownloading")} ({Math.Floor(chunksDownloaded / chunksInTotal * 100)}%)";
ProgressBar.IsIndeterminate = false;
ProgressBar.Visibility = Visibility.Visible;
ProgressBar.Value = chunksDownloaded / chunksInTotal * 100;
writeTask = WriteChunkToFile(rawFile, downloadTask.Result);
downloadTask = DownloadVodChunk(chunks[i]);
}
}
}
}
catch (WebException)
{
throw new Exception(ResourceLoader.GetForCurrentView().GetString("VideoPanelProgressLabelErrorInternetConnection"));
}
}
// Rendering
StorageFile outputFile = null;
if (!token.Token.IsCancellationRequested)
{
// Set progress to transcoding
ProgressLabelTextblock.Text = $"{ResourceLoader.GetForCurrentView().GetString("VideoPanelProgressLabelTranscoding")} (0%)";
ProgressBar.IsIndeterminate = false;
ProgressBar.Visibility = Visibility.Visible;
ProgressBar.Value = 0;
ProgressIcon.Source = new BitmapImage { UriSource = new Uri($"ms-appx:///Assets/Icons/Universal/{(Application.Current.RequestedTheme == ApplicationTheme.Dark ? "Dark" : "Light")}/Transcoding.png") };
// Create transcoding output file
outputFile = await tempFolder.CreateFileAsync($"processed.{extension.ToLower()}");
// Run processing
await new Media().Transcode(rawFile, outputFile, extension, mediaType, duration, trimStart, trimEnd, token.Token, ProgressLabelTextblock, ProgressBar);
}
// Return output file
return outputFile;
}
#endregion
#region INTERNAL
// EXTRACT CHUNKS FROM M3U8 PLAYLIST
private async Task<Uri[]> ExtractChunksFromM3U8(Uri streamUrl)
{
// Client settings
WebClient Client = new WebClient();
Client.Headers.Add("Client-ID", "kimne78kx3ncx6brgo4mv6wki5h1ko");
// Get playlist content
string request = await Client.DownloadStringTaskAsync(streamUrl);
// Pack into list
List<Uri> videos = new List<Uri>();
foreach (string l in request.Split("\n"))
{
if (l.Length > 0 && l[0] != '#')
{
string[] uriSegments = streamUrl.Segments;
string streamDir = "";
for (int i = 0; i < uriSegments.Length - 1; i++)
{
streamDir += uriSegments[i];
}
videos.Add(new Uri($@"{streamUrl.GetLeftPart(UriPartial.Scheme)}{streamUrl.Host}{streamDir}{l}"));
}
}
// Return videos
return videos.ToArray();
}
// DOWNLOAD VOD CHUNK
private async Task<byte[]> DownloadVodChunk(Uri url)
{
// Download
int errorCount = 0;
bool done = false;
while (!done && errorCount < 10)
{
try
{
using (WebClient Client = new WebClient())
{
return await Client.DownloadDataTaskAsync(url);
}
}
catch
{
errorCount++;
await Task.Delay(5000);
}
}
throw new WebException();
}
// WRITE CHUNK TO FILE
private Task WriteChunkToFile(StorageFile file, byte[] dataToWrite)
{
return Task.Factory.StartNew(() =>
{
using (var stream = new FileStream(file.Path, FileMode.Append))
{
stream.Write(dataToWrite, 0, dataToWrite.Length);
stream.Close();
}
});
}
#endregion
}
}

View File

@@ -1,686 +0,0 @@
// Internal
using VDownload.Objects.Enums;
using VDownload.Services;
// System
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Windows.ApplicationModel.ExtendedExecution;
using Windows.ApplicationModel.Resources;
using Windows.Storage;
using Windows.UI.Text;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Media.Imaging;
using Windows.UI;
using System.Diagnostics;
namespace VDownload.Sources
{
public class VObject
{
#region INIT
// VIDEO METADATA
public string UniqueID { get; private set; }
public VideoSource SourceType { get; private set; }
public string ID { get; private set; }
public string Title { get; private set; }
public string Author { get; private set; }
public DateTime Date { get; private set; }
public long Views { get; private set; }
public TimeSpan Duration { get; private set; }
public Uri Url { get; private set; }
public Uri Thumbnail { get; private set; }
public Uri SourceIcon { get; private set; }
public Dictionary<string, Uri> Streams { get; private set; }
// FILE PROCESS DATA
public string SelectedQuality { get; set; }
public TimeSpan TrimStart { get; set; }
public TimeSpan TrimEnd { get; set; }
public string MediaType { get; set; }
public string Filename { get; set; }
public string Extension { get; set; }
public string FilePath { get; set; }
public StorageFolder CustomSaveLocation { get; set; }
// VIDEO PANEL
private Grid VideoPanel { get; set; }
private StackPanel VideoPanelParent { get; set; }
private AppBarButton StartStopButton { get; set; }
private TextBlock ProgressLabelTextblock { get; set; }
private ProgressBar ProgressBar { get; set; }
private Image ProgressIcon { get; set; }
// VIDEO TASK
private object VideoSourceHandler { get; set; }
private CancellationTokenSource VideoTaskCancellationToken { get; set; }
private Task<StorageFile> VideoTask { get; set; }
public VideoStatus VideoStatus { get; private set; }
// CONSTRUCTOR
public VObject(Uri url)
{
(SourceType, ID) = Source.GetVideoSourceData(url);
switch (SourceType) // (TODO)
{
case VideoSource.TwitchVod:
VideoSourceHandler = new Twitch.Vod(ID);
SourceIcon = new Uri("ms-appx:///Assets/Icons/Sources/Twitch.png");
break;
case VideoSource.TwitchClip:
VideoSourceHandler = new Twitch.Clip(ID);
SourceIcon = new Uri("ms-appx:///Assets/Icons/Sources/Twitch.png");
break;
default:
throw new ArgumentException();
}
UniqueID = Videos.GetUniqueID();
}
#endregion
#region MAIN
// GET METADATA AND SET DATA VARIABLES
public async Task GetMetadata()
{
// Get metadata and streams
Task<Dictionary<string, object>> metadataTask;
Task<Dictionary<string, Uri>> streamsTask;
switch (SourceType) // (TODO)
{
case VideoSource.TwitchVod:
metadataTask = ((Twitch.Vod)VideoSourceHandler).GetMetadata();
streamsTask = ((Twitch.Vod)VideoSourceHandler).GetStreams();
break;
case VideoSource.TwitchClip:
metadataTask = ((Twitch.Clip)VideoSourceHandler).GetMetadata();
streamsTask = ((Twitch.Clip)VideoSourceHandler).GetStreams();
break;
default:
throw new Exception(message: "Unknown video source");
}
await Task.WhenAll(metadataTask, streamsTask);
Dictionary<string, object> metadata = metadataTask.Result;
Dictionary<string, Uri> streams = streamsTask.Result;
// Set metadata
Title = (string)metadata["title"];
Author = (string)metadata["author"];
Date = (DateTime)metadata["date"];
Views = (long)metadata["views"];
Duration = (TimeSpan)metadata["duration"];
Thumbnail = (Uri)metadata["thumbnail"];
Url = (Uri)metadata["url"];
Streams = streams;
// Set default media type
MediaType = Config.GetValue("default_media_type");
// Set default quality
SelectedQuality = Streams.Keys.ToArray()[0];
// Set trim timestamps
TrimStart = new TimeSpan(0);
TrimEnd = Duration;
// Set defualt filename
Dictionary<string, string> filenameTemplate = new Dictionary<string, string>()
{
{ "%title%", Title },
{ "%author%", Author },
{ "%date_pub%", Date.ToString(Config.GetValue("date_format")) },
{ "%date_now%", DateTime.Now.ToString(Config.GetValue("date_format")) },
{ "%views%", Views.ToString() },
{ "%id%", ID }
};
string temporaryFilename = Config.GetValue("default_output_filename");
foreach (KeyValuePair<string, string> t in filenameTemplate)
{ temporaryFilename = temporaryFilename.Replace(t.Key, t.Value); }
foreach (char c in Path.GetInvalidFileNameChars())
{ temporaryFilename = temporaryFilename.Replace(c, ' '); }
Filename = temporaryFilename;
// Set extension
Extension = MediaType == "A" ? Config.GetValue("default_audio_extension") : Config.GetValue("default_video_extension");
// Set visible path
if (CustomSaveLocation != null) FilePath = $@"{CustomSaveLocation.Path}\{Filename}.{Extension.ToLower()}";
else FilePath = $@"{UserDataPaths.GetDefault().Downloads}\VDownload\{Filename}.{Extension.ToLower()}";
}
// ADD VIDEO TO LIST
public void AddVideoToList(StackPanel parent)
{
// Set status to idle
VideoStatus = VideoStatus.Idle;
// Video panel management
VideoPanel = CreateVideoPanel();
VideoPanelParent = parent;
VideoPanelParent.Children.Add(VideoPanel);
// Add VObject to listed videos list
Videos.VideoObjectsList.Add(this);
}
// REMOVE VIDEO FROM LIST
public void RemoveVideoFromList()
{
// Remove video from video list
VideoPanelParent.Children.Remove(VideoPanel);
// Remove VObject from listed videos list
Videos.VideoObjectsList.Remove(this);
// Set status as removed
VideoStatus = VideoStatus.Removed;
}
// START VIDEO TASK
public async Task Start()
{
// Change video status to idle
VideoStatus = VideoStatus.Waiting;
// Set cancellation token
VideoTaskCancellationToken = new CancellationTokenSource();
// Set panel (start and waiting)
StartStopButton.Icon = new SymbolIcon(Symbol.Stop);
ProgressLabelTextblock.Text = ResourceLoader.GetForCurrentView().GetString("VideoPanelProgressLabelWaiting");
ProgressBar.IsIndeterminate = true;
ProgressBar.Visibility = Visibility.Visible;
ProgressIcon.Source = new BitmapImage { UriSource = new Uri($"ms-appx:///Assets/Icons/Universal/{(Application.Current.RequestedTheme == ApplicationTheme.Dark ? "Dark" : "Light")}/Waiting.png") };
// Wait for free space in active video tasks list
await Videos.WaitForFreeSpace(VideoTaskCancellationToken.Token);
// Set end message
string endMessage = ResourceLoader.GetForCurrentView().GetString("VideoPanelProgressLabelCancelled");
Uri endIconSource = new Uri($"ms-appx:///Assets/Icons/Universal/{(Application.Current.RequestedTheme == ApplicationTheme.Dark ? "Dark" : "Light")}/Cancelled.png");
// Start video downloading process
if (!VideoTaskCancellationToken.IsCancellationRequested)
{
// Init temp folder
StorageFolder tempFolder = await ApplicationData.Current.TemporaryFolder.CreateFolderAsync(UniqueID);
// Set video task handler (TODO)
switch (SourceType)
{
case VideoSource.TwitchVod:
VideoTask = ((Twitch.Vod)VideoSourceHandler).Download(tempFolder, SelectedQuality, Extension, MediaType, TrimStart, TrimEnd, Duration, VideoTaskCancellationToken, ProgressLabelTextblock, ProgressBar, ProgressIcon);
break;
case VideoSource.TwitchClip:
VideoTask = ((Twitch.Clip)VideoSourceHandler).Download(tempFolder, SelectedQuality, Extension, MediaType, TrimStart, TrimEnd, Duration, VideoTaskCancellationToken, ProgressLabelTextblock, ProgressBar, ProgressIcon);
break;
default:
throw new Exception(message: "Unknown video source");
}
// Add video task to active video tasks list
Videos.VideoTasksList.Add(VideoTask);
VideoStatus = VideoStatus.InProgress;
// Start video task
StorageFile downloadedFile = null;
bool error = false;
string errorMessage = "";
Stopwatch executeStopwatch = new Stopwatch();
executeStopwatch.Start();
try
{
// Request no suspendable session
ExtendedExecutionSession session = new ExtendedExecutionSession { Reason = ExtendedExecutionReason.Unspecified };
await session.RequestExtensionAsync();
// Run task
downloadedFile = await VideoTask;
// Dispose session
session.Dispose();
}
catch (Exception ex)
{
// Set error info (error identifier and endMessage)
error = true;
errorMessage = ex.Message;
}
finally
{
// Set progress to finalizing
ProgressLabelTextblock.Text = ResourceLoader.GetForCurrentView().GetString("VideoPanelProgressLabelFinalizing");
ProgressBar.IsIndeterminate = true;
ProgressBar.Visibility = Visibility.Visible;
ProgressIcon.Source = new BitmapImage { UriSource = new Uri($"ms-appx:///Assets/Icons/Universal/{(Application.Current.RequestedTheme == ApplicationTheme.Dark ? "Dark" : "Light")}/Finalizing.png") };
// Move to output destination
if (!VideoTaskCancellationToken.IsCancellationRequested && !error)
{
// Set output file
StorageFile outputFile;
if (CustomSaveLocation != null) outputFile = await CustomSaveLocation.CreateFileAsync($"{Filename}.{Extension.ToLower()}", CreationCollisionOption.GenerateUniqueName);
else outputFile = await DownloadsFolder.CreateFileAsync($"{Filename}.{Extension.ToLower()}", CreationCollisionOption.GenerateUniqueName);
// Create and move file
await downloadedFile.MoveAndReplaceAsync(outputFile);
}
// Delete temp
if (!error || Config.GetValue("delete_video_temp_after_error") == "1")
await tempFolder.DeleteAsync();
// Remove video task from active video tasks list
Videos.VideoTasksList.Remove(VideoTask);
VideoStatus = VideoStatus.Idle;
// Stop stopwatch
executeStopwatch.Stop();
// Set end message
if (!VideoTaskCancellationToken.IsCancellationRequested)
{
if (error)
{
endMessage = $"{ResourceLoader.GetForCurrentView().GetString("VideoPanelProgressLabelError")} ({errorMessage})";
endIconSource = new Uri($"ms-appx:///Assets/Icons/Universal/{(Application.Current.RequestedTheme == ApplicationTheme.Dark ? "Dark" : "Light")}/Error.png");
}
else
{
endMessage = $"{ResourceLoader.GetForCurrentView().GetString("VideoPanelProgressLabelDone1")} {Math.Round(executeStopwatch.Elapsed.TotalSeconds, 0)} {ResourceLoader.GetForCurrentView().GetString("VideoPanelProgressLabelDone2")}";
endIconSource = new Uri($"ms-appx:///Assets/Icons/Universal/{(Application.Current.RequestedTheme == ApplicationTheme.Dark ? "Dark" : "Light")}/Done.png");
}
}
}
}
// End message
ProgressLabelTextblock.Text = endMessage;
StartStopButton.Icon = new SymbolIcon(Symbol.Download);
ProgressBar.Visibility = Visibility.Collapsed;
ProgressIcon.Source = new BitmapImage { UriSource = endIconSource };
}
#endregion
#region PANEL
// CREATE VIDEO PANEL
private Grid CreateVideoPanel()
{
// Base grid
Grid baseGrid = new Grid
{
Background = new SolidColorBrush((Color)Application.Current.Resources["SystemChromeAltHighColor"]),
Margin = new Thickness(0, 5, 0, 5),
BorderThickness = new Thickness(20),
BorderBrush = new SolidColorBrush((Color)Application.Current.Resources["SystemChromeAltHighColor"]),
CornerRadius = new CornerRadius(5),
};
baseGrid.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto });
baseGrid.ColumnDefinitions.Add(new ColumnDefinition());
baseGrid.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto });
// Thumbnail image
Image thumbnailImage = new Image
{
Source = new BitmapImage { UriSource = Thumbnail },
Height = 144,
HorizontalAlignment = HorizontalAlignment.Left,
VerticalAlignment = VerticalAlignment.Top,
};
Grid.SetColumn(thumbnailImage, 0);
baseGrid.Children.Add(thumbnailImage);
// Data grid
Grid dataGrid = new Grid
{
Margin = new Thickness(20, 0, 20, 0),
};
dataGrid.RowDefinitions.Add(new RowDefinition());
dataGrid.RowDefinitions.Add(new RowDefinition());
dataGrid.RowDefinitions.Add(new RowDefinition());
dataGrid.RowDefinitions.Add(new RowDefinition());
Grid.SetColumn(dataGrid, 1);
baseGrid.Children.Add(dataGrid);
// Title textblock
TextBlock titleTextBlock = new TextBlock
{
Text = Title,
FontWeight = FontWeights.Bold,
FontSize = 16,
Margin = new Thickness(0, 0, 0, 8),
};
Grid.SetRow(titleTextBlock, 0);
dataGrid.Children.Add(titleTextBlock);
// Metadata grid
Grid metadataGrid = new Grid();
metadataGrid.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto });
metadataGrid.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto });
metadataGrid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(30) });
metadataGrid.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto });
metadataGrid.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto });
metadataGrid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(30) });
metadataGrid.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto });
metadataGrid.ColumnDefinitions.Add(new ColumnDefinition());
metadataGrid.RowDefinitions.Add(new RowDefinition());
metadataGrid.RowDefinitions.Add(new RowDefinition());
metadataGrid.RowDefinitions.Add(new RowDefinition());
metadataGrid.RowDefinitions.Add(new RowDefinition());
Grid.SetRow(metadataGrid, 1);
dataGrid.Children.Add(metadataGrid);
double iconSize = 15;
double textSize = 11;
double iconMargin = 5;
// Author icon
Image authorIcon = new Image
{
Source = new BitmapImage { UriSource = new Uri($"ms-appx:///Assets/Icons/Universal/{(Application.Current.RequestedTheme == ApplicationTheme.Dark ? "Dark" : "Light")}/Author.png") },
Margin = new Thickness(0, iconMargin, 0, iconMargin),
Width = iconSize,
};
Grid.SetColumn(authorIcon, 0);
Grid.SetRow(authorIcon, 0);
metadataGrid.Children.Add(authorIcon);
// Author data textblock
TextBlock authorDataTextBlock = new TextBlock
{
Text = Author,
Margin = new Thickness(10, 0, 0, 0),
VerticalAlignment = VerticalAlignment.Center,
FontSize = textSize,
};
Grid.SetColumn(authorDataTextBlock, 1);
Grid.SetRow(authorDataTextBlock, 0);
metadataGrid.Children.Add(authorDataTextBlock);
// Views icon
Image viewsIcon = new Image
{
Source = new BitmapImage { UriSource = new Uri($"ms-appx:///Assets/Icons/Universal/{(Application.Current.RequestedTheme == ApplicationTheme.Dark ? "Dark" : "Light")}/Views.png") },
Margin = new Thickness(0, iconMargin, 0, iconMargin),
Width = iconSize,
};
Grid.SetColumn(viewsIcon, 3);
Grid.SetRow(viewsIcon, 0);
metadataGrid.Children.Add(viewsIcon);
// Views data textblock
TextBlock viewsDataTextBlock = new TextBlock
{
Text = Views.ToString(),
Margin = new Thickness(10, 0, 0, 0),
VerticalAlignment = VerticalAlignment.Center,
FontSize = textSize,
};
Grid.SetColumn(viewsDataTextBlock, 4);
Grid.SetRow(viewsDataTextBlock, 0);
metadataGrid.Children.Add(viewsDataTextBlock);
// Date icon
Image dateIcon = new Image
{
Source = new BitmapImage { UriSource = new Uri($"ms-appx:///Assets/Icons/Universal/{(Application.Current.RequestedTheme == ApplicationTheme.Dark ? "Dark" : "Light")}/Date.png") },
Margin = new Thickness(0, iconMargin, 0, iconMargin),
Width = iconSize,
};
Grid.SetColumn(dateIcon, 6);
Grid.SetRow(dateIcon, 0);
metadataGrid.Children.Add(dateIcon);
// Date data textblock
TextBlock dateDataTextBlock = new TextBlock
{
Text = Date.ToString((string)Config.GetValue("date_format")),
Margin = new Thickness(10, 0, 0, 0),
VerticalAlignment = VerticalAlignment.Center,
FontSize = textSize,
};
Grid.SetColumn(dateDataTextBlock, 7);
Grid.SetRow(dateDataTextBlock, 0);
metadataGrid.Children.Add(dateDataTextBlock);
// Duration icon
Image durationIcon = new Image
{
Source = new BitmapImage { UriSource = new Uri($"ms-appx:///Assets/Icons/Universal/{(Application.Current.RequestedTheme == ApplicationTheme.Dark ? "Dark" : "Light")}/Duration.png") },
Margin = new Thickness(0, iconMargin, 0, iconMargin),
Width = iconSize,
};
Grid.SetColumn(durationIcon, 0);
Grid.SetRow(durationIcon, 1);
metadataGrid.Children.Add(durationIcon);
// Duration data textblock
TextBlock durationDataTextBlock = new TextBlock
{
Text = Duration.ToString(),
Margin = new Thickness(10, 0, 0, 0),
VerticalAlignment = VerticalAlignment.Center,
FontSize = textSize,
};
Grid.SetColumn(durationDataTextBlock, 1);
Grid.SetRow(durationDataTextBlock, 1);
metadataGrid.Children.Add(durationDataTextBlock);
// Media type & quality icon
Image qualityIcon = new Image
{
Source = new BitmapImage { UriSource = new Uri($"ms-appx:///Assets/Icons/Universal/{(Application.Current.RequestedTheme == ApplicationTheme.Dark ? "Dark" : "Light")}/Quality.png") },
Margin = new Thickness(0, iconMargin, 0, iconMargin),
Width = iconSize,
};
Grid.SetColumn(qualityIcon, 3);
Grid.SetRow(qualityIcon, 1);
metadataGrid.Children.Add(qualityIcon);
// Media type & quality data textblock
string mediaTypeQualityData = MediaType == "A" ? "" : SelectedQuality;
switch (MediaType)
{
case "AV": mediaTypeQualityData += $" ({ResourceLoader.GetForCurrentView().GetString("VideoPanelMediaTypeDataAV")})"; break;
case "A": mediaTypeQualityData += $"{ResourceLoader.GetForCurrentView().GetString("VideoPanelMediaTypeDataA")}"; break;
case "V": mediaTypeQualityData += $" ({ResourceLoader.GetForCurrentView().GetString("VideoPanelMediaTypeDataV")})"; break;
default: mediaTypeQualityData += $" ({ResourceLoader.GetForCurrentView().GetString("VideoPanelMediaTypeDataAV")})"; break;
}
TextBlock qualityDataTextBlock = new TextBlock
{
Text = mediaTypeQualityData,
Margin = new Thickness(10, 0, 0, 0),
VerticalAlignment = VerticalAlignment.Center,
FontSize = textSize,
};
Grid.SetColumn(qualityDataTextBlock, 4);
Grid.SetRow(qualityDataTextBlock, 1);
metadataGrid.Children.Add(qualityDataTextBlock);
// Trim icon
Image trimIcon = new Image
{
Source = new BitmapImage { UriSource = new Uri($"ms-appx:///Assets/Icons/Universal/{(Application.Current.RequestedTheme == ApplicationTheme.Dark ? "Dark" : "Light")}/Trim.png") },
Margin = new Thickness(0, iconMargin, 0, iconMargin),
Width = iconSize,
};
Grid.SetColumn(trimIcon, 6);
Grid.SetRow(trimIcon, 1);
metadataGrid.Children.Add(trimIcon);
// Trim data textblock
TextBlock trimDataTextBlock = new TextBlock
{
Text = $"{TrimStart} - {TrimEnd}",
Margin = new Thickness(10, 0, 0, 0),
VerticalAlignment = VerticalAlignment.Center,
FontSize = textSize,
};
Grid.SetColumn(trimDataTextBlock, 7);
Grid.SetRow(trimDataTextBlock, 1);
metadataGrid.Children.Add(trimDataTextBlock);
// Path icon
Image pathIcon = new Image
{
Source = new BitmapImage { UriSource = new Uri($"ms-appx:///Assets/Icons/Universal/{(Application.Current.RequestedTheme == ApplicationTheme.Dark ? "Dark" : "Light")}/Path.png") },
Margin = new Thickness(0, iconMargin, 0, iconMargin),
Width = iconSize,
};
Grid.SetColumn(pathIcon, 0);
Grid.SetRow(pathIcon, 2);
metadataGrid.Children.Add(pathIcon);
// File path data textblock
TextBlock filePathDataTextBlock = new TextBlock
{
Text = FilePath,
Margin = new Thickness(10, 0, 0, 0),
VerticalAlignment = VerticalAlignment.Center,
FontSize = textSize,
};
Grid.SetColumn(filePathDataTextBlock, 1);
Grid.SetColumnSpan(filePathDataTextBlock, 7);
Grid.SetRow(filePathDataTextBlock, 2);
metadataGrid.Children.Add(filePathDataTextBlock);
// Progress grid
Grid progressGrid = new Grid
{
Margin = new Thickness(0, 10, 0, 0),
};
progressGrid.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto });
progressGrid.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto });
progressGrid.ColumnDefinitions.Add(new ColumnDefinition());
Grid.SetRow(progressGrid, 3);
Grid.SetColumnSpan(progressGrid, 8);
metadataGrid.Children.Add(progressGrid);
// Progress icon
ProgressIcon = new Image
{
Source = new BitmapImage { UriSource = new Uri($"ms-appx:///Assets/Icons/Universal/{(Application.Current.RequestedTheme == ApplicationTheme.Dark ? "Dark" : "Light")}/Idle.png") },
Width = iconSize,
};
Grid.SetColumn(ProgressIcon, 0);
progressGrid.Children.Add(ProgressIcon);
// Progress textblock
ProgressLabelTextblock = new TextBlock
{
Text = ResourceLoader.GetForCurrentView().GetString("VideoPanelProgressLabelIdle"),
Margin = new Thickness(10, 0, 20, 0),
VerticalAlignment = VerticalAlignment.Center,
FontSize = textSize
};
Grid.SetColumn(ProgressLabelTextblock, 1);
progressGrid.Children.Add(ProgressLabelTextblock);
// Progress bar
ProgressBar = new ProgressBar
{
Visibility = Visibility.Collapsed,
};
Grid.SetColumn(ProgressBar, 2);
progressGrid.Children.Add(ProgressBar);
// Buttons grid
Grid buttonsGrid = new Grid
{
VerticalAlignment = VerticalAlignment.Center,
};
buttonsGrid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });
buttonsGrid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });
buttonsGrid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });
Grid.SetColumn(buttonsGrid, 2);
baseGrid.Children.Add(buttonsGrid);
// Source button
AppBarButton sourceButton = new AppBarButton
{
Icon = new BitmapIcon { UriSource = SourceIcon, ShowAsMonochrome = false },
Width = 40,
Height = 48,
};
sourceButton.Click += SourceButtonClicked;
Grid.SetRow(sourceButton, 0);
buttonsGrid.Children.Add(sourceButton);
// Delete button
AppBarButton deleteButton = new AppBarButton
{
Icon = new SymbolIcon(Symbol.Clear),
Width = 40,
Height = 48,
};
deleteButton.Click += DeleteButtonClicked;
Grid.SetRow(deleteButton, 1);
buttonsGrid.Children.Add(deleteButton);
// Download/Stop button
StartStopButton = new AppBarButton
{
Icon = new SymbolIcon(Symbol.Download),
Width = 40,
Height = 48,
};
StartStopButton.Click += DownloadStopButtonClicked;
Grid.SetRow(StartStopButton, 2);
buttonsGrid.Children.Add(StartStopButton);
// Return panel
return baseGrid;
}
// SOURCE BUTTON
private async void SourceButtonClicked(object sender, RoutedEventArgs e)
{
// Launch the website
await Windows.System.Launcher.LaunchUriAsync(Url);
}
// DELETE BUTTON
private void DeleteButtonClicked(object sender, RoutedEventArgs e)
{
// Cancel if video downloading was started
if (VideoStatus == VideoStatus.InProgress || VideoStatus == VideoStatus.Waiting)
VideoTaskCancellationToken.Cancel();
// Remove video from the list
RemoveVideoFromList();
}
// DOWNLOAD STOP BUTTON
private async void DownloadStopButtonClicked(object sender, RoutedEventArgs e)
{
// Cancel if video downloading was started
if (VideoStatus == VideoStatus.InProgress || VideoStatus == VideoStatus.Waiting)
VideoTaskCancellationToken.Cancel();
// Start if video downloading wasn't started
else
await Start();
}
#endregion
}
}

View File

@@ -117,172 +117,43 @@
<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 name="MainPageNavigationPanelHomeItem.Content" xml:space="preserve">
<value>Home</value>
</data>
<data name="AddPlaylistBase.PrimaryButtonText" xml:space="preserve">
<value>Add</value>
<data name="MainPageNavigationPanelSourcesItem.Content" xml:space="preserve">
<value>Sources</value>
</data>
<data name="AddPlaylistBase.Title" xml:space="preserve">
<value>ADD PLAYLIST</value>
<data name="MainPageNavigationPanelSubscriptionsItem.Content" xml:space="preserve">
<value>Subscriptions</value>
</data>
<data name="AddPlaylistDeletedVideosPanelButton" xml:space="preserve">
<value>Restore</value>
<data name="SourcesMainPage.Title" xml:space="preserve">
<value>Sources</value>
</data>
<data name="AddPlaylistDeletedVideosPanelTextBlock" xml:space="preserve">
<value>{x} videos removed</value>
<data name="SourcesMainTwitchLoginButtonTextLoading" xml:space="preserve">
<value>Loading...</value>
</data>
<data name="AddPlaylistLocationSelectionApplyLocationButton.Content" xml:space="preserve">
<value>Apply</value>
<data name="SourcesMainTwitchLoginButtonTextLoggedIn" xml:space="preserve">
<value>Log out</value>
</data>
<data name="AddPlaylistLocationSelectionTextBlock.Text" xml:space="preserve">
<value>Location</value>
<data name="SourcesMainTwitchLoginButtonTextNotLoggedIn" xml:space="preserve">
<value>Sign in</value>
</data>
<data name="AddPlaylistNotFoundText" xml:space="preserve">
<value>Playlist not found. Try again.</value>
<data name="SourcesMainTwitchLoginErrorDialogDescriptionUnknown" xml:space="preserve">
<value>Unknown error</value>
</data>
<data name="AddPlaylistSearchButton.Content" xml:space="preserve">
<value>Search</value>
<data name="SourcesMainTwitchLoginErrorDialogTitle" xml:space="preserve">
<value>Login to Twitch failed</value>
</data>
<data name="AddPlaylistStartText.Text" xml:space="preserve">
<value>Paste URL and click "Search" button</value>
<data name="SourcesMainTwitchSettingControl.Title" xml:space="preserve">
<value>Twitch</value>
</data>
<data name="AddPlaylistUrlPathText.Text" xml:space="preserve">
<value>URL</value>
<data name="SourcesMainTwitchSettingControlDescriptionLoading" xml:space="preserve">
<value>Loading...</value>
</data>
<data name="AddPlaylistVideoDownloadOptionsMediaTypeRadiobuttonA" xml:space="preserve">
<value>Only audio</value>
<data name="SourcesMainTwitchSettingControlDescriptionLoggedIn" xml:space="preserve">
<value>Logged in as</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="AddVideoDownloadOptionsMediaTypeRadiobuttonA.Content" xml:space="preserve">
<value>Only audio</value>
</data>
<data name="AddVideoDownloadOptionsMediaTypeRadiobuttonAV.Content" xml:space="preserve">
<value>Normal</value>
</data>
<data name="AddVideoDownloadOptionsMediaTypeRadiobuttonV.Content" xml:space="preserve">
<value>Only video</value>
</data>
<data name="AddVideoDownloadOptionsMediaTypeTextBlock.Text" xml:space="preserve">
<value>Media type</value>
</data>
<data name="AddVideoDownloadOptionsQualityTextBlock.Text" xml:space="preserve">
<value>Quality</value>
</data>
<data name="AddVideoDownloadOptionsTrimTextBlock.Text" xml:space="preserve">
<value>Trim</value>
</data>
<data name="AddVideoFileDataTextBlock.Text" xml:space="preserve">
<value>File</value>
</data>
<data name="AddVideoLocationDataTextBlock.Text" xml:space="preserve">
<value>Location</value>
</data>
<data name="AddVideoNotFoundText.Text" xml:space="preserve">
<value>Video not found. Try again.</value>
</data>
<data name="AddVideoSearchButton.Content" xml:space="preserve">
<value>Search</value>
</data>
<data name="AddVideoStartText.Text" xml:space="preserve">
<value>Paste URL and click "Search" button</value>
</data>
<data name="AppBarAddPlaylistButton.Label" xml:space="preserve">
<value>Add playlist</value>
</data>
<data name="AppBarAddPlaylistButton.Width" xml:space="preserve">
<value>85</value>
</data>
<data name="AppBarAddVideoButton.Label" xml:space="preserve">
<value>Add video</value>
</data>
<data name="AppBarAddVideoButton.Width" xml:space="preserve">
<value>75</value>
</data>
<data name="AppBarDownloadAllButton.Label" xml:space="preserve">
<value>Download All</value>
</data>
<data name="AppBarDownloadAllButton.Width" xml:space="preserve">
<value>90</value>
</data>
<data name="AppBarSettingsButton.Label" xml:space="preserve">
<value>Settings</value>
</data>
<data name="AppBarSettingsButton.Width" xml:space="preserve">
<value>65</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 name="SourcesMainTwitchSettingControlDescriptionNotLoggedIn" xml:space="preserve">
<value>Not logged in. Twitch authentication is required to download videos.</value>
</data>
</root>

View File

@@ -0,0 +1,348 @@
<?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

@@ -119,52 +119,49 @@
<Compile Include="App.xaml.cs">
<DependentUpon>App.xaml</DependentUpon>
</Compile>
<Compile Include="Objects\Enums\PlaylistSource.cs" />
<Compile Include="Objects\Enums\VideoSource.cs" />
<Compile Include="Objects\Enums\VideoStatus.cs" />
<Compile Include="Services\Config.cs" />
<Compile Include="Services\Media.cs" />
<Compile Include="Services\Source.cs" />
<Compile Include="Services\Videos.cs" />
<Compile Include="Sources\PObject.cs" />
<Compile Include="Sources\Twitch\Channel.cs" />
<Compile Include="Sources\Twitch\Clip.cs" />
<Compile Include="Sources\Twitch\Vod.cs" />
<Compile Include="Sources\VObject.cs" />
<Compile Include="Views\AddPlaylist\AddPlaylistBase.xaml.cs">
<DependentUpon>AddPlaylistBase.xaml</DependentUpon>
<Compile Include="Core\Enums\AudioFileExtension.cs" />
<Compile Include="Core\Enums\LogMessageType.cs" />
<Compile Include="Core\Enums\MediaFileExtension.cs" />
<Compile Include="Core\Enums\MediaType.cs" />
<Compile Include="Core\Enums\StreamType.cs" />
<Compile Include="Core\Enums\VideoFileExtension.cs" />
<Compile Include="Core\Exceptions\TwitchAccessTokenNotFoundException.cs" />
<Compile Include="Core\Exceptions\TwitchAccessTokenNotValidException.cs" />
<Compile Include="Core\Globals\Assets.cs" />
<Compile Include="Core\Interfaces\IAStream.cs" />
<Compile Include="Core\Interfaces\IPlaylistService.cs" />
<Compile Include="Core\Interfaces\IVideoService.cs" />
<Compile Include="Core\Interfaces\IVStream.cs" />
<Compile Include="Core\Models\Stream.cs" />
<Compile Include="Core\Services\Config.cs" />
<Compile Include="Core\Services\Log.cs" />
<Compile Include="Core\Services\MediaProcessor.cs" />
<Compile Include="Core\Services\Sources\Twitch\Auth.cs" />
<Compile Include="Core\Services\Sources\Twitch\Channel.cs" />
<Compile Include="Core\Services\Sources\Twitch\Clip.cs" />
<Compile Include="Core\Services\Sources\Twitch\Vod.cs" />
<Compile Include="GUI\Controls\MainPageLayoutControl.xaml.cs">
<DependentUpon>MainPageLayoutControl.xaml</DependentUpon>
</Compile>
<Compile Include="Views\AddPlaylist\AddPlaylistLoading.xaml.cs">
<DependentUpon>AddPlaylistLoading.xaml</DependentUpon>
<Compile Include="GUI\Controls\SettingControl.xaml.cs">
<DependentUpon>SettingControl.xaml</DependentUpon>
</Compile>
<Compile Include="Views\AddPlaylist\AddPlaylistMain.xaml.cs">
<DependentUpon>AddPlaylistMain.xaml</DependentUpon>
<Compile Include="GUI\Views\Home\HomeMain.xaml.cs">
<DependentUpon>HomeMain.xaml</DependentUpon>
</Compile>
<Compile Include="Views\AddPlaylist\AddPlaylistNotFound.xaml.cs">
<DependentUpon>AddPlaylistNotFound.xaml</DependentUpon>
</Compile>
<Compile Include="Views\AddPlaylist\AddPlaylistStart.xaml.cs">
<DependentUpon>AddPlaylistStart.xaml</DependentUpon>
</Compile>
<Compile Include="Views\AddVideo\AddVideoBase.xaml.cs">
<DependentUpon>AddVideoBase.xaml</DependentUpon>
</Compile>
<Compile Include="Views\AddVideo\AddVideoLoading.xaml.cs">
<DependentUpon>AddVideoLoading.xaml</DependentUpon>
</Compile>
<Compile Include="Views\AddVideo\AddVideoMain.xaml.cs">
<DependentUpon>AddVideoMain.xaml</DependentUpon>
</Compile>
<Compile Include="Views\AddVideo\AddVideoNotFound.xaml.cs">
<DependentUpon>AddVideoNotFound.xaml</DependentUpon>
</Compile>
<Compile Include="Views\AddVideo\AddVideoStart.xaml.cs">
<DependentUpon>AddVideoStart.xaml</DependentUpon>
</Compile>
<Compile Include="Views\MainPage.xaml.cs">
<Compile Include="GUI\Views\MainPage.xaml.cs">
<DependentUpon>MainPage.xaml</DependentUpon>
</Compile>
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="GUI\Views\Sources\SourcesMain.xaml.cs">
<DependentUpon>SourcesMain.xaml</DependentUpon>
</Compile>
<Compile Include="GUI\Views\Settings\SettingsMain.xaml.cs">
<DependentUpon>SettingsMain.xaml</DependentUpon>
</Compile>
<Compile Include="GUI\Views\Subscriptions\SubscriptionsMain.xaml.cs">
<DependentUpon>SubscriptionsMain.xaml</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup>
<AppxManifest Include="Package.appxmanifest">
@@ -174,41 +171,43 @@
<ItemGroup>
<Content Include="Assets\Icons\Add\NotFound.png" />
<Content Include="Assets\Icons\Add\Start.png" />
<Content Include="Assets\Icons\Error.png" />
<Content Include="Assets\Icons\MainPage\Sources.png" />
<Content Include="Assets\Icons\Sources\Twitch.png" />
<Content Include="Assets\Icons\Sources\Unknown.png" />
<Content Include="Assets\Icons\Sources\Youtube.png" />
<Content Include="Assets\Icons\Universal\Dark\Author.png" />
<Content Include="Assets\Icons\Universal\Dark\Cancelled.png" />
<Content Include="Assets\Icons\Universal\Dark\Date.png" />
<Content Include="Assets\Icons\Universal\Dark\Done.png" />
<Content Include="Assets\Icons\Universal\Dark\Downloading.png" />
<Content Include="Assets\Icons\Universal\Dark\Duration.png" />
<Content Include="Assets\Icons\Universal\Dark\Error.png" />
<Content Include="Assets\Icons\Universal\Dark\Finalizing.png" />
<Content Include="Assets\Icons\Universal\Dark\Idle.png" />
<Content Include="Assets\Icons\Universal\Dark\MediaType.png" />
<Content Include="Assets\Icons\Universal\Dark\Path.png" />
<Content Include="Assets\Icons\Universal\Dark\Transcoding.png" />
<Content Include="Assets\Icons\Universal\Dark\Quality.png" />
<Content Include="Assets\Icons\Universal\Dark\Trim.png" />
<Content Include="Assets\Icons\Universal\Dark\Views.png" />
<Content Include="Assets\Icons\Universal\Dark\Waiting.png" />
<Content Include="Assets\Icons\Universal\Light\Author.png" />
<Content Include="Assets\Icons\Universal\Light\Cancelled.png" />
<Content Include="Assets\Icons\Universal\Light\Date.png" />
<Content Include="Assets\Icons\Universal\Light\Done.png" />
<Content Include="Assets\Icons\Universal\Light\Downloading.png" />
<Content Include="Assets\Icons\Universal\Light\Duration.png" />
<Content Include="Assets\Icons\Universal\Light\Error.png" />
<Content Include="Assets\Icons\Universal\Light\Finalizing.png" />
<Content Include="Assets\Icons\Universal\Light\Idle.png" />
<Content Include="Assets\Icons\Universal\Light\MediaType.png" />
<Content Include="Assets\Icons\Universal\Light\Path.png" />
<Content Include="Assets\Icons\Universal\Light\Transcoding.png" />
<Content Include="Assets\Icons\Universal\Light\Quality.png" />
<Content Include="Assets\Icons\Universal\Light\Trim.png" />
<Content Include="Assets\Icons\Universal\Light\Views.png" />
<Content Include="Assets\Icons\Universal\Light\Waiting.png" />
<Content Include="Assets\Icons\VideoMetadata\Dark\Author.png" />
<Content Include="Assets\Icons\VideoMetadata\Dark\Cancelled.png" />
<Content Include="Assets\Icons\VideoMetadata\Dark\Date.png" />
<Content Include="Assets\Icons\VideoMetadata\Dark\Done.png" />
<Content Include="Assets\Icons\VideoMetadata\Dark\Downloading.png" />
<Content Include="Assets\Icons\VideoMetadata\Dark\Duration.png" />
<Content Include="Assets\Icons\VideoMetadata\Dark\Error.png" />
<Content Include="Assets\Icons\VideoMetadata\Dark\Finalizing.png" />
<Content Include="Assets\Icons\VideoMetadata\Dark\Idle.png" />
<Content Include="Assets\Icons\VideoMetadata\Dark\MediaType.png" />
<Content Include="Assets\Icons\VideoMetadata\Dark\File.png" />
<Content Include="Assets\Icons\VideoMetadata\Dark\Transcoding.png" />
<Content Include="Assets\Icons\VideoMetadata\Dark\Quality.png" />
<Content Include="Assets\Icons\VideoMetadata\Dark\Trim.png" />
<Content Include="Assets\Icons\VideoMetadata\Dark\Views.png" />
<Content Include="Assets\Icons\VideoMetadata\Dark\Waiting.png" />
<Content Include="Assets\Icons\VideoMetadata\Light\Author.png" />
<Content Include="Assets\Icons\VideoMetadata\Light\Cancelled.png" />
<Content Include="Assets\Icons\VideoMetadata\Light\Date.png" />
<Content Include="Assets\Icons\VideoMetadata\Light\Done.png" />
<Content Include="Assets\Icons\VideoMetadata\Light\Downloading.png" />
<Content Include="Assets\Icons\VideoMetadata\Light\Duration.png" />
<Content Include="Assets\Icons\VideoMetadata\Light\Error.png" />
<Content Include="Assets\Icons\VideoMetadata\Light\Finalizing.png" />
<Content Include="Assets\Icons\VideoMetadata\Light\Idle.png" />
<Content Include="Assets\Icons\VideoMetadata\Light\MediaType.png" />
<Content Include="Assets\Icons\VideoMetadata\Light\File.png" />
<Content Include="Assets\Icons\VideoMetadata\Light\Transcoding.png" />
<Content Include="Assets\Icons\VideoMetadata\Light\Quality.png" />
<Content Include="Assets\Icons\VideoMetadata\Light\Trim.png" />
<Content Include="Assets\Icons\VideoMetadata\Light\Views.png" />
<Content Include="Assets\Icons\VideoMetadata\Light\Waiting.png" />
<Content Include="Assets\Logo\Logo.png" />
<Content Include="Assets\Logo\LargeTile.scale-100.png" />
<Content Include="Assets\Logo\LargeTile.scale-125.png" />
@@ -268,50 +267,34 @@
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</ApplicationDefinition>
<Page Include="Views\AddPlaylist\AddPlaylistBase.xaml">
<Page Include="GUI\Controls\MainPageLayoutControl.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Views\AddPlaylist\AddPlaylistLoading.xaml">
<Page Include="GUI\Controls\SettingControl.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Views\AddPlaylist\AddPlaylistMain.xaml">
<Page Include="GUI\Views\Home\HomeMain.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Views\AddPlaylist\AddPlaylistNotFound.xaml">
<Page Include="GUI\Views\MainPage.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Page>
<Page Include="GUI\Views\Sources\SourcesMain.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Views\AddPlaylist\AddPlaylistStart.xaml">
<Page Include="GUI\Views\Settings\SettingsMain.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Views\AddVideo\AddVideoBase.xaml">
<Page Include="GUI\Views\Subscriptions\SubscriptionsMain.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Views\AddVideo\AddVideoLoading.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Views\AddVideo\AddVideoMain.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Views\AddVideo\AddVideoNotFound.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Views\AddVideo\AddVideoStart.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Views\MainPage.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Page>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NETCore.UniversalWindowsPlatform">
@@ -321,16 +304,19 @@
<Version>7.1.2</Version>
</PackageReference>
<PackageReference Include="Microsoft.UI.Xaml">
<Version>2.7.0</Version>
<Version>2.8.0-prerelease.220118001</Version>
</PackageReference>
<PackageReference Include="Newtonsoft.Json">
<Version>13.0.1</Version>
</PackageReference>
</ItemGroup>
<ItemGroup>
<PRIResource Include="Strings\en-US\Resources.resw" />
<PRIResource Include="Strings\en-US\ResourcesOld.resw" />
</ItemGroup>
<ItemGroup />
<ItemGroup>
<PRIResource Include="Strings\en-US\Resources.resw" />
</ItemGroup>
<PropertyGroup Condition=" '$(VisualStudioVersion)' == '' or '$(VisualStudioVersion)' &lt; '14.0' ">
<VisualStudioVersion>14.0</VisualStudioVersion>
</PropertyGroup>

View File

@@ -1,39 +0,0 @@
<ContentDialog
x:Class="VDownload.Views.AddPlaylist.AddPlaylistBase"
x:Uid="AddPlaylistBase"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:VDownload.Views.AddPlaylist"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Title="ADD PLAYLIST"
PrimaryButtonText="Add"
CloseButtonText="Cancel"
PrimaryButtonClick="ContentDialog_PrimaryButtonClick"
IsPrimaryButtonEnabled="False">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="20"/>
<RowDefinition/>
</Grid.RowDefinitions>
<!-- SEARCH BAR -->
<Grid VerticalAlignment="Top" Width="498" Grid.Row="0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="10"/>
<ColumnDefinition/>
<ColumnDefinition Width="10"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock x:Name="AddPlaylistUrlPathText" x:Uid="AddPlaylistUrlPathText" HorizontalAlignment="Left" Text="URL" TextWrapping="Wrap" VerticalAlignment="Center" Grid.Column="0"/>
<TextBox x:Name="AddPlaylistUrlTextBox" HorizontalAlignment="Stretch" VerticalAlignment="Center" Grid.Column="2"/>
<Button x:Name="AddPlaylistSearchButton" x:Uid="AddPlaylistSearchButton" Content="Search" VerticalAlignment="Center" Grid.Column="4" HorizontalAlignment="Right" Click="AddPlaylistSearchButton_Click"/>
</Grid>
<!-- CONTENT FRAME -->
<Frame x:Name="AddPlaylistContent" Height="500" Grid.Row="2"/>
</Grid>
</ContentDialog>

View File

@@ -1,89 +0,0 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using VDownload.Sources;
using Windows.Foundation;
using Windows.Foundation.Collections;
using Windows.Storage;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
using Windows.UI.Xaml.Data;
using Windows.UI.Xaml.Input;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Navigation;
namespace VDownload.Views.AddPlaylist
{
public sealed partial class AddPlaylistBase : ContentDialog
{
public AddPlaylistBase()
{
this.InitializeComponent();
AddPlaylistContent.Navigate(typeof(AddPlaylistStart));
}
private void ContentDialog_PrimaryButtonClick(ContentDialog sender, ContentDialogButtonClickEventArgs args)
{
// Attach video info as content
Content = AddPlaylistMain.Playlist.VObjects.Keys.ToArray();
}
private async void AddPlaylistSearchButton_Click(object sender, RoutedEventArgs e)
{
// Navigate to loading page
AddPlaylistContent.Navigate(typeof(AddPlaylistLoading));
IsPrimaryButtonEnabled = false;
// Check url and get data
Uri url = null;
try
{
// Get url from textbox
url = new Uri(AddPlaylistUrlTextBox.Text);
}
catch
{
// Navigate to not found page if url or path is invalid
AddPlaylistContent.Navigate(typeof(AddPlaylistNotFound));
}
finally
{
if (url != null)
{
try
{
// Get videos list
PObject playlist = new PObject(url);
await playlist.GetVideos();
if (playlist.VObjects.Count > 0)
{
// Navigate to playlist main page
AddPlaylistContent.Navigate(typeof(AddPlaylistMain), playlist);
IsPrimaryButtonEnabled = true;
}
else
{
// Navigate to not found page if playlist is empty
AddPlaylistContent.Navigate(typeof(AddPlaylistNotFound));
}
}
catch
{
// Navigate to not found page if url or path is invalid
AddPlaylistContent.Navigate(typeof(AddPlaylistNotFound));
}
}
else
{
// Navigate to not found page if url or path is invalid
AddPlaylistContent.Navigate(typeof(AddPlaylistNotFound));
}
}
}
}
}

View File

@@ -1,13 +0,0 @@
// System
using Windows.UI.Xaml.Controls;
namespace VDownload.Views.AddPlaylist
{
public sealed partial class AddPlaylistLoading : Page
{
public AddPlaylistLoading()
{
this.InitializeComponent();
}
}
}

View File

@@ -1,41 +0,0 @@
<Page
x:Class="VDownload.Views.AddPlaylist.AddPlaylistMain"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:VDownload.Views.AddPlaylist"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
Height="500"
Width="498">
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="20"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<!-- VIDEO LIST -->
<ScrollViewer Grid.Row="0">
<StackPanel x:Name="AddPlaylistVideoPanel"/>
</ScrollViewer>
<!-- LOCATION SELECTION -->
<Grid Grid.Row="2">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="15"/>
<ColumnDefinition/>
<ColumnDefinition Width="10"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="15"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock x:Name="AddPlaylistLocationSelectionTextBlock" x:Uid="AddPlaylistLocationSelectionTextBlock" Text="Location" FontWeight="SemiBold" VerticalAlignment="Center" Grid.Column="0"/>
<TextBlock x:Name="AddPlaylistLocationSelectionLocationTextBlock" Text="//Location" FontSize="11" VerticalAlignment="Center" Grid.Column="2"/>
<Button x:Name="AddPlaylistLocationSelectionSelectLocationButton" Content="..." Click="AddPlaylistLocationSelectionSelectLocationButton_Click" Grid.Column="4"/>
<Button x:Name="AddPlaylistLocationSelectionApplyLocationButton" x:Uid="AddPlaylistLocationSelectionApplyLocationButton" Content="Apply" Click="AddPlaylistLocationSelectionApplyLocationButton_Click" Grid.Column="6"/>
</Grid>
</Grid>
</Page>

View File

@@ -1,110 +0,0 @@
using Microsoft.UI.Xaml.Controls;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using VDownload.Sources;
using Windows.Foundation;
using Windows.Foundation.Collections;
using Windows.Storage;
using Windows.Storage.AccessCache;
using Windows.Storage.Pickers;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
using Windows.UI.Xaml.Data;
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.AddPlaylist
{
/// <summary>
/// An empty page that can be used on its own or navigated to within a Frame.
/// </summary>
public sealed partial class AddPlaylistMain : Page
{
#region INIT
// PLAYLIST OBJECT
public static PObject Playlist;
private StorageFolder ApplyToAllLocation;
// CONSTRUCTOR
public AddPlaylistMain()
{
InitializeComponent();
}
#endregion
#region MAIN
// NAVIGATED TO THIS PAGE
protected override async void OnNavigatedTo(NavigationEventArgs e)
{
// Set ApplyToAllLocation
if (StorageApplicationPermissions.FutureAccessList.ContainsItem("save"))
{
ApplyToAllLocation = await StorageApplicationPermissions.FutureAccessList.GetFolderAsync("save");
AddPlaylistLocationSelectionLocationTextBlock.Text = ApplyToAllLocation.Path;
}
else
{
AddPlaylistLocationSelectionLocationTextBlock.Text = $@"{UserDataPaths.GetDefault().Downloads}\VDownload\";
}
// Get playlist object from parent
base.OnNavigatedTo(e);
Playlist = (PObject)e.Parameter;
Playlist.PlaylistPanel = AddPlaylistVideoPanel;
await Playlist.InitPlaylistPanel();
}
// SELECT LOCATION
private async void AddPlaylistLocationSelectionSelectLocationButton_Click(object sender, RoutedEventArgs e)
{
FolderPicker picker = new FolderPicker();
picker.SuggestedStartLocation = PickerLocationId.ComputerFolder;
picker.FileTypeFilter.Add("*");
StorageFolder folder = await picker.PickSingleFolderAsync();
if (folder != null)
{
try
{
await(await folder.CreateFileAsync("VDownloadLocationAccessTest")).DeleteAsync();
ApplyToAllLocation = folder;
AddPlaylistLocationSelectionLocationTextBlock.Text = ApplyToAllLocation.Path[ApplyToAllLocation.Path.Length - 1] == '\\' ? ApplyToAllLocation.Path : ApplyToAllLocation.Path + '\\';
}
catch { }
}
}
// APPLY LOCATION
private void AddPlaylistLocationSelectionApplyLocationButton_Click(object sender, RoutedEventArgs e)
{
foreach (KeyValuePair<VObject, TextBlock> v in Playlist.VObjects)
{
if (ApplyToAllLocation != null)
{
v.Key.CustomSaveLocation = ApplyToAllLocation;
v.Value.Text = v.Key.CustomSaveLocation.Path;
v.Key.FilePath = $@"{(v.Key.CustomSaveLocation.Path[v.Key.CustomSaveLocation.Path.Length - 1] == '\\' ? v.Key.CustomSaveLocation.Path : v.Key.CustomSaveLocation.Path + '\\')}{v.Key.Filename}.{v.Key.Extension.ToLower()}";
}
else
{
v.Key.CustomSaveLocation = null;
v.Value.Text = $@"{UserDataPaths.GetDefault().Downloads}\VDownload\";
v.Key.FilePath = $@"{UserDataPaths.GetDefault().Downloads}\VDownload\{v.Key.Filename}.{v.Key.Extension.ToLower()}";
}
}
}
#endregion
}
}

View File

@@ -1,22 +0,0 @@
<Page
x:Class="VDownload.Views.AddPlaylist.AddPlaylistNotFound"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:VDownload.Views.AddPlaylist"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
Height="500"
Width="498">
<!-- MESSAGE -->
<Grid HorizontalAlignment="Center" VerticalAlignment="Center">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Image x:Name="AddPlaylistNotFoundImage" HorizontalAlignment="Center" Height="100" VerticalAlignment="Center" Width="100" Source="/Assets/Icons/Add/NotFound.png" Grid.Row="0"/>
<TextBlock x:Name="AddPlaylistNotFoundText" x:Uid="AddPlaylistNotFoundText" HorizontalAlignment="Center" Text="Playlist not found. Try again." TextWrapping="Wrap" VerticalAlignment="Top" Foreground="#AAAAAA" Grid.Row="1"/>
</Grid>
</Page>

View File

@@ -1,13 +0,0 @@
// System
using Windows.UI.Xaml.Controls;
namespace VDownload.Views.AddPlaylist
{
public sealed partial class AddPlaylistNotFound : Page
{
public AddPlaylistNotFound()
{
this.InitializeComponent();
}
}
}

View File

@@ -1,22 +0,0 @@
<Page
x:Class="VDownload.Views.AddPlaylist.AddPlaylistStart"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:VDownload.Views.AddPlaylist"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
Height="500"
Width="498">
<!-- MESSAGE -->
<Grid HorizontalAlignment="Center" VerticalAlignment="Center">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Image x:Name="AddPlaylistStartImage" HorizontalAlignment="Center" Height="100" VerticalAlignment="Center" Width="100" Source="/Assets/Icons/Add/Start.png" Grid.Row="0"/>
<TextBlock x:Name="AddPlaylistStartText" x:Uid="AddPlaylistStartText" HorizontalAlignment="Center" Text="Paste URL and click &quot;Search&quot; button" TextWrapping="Wrap" VerticalAlignment="Top" Foreground="#AAAAAA" Grid.Row="1"/>
</Grid>
</Page>

View File

@@ -1,13 +0,0 @@
// System
using Windows.UI.Xaml.Controls;
namespace VDownload.Views.AddPlaylist
{
public sealed partial class AddPlaylistStart : Page
{
public AddPlaylistStart()
{
this.InitializeComponent();
}
}
}

View File

@@ -1,39 +0,0 @@
<ContentDialog
x:Class="VDownload.Views.AddVideo.AddVideoBase"
x:Uid="AddVideoBase"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:VDownload.Views.AddVideo"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Title="ADD VIDEO"
PrimaryButtonText="Add"
CloseButtonText="Cancel"
PrimaryButtonClick="ContentDialog_PrimaryButtonClick"
IsPrimaryButtonEnabled="False">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="40"/>
<RowDefinition/>
</Grid.RowDefinitions>
<!-- SEARCH BAR -->
<Grid VerticalAlignment="Top" Width="498" Grid.Row="0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="10"/>
<ColumnDefinition/>
<ColumnDefinition Width="10"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock x:Name="AddVideoUrlText" HorizontalAlignment="Left" Text="URL" TextWrapping="Wrap" VerticalAlignment="Center" Grid.Column="0"/>
<TextBox x:Name="AddVideoUrlTextBox" HorizontalAlignment="Stretch" VerticalAlignment="Center" Grid.Column="2"/>
<Button x:Name="AddVideoSearchButton" x:Uid="AddVideoSearchButton" Content="Search" VerticalAlignment="Center" Grid.Column="4" HorizontalAlignment="Right" Click="AddVideoSearchButton_Click"/>
</Grid>
<!-- CONTENT FRAME -->
<Frame x:Name="AddVideoContent" Height="420" Grid.Row="2" />
</Grid>
</ContentDialog>

View File

@@ -1,81 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using VDownload.Sources;
using Windows.Foundation;
using Windows.Foundation.Collections;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
using Windows.UI.Xaml.Data;
using Windows.UI.Xaml.Input;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Navigation;
// The Content Dialog item template is documented at https://go.microsoft.com/fwlink/?LinkId=234238
namespace VDownload.Views.AddVideo
{
public sealed partial class AddVideoBase : ContentDialog
{
public AddVideoBase()
{
this.InitializeComponent();
AddVideoContent.Navigate(typeof(AddVideoStart));
}
private void ContentDialog_PrimaryButtonClick(ContentDialog sender, ContentDialogButtonClickEventArgs args)
{
// Attach video info as content
Content = AddVideoMain.Video;
}
private async void AddVideoSearchButton_Click(object sender, RoutedEventArgs e)
{
// Navigate to loading page
AddVideoContent.Navigate(typeof(AddVideoLoading));
IsPrimaryButtonEnabled = false;
// Check url and get data
Uri url = null;
try
{
// Get url from textbox
url = new Uri(AddVideoUrlTextBox.Text);
}
catch
{
// Navigate to not found page if url is invalid
AddVideoContent.Navigate(typeof(AddVideoNotFound));
}
finally
{
if (url != null)
{
try
{
// Get video data
VObject video = new VObject(url);
await video.GetMetadata();
// Navigate to video main page
AddVideoContent.Navigate(typeof(AddVideoMain), video);
IsPrimaryButtonEnabled = true;
}
catch
{
// Navigate to not found page if url is invalid
AddVideoContent.Navigate(typeof(AddVideoNotFound));
}
}
else
{
// Navigate to not found page if url is invalid
AddVideoContent.Navigate(typeof(AddVideoNotFound));
}
}
}
}
}

View File

@@ -1,30 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using Windows.Foundation;
using Windows.Foundation.Collections;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
using Windows.UI.Xaml.Data;
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.AddVideo
{
/// <summary>
/// An empty page that can be used on its own or navigated to within a Frame.
/// </summary>
public sealed partial class AddVideoLoading : Page
{
public AddVideoLoading()
{
this.InitializeComponent();
}
}
}

View File

@@ -1,174 +0,0 @@
<Page
x:Class="VDownload.Views.AddVideo.AddVideoMain"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:VDownload.Views.AddVideo"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:extensions="using:Microsoft.Toolkit.Uwp.UI"
mc:Ignorable="d"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
Height="420"
Width="498">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="50"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="50"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<!-- VIDEO DATA -->
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="15"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<!-- Thumbnail -->
<Image x:Name="AddVideoVideoDataThumbnailImage" Width="190" Source="/Assets/Other/UnknownThumbnail.png" Grid.Column="0"/>
<Grid Grid.Column="2">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="10"/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid Grid.Row="0">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition Width="10"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<!-- Title -->
<TextBlock x:Name="AddVideoVideoDataTitleTextBlock" Text="//Title" FontSize="16" FontWeight="Bold" Grid.Column="0"/>
<!-- Source icon -->
<Image x:Name="AddVideoVideoDataSourceIconImage" Source="/Assets/Icons/Sources/Unknown.png" Height="16" Grid.Column="2"/>
</Grid>
<Grid Grid.Row="2">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="5"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<!-- Author -->
<Image x:Name="AddVideoVideoDataAuthorIconImage" Height="11" Source="/Assets/Icons/Universal/Light/Author.png" Grid.Column="0" Grid.Row="0"/>
<TextBlock x:Name="AddVideoVideoDataAuthorTextBlock" FontSize="10" Text="//Author" VerticalAlignment="Center" HorizontalAlignment="Left" Grid.Row="0" Grid.Column="2"/>
<!-- Views -->
<Image x:Name="AddVideoVideoDataViewsIconImage" Height="11" Source="/Assets/Icons/Universal/Light/Views.png" Grid.Column="0" Grid.Row="1"/>
<TextBlock x:Name="AddVideoVideoDataViewsTextBlock" FontSize="10" Text="//Views" VerticalAlignment="Center" HorizontalAlignment="Left" Grid.Row="1" Grid.Column="2"/>
<!-- Date -->
<Image x:Name="AddVideoVideoDataDateIconImage" Height="11" Source="/Assets/Icons/Universal/Light/Date.png" Grid.Column="0" Grid.Row="2"/>
<TextBlock x:Name="AddVideoVideoDataDateTextBlock" FontSize="10" Text="//Date" VerticalAlignment="Center" HorizontalAlignment="Left" Grid.Row="2" Grid.Column="2"/>
<!-- Duration -->
<Image x:Name="AddVideoVideoDataDurationIconImage" Height="11" Source="/Assets/Icons/Universal/Light/Duration.png" Grid.Column="0" Grid.Row="3"/>
<TextBlock x:Name="AddVideoVideoDataDurationTextBlock" FontSize="10" Text="//Duration" VerticalAlignment="Center" HorizontalAlignment="Left" Grid.Row="3" Grid.Column="2"/>
</Grid>
</Grid>
</Grid>
<!-- DOWNLOAD OPTIONS -->
<Grid Grid.Row="2">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="30"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<!-- Media type -->
<Grid Grid.Column="0">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="8"/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<TextBlock x:Name="AddVideoDownloadOptionsMediaTypeTextBlock" x:Uid="AddVideoDownloadOptionsMediaTypeTextBlock" Text="Media type" FontWeight="SemiBold" HorizontalAlignment="Center" Grid.Row="0"/>
<RadioButton x:Name="AddVideoDownloadOptionsMediaTypeRadiobuttonAV" x:Uid="AddVideoDownloadOptionsMediaTypeRadiobuttonAV" Content="Normal" Checked="AddVideoDownloadOptionsMediaTypeRadiobuttonAV_Checked" HorizontalAlignment="Left" VerticalAlignment="Top" Grid.Row="2"/>
<RadioButton x:Name="AddVideoDownloadOptionsMediaTypeRadiobuttonA" x:Uid="AddVideoDownloadOptionsMediaTypeRadiobuttonA" Content="Only audio" Checked="AddVideoDownloadOptionsMediaTypeRadiobuttonA_Checked" HorizontalAlignment="Left" VerticalAlignment="Center" Grid.Row="3"/>
<RadioButton x:Name="AddVideoDownloadOptionsMediaTypeRadiobuttonV" x:Uid="AddVideoDownloadOptionsMediaTypeRadiobuttonV" Content="Only video" Checked="AddVideoDownloadOptionsMediaTypeRadiobuttonV_Checked" HorizontalAlignment="Left" VerticalAlignment="Bottom" Grid.Row="4"/>
</Grid>
<AppBarSeparator VerticalAlignment="Stretch" HorizontalAlignment="Center" Grid.Column="1"/>
<Grid Grid.Column="2">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="20"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<!-- Quality -->
<Grid Grid.Row="0">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="8"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBlock x:Name="AddVideoDownloadOptionsQualityTextBlock" x:Uid="AddVideoDownloadOptionsQualityTextBlock" Text="Quality" FontWeight="SemiBold" HorizontalAlignment="Center" Grid.Row="0"/>
<ComboBox x:Name="AddVideoDownloadOptionsQualityComboBox" SelectionChanged="AddVideoDownloadOptionsQualityComboBox_SelectionChanged" HorizontalAlignment="Stretch" Grid.Row="2"/>
</Grid>
<!-- Trim -->
<Grid Grid.Row="2">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="8"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBlock x:Name="AddVideoDownloadOptionsTrimTextBlock" x:Uid="AddVideoDownloadOptionsTrimTextBlock" Text="Trim" FontWeight="SemiBold" HorizontalAlignment="Center" Grid.Row="0"/>
<Grid Grid.Row="2">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition Width="8"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="8"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<TextBox x:Name="AddVideoDownloadOptionsTrimStartTextBox" extensions:TextBoxExtensions.CustomMask="5:[0-5]" extensions:TextBoxExtensions.Mask="99:59:59" TextChanged="AddVideoDownloadOptionsTrimStartTextBox_TextChanged" Grid.Column="0"/>
<TextBlock Text="-" VerticalAlignment="Center" HorizontalAlignment="Center" Grid.Column="2"/>
<TextBox x:Name="AddVideoDownloadOptionsTrimEndTextBox" extensions:TextBoxExtensions.CustomMask="5:[0-5]" extensions:TextBoxExtensions.Mask="99:59:59" TextChanged="AddVideoDownloadOptionsTrimEndTextBox_TextChanged" Grid.Column="4"/>
</Grid>
</Grid>
</Grid>
</Grid>
<!-- FILE & LOCATION -->
<Grid Grid.Row="4">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="10"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="15"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<!-- File -->
<TextBlock x:Name="AddVideoFileDataTextBlock" x:Uid="AddVideoFileDataTextBlock" Text="File" FontWeight="SemiBold" VerticalAlignment="Center" Grid.Row="0" Grid.Column="0"/>
<Grid VerticalAlignment="Center" Grid.Row="0" Grid.Column="2">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition Width="15"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBox x:Name="AddVideoFileDataFilenameTextBox" TextChanged="AddVideoFileDataFilenameTextBox_TextChanged" Grid.Column="0"/>
<ComboBox x:Name="AddVideoFileDataExtensionComboBox" SelectionChanged="AddVideoFileDataExtensionComboBox_SelectionChanged" Width="80" Grid.Column="2"/>
</Grid>
<!-- Location -->
<TextBlock x:Name="AddVideoLocationDataTextBlock" x:Uid="AddVideoLocationDataTextBlock" Text="Location" FontWeight="SemiBold" VerticalAlignment="Center" Grid.Row="2" Grid.Column="0"/>
<Grid VerticalAlignment="Center" Grid.Row="2" Grid.Column="2">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition Width="10"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock x:Name="AddVideoLocationDataLocationTextBlock" Text="//Location" FontSize="11" VerticalAlignment="Center" Grid.Column="0"/>
<Button x:Name="AddVideoLocationDataChooseLocationButton" Content="..." Click="AddVideoLocationDataChooseLocationButton_Click" Grid.Column="2"/>
</Grid>
</Grid>
</Grid>
</Page>

View File

@@ -1,272 +0,0 @@
using Microsoft.UI.Xaml.Controls;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using VDownload.Services;
using VDownload.Sources;
using Windows.ApplicationModel.Resources;
using Windows.Foundation;
using Windows.Foundation.Collections;
using Windows.Storage;
using Windows.Storage.AccessCache;
using Windows.Storage.Pickers;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
using Windows.UI.Xaml.Data;
using Windows.UI.Xaml.Input;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Media.Imaging;
using Windows.UI.Xaml.Navigation;
// The Blank Page item template is documented at https://go.microsoft.com/fwlink/?LinkId=234238
namespace VDownload.Views.AddVideo
{
/// <summary>
/// An empty page that can be used on its own or navigated to within a Frame.
/// </summary>
public sealed partial class AddVideoMain : Page
{
#region INIT
// VIDEO OBJECT
public static VObject Video;
// CONSTRUCTOR
public AddVideoMain()
{
InitializeComponent();
}
#endregion
#region MAIN
// NAVIGATED TO THIS PAGE
protected override async void OnNavigatedTo(NavigationEventArgs e)
{
// Get video object from parent
base.OnNavigatedTo(e);
Video = (VObject)e.Parameter;
// Set icons theme
AddVideoVideoDataAuthorIconImage.Source = new BitmapImage { UriSource = new Uri($"ms-appx:///Assets/Icons/Universal/{(Application.Current.RequestedTheme == ApplicationTheme.Dark ? "Dark" : "Light")}/Author.png") };
AddVideoVideoDataViewsIconImage.Source = new BitmapImage { UriSource = new Uri($"ms-appx:///Assets/Icons/Universal/{(Application.Current.RequestedTheme == ApplicationTheme.Dark ? "Dark" : "Light")}/Views.png") };
AddVideoVideoDataDateIconImage.Source = new BitmapImage { UriSource = new Uri($"ms-appx:///Assets/Icons/Universal/{(Application.Current.RequestedTheme == ApplicationTheme.Dark ? "Dark" : "Light")}/Date.png") };
AddVideoVideoDataDurationIconImage.Source = new BitmapImage { UriSource = new Uri($"ms-appx:///Assets/Icons/Universal/{(Application.Current.RequestedTheme == ApplicationTheme.Dark ? "Dark" : "Light")}/Duration.png") };
// Set source image
AddVideoVideoDataSourceIconImage.Source = new BitmapImage(Video.SourceIcon);
// Set title
AddVideoVideoDataTitleTextBlock.Text = Video.Title;
// Set thumbnail
try
{
AddVideoVideoDataThumbnailImage.Source = new BitmapImage(Video.Thumbnail);
}
catch { }
// Set metadata
AddVideoVideoDataAuthorTextBlock.Text = Video.Author;
AddVideoVideoDataViewsTextBlock.Text = Video.Views.ToString();
AddVideoVideoDataDateTextBlock.Text = Video.Date.ToString((string)Config.GetValue("date_format"));
AddVideoVideoDataDurationTextBlock.Text = Video.Duration.ToString();
// Set items in quality combobox
foreach (string q in Video.Streams.Keys)
{
AddVideoDownloadOptionsQualityComboBox.Items.Add(q);
}
// Set items in extension combobox
foreach (string x in Video.MediaType == "A" ? Config.DefaultAudioExtensionList : Config.DefaultVideoExtensionList)
{
AddVideoFileDataExtensionComboBox.Items.Add(x);
}
// Set quality option
if (Video.MediaType == "A")
{
AddVideoDownloadOptionsQualityComboBox.SelectedValue = ResourceLoader.GetForCurrentView().GetString("AddVideoQualityNoVideoStream");
AddVideoDownloadOptionsQualityComboBox.IsEnabled = false;
}
else
{
AddVideoDownloadOptionsQualityComboBox.SelectedValue = Video.SelectedQuality;
AddVideoDownloadOptionsQualityComboBox.IsEnabled = true;
}
// Set trim options
AddVideoDownloadOptionsTrimStartTextBox.Text = Video.TrimStart.ToString();
AddVideoDownloadOptionsTrimEndTextBox.Text = Video.TrimEnd.ToString();
// Set media type option
switch (Video.MediaType)
{
case "AV": AddVideoDownloadOptionsMediaTypeRadiobuttonAV.IsChecked = true; break;
case "V": AddVideoDownloadOptionsMediaTypeRadiobuttonV.IsChecked = true; break;
case "A": AddVideoDownloadOptionsMediaTypeRadiobuttonA.IsChecked = true; break;
default: AddVideoDownloadOptionsMediaTypeRadiobuttonAV.IsChecked = true; break;
}
// Set filename option
AddVideoFileDataFilenameTextBox.Text = Video.Filename;
// Set extension option
AddVideoFileDataExtensionComboBox.SelectedItem = Video.Extension;
// Set location option
if (StorageApplicationPermissions.FutureAccessList.ContainsItem("save"))
{
Video.CustomSaveLocation = await StorageApplicationPermissions.FutureAccessList.GetFolderAsync("save");
AddVideoLocationDataLocationTextBlock.Text = Video.CustomSaveLocation.Path;
Video.FilePath = $@"{(Video.CustomSaveLocation.Path[Video.CustomSaveLocation.Path.Length - 1] == '\\' ? Video.CustomSaveLocation.Path : Video.CustomSaveLocation.Path + '\\')}{Video.Filename}.{Video.Extension.ToLower()}";
}
else
{
AddVideoLocationDataLocationTextBlock.Text = $@"{UserDataPaths.GetDefault().Downloads}\VDownload\";
Video.FilePath = $@"{UserDataPaths.GetDefault().Downloads}\VDownload\{Video.Filename}.{Video.Extension.ToLower()}";
}
}
#endregion
#region VALUE CHANGED HANDLERS
// MEDIA TYPE RADIOBUTTON (AV)
private void AddVideoDownloadOptionsMediaTypeRadiobuttonAV_Checked(object sender, RoutedEventArgs e)
{
Video.MediaType = "AV";
AddVideoDownloadOptionsQualityComboBox.IsEnabled = true;
AddVideoDownloadOptionsQualityComboBox.SelectedValue = Video.Streams.Keys.ToArray()[0];
AddVideoFileDataExtensionComboBox.Items.Clear();
foreach (string x in Config.DefaultVideoExtensionList)
{
AddVideoFileDataExtensionComboBox.Items.Add(x);
}
AddVideoFileDataExtensionComboBox.SelectedItem = Config.GetValue("default_video_extension");
}
// MEDIA TYPE RADIOBUTTON (A)
private void AddVideoDownloadOptionsMediaTypeRadiobuttonA_Checked(object sender, RoutedEventArgs e)
{
Video.MediaType = "A";
AddVideoDownloadOptionsQualityComboBox.IsEnabled = false;
AddVideoDownloadOptionsQualityComboBox.SelectedValue = ResourceLoader.GetForCurrentView().GetString("AddVideoQualityNoVideoStream");
AddVideoFileDataExtensionComboBox.Items.Clear();
foreach (string x in Config.DefaultAudioExtensionList)
{
AddVideoFileDataExtensionComboBox.Items.Add(x);
}
AddVideoFileDataExtensionComboBox.SelectedItem = Config.GetValue("default_audio_extension");
}
// MEDIA TYPE RADIOBUTTON (V)
private void AddVideoDownloadOptionsMediaTypeRadiobuttonV_Checked(object sender, RoutedEventArgs e)
{
Video.MediaType = "V";
AddVideoDownloadOptionsQualityComboBox.IsEnabled = true;
AddVideoDownloadOptionsQualityComboBox.SelectedValue = Video.Streams.Keys.ToArray()[0];
AddVideoFileDataExtensionComboBox.Items.Clear();
foreach (string x in Config.DefaultVideoExtensionList)
{
AddVideoFileDataExtensionComboBox.Items.Add(x);
}
AddVideoFileDataExtensionComboBox.SelectedItem = Config.GetValue("default_video_extension");
}
// QUALITY COMBOBOX
private void AddVideoDownloadOptionsQualityComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (Video.MediaType == "A")
Video.SelectedQuality = Video.Streams.Keys.ToArray()[0];
else
Video.SelectedQuality = (string)AddVideoDownloadOptionsQualityComboBox.SelectedItem;
}
// TRIM START TEXTBOX
private void AddVideoDownloadOptionsTrimStartTextBox_TextChanged(object sender, TextChangedEventArgs e)
{
try
{
Video.TrimStart = TimeSpan.Parse(AddVideoDownloadOptionsTrimStartTextBox.Text);
}
catch { }
}
// TRIM END TEXTBOX
private void AddVideoDownloadOptionsTrimEndTextBox_TextChanged(object sender, TextChangedEventArgs e)
{
try
{
Video.TrimEnd = TimeSpan.Parse(AddVideoDownloadOptionsTrimEndTextBox.Text);
}
catch { }
}
// FILENAME TEXTBOX
private void AddVideoFileDataFilenameTextBox_TextChanged(object sender, TextChangedEventArgs e)
{
foreach (char c in Path.GetInvalidFileNameChars())
{
AddVideoFileDataFilenameTextBox.Text = AddVideoFileDataFilenameTextBox.Text.Replace(c, ' ');
}
Video.Filename = AddVideoFileDataFilenameTextBox.Text;
if (Video.CustomSaveLocation != null)
Video.FilePath = $@"{(Video.CustomSaveLocation.Path[Video.CustomSaveLocation.Path.Length - 1] == '\\' ? Video.CustomSaveLocation.Path : Video.CustomSaveLocation.Path + '\\')}{Video.Filename}.{Video.Extension.ToLower()}";
else
Video.FilePath = $@"{UserDataPaths.GetDefault().Downloads}\VDownload\{Video.Filename}.{Video.Extension.ToLower()}";
}
// EXTENSION COMBOBOX
private void AddVideoFileDataExtensionComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (!(AddVideoFileDataExtensionComboBox.SelectedItem == null))
{
Video.Extension = (string)AddVideoFileDataExtensionComboBox.SelectedItem;
if (Video.CustomSaveLocation != null)
Video.FilePath = $@"{(Video.CustomSaveLocation.Path[Video.CustomSaveLocation.Path.Length - 1] == '\\' ? Video.CustomSaveLocation.Path : Video.CustomSaveLocation.Path + '\\')}{Video.Filename}.{Video.Extension.ToLower()}";
else
Video.FilePath = $@"{UserDataPaths.GetDefault().Downloads}\VDownload\{Video.Filename}.{Video.Extension.ToLower()}";
}
}
#endregion
#region BUTTON CLICK HANDLERS
// CHOOSE LOCATION BUTTON
private async void AddVideoLocationDataChooseLocationButton_Click(object sender, RoutedEventArgs e)
{
FolderPicker picker = new FolderPicker();
picker.SuggestedStartLocation = PickerLocationId.ComputerFolder;
picker.FileTypeFilter.Add("*");
StorageFolder folder = await picker.PickSingleFolderAsync();
if (folder != null)
{
try
{
await (await folder.CreateFileAsync("VDownloadLocationAccessTest")).DeleteAsync();
Video.CustomSaveLocation = folder;
AddVideoLocationDataLocationTextBlock.Text = Video.CustomSaveLocation.Path[Video.CustomSaveLocation.Path.Length - 1] == '\\' ? Video.CustomSaveLocation.Path : Video.CustomSaveLocation.Path + '\\';
Video.FilePath = $@"{(Video.CustomSaveLocation.Path[Video.CustomSaveLocation.Path.Length - 1] == '\\' ? Video.CustomSaveLocation.Path : Video.CustomSaveLocation.Path + '\\')}{Video.Filename}.{Video.Extension.ToLower()}";
}
catch { }
}
}
#endregion
}
}

View File

@@ -1,22 +0,0 @@
<Page
x:Class="VDownload.Views.AddVideo.AddVideoNotFound"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:VDownload.Views.AddVideo"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
Height="420"
Width="498">
<!-- MESSAGE -->
<Grid HorizontalAlignment="Center" VerticalAlignment="Center">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Image x:Name="AddVideoNotFoundImage" HorizontalAlignment="Center" Height="100" VerticalAlignment="Center" Width="100" Source="/Assets/Icons/Add/NotFound.png" Grid.Row="0"/>
<TextBlock x:Name="AddVideoNotFoundText" x:Uid="AddVideoNotFoundText" HorizontalAlignment="Center" Text="Video not found. Try again." TextWrapping="Wrap" VerticalAlignment="Top" Foreground="#AAAAAA" Grid.Row="1"/>
</Grid>
</Page>

Some files were not shown because too many files have changed in this diff Show More