1.0-dev4 (New GUI and Twitch API change to Helix)
@@ -1,5 +1,5 @@
|
|||||||
// Internal
|
// Internal
|
||||||
using VDownload.Services;
|
using VDownload.Core.Services;
|
||||||
|
|
||||||
// System
|
// System
|
||||||
using System;
|
using System;
|
||||||
@@ -11,6 +11,7 @@ using Windows.Storage;
|
|||||||
using Windows.UI.Xaml;
|
using Windows.UI.Xaml;
|
||||||
using Windows.UI.Xaml.Controls;
|
using Windows.UI.Xaml.Controls;
|
||||||
using Windows.UI.Xaml.Navigation;
|
using Windows.UI.Xaml.Navigation;
|
||||||
|
using System.Diagnostics;
|
||||||
|
|
||||||
namespace VDownload
|
namespace VDownload
|
||||||
{
|
{
|
||||||
@@ -24,16 +25,30 @@ namespace VDownload
|
|||||||
|
|
||||||
protected override async void OnLaunched(LaunchActivatedEventArgs e)
|
protected override async void OnLaunched(LaunchActivatedEventArgs e)
|
||||||
{
|
{
|
||||||
|
Log.AddHeader("APP LAUNCHED");
|
||||||
|
Log.Break();
|
||||||
|
|
||||||
// Rebuild configuration file
|
// Rebuild configuration file
|
||||||
|
Log.AddHeader("REBUILDING CONFIGURATION FILE");
|
||||||
|
|
||||||
Config.Rebuild();
|
Config.Rebuild();
|
||||||
|
|
||||||
|
Log.Add("Configuration file rebuilded successfully");
|
||||||
|
Log.Break();
|
||||||
|
|
||||||
|
|
||||||
// Delete temp on start
|
// 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();
|
IReadOnlyList<IStorageItem> tempItems = await ApplicationData.Current.TemporaryFolder.GetItemsAsync();
|
||||||
List<Task> tasks = new List<Task>();
|
List<Task> tasks = new List<Task>();
|
||||||
foreach (IStorageItem item in tempItems) tasks.Add(item.DeleteAsync().AsTask());
|
foreach (IStorageItem item in tempItems) tasks.Add(item.DeleteAsync().AsTask());
|
||||||
await Task.WhenAll(tasks);
|
await Task.WhenAll(tasks);
|
||||||
|
Log.Add("Temporary files deleted successfully");
|
||||||
|
Log.Break();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Do not repeat app initialization when the Window already has content,
|
// 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,
|
// When the navigation stack isn't restored navigate to the first page,
|
||||||
// configuring the new page by passing required information as a navigation
|
// configuring the new page by passing required information as a navigation
|
||||||
// parameter
|
// parameter
|
||||||
rootFrame.Navigate(typeof(MainPage), e.Arguments);
|
rootFrame.Navigate(typeof(GUI.Views.MainPage), e.Arguments);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure the current window is active
|
// 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
|
<Page
|
||||||
x:Class="VDownload.Views.AddVideo.AddVideoLoading"
|
x:Class="VDownload.GUI.Views.Home.HomeMain"
|
||||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
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:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:muxc="using:Microsoft.UI.Xaml.Controls"
|
||||||
mc:Ignorable="d"
|
mc:Ignorable="d"
|
||||||
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
|
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
|
||||||
Height="420"
|
|
||||||
Width="498">
|
|
||||||
|
|
||||||
<!-- PROGRESS RING -->
|
|
||||||
<Grid>
|
<Grid>
|
||||||
<ProgressRing HorizontalAlignment="Center" VerticalAlignment="Center" IsActive="True" Width="50" Height="50"/>
|
<Button Content="aaaaa" Click="Button_Click"/>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Page>
|
</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
|
<Page
|
||||||
x:Class="VDownload.Views.AddPlaylist.AddPlaylistLoading"
|
x:Class="VDownload.GUI.Views.Settings.SettingsMain"
|
||||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
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:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
mc:Ignorable="d"
|
mc:Ignorable="d"
|
||||||
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
|
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
|
||||||
Height="500"
|
|
||||||
Width="498">
|
|
||||||
|
|
||||||
<!-- PROGRESS RING -->
|
|
||||||
<Grid>
|
<Grid>
|
||||||
<ProgressRing HorizontalAlignment="Center" VerticalAlignment="Center" IsActive="True" Width="50" Height="50"/>
|
|
||||||
</Grid>
|
</Grid>
|
||||||
</Page>
|
</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
|
// 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>
|
/// <summary>
|
||||||
/// An empty page that can be used on its own or navigated to within a Frame.
|
/// An empty page that can be used on its own or navigated to within a Frame.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed partial class AddVideoStart : Page
|
public sealed partial class SettingsMain : Page
|
||||||
{
|
{
|
||||||
public AddVideoStart()
|
public SettingsMain()
|
||||||
{
|
{
|
||||||
this.InitializeComponent();
|
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
|
// 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>
|
/// <summary>
|
||||||
/// An empty page that can be used on its own or navigated to within a Frame.
|
/// An empty page that can be used on its own or navigated to within a Frame.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed partial class AddVideoNotFound : Page
|
public sealed partial class SubscriptionsMain : Page
|
||||||
{
|
{
|
||||||
public AddVideoNotFound()
|
public SubscriptionsMain()
|
||||||
{
|
{
|
||||||
this.InitializeComponent();
|
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">
|
<resheader name="writer">
|
||||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||||
</resheader>
|
</resheader>
|
||||||
<data name="AddPlaylistBase.CloseButtonText" xml:space="preserve">
|
<data name="MainPageNavigationPanelHomeItem.Content" xml:space="preserve">
|
||||||
<value>Cancel</value>
|
<value>Home</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="AddPlaylistBase.PrimaryButtonText" xml:space="preserve">
|
<data name="MainPageNavigationPanelSourcesItem.Content" xml:space="preserve">
|
||||||
<value>Add</value>
|
<value>Sources</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="AddPlaylistBase.Title" xml:space="preserve">
|
<data name="MainPageNavigationPanelSubscriptionsItem.Content" xml:space="preserve">
|
||||||
<value>ADD PLAYLIST</value>
|
<value>Subscriptions</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="AddPlaylistDeletedVideosPanelButton" xml:space="preserve">
|
<data name="SourcesMainPage.Title" xml:space="preserve">
|
||||||
<value>Restore</value>
|
<value>Sources</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="AddPlaylistDeletedVideosPanelTextBlock" xml:space="preserve">
|
<data name="SourcesMainTwitchLoginButtonTextLoading" xml:space="preserve">
|
||||||
<value>{x} videos removed</value>
|
<value>Loading...</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="AddPlaylistLocationSelectionApplyLocationButton.Content" xml:space="preserve">
|
<data name="SourcesMainTwitchLoginButtonTextLoggedIn" xml:space="preserve">
|
||||||
<value>Apply</value>
|
<value>Log out</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="AddPlaylistLocationSelectionTextBlock.Text" xml:space="preserve">
|
<data name="SourcesMainTwitchLoginButtonTextNotLoggedIn" xml:space="preserve">
|
||||||
<value>Location</value>
|
<value>Sign in</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="AddPlaylistNotFoundText" xml:space="preserve">
|
<data name="SourcesMainTwitchLoginErrorDialogDescriptionUnknown" xml:space="preserve">
|
||||||
<value>Playlist not found. Try again.</value>
|
<value>Unknown error</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="AddPlaylistSearchButton.Content" xml:space="preserve">
|
<data name="SourcesMainTwitchLoginErrorDialogTitle" xml:space="preserve">
|
||||||
<value>Search</value>
|
<value>Login to Twitch failed</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="AddPlaylistStartText.Text" xml:space="preserve">
|
<data name="SourcesMainTwitchSettingControl.Title" xml:space="preserve">
|
||||||
<value>Paste URL and click "Search" button</value>
|
<value>Twitch</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="AddPlaylistUrlPathText.Text" xml:space="preserve">
|
<data name="SourcesMainTwitchSettingControlDescriptionLoading" xml:space="preserve">
|
||||||
<value>URL</value>
|
<value>Loading...</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="AddPlaylistVideoDownloadOptionsMediaTypeRadiobuttonA" xml:space="preserve">
|
<data name="SourcesMainTwitchSettingControlDescriptionLoggedIn" xml:space="preserve">
|
||||||
<value>Only audio</value>
|
<value>Logged in as</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="AddPlaylistVideoDownloadOptionsMediaTypeRadiobuttonAV" xml:space="preserve">
|
<data name="SourcesMainTwitchSettingControlDescriptionNotLoggedIn" xml:space="preserve">
|
||||||
<value>Normal</value>
|
<value>Not logged in. Twitch authentication is required to download videos.</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>
|
</data>
|
||||||
</root>
|
</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">
|
<Compile Include="App.xaml.cs">
|
||||||
<DependentUpon>App.xaml</DependentUpon>
|
<DependentUpon>App.xaml</DependentUpon>
|
||||||
</Compile>
|
</Compile>
|
||||||
<Compile Include="Objects\Enums\PlaylistSource.cs" />
|
<Compile Include="Core\Enums\AudioFileExtension.cs" />
|
||||||
<Compile Include="Objects\Enums\VideoSource.cs" />
|
<Compile Include="Core\Enums\LogMessageType.cs" />
|
||||||
<Compile Include="Objects\Enums\VideoStatus.cs" />
|
<Compile Include="Core\Enums\MediaFileExtension.cs" />
|
||||||
<Compile Include="Services\Config.cs" />
|
<Compile Include="Core\Enums\MediaType.cs" />
|
||||||
<Compile Include="Services\Media.cs" />
|
<Compile Include="Core\Enums\StreamType.cs" />
|
||||||
<Compile Include="Services\Source.cs" />
|
<Compile Include="Core\Enums\VideoFileExtension.cs" />
|
||||||
<Compile Include="Services\Videos.cs" />
|
<Compile Include="Core\Exceptions\TwitchAccessTokenNotFoundException.cs" />
|
||||||
<Compile Include="Sources\PObject.cs" />
|
<Compile Include="Core\Exceptions\TwitchAccessTokenNotValidException.cs" />
|
||||||
<Compile Include="Sources\Twitch\Channel.cs" />
|
<Compile Include="Core\Globals\Assets.cs" />
|
||||||
<Compile Include="Sources\Twitch\Clip.cs" />
|
<Compile Include="Core\Interfaces\IAStream.cs" />
|
||||||
<Compile Include="Sources\Twitch\Vod.cs" />
|
<Compile Include="Core\Interfaces\IPlaylistService.cs" />
|
||||||
<Compile Include="Sources\VObject.cs" />
|
<Compile Include="Core\Interfaces\IVideoService.cs" />
|
||||||
<Compile Include="Views\AddPlaylist\AddPlaylistBase.xaml.cs">
|
<Compile Include="Core\Interfaces\IVStream.cs" />
|
||||||
<DependentUpon>AddPlaylistBase.xaml</DependentUpon>
|
<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>
|
||||||
<Compile Include="Views\AddPlaylist\AddPlaylistLoading.xaml.cs">
|
<Compile Include="GUI\Controls\SettingControl.xaml.cs">
|
||||||
<DependentUpon>AddPlaylistLoading.xaml</DependentUpon>
|
<DependentUpon>SettingControl.xaml</DependentUpon>
|
||||||
</Compile>
|
</Compile>
|
||||||
<Compile Include="Views\AddPlaylist\AddPlaylistMain.xaml.cs">
|
<Compile Include="GUI\Views\Home\HomeMain.xaml.cs">
|
||||||
<DependentUpon>AddPlaylistMain.xaml</DependentUpon>
|
<DependentUpon>HomeMain.xaml</DependentUpon>
|
||||||
</Compile>
|
</Compile>
|
||||||
<Compile Include="Views\AddPlaylist\AddPlaylistNotFound.xaml.cs">
|
<Compile Include="GUI\Views\MainPage.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">
|
|
||||||
<DependentUpon>MainPage.xaml</DependentUpon>
|
<DependentUpon>MainPage.xaml</DependentUpon>
|
||||||
</Compile>
|
</Compile>
|
||||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
<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>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<AppxManifest Include="Package.appxmanifest">
|
<AppxManifest Include="Package.appxmanifest">
|
||||||
@@ -174,41 +171,43 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Content Include="Assets\Icons\Add\NotFound.png" />
|
<Content Include="Assets\Icons\Add\NotFound.png" />
|
||||||
<Content Include="Assets\Icons\Add\Start.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\Twitch.png" />
|
||||||
<Content Include="Assets\Icons\Sources\Unknown.png" />
|
<Content Include="Assets\Icons\Sources\Unknown.png" />
|
||||||
<Content Include="Assets\Icons\Sources\Youtube.png" />
|
<Content Include="Assets\Icons\Sources\Youtube.png" />
|
||||||
<Content Include="Assets\Icons\Universal\Dark\Author.png" />
|
<Content Include="Assets\Icons\VideoMetadata\Dark\Author.png" />
|
||||||
<Content Include="Assets\Icons\Universal\Dark\Cancelled.png" />
|
<Content Include="Assets\Icons\VideoMetadata\Dark\Cancelled.png" />
|
||||||
<Content Include="Assets\Icons\Universal\Dark\Date.png" />
|
<Content Include="Assets\Icons\VideoMetadata\Dark\Date.png" />
|
||||||
<Content Include="Assets\Icons\Universal\Dark\Done.png" />
|
<Content Include="Assets\Icons\VideoMetadata\Dark\Done.png" />
|
||||||
<Content Include="Assets\Icons\Universal\Dark\Downloading.png" />
|
<Content Include="Assets\Icons\VideoMetadata\Dark\Downloading.png" />
|
||||||
<Content Include="Assets\Icons\Universal\Dark\Duration.png" />
|
<Content Include="Assets\Icons\VideoMetadata\Dark\Duration.png" />
|
||||||
<Content Include="Assets\Icons\Universal\Dark\Error.png" />
|
<Content Include="Assets\Icons\VideoMetadata\Dark\Error.png" />
|
||||||
<Content Include="Assets\Icons\Universal\Dark\Finalizing.png" />
|
<Content Include="Assets\Icons\VideoMetadata\Dark\Finalizing.png" />
|
||||||
<Content Include="Assets\Icons\Universal\Dark\Idle.png" />
|
<Content Include="Assets\Icons\VideoMetadata\Dark\Idle.png" />
|
||||||
<Content Include="Assets\Icons\Universal\Dark\MediaType.png" />
|
<Content Include="Assets\Icons\VideoMetadata\Dark\MediaType.png" />
|
||||||
<Content Include="Assets\Icons\Universal\Dark\Path.png" />
|
<Content Include="Assets\Icons\VideoMetadata\Dark\File.png" />
|
||||||
<Content Include="Assets\Icons\Universal\Dark\Transcoding.png" />
|
<Content Include="Assets\Icons\VideoMetadata\Dark\Transcoding.png" />
|
||||||
<Content Include="Assets\Icons\Universal\Dark\Quality.png" />
|
<Content Include="Assets\Icons\VideoMetadata\Dark\Quality.png" />
|
||||||
<Content Include="Assets\Icons\Universal\Dark\Trim.png" />
|
<Content Include="Assets\Icons\VideoMetadata\Dark\Trim.png" />
|
||||||
<Content Include="Assets\Icons\Universal\Dark\Views.png" />
|
<Content Include="Assets\Icons\VideoMetadata\Dark\Views.png" />
|
||||||
<Content Include="Assets\Icons\Universal\Dark\Waiting.png" />
|
<Content Include="Assets\Icons\VideoMetadata\Dark\Waiting.png" />
|
||||||
<Content Include="Assets\Icons\Universal\Light\Author.png" />
|
<Content Include="Assets\Icons\VideoMetadata\Light\Author.png" />
|
||||||
<Content Include="Assets\Icons\Universal\Light\Cancelled.png" />
|
<Content Include="Assets\Icons\VideoMetadata\Light\Cancelled.png" />
|
||||||
<Content Include="Assets\Icons\Universal\Light\Date.png" />
|
<Content Include="Assets\Icons\VideoMetadata\Light\Date.png" />
|
||||||
<Content Include="Assets\Icons\Universal\Light\Done.png" />
|
<Content Include="Assets\Icons\VideoMetadata\Light\Done.png" />
|
||||||
<Content Include="Assets\Icons\Universal\Light\Downloading.png" />
|
<Content Include="Assets\Icons\VideoMetadata\Light\Downloading.png" />
|
||||||
<Content Include="Assets\Icons\Universal\Light\Duration.png" />
|
<Content Include="Assets\Icons\VideoMetadata\Light\Duration.png" />
|
||||||
<Content Include="Assets\Icons\Universal\Light\Error.png" />
|
<Content Include="Assets\Icons\VideoMetadata\Light\Error.png" />
|
||||||
<Content Include="Assets\Icons\Universal\Light\Finalizing.png" />
|
<Content Include="Assets\Icons\VideoMetadata\Light\Finalizing.png" />
|
||||||
<Content Include="Assets\Icons\Universal\Light\Idle.png" />
|
<Content Include="Assets\Icons\VideoMetadata\Light\Idle.png" />
|
||||||
<Content Include="Assets\Icons\Universal\Light\MediaType.png" />
|
<Content Include="Assets\Icons\VideoMetadata\Light\MediaType.png" />
|
||||||
<Content Include="Assets\Icons\Universal\Light\Path.png" />
|
<Content Include="Assets\Icons\VideoMetadata\Light\File.png" />
|
||||||
<Content Include="Assets\Icons\Universal\Light\Transcoding.png" />
|
<Content Include="Assets\Icons\VideoMetadata\Light\Transcoding.png" />
|
||||||
<Content Include="Assets\Icons\Universal\Light\Quality.png" />
|
<Content Include="Assets\Icons\VideoMetadata\Light\Quality.png" />
|
||||||
<Content Include="Assets\Icons\Universal\Light\Trim.png" />
|
<Content Include="Assets\Icons\VideoMetadata\Light\Trim.png" />
|
||||||
<Content Include="Assets\Icons\Universal\Light\Views.png" />
|
<Content Include="Assets\Icons\VideoMetadata\Light\Views.png" />
|
||||||
<Content Include="Assets\Icons\Universal\Light\Waiting.png" />
|
<Content Include="Assets\Icons\VideoMetadata\Light\Waiting.png" />
|
||||||
<Content Include="Assets\Logo\Logo.png" />
|
<Content Include="Assets\Logo\Logo.png" />
|
||||||
<Content Include="Assets\Logo\LargeTile.scale-100.png" />
|
<Content Include="Assets\Logo\LargeTile.scale-100.png" />
|
||||||
<Content Include="Assets\Logo\LargeTile.scale-125.png" />
|
<Content Include="Assets\Logo\LargeTile.scale-125.png" />
|
||||||
@@ -268,50 +267,34 @@
|
|||||||
<Generator>MSBuild:Compile</Generator>
|
<Generator>MSBuild:Compile</Generator>
|
||||||
<SubType>Designer</SubType>
|
<SubType>Designer</SubType>
|
||||||
</ApplicationDefinition>
|
</ApplicationDefinition>
|
||||||
<Page Include="Views\AddPlaylist\AddPlaylistBase.xaml">
|
<Page Include="GUI\Controls\MainPageLayoutControl.xaml">
|
||||||
<SubType>Designer</SubType>
|
<SubType>Designer</SubType>
|
||||||
<Generator>MSBuild:Compile</Generator>
|
<Generator>MSBuild:Compile</Generator>
|
||||||
</Page>
|
</Page>
|
||||||
<Page Include="Views\AddPlaylist\AddPlaylistLoading.xaml">
|
<Page Include="GUI\Controls\SettingControl.xaml">
|
||||||
<SubType>Designer</SubType>
|
<SubType>Designer</SubType>
|
||||||
<Generator>MSBuild:Compile</Generator>
|
<Generator>MSBuild:Compile</Generator>
|
||||||
</Page>
|
</Page>
|
||||||
<Page Include="Views\AddPlaylist\AddPlaylistMain.xaml">
|
<Page Include="GUI\Views\Home\HomeMain.xaml">
|
||||||
<SubType>Designer</SubType>
|
<SubType>Designer</SubType>
|
||||||
<Generator>MSBuild:Compile</Generator>
|
<Generator>MSBuild:Compile</Generator>
|
||||||
</Page>
|
</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>
|
<SubType>Designer</SubType>
|
||||||
<Generator>MSBuild:Compile</Generator>
|
<Generator>MSBuild:Compile</Generator>
|
||||||
</Page>
|
</Page>
|
||||||
<Page Include="Views\AddPlaylist\AddPlaylistStart.xaml">
|
<Page Include="GUI\Views\Settings\SettingsMain.xaml">
|
||||||
<SubType>Designer</SubType>
|
<SubType>Designer</SubType>
|
||||||
<Generator>MSBuild:Compile</Generator>
|
<Generator>MSBuild:Compile</Generator>
|
||||||
</Page>
|
</Page>
|
||||||
<Page Include="Views\AddVideo\AddVideoBase.xaml">
|
<Page Include="GUI\Views\Subscriptions\SubscriptionsMain.xaml">
|
||||||
<SubType>Designer</SubType>
|
<SubType>Designer</SubType>
|
||||||
<Generator>MSBuild:Compile</Generator>
|
<Generator>MSBuild:Compile</Generator>
|
||||||
</Page>
|
</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>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.NETCore.UniversalWindowsPlatform">
|
<PackageReference Include="Microsoft.NETCore.UniversalWindowsPlatform">
|
||||||
@@ -321,16 +304,19 @@
|
|||||||
<Version>7.1.2</Version>
|
<Version>7.1.2</Version>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="Microsoft.UI.Xaml">
|
<PackageReference Include="Microsoft.UI.Xaml">
|
||||||
<Version>2.7.0</Version>
|
<Version>2.8.0-prerelease.220118001</Version>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="Newtonsoft.Json">
|
<PackageReference Include="Newtonsoft.Json">
|
||||||
<Version>13.0.1</Version>
|
<Version>13.0.1</Version>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PRIResource Include="Strings\en-US\Resources.resw" />
|
<PRIResource Include="Strings\en-US\ResourcesOld.resw" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup />
|
<ItemGroup />
|
||||||
|
<ItemGroup>
|
||||||
|
<PRIResource Include="Strings\en-US\Resources.resw" />
|
||||||
|
</ItemGroup>
|
||||||
<PropertyGroup Condition=" '$(VisualStudioVersion)' == '' or '$(VisualStudioVersion)' < '14.0' ">
|
<PropertyGroup Condition=" '$(VisualStudioVersion)' == '' or '$(VisualStudioVersion)' < '14.0' ">
|
||||||
<VisualStudioVersion>14.0</VisualStudioVersion>
|
<VisualStudioVersion>14.0</VisualStudioVersion>
|
||||||
</PropertyGroup>
|
</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>
|
|
||||||