1.0-dev4 (New GUI and Twitch API change to Helix)
@@ -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
|
||||
|
||||
BIN
VDownload/Assets/Icons/Error.png
Normal file
|
After Width: | Height: | Size: 4.4 KiB |
BIN
VDownload/Assets/Icons/MainPage/Sources.png
Normal file
|
After Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 43 KiB After Width: | Height: | Size: 43 KiB |
|
Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 54 KiB |
|
Before Width: | Height: | Size: 9.8 KiB After Width: | Height: | Size: 9.8 KiB |
|
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 48 KiB |
|
Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 54 KiB |
|
Before Width: | Height: | Size: 56 KiB After Width: | Height: | Size: 56 KiB |
|
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 49 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 46 KiB |
|
Before Width: | Height: | Size: 57 KiB After Width: | Height: | Size: 57 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 9.7 KiB After Width: | Height: | Size: 9.7 KiB |
|
Before Width: | Height: | Size: 60 KiB After Width: | Height: | Size: 60 KiB |
|
Before Width: | Height: | Size: 39 KiB After Width: | Height: | Size: 39 KiB |
|
Before Width: | Height: | Size: 51 KiB After Width: | Height: | Size: 51 KiB |
|
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 49 KiB |
|
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 38 KiB |
|
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 48 KiB |
|
Before Width: | Height: | Size: 9.6 KiB After Width: | Height: | Size: 9.6 KiB |
|
Before Width: | Height: | Size: 43 KiB After Width: | Height: | Size: 43 KiB |
|
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 47 KiB |
|
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 49 KiB |
|
Before Width: | Height: | Size: 43 KiB After Width: | Height: | Size: 43 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 39 KiB After Width: | Height: | Size: 39 KiB |
|
Before Width: | Height: | Size: 50 KiB After Width: | Height: | Size: 50 KiB |
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 9.6 KiB After Width: | Height: | Size: 9.6 KiB |
|
Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 52 KiB |
|
Before Width: | Height: | Size: 35 KiB After Width: | Height: | Size: 35 KiB |
|
Before Width: | Height: | Size: 45 KiB After Width: | Height: | Size: 45 KiB |
|
Before Width: | Height: | Size: 43 KiB After Width: | Height: | Size: 43 KiB |
12
VDownload/Core/Enums/AudioFileExtension.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
namespace VDownload.Core.Enums
|
||||
{
|
||||
public enum AudioFileExtension
|
||||
{
|
||||
MP3 = 4,
|
||||
FLAC = 5,
|
||||
WAV = 6,
|
||||
M4A = 7,
|
||||
ALAC = 8,
|
||||
WMA = 9,
|
||||
}
|
||||
}
|
||||
9
VDownload/Core/Enums/LogMessageType.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
namespace VDownload.Core.Enums
|
||||
{
|
||||
public enum LogMessageType
|
||||
{
|
||||
Header,
|
||||
Normal,
|
||||
Break,
|
||||
}
|
||||
}
|
||||
15
VDownload/Core/Enums/MediaFileExtension.cs
Normal 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,
|
||||
}
|
||||
}
|
||||
9
VDownload/Core/Enums/MediaType.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
namespace VDownload.Core.Enums
|
||||
{
|
||||
public enum MediaType
|
||||
{
|
||||
AudioVideo,
|
||||
OnlyAudio,
|
||||
OnlyVideo,
|
||||
}
|
||||
}
|
||||
9
VDownload/Core/Enums/StreamType.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
namespace VDownload.Core.Enums
|
||||
{
|
||||
public enum StreamType
|
||||
{
|
||||
AudioVideo,
|
||||
OnlyAudio,
|
||||
OnlyVideo,
|
||||
}
|
||||
}
|
||||
9
VDownload/Core/Enums/VideoFileExtension.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
namespace VDownload.Core.Enums
|
||||
{
|
||||
public enum VideoFileExtension
|
||||
{
|
||||
MP4 = 1,
|
||||
WMV = 2,
|
||||
HEVC = 3,
|
||||
}
|
||||
}
|
||||
@@ -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) { }
|
||||
}
|
||||
}
|
||||
@@ -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) { }
|
||||
}
|
||||
}
|
||||
13
VDownload/Core/Globals/Assets.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace VDownload.Core.Globals
|
||||
{
|
||||
public static class Assets
|
||||
{
|
||||
public static readonly Uri UnknownThumbnailImage = new Uri("ms-appx:///Assets/Other/UnknownThumbnail.png");
|
||||
}
|
||||
}
|
||||
22
VDownload/Core/Interfaces/IAStream.cs
Normal 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
|
||||
}
|
||||
}
|
||||
28
VDownload/Core/Interfaces/IPlaylistService.cs
Normal 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
|
||||
}
|
||||
}
|
||||
20
VDownload/Core/Interfaces/IVStream.cs
Normal 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
|
||||
}
|
||||
}
|
||||
65
VDownload/Core/Interfaces/IVideoService.cs
Normal 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
|
||||
}
|
||||
}
|
||||
36
VDownload/Core/Models/Stream.cs
Normal 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
|
||||
}
|
||||
}
|
||||
72
VDownload/Core/Services/Config.cs
Normal 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
|
||||
}
|
||||
}
|
||||
35
VDownload/Core/Services/Log.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
177
VDownload/Core/Services/MediaProcessor.cs
Normal 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
|
||||
}
|
||||
}
|
||||
122
VDownload/Core/Services/Sources/Twitch/Auth.cs
Normal 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
|
||||
}
|
||||
}
|
||||
126
VDownload/Core/Services/Sources/Twitch/Channel.cs
Normal 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
|
||||
}
|
||||
}
|
||||
189
VDownload/Core/Services/Sources/Twitch/Clip.cs
Normal 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
|
||||
}
|
||||
}
|
||||
329
VDownload/Core/Services/Sources/Twitch/Vod.cs
Normal 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
|
||||
}
|
||||
}
|
||||
21
VDownload/GUI/Controls/MainPageLayoutControl.xaml
Normal 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>
|
||||
39
VDownload/GUI/Controls/MainPageLayoutControl.xaml.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
48
VDownload/GUI/Controls/SettingControl.xaml
Normal 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>
|
||||
65
VDownload/GUI/Controls/SettingControl.xaml.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
46
VDownload/GUI/Views/Home/HomeMain.xaml.cs
Normal 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);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
56
VDownload/GUI/Views/MainPage.xaml
Normal 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>
|
||||
60
VDownload/GUI/Views/MainPage.xaml.cs
Normal 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
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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();
|
||||
}
|
||||
31
VDownload/GUI/Views/Sources/SourcesMain.xaml
Normal 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>
|
||||
191
VDownload/GUI/Views/Sources/SourcesMain.xaml.cs
Normal 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
|
||||
}
|
||||
}
|
||||
14
VDownload/GUI/Views/Subscriptions/SubscriptionsMain.xaml
Normal 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>
|
||||
@@ -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();
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
namespace VDownload.Objects.Enums
|
||||
{
|
||||
public enum PlaylistSource
|
||||
{
|
||||
TwitchChannel,
|
||||
LocalFile,
|
||||
Null,
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
namespace VDownload.Objects.Enums
|
||||
{
|
||||
public enum VideoSource
|
||||
{
|
||||
TwitchVod,
|
||||
TwitchClip,
|
||||
YoutubeVideo,
|
||||
Null
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
namespace VDownload.Objects.Enums
|
||||
{
|
||||
public enum VideoStatus
|
||||
{
|
||||
Idle,
|
||||
Waiting,
|
||||
InProgress,
|
||||
Removed
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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, "");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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 & 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>
|
||||
348
VDownload/Strings/en-US/ResourcesOld.resw
Normal 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 & 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>
|
||||
@@ -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)' < '14.0' ">
|
||||
<VisualStudioVersion>14.0</VisualStudioVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
@@ -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>
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
// System
|
||||
using Windows.UI.Xaml.Controls;
|
||||
|
||||
namespace VDownload.Views.AddPlaylist
|
||||
{
|
||||
public sealed partial class AddPlaylistLoading : Page
|
||||
{
|
||||
public AddPlaylistLoading()
|
||||
{
|
||||
this.InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -1,13 +0,0 @@
|
||||
// System
|
||||
using Windows.UI.Xaml.Controls;
|
||||
|
||||
namespace VDownload.Views.AddPlaylist
|
||||
{
|
||||
public sealed partial class AddPlaylistNotFound : Page
|
||||
{
|
||||
public AddPlaylistNotFound()
|
||||
{
|
||||
this.InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 "Search" button" TextWrapping="Wrap" VerticalAlignment="Top" Foreground="#AAAAAA" Grid.Row="1"/>
|
||||
</Grid>
|
||||
</Page>
|
||||
@@ -1,13 +0,0 @@
|
||||
// System
|
||||
using Windows.UI.Xaml.Controls;
|
||||
|
||||
namespace VDownload.Views.AddPlaylist
|
||||
{
|
||||
public sealed partial class AddPlaylistStart : Page
|
||||
{
|
||||
public AddPlaylistStart()
|
||||
{
|
||||
this.InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||