1.0-dev15 (Core code cleaning)
This commit is contained in:
BIN
.github/Images/Home.png
vendored
Normal file
BIN
.github/Images/Home.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 217 KiB |
@@ -1,6 +1,7 @@
|
|||||||
# VDownload
|
# VDownload
|
||||||
VDownload is universal video downloader written in .NET/C# and Universal Windows Platform.
|
VDownload is universal video downloader written in .NET/C# and Universal Windows Platform.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
|
|||||||
@@ -1,9 +0,0 @@
|
|||||||
namespace VDownload.Core.Enums
|
|
||||||
{
|
|
||||||
public enum StreamType
|
|
||||||
{
|
|
||||||
AudioVideo,
|
|
||||||
OnlyAudio,
|
|
||||||
OnlyVideo,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
14
VDownload.Core/Enums/TaskAddingRequestSource.cs
Normal file
14
VDownload.Core/Enums/TaskAddingRequestSource.cs
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace VDownload.Core.Enums
|
||||||
|
{
|
||||||
|
public enum TaskAddingRequestSource
|
||||||
|
{
|
||||||
|
Video,
|
||||||
|
Playlist
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
using System;
|
|
||||||
using VDownload.Core.Enums;
|
|
||||||
using VDownload.Core.Interfaces;
|
|
||||||
using VDownload.Core.Objects;
|
|
||||||
using Windows.Storage;
|
|
||||||
|
|
||||||
namespace VDownload.Core.EventArgs
|
|
||||||
{
|
|
||||||
public class PlaylistAddEventArgs : System.EventArgs
|
|
||||||
{
|
|
||||||
public (
|
|
||||||
IVideoService VideoService,
|
|
||||||
MediaType MediaType,
|
|
||||||
IBaseStream Stream,
|
|
||||||
TimeSpan TrimStart,
|
|
||||||
TimeSpan TrimEnd,
|
|
||||||
string Filename,
|
|
||||||
MediaFileExtension Extension,
|
|
||||||
StorageFolder Location,
|
|
||||||
double Schedule
|
|
||||||
)[] Videos { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
{
|
{
|
||||||
public class PlaylistSearchEventArgs : System.EventArgs
|
public class PlaylistSearchEventArgs : System.EventArgs
|
||||||
{
|
{
|
||||||
public string Phrase { get; set; }
|
public string Url { get; set; }
|
||||||
public int Count { get; set; }
|
public int VideosCount { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
24
VDownload.Core/EventArgs/ProgressChangedEventArgs.cs
Normal file
24
VDownload.Core/EventArgs/ProgressChangedEventArgs.cs
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
namespace VDownload.Core.EventArgs
|
||||||
|
{
|
||||||
|
public class ProgressChangedEventArgs : System.EventArgs
|
||||||
|
{
|
||||||
|
#region CONSTRUCTORS
|
||||||
|
|
||||||
|
public ProgressChangedEventArgs(double progress, bool isCompleted = false)
|
||||||
|
{
|
||||||
|
Progress = progress;
|
||||||
|
IsCompleted = isCompleted;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region PROPERTIES
|
||||||
|
|
||||||
|
public double Progress { get; set; }
|
||||||
|
public bool IsCompleted { get; set; }
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
11
VDownload.Core/EventArgs/TasksAddingRequestedEventArgs.cs
Normal file
11
VDownload.Core/EventArgs/TasksAddingRequestedEventArgs.cs
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
using VDownload.Core.Enums;
|
||||||
|
using VDownload.Core.Structs;
|
||||||
|
|
||||||
|
namespace VDownload.Core.EventArgs
|
||||||
|
{
|
||||||
|
public class TasksAddingRequestedEventArgs : System.EventArgs
|
||||||
|
{
|
||||||
|
public TaskData[] TaskData { get; set; }
|
||||||
|
public TaskAddingRequestSource RequestSource { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,6 +2,6 @@
|
|||||||
{
|
{
|
||||||
public class VideoSearchEventArgs : System.EventArgs
|
public class VideoSearchEventArgs : System.EventArgs
|
||||||
{
|
{
|
||||||
public string Phrase { get; set; }
|
public string Url { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,22 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using VDownload.Core.Enums;
|
|
||||||
|
|
||||||
namespace VDownload.Core.Interfaces
|
|
||||||
{
|
|
||||||
public interface IBaseStream
|
|
||||||
{
|
|
||||||
#region PROPERTIES
|
|
||||||
|
|
||||||
Uri Url { get; }
|
|
||||||
bool IsChunked { get; }
|
|
||||||
StreamType StreamType { get; }
|
|
||||||
int Height { get; }
|
|
||||||
int FrameRate { get; }
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -3,6 +3,7 @@ using System.ComponentModel;
|
|||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using VDownload.Core.Enums;
|
using VDownload.Core.Enums;
|
||||||
|
using VDownload.Core.Structs;
|
||||||
using Windows.Storage;
|
using Windows.Storage;
|
||||||
|
|
||||||
namespace VDownload.Core.Interfaces
|
namespace VDownload.Core.Interfaces
|
||||||
@@ -14,13 +15,8 @@ namespace VDownload.Core.Interfaces
|
|||||||
// VIDEO PROPERTIES
|
// VIDEO PROPERTIES
|
||||||
string ID { get; }
|
string ID { get; }
|
||||||
Uri VideoUrl { get; }
|
Uri VideoUrl { get; }
|
||||||
string Title { get; }
|
Metadata Metadata { get; }
|
||||||
string Author { get; }
|
BaseStream[] BaseStreams { get; }
|
||||||
DateTime Date { get; }
|
|
||||||
TimeSpan Duration { get; }
|
|
||||||
long Views { get; }
|
|
||||||
Uri Thumbnail { get; }
|
|
||||||
IBaseStream[] BaseStreams { get; }
|
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
@@ -35,7 +31,7 @@ namespace VDownload.Core.Interfaces
|
|||||||
Task GetStreamsAsync(CancellationToken cancellationToken = default);
|
Task GetStreamsAsync(CancellationToken cancellationToken = default);
|
||||||
|
|
||||||
// DOWNLOAD VIDEO
|
// DOWNLOAD VIDEO
|
||||||
Task<StorageFile> DownloadAndTranscodeAsync(StorageFolder downloadingFolder, IBaseStream baseStream, MediaFileExtension extension, MediaType mediaType, TimeSpan trimStart, TimeSpan trimEnd, CancellationToken cancellationToken = default);
|
Task<StorageFile> DownloadAndTranscodeAsync(StorageFolder downloadingFolder, BaseStream baseStream, MediaFileExtension extension, MediaType mediaType, TimeSpan trimStart, TimeSpan trimEnd, CancellationToken cancellationToken = default);
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
@@ -43,12 +39,8 @@ namespace VDownload.Core.Interfaces
|
|||||||
|
|
||||||
#region EVENT HANDLERS
|
#region EVENT HANDLERS
|
||||||
|
|
||||||
event EventHandler DownloadingStarted;
|
event EventHandler<EventArgs.ProgressChangedEventArgs> DownloadingProgressChanged;
|
||||||
event EventHandler<ProgressChangedEventArgs> DownloadingProgressChanged;
|
event EventHandler<EventArgs.ProgressChangedEventArgs> ProcessingProgressChanged;
|
||||||
event EventHandler DownloadingCompleted;
|
|
||||||
event EventHandler ProcessingStarted;
|
|
||||||
event EventHandler<ProgressChangedEventArgs> ProcessingProgressChanged;
|
|
||||||
event EventHandler ProcessingCompleted;
|
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,36 +0,0 @@
|
|||||||
using System;
|
|
||||||
using VDownload.Core.Enums;
|
|
||||||
using VDownload.Core.Interfaces;
|
|
||||||
|
|
||||||
namespace VDownload.Core.Objects
|
|
||||||
{
|
|
||||||
public class Stream : IBaseStream
|
|
||||||
{
|
|
||||||
#region CONSTRUCTORS
|
|
||||||
|
|
||||||
public Stream(Uri url, bool isChunked, StreamType streamType)
|
|
||||||
{
|
|
||||||
Url = url;
|
|
||||||
IsChunked = isChunked;
|
|
||||||
StreamType = streamType;
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#region PROPERTIES
|
|
||||||
|
|
||||||
public Uri Url { get; private set; }
|
|
||||||
public bool IsChunked { get; private set; }
|
|
||||||
public StreamType StreamType { get; private set; }
|
|
||||||
public int Width { get; set; }
|
|
||||||
public int Height { get; set; }
|
|
||||||
public int FrameRate { get; set; }
|
|
||||||
public string VideoCodec { get; set; }
|
|
||||||
public int AudioBitrate { get; set; }
|
|
||||||
public string AudioCodec { get; set; }
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using VDownload.Core.Enums;
|
using VDownload.Core.Enums;
|
||||||
using Windows.Media.Editing;
|
using Windows.Media.Editing;
|
||||||
|
using Windows.Media.Transcoding;
|
||||||
using Windows.Storage;
|
using Windows.Storage;
|
||||||
|
|
||||||
namespace VDownload.Core.Services
|
namespace VDownload.Core.Services
|
||||||
@@ -21,7 +22,7 @@ namespace VDownload.Core.Services
|
|||||||
{ "twitch_vod_downloading_chunk_max_retries", 10 },
|
{ "twitch_vod_downloading_chunk_max_retries", 10 },
|
||||||
{ "twitch_vod_downloading_chunk_retries_delay", 5000 },
|
{ "twitch_vod_downloading_chunk_retries_delay", 5000 },
|
||||||
{ "media_transcoding_use_hardware_acceleration", true },
|
{ "media_transcoding_use_hardware_acceleration", true },
|
||||||
{ "media_transcoding_use_mrfcrf444_algorithm", true },
|
{ "media_transcoding_algorithm", (int)MediaVideoProcessingAlgorithm.MrfCrf444 },
|
||||||
{ "media_editing_algorithm", (int)MediaTrimmingPreference.Fast },
|
{ "media_editing_algorithm", (int)MediaTrimmingPreference.Fast },
|
||||||
{ "default_max_playlist_videos", 0 },
|
{ "default_max_playlist_videos", 0 },
|
||||||
{ "default_media_type", (int)MediaType.AudioVideo },
|
{ "default_media_type", (int)MediaType.AudioVideo },
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.ComponentModel;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
@@ -15,66 +14,50 @@ namespace VDownload.Core.Services
|
|||||||
{
|
{
|
||||||
public class MediaProcessor
|
public class MediaProcessor
|
||||||
{
|
{
|
||||||
#region CONSTRUCTORS
|
|
||||||
|
|
||||||
public MediaProcessor(StorageFile outputFile, TimeSpan trimStart, TimeSpan trimEnd)
|
|
||||||
{
|
|
||||||
OutputFile = outputFile;
|
|
||||||
TrimStart = trimStart;
|
|
||||||
TrimEnd = trimEnd;
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#region PROPERTIES
|
|
||||||
|
|
||||||
public StorageFile OutputFile { get; private set; }
|
|
||||||
public TimeSpan TrimStart { get; private set; }
|
|
||||||
public TimeSpan TrimEnd { get; private set; }
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#region STANDARD METHODS
|
#region STANDARD METHODS
|
||||||
|
|
||||||
// SINGLE AUDIO & VIDEO FILE PROCESSING
|
// SINGLE AUDIO & VIDEO FILE PROCESSING
|
||||||
public async Task Run(StorageFile audioVideoInputFile, MediaFileExtension extension, MediaType mediaType, CancellationToken cancellationToken = default)
|
public async Task Run(StorageFile mediaFile, MediaFileExtension extension, MediaType mediaType, StorageFile outputFile, TimeSpan? trimStart = null, TimeSpan? trimEnd = null, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
// Invoke ProcessingStarted event
|
// Invoke event at start
|
||||||
ProcessingStarted?.Invoke(this, System.EventArgs.Empty);
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
|
ProgressChanged(this, new EventArgs.ProgressChangedEventArgs(0));
|
||||||
|
|
||||||
// Init transcoder
|
// Init transcoder
|
||||||
MediaTranscoder mediaTranscoder = new MediaTranscoder
|
MediaTranscoder mediaTranscoder = new MediaTranscoder
|
||||||
{
|
{
|
||||||
HardwareAccelerationEnabled = (bool)Config.GetValue("media_transcoding_use_hardware_acceleration"),
|
HardwareAccelerationEnabled = (bool)Config.GetValue("media_transcoding_use_hardware_acceleration"),
|
||||||
VideoProcessingAlgorithm = (bool)Config.GetValue("media_transcoding_use_mrfcrf444_algorithm") ? MediaVideoProcessingAlgorithm.MrfCrf444 : MediaVideoProcessingAlgorithm.Default,
|
VideoProcessingAlgorithm = (MediaVideoProcessingAlgorithm)Config.GetValue("media_transcoding_algorithm"),
|
||||||
TrimStartTime = TrimStart,
|
|
||||||
TrimStopTime = TrimEnd,
|
|
||||||
};
|
};
|
||||||
|
if (trimStart != null) mediaTranscoder.TrimStartTime = (TimeSpan)trimStart;
|
||||||
|
if (trimEnd != null) mediaTranscoder.TrimStopTime = (TimeSpan)trimEnd;
|
||||||
|
|
||||||
// Start transcoding operation
|
// Start transcoding operation
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
using (IRandomAccessStream openedOutputFile = await outputFile.OpenAsync(FileAccessMode.ReadWrite))
|
||||||
using (IRandomAccessStream outputFileOpened = await OutputFile.OpenAsync(FileAccessMode.ReadWrite))
|
|
||||||
{
|
{
|
||||||
PrepareTranscodeResult transcodingPreparated = await mediaTranscoder.PrepareStreamTranscodeAsync(await audioVideoInputFile.OpenAsync(FileAccessMode.Read), outputFileOpened, await GetMediaEncodingProfile(audioVideoInputFile, extension, mediaType));
|
// Prepare transcode task
|
||||||
|
PrepareTranscodeResult transcodingPreparated = await mediaTranscoder.PrepareStreamTranscodeAsync(await mediaFile.OpenAsync(FileAccessMode.Read), openedOutputFile, await GetMediaEncodingProfile(mediaFile, extension, mediaType));
|
||||||
|
|
||||||
|
// Start transcoding
|
||||||
IAsyncActionWithProgress<double> transcodingTask = transcodingPreparated.TranscodeAsync();
|
IAsyncActionWithProgress<double> transcodingTask = transcodingPreparated.TranscodeAsync();
|
||||||
await transcodingTask.AsTask(cancellationToken, new Progress<double>((percent) => { ProcessingProgressChanged(this, new ProgressChangedEventArgs((int)Math.Round(percent), null)); }));
|
await transcodingTask.AsTask(cancellationToken, new Progress<double>((percent) => { ProgressChanged(this, new EventArgs.ProgressChangedEventArgs(percent)); }));
|
||||||
await outputFileOpened.FlushAsync();
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
|
// Finalizing
|
||||||
|
await openedOutputFile.FlushAsync();
|
||||||
transcodingTask.Close();
|
transcodingTask.Close();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Invoke ProcessingCompleted event
|
// Invoke event at end
|
||||||
ProcessingCompleted?.Invoke(this, System.EventArgs.Empty);
|
ProgressChanged(this, new EventArgs.ProgressChangedEventArgs(100, true));
|
||||||
}
|
}
|
||||||
|
|
||||||
// SEPARATE AUDIO & VIDEO FILES PROCESSING
|
// SEPARATE AUDIO & VIDEO FILES PROCESSING
|
||||||
public async Task Run(StorageFile audioFile, StorageFile videoFile, VideoFileExtension extension, CancellationToken cancellationToken = default)
|
public async Task Run(StorageFile audioFile, StorageFile videoFile, VideoFileExtension extension, StorageFile outputFile, TimeSpan? trimStart = null, TimeSpan? trimEnd = null, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
// Invoke ProcessingStarted event
|
// Invoke event at start
|
||||||
ProcessingStarted?.Invoke(this, System.EventArgs.Empty);
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
|
ProgressChanged(this, new EventArgs.ProgressChangedEventArgs(0));
|
||||||
|
|
||||||
// Init editor
|
// Init editor
|
||||||
MediaComposition mediaEditor = new MediaComposition();
|
MediaComposition mediaEditor = new MediaComposition();
|
||||||
@@ -86,28 +69,36 @@ namespace VDownload.Core.Services
|
|||||||
await Task.WhenAll(getVideoFileTask, getAudioFileTask);
|
await Task.WhenAll(getVideoFileTask, getAudioFileTask);
|
||||||
|
|
||||||
MediaClip videoElement = getVideoFileTask.Result;
|
MediaClip videoElement = getVideoFileTask.Result;
|
||||||
videoElement.TrimTimeFromStart = TrimStart;
|
if (trimStart != null) videoElement.TrimTimeFromStart = (TimeSpan)trimStart;
|
||||||
videoElement.TrimTimeFromEnd = TrimEnd;
|
if (trimEnd != null) videoElement.TrimTimeFromEnd = (TimeSpan)trimEnd;
|
||||||
BackgroundAudioTrack audioElement = getAudioFileTask.Result;
|
BackgroundAudioTrack audioElement = getAudioFileTask.Result;
|
||||||
audioElement.TrimTimeFromStart = TrimStart;
|
if (trimStart != null) audioElement.TrimTimeFromStart = (TimeSpan)trimStart;
|
||||||
audioElement.TrimTimeFromEnd = TrimEnd;
|
if (trimEnd != null) audioElement.TrimTimeFromEnd = (TimeSpan)trimEnd;
|
||||||
|
|
||||||
mediaEditor.Clips.Add(getVideoFileTask.Result);
|
mediaEditor.Clips.Add(videoElement);
|
||||||
mediaEditor.BackgroundAudioTracks.Add(getAudioFileTask.Result);
|
mediaEditor.BackgroundAudioTracks.Add(audioElement);
|
||||||
|
|
||||||
// Start rendering operation
|
// Start rendering operation
|
||||||
var renderOperation = mediaEditor.RenderToFileAsync(OutputFile, (MediaTrimmingPreference)Config.GetValue("media_editing_algorithm"), await GetMediaEncodingProfile(videoFile, audioFile, (MediaFileExtension)extension, MediaType.AudioVideo));
|
var renderOperation = mediaEditor.RenderToFileAsync(outputFile, (MediaTrimmingPreference)Config.GetValue("media_editing_algorithm"), await GetMediaEncodingProfile(videoFile, audioFile, (MediaFileExtension)extension, MediaType.AudioVideo));
|
||||||
renderOperation.Progress += (info, progress) => { ProcessingProgressChanged(this, new ProgressChangedEventArgs((int)Math.Round(progress), null)); };
|
renderOperation.Progress += (info, progress) => { ProgressChanged(this, new EventArgs.ProgressChangedEventArgs(progress)); };
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
await renderOperation.AsTask(cancellationToken);
|
await renderOperation.AsTask(cancellationToken);
|
||||||
|
|
||||||
// Invoke ProcessingCompleted event
|
// Invoke event at end
|
||||||
ProcessingCompleted?.Invoke(this, System.EventArgs.Empty);
|
ProgressChanged(this, new EventArgs.ProgressChangedEventArgs(100, true));
|
||||||
}
|
}
|
||||||
|
|
||||||
// SINGLE AUDIO OR VIDEO FILES PROCESSING
|
// AUDIO FILE PROCESSING
|
||||||
public async Task Run(StorageFile audioFile, AudioFileExtension extension, CancellationToken cancellationToken = default) { await Run(audioFile, (MediaFileExtension)extension, MediaType.OnlyAudio, cancellationToken); }
|
public async Task Run(StorageFile audioFile, AudioFileExtension extension, StorageFile outputFile, TimeSpan? trimStart = null, TimeSpan? trimEnd = null, CancellationToken cancellationToken = default)
|
||||||
public async Task Run(StorageFile videoFile, VideoFileExtension extension, CancellationToken cancellationToken = default) { await Run(videoFile, (MediaFileExtension)extension, MediaType.OnlyVideo, cancellationToken); }
|
{
|
||||||
|
await Run(audioFile, (MediaFileExtension)extension, MediaType.OnlyAudio, outputFile, trimStart, trimEnd, cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
// VIDEO FILE PROCESSING
|
||||||
|
public async Task Run(StorageFile videoFile, VideoFileExtension extension, StorageFile outputFile, TimeSpan? trimStart = null, TimeSpan? trimEnd = null, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
await Run(videoFile, (MediaFileExtension)extension, MediaType.OnlyVideo, outputFile, trimStart, trimEnd, cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
@@ -116,7 +107,7 @@ namespace VDownload.Core.Services
|
|||||||
#region LOCAL METHODS
|
#region LOCAL METHODS
|
||||||
|
|
||||||
// GET ENCODING PROFILE
|
// GET ENCODING PROFILE
|
||||||
public static async Task<MediaEncodingProfile> GetMediaEncodingProfile(StorageFile videoFile, StorageFile audioFile, MediaFileExtension extension, MediaType mediaType)
|
private static async Task<MediaEncodingProfile> GetMediaEncodingProfile(StorageFile videoFile, StorageFile audioFile, MediaFileExtension extension, MediaType mediaType)
|
||||||
{
|
{
|
||||||
// Create profile object
|
// Create profile object
|
||||||
MediaEncodingProfile profile;
|
MediaEncodingProfile profile;
|
||||||
@@ -164,7 +155,10 @@ namespace VDownload.Core.Services
|
|||||||
// Return profile
|
// Return profile
|
||||||
return profile;
|
return profile;
|
||||||
}
|
}
|
||||||
public static async Task<MediaEncodingProfile> GetMediaEncodingProfile(StorageFile audioVideoFile, MediaFileExtension extension, MediaType mediaType) { return await GetMediaEncodingProfile(audioVideoFile, audioVideoFile, extension, mediaType); }
|
private static async Task<MediaEncodingProfile> GetMediaEncodingProfile(StorageFile audioVideoFile, MediaFileExtension extension, MediaType mediaType)
|
||||||
|
{
|
||||||
|
return await GetMediaEncodingProfile(audioVideoFile, audioVideoFile, extension, mediaType);
|
||||||
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
@@ -172,9 +166,7 @@ namespace VDownload.Core.Services
|
|||||||
|
|
||||||
#region EVENT HANDLERS
|
#region EVENT HANDLERS
|
||||||
|
|
||||||
public event EventHandler ProcessingStarted;
|
public event EventHandler<EventArgs.ProgressChangedEventArgs> ProgressChanged;
|
||||||
public event EventHandler<ProgressChangedEventArgs> ProcessingProgressChanged;
|
|
||||||
public event EventHandler ProcessingCompleted;
|
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ using System.Threading;
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using VDownload.Core.Exceptions;
|
using VDownload.Core.Exceptions;
|
||||||
using VDownload.Core.Interfaces;
|
using VDownload.Core.Interfaces;
|
||||||
|
using VDownload.Core.Services.Sources.Twitch.Helpers;
|
||||||
|
|
||||||
namespace VDownload.Core.Services.Sources.Twitch
|
namespace VDownload.Core.Services.Sources.Twitch
|
||||||
{
|
{
|
||||||
@@ -41,25 +42,15 @@ namespace VDownload.Core.Services.Sources.Twitch
|
|||||||
// GET CHANNEL METADATA
|
// GET CHANNEL METADATA
|
||||||
public async Task GetMetadataAsync(CancellationToken cancellationToken = default)
|
public async Task GetMetadataAsync(CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
// Get access token
|
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
string accessToken = await Auth.ReadAccessTokenAsync();
|
|
||||||
if (accessToken == null) throw new TwitchAccessTokenNotFoundException();
|
|
||||||
|
|
||||||
// Check access token
|
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
|
||||||
var twitchAccessTokenValidation = await Auth.ValidateAccessTokenAsync(accessToken);
|
|
||||||
if (!twitchAccessTokenValidation.IsValid) throw new TwitchAccessTokenNotValidException();
|
|
||||||
|
|
||||||
// Create client
|
|
||||||
WebClient client = new WebClient();
|
|
||||||
client.Headers.Add("Authorization", $"Bearer {accessToken}");
|
|
||||||
client.Headers.Add("Client-Id", Auth.ClientID);
|
|
||||||
|
|
||||||
// Get response
|
// Get response
|
||||||
|
JToken response = null;
|
||||||
|
using (WebClient client = await Client.Helix())
|
||||||
|
{
|
||||||
client.QueryString.Add("login", ID);
|
client.QueryString.Add("login", ID);
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
response = JObject.Parse(await client.DownloadStringTaskAsync("https://api.twitch.tv/helix/users"))["data"][0];
|
||||||
JToken response = JObject.Parse(await client.DownloadStringTaskAsync("https://api.twitch.tv/helix/users"))["data"][0];
|
}
|
||||||
|
|
||||||
// Create unified playlist url
|
// Create unified playlist url
|
||||||
PlaylistUrl = new Uri($"https://twitch.tv/{ID}");
|
PlaylistUrl = new Uri($"https://twitch.tv/{ID}");
|
||||||
@@ -72,12 +63,9 @@ namespace VDownload.Core.Services.Sources.Twitch
|
|||||||
// GET CHANNEL VIDEOS
|
// GET CHANNEL VIDEOS
|
||||||
public async Task GetVideosAsync(int numberOfVideos, CancellationToken cancellationToken = default)
|
public async Task GetVideosAsync(int numberOfVideos, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
// Get access token
|
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
string accessToken = await Auth.ReadAccessTokenAsync();
|
|
||||||
if (accessToken == null) throw new TwitchAccessTokenNotFoundException();
|
|
||||||
|
|
||||||
// Set pagination
|
// Set page id
|
||||||
string pagination = "";
|
string pagination = "";
|
||||||
|
|
||||||
// Set array of videos
|
// Set array of videos
|
||||||
@@ -92,30 +80,24 @@ namespace VDownload.Core.Services.Sources.Twitch
|
|||||||
List<Task> getStreamsTasks = new List<Task>();
|
List<Task> getStreamsTasks = new List<Task>();
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
// Check access token
|
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
|
||||||
var twitchAccessTokenValidation = await Auth.ValidateAccessTokenAsync(accessToken);
|
|
||||||
if (!twitchAccessTokenValidation.IsValid) throw new TwitchAccessTokenNotValidException();
|
|
||||||
|
|
||||||
// Create client
|
|
||||||
WebClient client = new WebClient();
|
|
||||||
client.Headers.Add("Authorization", $"Bearer {accessToken}");
|
|
||||||
client.Headers.Add("Client-Id", Auth.ClientID);
|
|
||||||
|
|
||||||
// Set number of videos to get in this iteration
|
// Set number of videos to get in this iteration
|
||||||
count = numberOfVideos < 100 && !getAll ? numberOfVideos : 100;
|
count = numberOfVideos < 100 && !getAll ? numberOfVideos : 100;
|
||||||
|
|
||||||
// Get response
|
// Get response
|
||||||
|
JToken response = null;
|
||||||
|
using (WebClient client = await Client.Helix())
|
||||||
|
{
|
||||||
client.QueryString.Add("user_id", ID);
|
client.QueryString.Add("user_id", ID);
|
||||||
client.QueryString.Add("first", count.ToString());
|
client.QueryString.Add("first", count.ToString());
|
||||||
client.QueryString.Add("after", pagination);
|
client.QueryString.Add("after", pagination);
|
||||||
|
response = JObject.Parse(await client.DownloadStringTaskAsync("https://api.twitch.tv/helix/videos"));
|
||||||
|
}
|
||||||
|
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
// Set page id
|
||||||
JToken response = JObject.Parse(await client.DownloadStringTaskAsync("https://api.twitch.tv/helix/videos"));
|
|
||||||
|
|
||||||
pagination = (string)response["pagination"]["cursor"];
|
pagination = (string)response["pagination"]["cursor"];
|
||||||
videosData = response["data"].ToArray();
|
|
||||||
|
|
||||||
|
// Set videos data
|
||||||
|
videosData = response["data"].ToArray();
|
||||||
foreach (JToken videoData in videosData)
|
foreach (JToken videoData in videosData)
|
||||||
{
|
{
|
||||||
Vod video = new Vod((string)videoData["id"]);
|
Vod video = new Vod((string)videoData["id"]);
|
||||||
@@ -131,7 +113,7 @@ namespace VDownload.Core.Services.Sources.Twitch
|
|||||||
// Wait for all getStreams tasks
|
// Wait for all getStreams tasks
|
||||||
await Task.WhenAll(getStreamsTasks);
|
await Task.WhenAll(getStreamsTasks);
|
||||||
|
|
||||||
// Set Videos parameter
|
// Set videos
|
||||||
Videos = videos.ToArray();
|
Videos = videos.ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,21 +12,14 @@ using System.Web;
|
|||||||
using VDownload.Core.Enums;
|
using VDownload.Core.Enums;
|
||||||
using VDownload.Core.Exceptions;
|
using VDownload.Core.Exceptions;
|
||||||
using VDownload.Core.Interfaces;
|
using VDownload.Core.Interfaces;
|
||||||
using VDownload.Core.Objects;
|
using VDownload.Core.Services.Sources.Twitch.Helpers;
|
||||||
|
using VDownload.Core.Structs;
|
||||||
using Windows.Storage;
|
using Windows.Storage;
|
||||||
|
|
||||||
namespace VDownload.Core.Services.Sources.Twitch
|
namespace VDownload.Core.Services.Sources.Twitch
|
||||||
{
|
{
|
||||||
public class Clip : IVideoService
|
public class Clip : IVideoService
|
||||||
{
|
{
|
||||||
#region CONSTANTS
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#region CONSTRUCTORS
|
#region CONSTRUCTORS
|
||||||
|
|
||||||
public Clip(string id)
|
public Clip(string id)
|
||||||
@@ -42,13 +35,8 @@ namespace VDownload.Core.Services.Sources.Twitch
|
|||||||
|
|
||||||
public string ID { get; private set; }
|
public string ID { get; private set; }
|
||||||
public Uri VideoUrl { get; private set; }
|
public Uri VideoUrl { get; private set; }
|
||||||
public string Title { get; private set; }
|
public Metadata Metadata { get; private set; }
|
||||||
public string Author { get; private set; }
|
public BaseStream[] BaseStreams { get; private set; }
|
||||||
public DateTime Date { get; private set; }
|
|
||||||
public TimeSpan Duration { get; private set; }
|
|
||||||
public long Views { get; private set; }
|
|
||||||
public Uri Thumbnail { get; private set; }
|
|
||||||
public IBaseStream[] BaseStreams { get; private set; }
|
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
@@ -59,92 +47,83 @@ namespace VDownload.Core.Services.Sources.Twitch
|
|||||||
// GET CLIP METADATA
|
// GET CLIP METADATA
|
||||||
public async Task GetMetadataAsync(CancellationToken cancellationToken = default)
|
public async Task GetMetadataAsync(CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
// Get access token
|
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
string accessToken = await Auth.ReadAccessTokenAsync();
|
|
||||||
if (accessToken == null) throw new TwitchAccessTokenNotFoundException();
|
|
||||||
|
|
||||||
// Check access token
|
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
|
||||||
var twitchAccessTokenValidation = await Auth.ValidateAccessTokenAsync(accessToken);
|
|
||||||
if (!twitchAccessTokenValidation.IsValid) throw new TwitchAccessTokenNotValidException();
|
|
||||||
|
|
||||||
// Create client
|
|
||||||
WebClient client = new WebClient();
|
|
||||||
client.Headers.Add("Authorization", $"Bearer {accessToken}");
|
|
||||||
client.Headers.Add("Client-Id", Auth.ClientID);
|
|
||||||
|
|
||||||
// Get response
|
// Get response
|
||||||
|
JToken response = null;
|
||||||
|
using (WebClient client = await Client.Helix())
|
||||||
|
{
|
||||||
client.QueryString.Add("id", ID);
|
client.QueryString.Add("id", ID);
|
||||||
JToken response = JObject.Parse(await client.DownloadStringTaskAsync("https://api.twitch.tv/helix/clips")).GetValue("data")[0];
|
response = JObject.Parse(await client.DownloadStringTaskAsync("https://api.twitch.tv/helix/clips")).GetValue("data")[0];
|
||||||
|
}
|
||||||
|
|
||||||
// Create unified video url
|
// Create unified video url
|
||||||
VideoUrl = new Uri($"https://clips.twitch.tv/{ID}");
|
VideoUrl = new Uri($"https://clips.twitch.tv/{ID}");
|
||||||
|
|
||||||
// Set parameters
|
// Set metadata
|
||||||
Title = (string)response["title"];
|
Metadata = new Metadata()
|
||||||
Author = (string)response["broadcaster_name"];
|
{
|
||||||
Date = Convert.ToDateTime(response["created_at"]);
|
Title = (string)response["title"],
|
||||||
Duration = TimeSpan.FromSeconds((double)response["duration"]);
|
Author = (string)response["broadcaster_name"],
|
||||||
Views = (long)response["view_count"];
|
Date = Convert.ToDateTime(response["created_at"]),
|
||||||
Thumbnail = new Uri((string)response["thumbnail_url"]);
|
Duration = TimeSpan.FromSeconds((double)response["duration"]),
|
||||||
|
Views = (long)response["view_count"],
|
||||||
|
Thumbnail = new Uri((string)response["thumbnail_url"]),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task GetStreamsAsync(CancellationToken cancellationToken = default)
|
public async Task GetStreamsAsync(CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
// Create client
|
|
||||||
WebClient client = new WebClient { Encoding = Encoding.UTF8 };
|
|
||||||
client.Headers.Add("Client-ID", Auth.GQLApiClientID);
|
|
||||||
|
|
||||||
// Get video streams
|
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
JToken[] response = JArray.Parse(await client.UploadStringTaskAsync("https://gql.twitch.tv/gql", "[{\"operationName\":\"VideoAccessToken_Clip\",\"variables\":{\"slug\":\"" + ID + "\"},\"extensions\":{\"persistedQuery\":{\"version\":1,\"sha256Hash\":\"36b89d2507fce29e5ca551df756d27c1cfe079e2609642b4390aa4c35796eb11\"}}}]"))[0]["data"]["clip"]["videoQualities"].ToArray();
|
|
||||||
|
// Get response
|
||||||
|
JToken[] response;
|
||||||
|
using (WebClient client = Client.GQL())
|
||||||
|
{
|
||||||
|
response = JArray.Parse(await client.UploadStringTaskAsync("https://gql.twitch.tv/gql", "[{\"operationName\":\"VideoAccessToken_Clip\",\"variables\":{\"slug\":\"" + ID + "\"},\"extensions\":{\"persistedQuery\":{\"version\":1,\"sha256Hash\":\"36b89d2507fce29e5ca551df756d27c1cfe079e2609642b4390aa4c35796eb11\"}}}]"))[0]["data"]["clip"]["videoQualities"].ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
// Init streams list
|
// Init streams list
|
||||||
List<Stream> streams = new List<Stream>();
|
List<BaseStream> streams = new List<BaseStream>();
|
||||||
|
|
||||||
// Parse response
|
// Parse response
|
||||||
foreach (JToken streamData in response)
|
foreach (JToken streamData in response)
|
||||||
{
|
{
|
||||||
// Get info
|
|
||||||
Uri url = new Uri((string)streamData["sourceURL"]);
|
|
||||||
int height = int.Parse((string)streamData["quality"]);
|
|
||||||
int frameRate = (int)streamData["frameRate"];
|
|
||||||
|
|
||||||
// Create stream
|
// Create stream
|
||||||
Stream stream = new Stream(url, false, StreamType.AudioVideo)
|
BaseStream stream = new BaseStream()
|
||||||
{
|
{
|
||||||
Height = height,
|
Url = new Uri((string)streamData["sourceURL"]),
|
||||||
FrameRate = frameRate
|
Height = int.Parse((string)streamData["quality"]),
|
||||||
|
FrameRate = (int)streamData["frameRate"],
|
||||||
};
|
};
|
||||||
|
|
||||||
// Add stream
|
// Add stream
|
||||||
streams.Add(stream);
|
streams.Add(stream);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set Streams parameter
|
// Set streams
|
||||||
BaseStreams = streams.ToArray();
|
BaseStreams = streams.ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<StorageFile> DownloadAndTranscodeAsync(StorageFolder downloadingFolder, IBaseStream baseStream, MediaFileExtension extension, MediaType mediaType, TimeSpan trimStart, TimeSpan trimEnd, CancellationToken cancellationToken = default)
|
public async Task<StorageFile> DownloadAndTranscodeAsync(StorageFolder downloadingFolder, BaseStream baseStream, MediaFileExtension extension, MediaType mediaType, TimeSpan trimStart, TimeSpan trimEnd, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
// Invoke DownloadingStarted event
|
// Invoke DownloadingStarted event
|
||||||
DownloadingStarted?.Invoke(this, System.EventArgs.Empty);
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
|
DownloadingProgressChanged(this, new EventArgs.ProgressChangedEventArgs(0));
|
||||||
// Create client
|
|
||||||
WebClient client = new WebClient();
|
|
||||||
client.Headers.Add("Client-Id", Auth.GQLApiClientID);
|
|
||||||
|
|
||||||
// Get video GQL access token
|
// Get video GQL access token
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
JToken videoAccessToken = null;
|
||||||
JToken videoAccessToken = JArray.Parse(await client.UploadStringTaskAsync("https://gql.twitch.tv/gql", "[{\"operationName\":\"VideoAccessToken_Clip\",\"variables\":{\"slug\":\"" + ID + "\"},\"extensions\":{\"persistedQuery\":{\"version\":1,\"sha256Hash\":\"36b89d2507fce29e5ca551df756d27c1cfe079e2609642b4390aa4c35796eb11\"}}}]"))[0]["data"]["clip"]["playbackAccessToken"];
|
using (WebClient client = Client.GQL())
|
||||||
|
{
|
||||||
|
videoAccessToken = JArray.Parse(await client.UploadStringTaskAsync("https://gql.twitch.tv/gql", "[{\"operationName\":\"VideoAccessToken_Clip\",\"variables\":{\"slug\":\"" + ID + "\"},\"extensions\":{\"persistedQuery\":{\"version\":1,\"sha256Hash\":\"36b89d2507fce29e5ca551df756d27c1cfe079e2609642b4390aa4c35796eb11\"}}}]"))[0]["data"]["clip"]["playbackAccessToken"];
|
||||||
|
}
|
||||||
|
|
||||||
// Download
|
// Download
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
StorageFile rawFile = await downloadingFolder.CreateFileAsync("raw.mp4");
|
StorageFile rawFile = await downloadingFolder.CreateFileAsync("raw.mp4");
|
||||||
using (client = new WebClient())
|
using (WebClient client = new WebClient())
|
||||||
{
|
{
|
||||||
client.DownloadProgressChanged += (s, a) => { DownloadingProgressChanged(this, new ProgressChangedEventArgs(a.ProgressPercentage, null)); };
|
client.DownloadProgressChanged += (s, a) => { DownloadingProgressChanged(this, new EventArgs.ProgressChangedEventArgs(a.ProgressPercentage)); };
|
||||||
client.QueryString.Add("sig", (string)videoAccessToken["signature"]);
|
client.QueryString.Add("sig", (string)videoAccessToken["signature"]);
|
||||||
client.QueryString.Add("token", HttpUtility.UrlEncode((string)videoAccessToken["value"]));
|
client.QueryString.Add("token", HttpUtility.UrlEncode((string)videoAccessToken["value"]));
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
@@ -153,20 +132,24 @@ namespace VDownload.Core.Services.Sources.Twitch
|
|||||||
await client.DownloadFileTaskAsync(baseStream.Url, rawFile.Path);
|
await client.DownloadFileTaskAsync(baseStream.Url, rawFile.Path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
DownloadingCompleted?.Invoke(this, System.EventArgs.Empty);
|
DownloadingProgressChanged(this, new EventArgs.ProgressChangedEventArgs(100, true));
|
||||||
|
|
||||||
// Processing
|
// Processing
|
||||||
StorageFile outputFile = rawFile;
|
StorageFile outputFile = rawFile;
|
||||||
if (extension != MediaFileExtension.MP4 || mediaType != MediaType.AudioVideo || trimStart > new TimeSpan(0) || trimEnd < Duration)
|
if (extension != MediaFileExtension.MP4 || mediaType != MediaType.AudioVideo || trimStart != null || trimEnd != null)
|
||||||
{
|
{
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
outputFile = await downloadingFolder.CreateFileAsync($"transcoded.{extension.ToString().ToLower()}");
|
outputFile = await downloadingFolder.CreateFileAsync($"transcoded.{extension.ToString().ToLower()}");
|
||||||
MediaProcessor mediaProcessor = new MediaProcessor(outputFile, trimStart, trimEnd);
|
|
||||||
mediaProcessor.ProcessingStarted += ProcessingStarted;
|
MediaProcessor mediaProcessor = new MediaProcessor();
|
||||||
mediaProcessor.ProcessingProgressChanged += ProcessingProgressChanged;
|
mediaProcessor.ProgressChanged += ProcessingProgressChanged;
|
||||||
mediaProcessor.ProcessingCompleted += ProcessingCompleted;
|
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
Task mediaProcessorTask;
|
||||||
await mediaProcessor.Run(rawFile, extension, mediaType, cancellationToken);
|
if (trimStart == TimeSpan.Zero && trimEnd == Metadata.Duration) mediaProcessorTask = mediaProcessor.Run(rawFile, extension, mediaType, outputFile, cancellationToken: cancellationToken);
|
||||||
|
else if (trimStart == TimeSpan.Zero) mediaProcessorTask = mediaProcessor.Run(rawFile, extension, mediaType, outputFile, trimStart: trimStart, cancellationToken: cancellationToken);
|
||||||
|
else if (trimEnd == Metadata.Duration) mediaProcessorTask = mediaProcessor.Run(rawFile, extension, mediaType, outputFile, trimEnd: trimEnd, cancellationToken: cancellationToken);
|
||||||
|
else mediaProcessorTask = mediaProcessor.Run(rawFile, extension, mediaType, outputFile, trimStart, trimEnd, cancellationToken);
|
||||||
|
await mediaProcessorTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return output file
|
// Return output file
|
||||||
@@ -179,12 +162,8 @@ namespace VDownload.Core.Services.Sources.Twitch
|
|||||||
|
|
||||||
#region EVENT HANDLERS
|
#region EVENT HANDLERS
|
||||||
|
|
||||||
public event EventHandler DownloadingStarted;
|
public event EventHandler<EventArgs.ProgressChangedEventArgs> DownloadingProgressChanged;
|
||||||
public event EventHandler<ProgressChangedEventArgs> DownloadingProgressChanged;
|
public event EventHandler<EventArgs.ProgressChangedEventArgs> ProcessingProgressChanged;
|
||||||
public event EventHandler DownloadingCompleted;
|
|
||||||
public event EventHandler ProcessingStarted;
|
|
||||||
public event EventHandler<ProgressChangedEventArgs> ProcessingProgressChanged;
|
|
||||||
public event EventHandler ProcessingCompleted;
|
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,12 @@
|
|||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Diagnostics;
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Windows.Storage;
|
using Windows.Storage;
|
||||||
using Windows.Storage.Streams;
|
|
||||||
|
|
||||||
namespace VDownload.Core.Services.Sources.Twitch
|
namespace VDownload.Core.Services.Sources.Twitch.Helpers
|
||||||
{
|
{
|
||||||
public class Auth
|
public class Auth
|
||||||
{
|
{
|
||||||
38
VDownload.Core/Services/Sources/Twitch/Helpers/Client.cs
Normal file
38
VDownload.Core/Services/Sources/Twitch/Helpers/Client.cs
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
using System.Net;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using VDownload.Core.Exceptions;
|
||||||
|
|
||||||
|
namespace VDownload.Core.Services.Sources.Twitch.Helpers
|
||||||
|
{
|
||||||
|
internal class Client
|
||||||
|
{
|
||||||
|
internal static async Task<WebClient> Helix()
|
||||||
|
{
|
||||||
|
// Get access token
|
||||||
|
string accessToken = await Auth.ReadAccessTokenAsync();
|
||||||
|
if (accessToken == null) throw new TwitchAccessTokenNotFoundException();
|
||||||
|
|
||||||
|
// Check access token
|
||||||
|
var twitchAccessTokenValidation = await Auth.ValidateAccessTokenAsync(accessToken);
|
||||||
|
if (!twitchAccessTokenValidation.IsValid) throw new TwitchAccessTokenNotValidException();
|
||||||
|
|
||||||
|
// Create client
|
||||||
|
WebClient client = new WebClient();
|
||||||
|
client.Headers.Add("Authorization", $"Bearer {accessToken}");
|
||||||
|
client.Headers.Add("Client-Id", Auth.ClientID);
|
||||||
|
|
||||||
|
// Return client
|
||||||
|
return client;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static WebClient GQL()
|
||||||
|
{
|
||||||
|
// Create client
|
||||||
|
WebClient client = new WebClient();
|
||||||
|
client.Headers.Add("Client-Id", Auth.GQLApiClientID);
|
||||||
|
|
||||||
|
// Return client
|
||||||
|
return client;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel;
|
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
@@ -9,27 +8,15 @@ using System.Text.RegularExpressions;
|
|||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using VDownload.Core.Enums;
|
using VDownload.Core.Enums;
|
||||||
using VDownload.Core.Exceptions;
|
|
||||||
using VDownload.Core.Interfaces;
|
using VDownload.Core.Interfaces;
|
||||||
using VDownload.Core.Objects;
|
using VDownload.Core.Services.Sources.Twitch.Helpers;
|
||||||
|
using VDownload.Core.Structs;
|
||||||
using Windows.Storage;
|
using Windows.Storage;
|
||||||
|
|
||||||
namespace VDownload.Core.Services.Sources.Twitch
|
namespace VDownload.Core.Services.Sources.Twitch
|
||||||
{
|
{
|
||||||
public class Vod : IVideoService
|
public class Vod : IVideoService
|
||||||
{
|
{
|
||||||
#region CONSTANTS
|
|
||||||
|
|
||||||
// STREAMS RESPONSE REGULAR EXPRESSIONS
|
|
||||||
private static readonly Regex L2Regex = new Regex(@"^#EXT-X-STREAM-INF:BANDWIDTH=\d+,CODECS=""(?<video_codec>\S+),(?<audio_codec>\S+)"",RESOLUTION=(?<width>\d+)x(?<height>\d+),VIDEO=""\w+""(,FRAME-RATE=(?<frame_rate>\d+.\d+))?");
|
|
||||||
|
|
||||||
// CHUNK RESPONSE REGULAR EXPRESSION
|
|
||||||
private static readonly Regex ChunkRegex = new Regex(@"#EXTINF:(?<duration>\d+.\d+),\n(?<filename>\S+.ts)");
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#region CONSTRUCTORS
|
#region CONSTRUCTORS
|
||||||
|
|
||||||
public Vod(string id)
|
public Vod(string id)
|
||||||
@@ -45,13 +32,8 @@ namespace VDownload.Core.Services.Sources.Twitch
|
|||||||
|
|
||||||
public string ID { get; private set; }
|
public string ID { get; private set; }
|
||||||
public Uri VideoUrl { get; private set; }
|
public Uri VideoUrl { get; private set; }
|
||||||
public string Title { get; private set; }
|
public Metadata Metadata { get; private set; }
|
||||||
public string Author { get; private set; }
|
public BaseStream[] BaseStreams { get; private set; }
|
||||||
public DateTime Date { get; private set; }
|
|
||||||
public TimeSpan Duration { get; private set; }
|
|
||||||
public long Views { get; private set; }
|
|
||||||
public Uri Thumbnail { get; private set; }
|
|
||||||
public IBaseStream[] BaseStreams { get; private set; }
|
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
@@ -62,25 +44,16 @@ namespace VDownload.Core.Services.Sources.Twitch
|
|||||||
// GET VOD METADATA
|
// GET VOD METADATA
|
||||||
public async Task GetMetadataAsync(CancellationToken cancellationToken = default)
|
public async Task GetMetadataAsync(CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
// Get access token
|
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
string accessToken = await Auth.ReadAccessTokenAsync();
|
|
||||||
if (accessToken == null) throw new TwitchAccessTokenNotFoundException();
|
|
||||||
|
|
||||||
// Check access token
|
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
|
||||||
var twitchAccessTokenValidation = await Auth.ValidateAccessTokenAsync(accessToken);
|
|
||||||
if (!twitchAccessTokenValidation.IsValid) throw new TwitchAccessTokenNotValidException();
|
|
||||||
|
|
||||||
// Create client
|
|
||||||
WebClient client = new WebClient();
|
|
||||||
client.Headers.Add("Authorization", $"Bearer {accessToken}");
|
|
||||||
client.Headers.Add("Client-Id", Auth.ClientID);
|
|
||||||
|
|
||||||
// Get response
|
// Get response
|
||||||
|
JToken response = null;
|
||||||
|
using (WebClient client = await Client.Helix())
|
||||||
|
{
|
||||||
client.QueryString.Add("id", ID);
|
client.QueryString.Add("id", ID);
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
JToken response = JObject.Parse(await client.DownloadStringTaskAsync("https://api.twitch.tv/helix/videos")).GetValue("data")[0];
|
response = JObject.Parse(await client.DownloadStringTaskAsync("https://api.twitch.tv/helix/videos")).GetValue("data")[0];
|
||||||
|
}
|
||||||
|
|
||||||
// Set parameters
|
// Set parameters
|
||||||
GetMetadataAsync(response);
|
GetMetadataAsync(response);
|
||||||
@@ -90,83 +63,86 @@ namespace VDownload.Core.Services.Sources.Twitch
|
|||||||
// Create unified video url
|
// Create unified video url
|
||||||
VideoUrl = new Uri($"https://www.twitch.tv/videos/{ID}");
|
VideoUrl = new Uri($"https://www.twitch.tv/videos/{ID}");
|
||||||
|
|
||||||
// Set parameters
|
// Set metadata
|
||||||
Title = ((string)response["title"]).Replace("\n", "");
|
Metadata = new Metadata()
|
||||||
Author = (string)response["user_name"];
|
{
|
||||||
Date = Convert.ToDateTime(response["created_at"]);
|
Title = ((string)response["title"]).Replace("\n", ""),
|
||||||
Duration = ParseDuration((string)response["duration"]);
|
Author = (string)response["user_name"],
|
||||||
Views = (long)response["view_count"];
|
Date = Convert.ToDateTime(response["created_at"]),
|
||||||
Thumbnail = (string)response["thumbnail_url"] == string.Empty ? null : new Uri(((string)response["thumbnail_url"]).Replace("%{width}", "1920").Replace("%{height}", "1080"));
|
Duration = ParseDuration((string)response["duration"]),
|
||||||
|
Views = (long)response["view_count"],
|
||||||
|
Thumbnail = (string)response["thumbnail_url"] == string.Empty ? null : new Uri(((string)response["thumbnail_url"]).Replace("%{width}", "1920").Replace("%{height}", "1080")),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// GET VOD STREAMS
|
// GET VOD STREAMS
|
||||||
public async Task GetStreamsAsync(CancellationToken cancellationToken = default)
|
public async Task GetStreamsAsync(CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
// Create client
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
WebClient client = new WebClient();
|
|
||||||
client.Headers.Add("Client-Id", Auth.GQLApiClientID);
|
|
||||||
|
|
||||||
|
// Get response
|
||||||
|
string[] response = null;
|
||||||
|
using (WebClient client = Client.GQL())
|
||||||
|
{
|
||||||
// Get video GQL access token
|
// Get video GQL access token
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
JToken videoAccessToken = JObject.Parse(await client.UploadStringTaskAsync("https://gql.twitch.tv/gql", "{\"operationName\":\"PlaybackAccessToken_Template\",\"query\":\"query PlaybackAccessToken_Template($login: String!, $isLive: Boolean!, $vodID: ID!, $isVod: Boolean!, $playerType: String!) { streamPlaybackAccessToken(channelName: $login, params: {platform: \\\"web\\\", playerBackend: \\\"mediaplayer\\\", playerType: $playerType}) @include(if: $isLive) { value signature __typename } videoPlaybackAccessToken(id: $vodID, params: {platform: \\\"web\\\", playerBackend: \\\"mediaplayer\\\", playerType: $playerType}) @include(if: $isVod) { value signature __typename }}\",\"variables\":{\"isLive\":false,\"login\":\"\",\"isVod\":true,\"vodID\":\"" + ID + "\",\"playerType\":\"embed\"}}"))["data"]["videoPlaybackAccessToken"];
|
JToken videoAccessToken = JObject.Parse(await client.UploadStringTaskAsync("https://gql.twitch.tv/gql", "{\"operationName\":\"PlaybackAccessToken_Template\",\"query\":\"query PlaybackAccessToken_Template($login: String!, $isLive: Boolean!, $vodID: ID!, $isVod: Boolean!, $playerType: String!) { streamPlaybackAccessToken(channelName: $login, params: {platform: \\\"web\\\", playerBackend: \\\"mediaplayer\\\", playerType: $playerType}) @include(if: $isLive) { value signature __typename } videoPlaybackAccessToken(id: $vodID, params: {platform: \\\"web\\\", playerBackend: \\\"mediaplayer\\\", playerType: $playerType}) @include(if: $isVod) { value signature __typename }}\",\"variables\":{\"isLive\":false,\"login\":\"\",\"isVod\":true,\"vodID\":\"" + ID + "\",\"playerType\":\"embed\"}}"))["data"]["videoPlaybackAccessToken"];
|
||||||
|
|
||||||
// Get video streams
|
// Get video streams
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
string[] response = (await client.DownloadStringTaskAsync($"http://usher.twitch.tv/vod/{ID}?nauth={videoAccessToken["value"]}&nauthsig={videoAccessToken["signature"]}&allow_source=true&player=twitchweb")).Split("\n");
|
response = (await client.DownloadStringTaskAsync($"http://usher.twitch.tv/vod/{ID}?nauth={videoAccessToken["value"]}&nauthsig={videoAccessToken["signature"]}&allow_source=true&player=twitchweb")).Split("\n");
|
||||||
|
}
|
||||||
|
|
||||||
// Init streams list
|
// Init streams list
|
||||||
List<Stream> streams = new List<Stream>();
|
List<BaseStream> streams = new List<BaseStream>();
|
||||||
|
|
||||||
|
// Stream data line2 regular expression
|
||||||
|
Regex streamDataL2Regex = new Regex(@"^#EXT-X-STREAM-INF:BANDWIDTH=\d+,CODECS=""\S+,\S+"",RESOLUTION=\d+x(?<height>\d+),VIDEO=""\w+""(,FRAME-RATE=(?<frame_rate>\d+.\d+))?");
|
||||||
|
|
||||||
// Parse response
|
// Parse response
|
||||||
for (int i = 2; i < response.Length; i += 3)
|
for (int i = 2; i < response.Length; i += 3)
|
||||||
{
|
{
|
||||||
// Parse line 2
|
// Parse line 2
|
||||||
Match line2 = L2Regex.Match(response[i + 1]);
|
Match line2 = streamDataL2Regex.Match(response[i + 1]);
|
||||||
|
|
||||||
// Get info
|
|
||||||
Uri url = new Uri(response[i + 2]);
|
|
||||||
int width = int.Parse(line2.Groups["width"].Value);
|
|
||||||
int height = int.Parse(line2.Groups["height"].Value);
|
|
||||||
int frameRate = line2.Groups["frame_rate"].Value != string.Empty ? (int)Math.Round(double.Parse(line2.Groups["frame_rate"].Value)) : 0;
|
|
||||||
string videoCodec = line2.Groups["video_codec"].Value;
|
|
||||||
string audioCodec = line2.Groups["audio_codec"].Value;
|
|
||||||
|
|
||||||
// Create stream
|
// Create stream
|
||||||
Stream stream = new Stream(url, true, StreamType.AudioVideo)
|
BaseStream stream = new BaseStream()
|
||||||
{
|
{
|
||||||
Width = width,
|
Url = new Uri(response[i + 2]),
|
||||||
Height = height,
|
Height = int.Parse(line2.Groups["height"].Value),
|
||||||
FrameRate = frameRate,
|
FrameRate = line2.Groups["frame_rate"].Value != string.Empty ? (int)Math.Round(double.Parse(line2.Groups["frame_rate"].Value)) : 0,
|
||||||
VideoCodec = videoCodec,
|
|
||||||
AudioCodec = audioCodec,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Add stream
|
// Add stream
|
||||||
streams.Add(stream);
|
streams.Add(stream);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set Streams parameter
|
// Set streams
|
||||||
BaseStreams = streams.ToArray();
|
BaseStreams = streams.ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
// DOWNLOAD AND TRANSCODE VOD
|
// DOWNLOAD AND TRANSCODE VOD
|
||||||
public async Task<StorageFile> DownloadAndTranscodeAsync(StorageFolder downloadingFolder, IBaseStream baseStream, MediaFileExtension extension, MediaType mediaType, TimeSpan trimStart, TimeSpan trimEnd, CancellationToken cancellationToken = default)
|
public async Task<StorageFile> DownloadAndTranscodeAsync(StorageFolder downloadingFolder, BaseStream baseStream, MediaFileExtension extension, MediaType mediaType, TimeSpan trimStart, TimeSpan trimEnd, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
// Invoke DownloadingStarted event
|
// Invoke DownloadingStarted event
|
||||||
DownloadingStarted?.Invoke(this, System.EventArgs.Empty);
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
|
DownloadingProgressChanged(this, new EventArgs.ProgressChangedEventArgs(0));
|
||||||
|
|
||||||
// Get video chunks
|
// Get video chunks
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
List<(Uri ChunkUrl, TimeSpan ChunkDuration)> chunksList = await ExtractChunksFromM3U8Async(baseStream.Url, cancellationToken);
|
List<(Uri ChunkUrl, TimeSpan ChunkDuration)> chunksList = await ExtractChunksFromM3U8Async(baseStream.Url, cancellationToken);
|
||||||
|
|
||||||
|
// Changeable duration
|
||||||
|
TimeSpan duration = Metadata.Duration;
|
||||||
|
|
||||||
// Passive trim
|
// Passive trim
|
||||||
if ((bool)Config.GetValue("twitch_vod_passive_trim")) (trimStart, trimEnd) = PassiveVideoTrim(chunksList, trimStart, trimEnd, Duration);
|
if ((bool)Config.GetValue("twitch_vod_passive_trim") && trimStart != TimeSpan.Zero && trimEnd != duration) (trimStart, trimEnd, duration) = PassiveVideoTrim(chunksList, trimStart, trimEnd, Metadata.Duration);
|
||||||
|
|
||||||
// Download
|
// Download
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
StorageFile rawFile = await downloadingFolder.CreateFileAsync("raw.ts");
|
StorageFile rawFile = await downloadingFolder.CreateFileAsync("raw.ts");
|
||||||
|
|
||||||
float chunksDownloaded = 0;
|
double chunksDownloaded = 0;
|
||||||
|
|
||||||
Task<byte[]> downloadTask;
|
Task<byte[]> downloadTask;
|
||||||
Task writeTask;
|
Task writeTask;
|
||||||
@@ -180,26 +156,25 @@ namespace VDownload.Core.Services.Sources.Twitch
|
|||||||
writeTask = WriteChunkToFileAsync(rawFile, downloadTask.Result);
|
writeTask = WriteChunkToFileAsync(rawFile, downloadTask.Result);
|
||||||
downloadTask = DownloadChunkAsync(chunksList[i].ChunkUrl);
|
downloadTask = DownloadChunkAsync(chunksList[i].ChunkUrl);
|
||||||
await Task.WhenAll(writeTask, downloadTask);
|
await Task.WhenAll(writeTask, downloadTask);
|
||||||
DownloadingProgressChanged(this, new ProgressChangedEventArgs((int)Math.Round(++chunksDownloaded * 100 / chunksList.Count), null));
|
DownloadingProgressChanged(this, new EventArgs.ProgressChangedEventArgs(++chunksDownloaded * 100 / chunksList.Count));
|
||||||
}
|
}
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
await WriteChunkToFileAsync(rawFile, downloadTask.Result);
|
await WriteChunkToFileAsync(rawFile, downloadTask.Result);
|
||||||
DownloadingProgressChanged(this, new ProgressChangedEventArgs((int)Math.Round(++chunksDownloaded * 100 / chunksList.Count), null));
|
DownloadingProgressChanged(this, new EventArgs.ProgressChangedEventArgs(100, true));
|
||||||
|
|
||||||
DownloadingCompleted?.Invoke(this, System.EventArgs.Empty);
|
|
||||||
|
|
||||||
|
|
||||||
// Processing
|
// Processing
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
StorageFile outputFile = await downloadingFolder.CreateFileAsync($"transcoded.{extension.ToString().ToLower()}");
|
StorageFile outputFile = await downloadingFolder.CreateFileAsync($"transcoded.{extension.ToString().ToLower()}");
|
||||||
|
|
||||||
MediaProcessor mediaProcessor = new MediaProcessor(outputFile, trimStart, trimEnd);
|
MediaProcessor mediaProcessor = new MediaProcessor();
|
||||||
mediaProcessor.ProcessingStarted += ProcessingStarted;
|
mediaProcessor.ProgressChanged += ProcessingProgressChanged;
|
||||||
mediaProcessor.ProcessingProgressChanged += ProcessingProgressChanged;
|
|
||||||
mediaProcessor.ProcessingCompleted += ProcessingCompleted;
|
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
|
||||||
await mediaProcessor.Run(rawFile, extension, mediaType, cancellationToken);
|
|
||||||
|
|
||||||
|
Task mediaProcessorTask;
|
||||||
|
if (trimStart == TimeSpan.Zero && trimEnd == duration) mediaProcessorTask = mediaProcessor.Run(rawFile, extension, mediaType, outputFile, cancellationToken: cancellationToken);
|
||||||
|
else if (trimStart == TimeSpan.Zero) mediaProcessorTask = mediaProcessor.Run(rawFile, extension, mediaType, outputFile, trimStart: trimStart, cancellationToken: cancellationToken);
|
||||||
|
else if (trimEnd == duration) mediaProcessorTask = mediaProcessor.Run(rawFile, extension, mediaType, outputFile, trimEnd: trimEnd, cancellationToken: cancellationToken);
|
||||||
|
else mediaProcessorTask = mediaProcessor.Run(rawFile, extension, mediaType, outputFile, trimStart, trimEnd, cancellationToken);
|
||||||
|
await mediaProcessorTask;
|
||||||
|
|
||||||
// Return output file
|
// Return output file
|
||||||
return outputFile;
|
return outputFile;
|
||||||
@@ -214,21 +189,28 @@ namespace VDownload.Core.Services.Sources.Twitch
|
|||||||
// GET CHUNKS DATA FROM M3U8 PLAYLIST
|
// GET CHUNKS DATA FROM M3U8 PLAYLIST
|
||||||
private static async Task<List<(Uri ChunkUrl, TimeSpan ChunkDuration)>> ExtractChunksFromM3U8Async(Uri streamUrl, CancellationToken cancellationToken = default)
|
private static async Task<List<(Uri ChunkUrl, TimeSpan ChunkDuration)>> ExtractChunksFromM3U8Async(Uri streamUrl, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
// Create client
|
|
||||||
WebClient client = new WebClient();
|
|
||||||
client.Headers.Add("Client-Id", Auth.GQLApiClientID);
|
|
||||||
|
|
||||||
// Get playlist
|
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
string response = await client.DownloadStringTaskAsync(streamUrl);
|
|
||||||
|
// Get response
|
||||||
|
string response = null;
|
||||||
|
using (WebClient client = Client.GQL())
|
||||||
|
{
|
||||||
|
response = await client.DownloadStringTaskAsync(streamUrl);
|
||||||
|
}
|
||||||
|
|
||||||
// Create dictionary
|
// Create dictionary
|
||||||
List<(Uri ChunkUrl, TimeSpan ChunkDuration)> chunks = new List<(Uri ChunkUrl, TimeSpan ChunkDuration)>();
|
List<(Uri ChunkUrl, TimeSpan ChunkDuration)> chunks = new List<(Uri ChunkUrl, TimeSpan ChunkDuration)>();
|
||||||
|
|
||||||
|
// Chunk data regular expression
|
||||||
|
Regex chunkDataRegex = new Regex(@"#EXTINF:(?<duration>\d+.\d+),\n(?<filename>\S+.ts)");
|
||||||
|
|
||||||
|
// Chunks location
|
||||||
|
string chunkLocationPath = streamUrl.AbsoluteUri.Replace(System.IO.Path.GetFileName(streamUrl.AbsoluteUri), "");
|
||||||
|
|
||||||
// Pack data into dictionary
|
// Pack data into dictionary
|
||||||
foreach (Match chunk in ChunkRegex.Matches(response))
|
foreach (Match chunk in chunkDataRegex.Matches(response))
|
||||||
{
|
{
|
||||||
Uri chunkUrl = new Uri($"{streamUrl.AbsoluteUri.Replace(System.IO.Path.GetFileName(streamUrl.AbsoluteUri), "")}{chunk.Groups["filename"].Value}");
|
Uri chunkUrl = new Uri($"{chunkLocationPath}{chunk.Groups["filename"].Value}");
|
||||||
TimeSpan chunkDuration = TimeSpan.FromSeconds(double.Parse(chunk.Groups["duration"].Value));
|
TimeSpan chunkDuration = TimeSpan.FromSeconds(double.Parse(chunk.Groups["duration"].Value));
|
||||||
chunks.Add((chunkUrl, chunkDuration));
|
chunks.Add((chunkUrl, chunkDuration));
|
||||||
}
|
}
|
||||||
@@ -238,7 +220,7 @@ namespace VDownload.Core.Services.Sources.Twitch
|
|||||||
}
|
}
|
||||||
|
|
||||||
// PASSIVE TRIM
|
// PASSIVE TRIM
|
||||||
private static (TimeSpan TrimStart, TimeSpan TrimEnd) PassiveVideoTrim(List<(Uri ChunkUrl, TimeSpan ChunkDuration)> chunksList, TimeSpan trimStart, TimeSpan trimEnd, TimeSpan duration)
|
private static (TimeSpan NewTrimStart, TimeSpan NewTrimEnd, TimeSpan NewDuration) PassiveVideoTrim(List<(Uri ChunkUrl, TimeSpan ChunkDuration)> chunksList, TimeSpan trimStart, TimeSpan trimEnd, TimeSpan duration)
|
||||||
{
|
{
|
||||||
// Copy duration
|
// Copy duration
|
||||||
TimeSpan newDuration = duration;
|
TimeSpan newDuration = duration;
|
||||||
@@ -260,7 +242,7 @@ namespace VDownload.Core.Services.Sources.Twitch
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Return data
|
// Return data
|
||||||
return (trimStart, trimEnd);
|
return (trimStart, trimEnd, newDuration);
|
||||||
}
|
}
|
||||||
|
|
||||||
// DOWNLOAD CHUNK
|
// DOWNLOAD CHUNK
|
||||||
@@ -322,12 +304,8 @@ namespace VDownload.Core.Services.Sources.Twitch
|
|||||||
|
|
||||||
#region EVENT HANDLERS
|
#region EVENT HANDLERS
|
||||||
|
|
||||||
public event EventHandler DownloadingStarted;
|
public event EventHandler<EventArgs.ProgressChangedEventArgs> DownloadingProgressChanged;
|
||||||
public event EventHandler<ProgressChangedEventArgs> DownloadingProgressChanged;
|
public event EventHandler<EventArgs.ProgressChangedEventArgs> ProcessingProgressChanged;
|
||||||
public event EventHandler DownloadingCompleted;
|
|
||||||
public event EventHandler ProcessingStarted;
|
|
||||||
public event EventHandler<ProgressChangedEventArgs> ProcessingProgressChanged;
|
|
||||||
public event EventHandler ProcessingCompleted;
|
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,21 +7,12 @@ namespace VDownload.Core.Services
|
|||||||
{
|
{
|
||||||
#region CONSTANTS
|
#region CONSTANTS
|
||||||
|
|
||||||
// RANDOM
|
|
||||||
private static readonly Random Random = new Random();
|
|
||||||
|
|
||||||
// ID SETTINGS
|
// ID SETTINGS
|
||||||
private static readonly char[] IDChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890".ToCharArray();
|
private static readonly char[] IDChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890".ToCharArray();
|
||||||
private static readonly int IDLength = 10;
|
private static readonly int IDLength = 10;
|
||||||
|
|
||||||
#endregion
|
// IDS LIST
|
||||||
|
private static readonly List<string> IDList = new List<string>();
|
||||||
|
|
||||||
|
|
||||||
#region PROPERTIES
|
|
||||||
|
|
||||||
// USED IDS LIST
|
|
||||||
private static readonly List<string> UsedIDs = new List<string>();
|
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
@@ -38,17 +29,17 @@ namespace VDownload.Core.Services
|
|||||||
id = "";
|
id = "";
|
||||||
while (id.Length < IDLength)
|
while (id.Length < IDLength)
|
||||||
{
|
{
|
||||||
id += IDChars[Random.Next(0, IDChars.Length)];
|
id += IDChars[new Random().Next(0, IDChars.Length)];
|
||||||
}
|
}
|
||||||
} while (UsedIDs.Contains(id));
|
} while (IDList.Contains(id));
|
||||||
UsedIDs.Add(id);
|
IDList.Add(id);
|
||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
|
|
||||||
// DISPOSE TASK ID
|
// DISPOSE TASK ID
|
||||||
public static void Dispose(string id)
|
public static void Dispose(string id)
|
||||||
{
|
{
|
||||||
UsedIDs.Remove(id);
|
IDList.Remove(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|||||||
61
VDownload.Core/Services/TimeSpanCustomFormat.cs
Normal file
61
VDownload.Core/Services/TimeSpanCustomFormat.cs
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace VDownload.Core.Services
|
||||||
|
{
|
||||||
|
public class TimeSpanCustomFormat
|
||||||
|
{
|
||||||
|
// (TH:)MM:SS
|
||||||
|
public static string ToOptTHBaseMMSS(TimeSpan timeSpan, params TimeSpan[] formatBase)
|
||||||
|
{
|
||||||
|
string formattedTimeSpan = string.Empty;
|
||||||
|
|
||||||
|
int maxTHLength = 0;
|
||||||
|
if (Math.Floor(timeSpan.TotalHours) > 0)
|
||||||
|
{
|
||||||
|
maxTHLength = Math.Floor(timeSpan.TotalHours).ToString().Length;
|
||||||
|
foreach (TimeSpan format in formatBase)
|
||||||
|
{
|
||||||
|
int THLength = Math.Floor(format.TotalHours) > 0 ? Math.Floor(timeSpan.TotalHours).ToString().Length : 0;
|
||||||
|
if (THLength > maxTHLength) maxTHLength = THLength;
|
||||||
|
}
|
||||||
|
formattedTimeSpan += $"{((int)Math.Floor(timeSpan.TotalHours)).ToString($"D{maxTHLength}")}:";
|
||||||
|
}
|
||||||
|
formattedTimeSpan += maxTHLength == 0 ? $"{timeSpan.Minutes}:" : $"{timeSpan.Minutes:00}:";
|
||||||
|
formattedTimeSpan += $"{timeSpan.Seconds:00}";
|
||||||
|
|
||||||
|
return formattedTimeSpan;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ((TH:)MM:)SS
|
||||||
|
public static string ToOptTHMMBaseSS(TimeSpan timeSpan, params TimeSpan[] formatBase)
|
||||||
|
{
|
||||||
|
string formattedTimeSpan = string.Empty;
|
||||||
|
|
||||||
|
int maxTHLength = 0;
|
||||||
|
if (Math.Floor(timeSpan.TotalHours) > 0)
|
||||||
|
{
|
||||||
|
maxTHLength = Math.Floor(timeSpan.TotalHours).ToString().Length;
|
||||||
|
foreach (TimeSpan format in formatBase)
|
||||||
|
{
|
||||||
|
int THLength = Math.Floor(format.TotalHours) > 0 ? Math.Floor(timeSpan.TotalHours).ToString().Length : 0;
|
||||||
|
if (THLength > maxTHLength) maxTHLength = THLength;
|
||||||
|
}
|
||||||
|
formattedTimeSpan += $"{((int)Math.Floor(timeSpan.TotalHours)).ToString($"D{maxTHLength}")}:";
|
||||||
|
}
|
||||||
|
bool MM = false;
|
||||||
|
if (Math.Floor(timeSpan.TotalMinutes) > 0)
|
||||||
|
{
|
||||||
|
formattedTimeSpan += maxTHLength > 0 ? $"{timeSpan.Minutes:00}:" : $"{timeSpan.Minutes}:";
|
||||||
|
MM = true;
|
||||||
|
}
|
||||||
|
formattedTimeSpan += MM ? $"{timeSpan.Seconds:00}:" : $"{timeSpan.Seconds}:";
|
||||||
|
|
||||||
|
return formattedTimeSpan;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
15
VDownload.Core/Structs/BaseStream.cs
Normal file
15
VDownload.Core/Structs/BaseStream.cs
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace VDownload.Core.Structs
|
||||||
|
{
|
||||||
|
public struct BaseStream
|
||||||
|
{
|
||||||
|
public Uri Url { get; set; }
|
||||||
|
public int Height { get; set; }
|
||||||
|
public int FrameRate { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
18
VDownload.Core/Structs/Metadata.cs
Normal file
18
VDownload.Core/Structs/Metadata.cs
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace VDownload.Core.Structs
|
||||||
|
{
|
||||||
|
public struct Metadata
|
||||||
|
{
|
||||||
|
public string Title { get; set; }
|
||||||
|
public string Author { get; set; }
|
||||||
|
public DateTime Date { get; set; }
|
||||||
|
public TimeSpan Duration { get; set; }
|
||||||
|
public long Views { get; set; }
|
||||||
|
public Uri Thumbnail { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,16 +1,15 @@
|
|||||||
using System;
|
using System;
|
||||||
using VDownload.Core.Enums;
|
using VDownload.Core.Enums;
|
||||||
using VDownload.Core.Interfaces;
|
using VDownload.Core.Interfaces;
|
||||||
using VDownload.Core.Objects;
|
|
||||||
using Windows.Storage;
|
using Windows.Storage;
|
||||||
|
|
||||||
namespace VDownload.Core.EventArgs
|
namespace VDownload.Core.Structs
|
||||||
{
|
{
|
||||||
public class VideoAddEventArgs : System.EventArgs
|
public struct TaskData
|
||||||
{
|
{
|
||||||
public IVideoService VideoService { get; set; }
|
public IVideoService VideoService { get; set; }
|
||||||
public MediaType MediaType { get; set; }
|
public MediaType MediaType { get; set; }
|
||||||
public IBaseStream Stream { get; set; }
|
public BaseStream Stream { get; set; }
|
||||||
public TimeSpan TrimStart { get; set; }
|
public TimeSpan TrimStart { get; set; }
|
||||||
public TimeSpan TrimEnd { get; set; }
|
public TimeSpan TrimEnd { get; set; }
|
||||||
public string Filename { get; set; }
|
public string Filename { get; set; }
|
||||||
@@ -124,24 +124,27 @@
|
|||||||
<Compile Include="Enums\MediaFileExtension.cs" />
|
<Compile Include="Enums\MediaFileExtension.cs" />
|
||||||
<Compile Include="Enums\MediaType.cs" />
|
<Compile Include="Enums\MediaType.cs" />
|
||||||
<Compile Include="Enums\PlaylistSource.cs" />
|
<Compile Include="Enums\PlaylistSource.cs" />
|
||||||
<Compile Include="Enums\StreamType.cs" />
|
<Compile Include="Enums\TaskAddingRequestSource.cs" />
|
||||||
<Compile Include="Enums\VideoFileExtension.cs" />
|
<Compile Include="Enums\VideoFileExtension.cs" />
|
||||||
<Compile Include="Enums\VideoSource.cs" />
|
<Compile Include="Enums\VideoSource.cs" />
|
||||||
<Compile Include="Enums\TaskStatus.cs" />
|
<Compile Include="Enums\TaskStatus.cs" />
|
||||||
<Compile Include="EventArgs\PlaylistAddEventArgs.cs" />
|
<Compile Include="EventArgs\ProgressChangedEventArgs.cs" />
|
||||||
<Compile Include="EventArgs\VideoAddEventArgs.cs" />
|
<Compile Include="EventArgs\TasksAddingRequestedEventArgs.cs" />
|
||||||
<Compile Include="EventArgs\VideoSearchEventArgs.cs" />
|
<Compile Include="EventArgs\VideoSearchEventArgs.cs" />
|
||||||
<Compile Include="EventArgs\PlaylistSearchEventArgs.cs" />
|
<Compile Include="EventArgs\PlaylistSearchEventArgs.cs" />
|
||||||
<Compile Include="Exceptions\TwitchAccessTokenNotFoundException.cs" />
|
<Compile Include="Exceptions\TwitchAccessTokenNotFoundException.cs" />
|
||||||
<Compile Include="Exceptions\TwitchAccessTokenNotValidException.cs" />
|
<Compile Include="Exceptions\TwitchAccessTokenNotValidException.cs" />
|
||||||
<Compile Include="Interfaces\IBaseStream.cs" />
|
|
||||||
<Compile Include="Interfaces\IPlaylistService.cs" />
|
<Compile Include="Interfaces\IPlaylistService.cs" />
|
||||||
<Compile Include="Interfaces\IVideoService.cs" />
|
<Compile Include="Interfaces\IVideoService.cs" />
|
||||||
<Compile Include="Objects\Stream.cs" />
|
<Compile Include="Services\Sources\Twitch\Helpers\Client.cs" />
|
||||||
|
<Compile Include="Services\TimeSpanCustomFormat.cs" />
|
||||||
|
<Compile Include="Structs\BaseStream.cs" />
|
||||||
|
<Compile Include="Structs\Metadata.cs" />
|
||||||
|
<Compile Include="Structs\TaskData.cs" />
|
||||||
<Compile Include="Services\Config.cs" />
|
<Compile Include="Services\Config.cs" />
|
||||||
<Compile Include="Services\MediaProcessor.cs" />
|
<Compile Include="Services\MediaProcessor.cs" />
|
||||||
<Compile Include="Services\Source.cs" />
|
<Compile Include="Services\Source.cs" />
|
||||||
<Compile Include="Services\Sources\Twitch\Auth.cs" />
|
<Compile Include="Services\Sources\Twitch\Helpers\Auth.cs" />
|
||||||
<Compile Include="Services\Sources\Twitch\Channel.cs" />
|
<Compile Include="Services\Sources\Twitch\Channel.cs" />
|
||||||
<Compile Include="Services\Sources\Twitch\Clip.cs" />
|
<Compile Include="Services\Sources\Twitch\Clip.cs" />
|
||||||
<Compile Include="Services\Sources\Twitch\Vod.cs" />
|
<Compile Include="Services\Sources\Twitch\Vod.cs" />
|
||||||
|
|||||||
@@ -182,7 +182,7 @@ The number in the numberbox indicades how many videos will be got from playlist.
|
|||||||
<value>90</value>
|
<value>90</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="HomeOptionsBarLoadSubscripionsButton.Label" xml:space="preserve">
|
<data name="HomeOptionsBarLoadSubscripionsButton.Label" xml:space="preserve">
|
||||||
<value>Load subscription</value>
|
<value>Load subscriptions</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="HomeOptionsBarLoadSubscripionsButton.Width" xml:space="preserve">
|
<data name="HomeOptionsBarLoadSubscripionsButton.Width" xml:space="preserve">
|
||||||
<value>120</value>
|
<value>120</value>
|
||||||
|
|||||||
@@ -1,348 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<root>
|
|
||||||
<!--
|
|
||||||
Microsoft ResX Schema
|
|
||||||
|
|
||||||
Version 2.0
|
|
||||||
|
|
||||||
The primary goals of this format is to allow a simple XML format
|
|
||||||
that is mostly human readable. The generation and parsing of the
|
|
||||||
various data types are done through the TypeConverter classes
|
|
||||||
associated with the data types.
|
|
||||||
|
|
||||||
Example:
|
|
||||||
|
|
||||||
... ado.net/XML headers & schema ...
|
|
||||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
|
||||||
<resheader name="version">2.0</resheader>
|
|
||||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
|
||||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
|
||||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
|
||||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
|
||||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
|
||||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
|
||||||
</data>
|
|
||||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
|
||||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
|
||||||
<comment>This is a comment</comment>
|
|
||||||
</data>
|
|
||||||
|
|
||||||
There are any number of "resheader" rows that contain simple
|
|
||||||
name/value pairs.
|
|
||||||
|
|
||||||
Each data row contains a name, and value. The row also contains a
|
|
||||||
type or mimetype. Type corresponds to a .NET class that support
|
|
||||||
text/value conversion through the TypeConverter architecture.
|
|
||||||
Classes that don't support this are serialized and stored with the
|
|
||||||
mimetype set.
|
|
||||||
|
|
||||||
The mimetype is used for serialized objects, and tells the
|
|
||||||
ResXResourceReader how to depersist the object. This is currently not
|
|
||||||
extensible. For a given mimetype the value must be set accordingly:
|
|
||||||
|
|
||||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
|
||||||
that the ResXResourceWriter will generate, however the reader can
|
|
||||||
read any of the formats listed below.
|
|
||||||
|
|
||||||
mimetype: application/x-microsoft.net.object.binary.base64
|
|
||||||
value : The object must be serialized with
|
|
||||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
|
||||||
: and then encoded with base64 encoding.
|
|
||||||
|
|
||||||
mimetype: application/x-microsoft.net.object.soap.base64
|
|
||||||
value : The object must be serialized with
|
|
||||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
|
||||||
: and then encoded with base64 encoding.
|
|
||||||
|
|
||||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
|
||||||
value : The object must be serialized into a byte array
|
|
||||||
: using a System.ComponentModel.TypeConverter
|
|
||||||
: and then encoded with base64 encoding.
|
|
||||||
-->
|
|
||||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
|
||||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
|
||||||
<xsd:element name="root" msdata:IsDataSet="true">
|
|
||||||
<xsd:complexType>
|
|
||||||
<xsd:choice maxOccurs="unbounded">
|
|
||||||
<xsd:element name="metadata">
|
|
||||||
<xsd:complexType>
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
|
||||||
</xsd:sequence>
|
|
||||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
|
||||||
<xsd:attribute name="type" type="xsd:string" />
|
|
||||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
|
||||||
<xsd:attribute ref="xml:space" />
|
|
||||||
</xsd:complexType>
|
|
||||||
</xsd:element>
|
|
||||||
<xsd:element name="assembly">
|
|
||||||
<xsd:complexType>
|
|
||||||
<xsd:attribute name="alias" type="xsd:string" />
|
|
||||||
<xsd:attribute name="name" type="xsd:string" />
|
|
||||||
</xsd:complexType>
|
|
||||||
</xsd:element>
|
|
||||||
<xsd:element name="data">
|
|
||||||
<xsd:complexType>
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
|
||||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
|
||||||
</xsd:sequence>
|
|
||||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
|
||||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
|
||||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
|
||||||
<xsd:attribute ref="xml:space" />
|
|
||||||
</xsd:complexType>
|
|
||||||
</xsd:element>
|
|
||||||
<xsd:element name="resheader">
|
|
||||||
<xsd:complexType>
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
|
||||||
</xsd:sequence>
|
|
||||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
|
||||||
</xsd:complexType>
|
|
||||||
</xsd:element>
|
|
||||||
</xsd:choice>
|
|
||||||
</xsd:complexType>
|
|
||||||
</xsd:element>
|
|
||||||
</xsd:schema>
|
|
||||||
<resheader name="resmimetype">
|
|
||||||
<value>text/microsoft-resx</value>
|
|
||||||
</resheader>
|
|
||||||
<resheader name="version">
|
|
||||||
<value>2.0</value>
|
|
||||||
</resheader>
|
|
||||||
<resheader name="reader">
|
|
||||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
|
||||||
</resheader>
|
|
||||||
<resheader name="writer">
|
|
||||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
|
||||||
</resheader>
|
|
||||||
<data name="AddPlaylistBase.CloseButtonText" xml:space="preserve">
|
|
||||||
<value>Cancel</value>
|
|
||||||
</data>
|
|
||||||
<data name="AddPlaylistBase.PrimaryButtonText" xml:space="preserve">
|
|
||||||
<value>Add</value>
|
|
||||||
</data>
|
|
||||||
<data name="AddPlaylistBase.Title" xml:space="preserve">
|
|
||||||
<value>ADD PLAYLIST</value>
|
|
||||||
</data>
|
|
||||||
<data name="AddPlaylistDeletedVideosPanelButton" xml:space="preserve">
|
|
||||||
<value>Restore</value>
|
|
||||||
</data>
|
|
||||||
<data name="AddPlaylistDeletedVideosPanelTextBlock" xml:space="preserve">
|
|
||||||
<value>{x} videos removed</value>
|
|
||||||
</data>
|
|
||||||
<data name="AddPlaylistLocationSelectionApplyLocationButton.Content" xml:space="preserve">
|
|
||||||
<value>Apply</value>
|
|
||||||
</data>
|
|
||||||
<data name="AddPlaylistLocationSelectionTextBlock.Text" xml:space="preserve">
|
|
||||||
<value>Location</value>
|
|
||||||
</data>
|
|
||||||
<data name="AddPlaylistNotFoundText" xml:space="preserve">
|
|
||||||
<value>Playlist not found. Try again.</value>
|
|
||||||
</data>
|
|
||||||
<data name="AddPlaylistSearchButton.Content" xml:space="preserve">
|
|
||||||
<value>Search</value>
|
|
||||||
</data>
|
|
||||||
<data name="AddPlaylistStartText.Text" xml:space="preserve">
|
|
||||||
<value>Paste URL and click "Search" button</value>
|
|
||||||
</data>
|
|
||||||
<data name="AddPlaylistUrlPathText.Text" xml:space="preserve">
|
|
||||||
<value>URL</value>
|
|
||||||
</data>
|
|
||||||
<data name="AddPlaylistVideoDownloadOptionsMediaTypeRadiobuttonA" xml:space="preserve">
|
|
||||||
<value>Only audio</value>
|
|
||||||
</data>
|
|
||||||
<data name="AddPlaylistVideoDownloadOptionsMediaTypeRadiobuttonAV" xml:space="preserve">
|
|
||||||
<value>Normal</value>
|
|
||||||
</data>
|
|
||||||
<data name="AddPlaylistVideoDownloadOptionsMediaTypeRadiobuttonV" xml:space="preserve">
|
|
||||||
<value>Only video</value>
|
|
||||||
</data>
|
|
||||||
<data name="AddPlaylistVideoDownloadOptionsMediaTypeTextBlock" xml:space="preserve">
|
|
||||||
<value>Media type</value>
|
|
||||||
</data>
|
|
||||||
<data name="AddPlaylistVideoDownloadOptionsQualityTextBlock" xml:space="preserve">
|
|
||||||
<value>Quality</value>
|
|
||||||
</data>
|
|
||||||
<data name="AddPlaylistVideoDownloadOptionsTrimTextBlock" xml:space="preserve">
|
|
||||||
<value>Trim</value>
|
|
||||||
</data>
|
|
||||||
<data name="AddPlaylistVideoFileDataTextBlock" xml:space="preserve">
|
|
||||||
<value>File</value>
|
|
||||||
</data>
|
|
||||||
<data name="AddPlaylistVideoLocationDataTextBlock" xml:space="preserve">
|
|
||||||
<value>Location</value>
|
|
||||||
</data>
|
|
||||||
<data name="AddPlaylistVideoQualityNoVideoStream" xml:space="preserve">
|
|
||||||
<value>Only audio</value>
|
|
||||||
</data>
|
|
||||||
<data name="AddVideoBase.CloseButtonText" xml:space="preserve">
|
|
||||||
<value>Cancel</value>
|
|
||||||
</data>
|
|
||||||
<data name="AddVideoBase.PrimaryButtonText" xml:space="preserve">
|
|
||||||
<value>Add</value>
|
|
||||||
</data>
|
|
||||||
<data name="AddVideoBase.Title" xml:space="preserve">
|
|
||||||
<value>ADD VIDEO</value>
|
|
||||||
</data>
|
|
||||||
<data name="AddVideoNotFoundText.Text" xml:space="preserve">
|
|
||||||
<value>Video not found. Try again.</value>
|
|
||||||
</data>
|
|
||||||
<data name="AddVideoStartText.Text" xml:space="preserve">
|
|
||||||
<value>Paste URL and click "Search" button</value>
|
|
||||||
</data>
|
|
||||||
<data name="MainPageNavigationPanelStartItem.Content" xml:space="preserve">
|
|
||||||
<value>Start</value>
|
|
||||||
</data>
|
|
||||||
<data name="MainPageNavigationPanelSubscriptionsItem.Content" xml:space="preserve">
|
|
||||||
<value>Subscriptions</value>
|
|
||||||
</data>
|
|
||||||
<data name="StartAddingBarDownloadOptionsMediaTypeTextBlockText" xml:space="preserve">
|
|
||||||
<value>Media type</value>
|
|
||||||
</data>
|
|
||||||
<data name="StartAddingBarDownloadOptionsQualityTextBlockText" xml:space="preserve">
|
|
||||||
<value>Quality</value>
|
|
||||||
</data>
|
|
||||||
<data name="StartAddingBarDownloadOptionsTextBlockText" xml:space="preserve">
|
|
||||||
<value>Download options</value>
|
|
||||||
</data>
|
|
||||||
<data name="StartAddingBarDownloadOptionsTrimTextBlockText" xml:space="preserve">
|
|
||||||
<value>Trim</value>
|
|
||||||
</data>
|
|
||||||
<data name="StartAddingBarDownloadQualityNoVideoStream" xml:space="preserve">
|
|
||||||
<value>Only audio</value>
|
|
||||||
</data>
|
|
||||||
<data name="StartAddingBarPlaylistLocationApplyButtonText" xml:space="preserve">
|
|
||||||
<value>Apply</value>
|
|
||||||
</data>
|
|
||||||
<data name="StartAddingBarSaveOptionsFileDescriptionTextBlockText" xml:space="preserve">
|
|
||||||
<value>Filename and extension</value>
|
|
||||||
</data>
|
|
||||||
<data name="StartAddingBarSaveOptionsFileTextBlockText" xml:space="preserve">
|
|
||||||
<value>File</value>
|
|
||||||
</data>
|
|
||||||
<data name="StartAddingBarSaveOptionsLocationTextBlockText" xml:space="preserve">
|
|
||||||
<value>Location</value>
|
|
||||||
</data>
|
|
||||||
<data name="StartAddingBarSaveOptionsTextBlockText" xml:space="preserve">
|
|
||||||
<value>Save options</value>
|
|
||||||
</data>
|
|
||||||
<data name="StartAddingBarVideoOptionsFileTextBlockText" xml:space="preserve">
|
|
||||||
<value>File</value>
|
|
||||||
</data>
|
|
||||||
<data name="StartAddingBarVideoOptionsLocationButtonText" xml:space="preserve">
|
|
||||||
<value>Browse</value>
|
|
||||||
</data>
|
|
||||||
<data name="StartAddingBarVideoOptionsLocationTextBlockText" xml:space="preserve">
|
|
||||||
<value>Location</value>
|
|
||||||
</data>
|
|
||||||
<data name="StartAddingBarVideoOptionsMediaTypeComboBoxAText" xml:space="preserve">
|
|
||||||
<value>Only audio</value>
|
|
||||||
</data>
|
|
||||||
<data name="StartAddingBarVideoOptionsMediaTypeComboBoxAVText" xml:space="preserve">
|
|
||||||
<value>Normal</value>
|
|
||||||
</data>
|
|
||||||
<data name="StartAddingBarVideoOptionsMediaTypeComboBoxVText" xml:space="preserve">
|
|
||||||
<value>Only video</value>
|
|
||||||
</data>
|
|
||||||
<data name="StartAddPlaylistInputInfoTeachingTipSubtitleNumberBox" xml:space="preserve">
|
|
||||||
<value>If numberbox is set to 0, app will load all videos from the playlist. Otherwise, the program will load the specified number of videos.</value>
|
|
||||||
</data>
|
|
||||||
<data name="StartAddPlaylistInputInfoTeachingTipSubtitleTwitch" xml:space="preserve">
|
|
||||||
<value>- Twitch (Channels)</value>
|
|
||||||
</data>
|
|
||||||
<data name="StartAddPlaylistInputInfoTeachingTipSubtitleYoutube" xml:space="preserve">
|
|
||||||
<value>- Youtube (Playlists, Channels)</value>
|
|
||||||
</data>
|
|
||||||
<data name="StartAddPlaylistInputInfoTeachingTipTitle" xml:space="preserve">
|
|
||||||
<value>Supported websites, types of playlist and numberbox instruction</value>
|
|
||||||
</data>
|
|
||||||
<data name="StartAddPlaylistInputSearchButtonText" xml:space="preserve">
|
|
||||||
<value>Search</value>
|
|
||||||
</data>
|
|
||||||
<data name="StartAddPlaylistInputTextBoxPlaceholderText" xml:space="preserve">
|
|
||||||
<value>Playlist URL</value>
|
|
||||||
</data>
|
|
||||||
<data name="StartAddVideoInputInfoTeachingTipSubtitleTwitch" xml:space="preserve">
|
|
||||||
<value>- Twitch (VODs, Clips)</value>
|
|
||||||
</data>
|
|
||||||
<data name="StartAddVideoInputInfoTeachingTipSubtitleYoutube" xml:space="preserve">
|
|
||||||
<value>- Youtube (Videos)</value>
|
|
||||||
</data>
|
|
||||||
<data name="StartAddVideoInputInfoTeachingTipTitle" xml:space="preserve">
|
|
||||||
<value>Supported websites and types of videos</value>
|
|
||||||
</data>
|
|
||||||
<data name="StartAddVideoInputSearchButtonText" xml:space="preserve">
|
|
||||||
<value>Search</value>
|
|
||||||
</data>
|
|
||||||
<data name="StartAddVideoInputTextBoxPlaceholderText" xml:space="preserve">
|
|
||||||
<value>Video URL</value>
|
|
||||||
</data>
|
|
||||||
<data name="StartOptionsBarAddPlaylistButton.Label" xml:space="preserve">
|
|
||||||
<value>Add playlist</value>
|
|
||||||
</data>
|
|
||||||
<data name="StartOptionsBarAddPlaylistButton.Width" xml:space="preserve">
|
|
||||||
<value>85</value>
|
|
||||||
</data>
|
|
||||||
<data name="StartOptionsBarAddVideoButton.Label" xml:space="preserve">
|
|
||||||
<value>Add video</value>
|
|
||||||
</data>
|
|
||||||
<data name="StartOptionsBarAddVideoButton.Width" xml:space="preserve">
|
|
||||||
<value>75</value>
|
|
||||||
</data>
|
|
||||||
<data name="StartOptionsBarDownloadAllButton.Label" xml:space="preserve">
|
|
||||||
<value>Download All</value>
|
|
||||||
</data>
|
|
||||||
<data name="StartOptionsBarDownloadAllButton.Width" xml:space="preserve">
|
|
||||||
<value>90</value>
|
|
||||||
</data>
|
|
||||||
<data name="StartOptionsBarLoadSubscripionsButton.Label" xml:space="preserve">
|
|
||||||
<value>Load subscriptions</value>
|
|
||||||
</data>
|
|
||||||
<data name="StartOptionsBarLoadSubscripionsButton.Width" xml:space="preserve">
|
|
||||||
<value>120</value>
|
|
||||||
</data>
|
|
||||||
<data name="VideoPanelMediaTypeDataA" xml:space="preserve">
|
|
||||||
<value>Only audio</value>
|
|
||||||
</data>
|
|
||||||
<data name="VideoPanelMediaTypeDataAV" xml:space="preserve">
|
|
||||||
<value>Audio & 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>
|
|
||||||
@@ -365,9 +365,6 @@
|
|||||||
<Version>2.8.0-prerelease.220118001</Version>
|
<Version>2.8.0-prerelease.220118001</Version>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
|
||||||
<PRIResource Include="Strings\en-US\ResourcesOld.resw" />
|
|
||||||
</ItemGroup>
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PRIResource Include="Strings\en-US\Resources.resw" />
|
<PRIResource Include="Strings\en-US\Resources.resw" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|||||||
@@ -60,7 +60,7 @@
|
|||||||
|
|
||||||
<!-- VIDEOS LIST -->
|
<!-- VIDEOS LIST -->
|
||||||
<ScrollViewer Margin="0,0,0,10" CornerRadius="{ThemeResource ControlCornerRadius}">
|
<ScrollViewer Margin="0,0,0,10" CornerRadius="{ThemeResource ControlCornerRadius}">
|
||||||
<ContentControl x:Name="HomeTasksListPlace" HorizontalContentAlignment="Stretch" VerticalContentAlignment="Stretch"/>
|
<ContentControl x:Name="HomeTasksListParent" HorizontalContentAlignment="Stretch" VerticalContentAlignment="Stretch"/>
|
||||||
</ScrollViewer>
|
</ScrollViewer>
|
||||||
|
|
||||||
<!-- OPTIONS BAR AND ADDING PANEL -->
|
<!-- OPTIONS BAR AND ADDING PANEL -->
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ using VDownload.Core.EventArgs;
|
|||||||
using VDownload.Core.Exceptions;
|
using VDownload.Core.Exceptions;
|
||||||
using VDownload.Core.Interfaces;
|
using VDownload.Core.Interfaces;
|
||||||
using VDownload.Core.Services;
|
using VDownload.Core.Services;
|
||||||
|
using VDownload.Core.Structs;
|
||||||
using Windows.ApplicationModel.Resources;
|
using Windows.ApplicationModel.Resources;
|
||||||
using Windows.UI.Xaml;
|
using Windows.UI.Xaml;
|
||||||
using Windows.UI.Xaml.Controls;
|
using Windows.UI.Xaml.Controls;
|
||||||
@@ -21,11 +22,29 @@ namespace VDownload.Views.Home
|
|||||||
{
|
{
|
||||||
public sealed partial class HomeMain : Page
|
public sealed partial class HomeMain : Page
|
||||||
{
|
{
|
||||||
|
#region CONSTANTS
|
||||||
|
|
||||||
|
// RESOURCES
|
||||||
|
private static readonly ResourceDictionary ImageRes = new ResourceDictionary { Source = new Uri("ms-appx:///Resources/Icons.xaml") };
|
||||||
|
|
||||||
|
// SEARCHING STATUS CONTROLS
|
||||||
|
private static readonly Microsoft.UI.Xaml.Controls.ProgressRing HomeOptionsBarSearchingStatusProgressRing = new Microsoft.UI.Xaml.Controls.ProgressRing { Width = 15, Height = 15, Margin = new Thickness(5), IsActive = true };
|
||||||
|
private static readonly Image HomeOptionsBarSearchingStatusErrorImage = new Image { Width = 15, Height = 15, Margin = new Thickness(5), Source = (SvgImageSource)ImageRes["ErrorIcon"] };
|
||||||
|
|
||||||
|
// TASKS LIST PLACEHOLDER
|
||||||
|
private static readonly HomeTasksListPlaceholder HomeTasksListPlaceholder = new HomeTasksListPlaceholder();
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
#region CONSTRUCTORS
|
#region CONSTRUCTORS
|
||||||
|
|
||||||
public HomeMain()
|
public HomeMain()
|
||||||
{
|
{
|
||||||
this.InitializeComponent();
|
this.InitializeComponent();
|
||||||
|
|
||||||
|
// Set cancellation token
|
||||||
|
SearchingCancellationToken = new CancellationTokenSource();
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
@@ -34,20 +53,13 @@ namespace VDownload.Views.Home
|
|||||||
|
|
||||||
#region PROPERTIES
|
#region PROPERTIES
|
||||||
|
|
||||||
// SEARCHING STATUS CONTROLS
|
|
||||||
private readonly Microsoft.UI.Xaml.Controls.ProgressRing HomeOptionsBarSearchingStatusProgressRing = new Microsoft.UI.Xaml.Controls.ProgressRing { Width = 15, Height = 15, Margin = new Thickness(5), IsActive = true };
|
|
||||||
private readonly Image HomeOptionsBarSearchingStatusErrorImage = new Image { Width = 15, Height = 15, Margin = new Thickness(5), Source = (SvgImageSource)new ResourceDictionary { Source = new Uri("ms-appx:///Resources/Icons.xaml") }["ErrorIcon"] };
|
|
||||||
|
|
||||||
// CANCELLATON TOKEN
|
// CANCELLATON TOKEN
|
||||||
private CancellationTokenSource SearchingCancellationToken = new CancellationTokenSource();
|
private CancellationTokenSource SearchingCancellationToken { get; set; }
|
||||||
|
|
||||||
// HOME TASKS LIST PLACEHOLDER
|
// HOME TASKS LIST
|
||||||
private readonly HomeTasksListPlaceholder HomeTasksListPlaceholder = new HomeTasksListPlaceholder();
|
private static ContentControl HomeTasksListCurrentParent = null;
|
||||||
|
|
||||||
// HOME VIDEOS LIST
|
|
||||||
private static ContentControl HomeTasksListPlaceCurrent { get; set; }
|
|
||||||
private static StackPanel HomeTasksList = null;
|
private static StackPanel HomeTasksList = null;
|
||||||
public static List<HomeTaskPanel> TaskPanelsList = new List<HomeTaskPanel>();
|
public static List<HomeTaskPanel> TasksList = new List<HomeTaskPanel>();
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
@@ -58,20 +70,28 @@ namespace VDownload.Views.Home
|
|||||||
// ON NAVIGATED TO
|
// ON NAVIGATED TO
|
||||||
protected override void OnNavigatedTo(NavigationEventArgs e)
|
protected override void OnNavigatedTo(NavigationEventArgs e)
|
||||||
{
|
{
|
||||||
HomeTasksListPlaceCurrent = HomeTasksListPlace;
|
// Set current panel
|
||||||
|
HomeTasksListCurrentParent = HomeTasksListParent;
|
||||||
|
|
||||||
|
// Detach task panels from old task list
|
||||||
if (HomeTasksList != null) HomeTasksList.Children.Clear();
|
if (HomeTasksList != null) HomeTasksList.Children.Clear();
|
||||||
|
|
||||||
|
// Create new task list
|
||||||
HomeTasksList = new StackPanel { Spacing = 10 };
|
HomeTasksList = new StackPanel { Spacing = 10 };
|
||||||
if (TaskPanelsList.Count > 0)
|
|
||||||
|
// Attach task panels to new task list
|
||||||
|
if (TasksList.Count > 0)
|
||||||
{
|
{
|
||||||
foreach (HomeTaskPanel homeVideoPanel in TaskPanelsList) HomeTasksList.Children.Add(homeVideoPanel);
|
foreach (HomeTaskPanel homeVideoPanel in TasksList) HomeTasksList.Children.Add(homeVideoPanel);
|
||||||
HomeTasksListPlaceCurrent.Content = HomeTasksList;
|
HomeTasksListCurrentParent.Content = HomeTasksList;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
HomeTasksListPlaceCurrent.Content = HomeTasksListPlaceholder;
|
HomeTasksListCurrentParent.Content = HomeTasksListPlaceholder;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// ADD VIDEO BUTTON CHECKED
|
// ADD VIDEO BUTTON CHECKED
|
||||||
private void HomeOptionsBarAddVideoButton_Checked(object sender, RoutedEventArgs e)
|
private void HomeOptionsBarAddVideoButton_Checked(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
@@ -100,7 +120,7 @@ namespace VDownload.Views.Home
|
|||||||
HomeOptionsBarSearchingStatusControl.Content = HomeOptionsBarSearchingStatusProgressRing;
|
HomeOptionsBarSearchingStatusControl.Content = HomeOptionsBarSearchingStatusProgressRing;
|
||||||
|
|
||||||
// Parse url
|
// Parse url
|
||||||
(VideoSource Type, string ID) source = Source.GetVideoSource(e.Phrase);
|
(VideoSource Type, string ID) source = Source.GetVideoSource(e.Url);
|
||||||
|
|
||||||
// Check url
|
// Check url
|
||||||
if (source.Type == VideoSource.Null)
|
if (source.Type == VideoSource.Null)
|
||||||
@@ -176,37 +196,13 @@ namespace VDownload.Views.Home
|
|||||||
HomeOptionBarAndAddingPanelRow.Height = new GridLength(1, GridUnitType.Star);
|
HomeOptionBarAndAddingPanelRow.Height = new GridLength(1, GridUnitType.Star);
|
||||||
HomeTasksListRow.Height = new GridLength(0);
|
HomeTasksListRow.Height = new GridLength(0);
|
||||||
|
|
||||||
|
// Open adding panel
|
||||||
HomeVideoAddingPanel addingPanel = new HomeVideoAddingPanel(videoService);
|
HomeVideoAddingPanel addingPanel = new HomeVideoAddingPanel(videoService);
|
||||||
addingPanel.VideoAddRequest += HomeVideoAddingPanel_VideoAddRequest;
|
addingPanel.TasksAddingRequested += HomeTasksAddingRequest;
|
||||||
HomeAddingPanel.Content = addingPanel;
|
HomeAddingPanel.Content = addingPanel;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ADD VIDEO REQUEST FROM VIDEO ADDING PANEL
|
|
||||||
private void HomeVideoAddingPanel_VideoAddRequest(object sender, VideoAddEventArgs e)
|
|
||||||
{
|
|
||||||
// Replace placeholder
|
|
||||||
HomeTasksListPlaceCurrent.Content = HomeTasksList;
|
|
||||||
|
|
||||||
// Uncheck video button
|
|
||||||
HomeOptionsBarAddVideoButton.IsChecked = false;
|
|
||||||
|
|
||||||
// Create video task
|
|
||||||
HomeTaskPanel taskPanel = new HomeTaskPanel(e.VideoService, e.MediaType, e.Stream, e.TrimStart, e.TrimEnd, e.Filename, e.Extension, e.Location, e.Schedule);
|
|
||||||
|
|
||||||
taskPanel.TaskRemovingRequested += (s, a) =>
|
|
||||||
{
|
|
||||||
// Remove task from tasks lists
|
|
||||||
TaskPanelsList.Remove(taskPanel);
|
|
||||||
HomeTasksList.Children.Remove(taskPanel);
|
|
||||||
if (TaskPanelsList.Count <= 0) HomeTasksListPlaceCurrent.Content = HomeTasksListPlaceholder;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Add task to tasks lists
|
|
||||||
HomeTasksList.Children.Add(taskPanel);
|
|
||||||
TaskPanelsList.Add(taskPanel);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// ADD PLAYLIST BUTTON CHECKED
|
// ADD PLAYLIST BUTTON CHECKED
|
||||||
private void HomeOptionsBarAddPlaylistButton_Checked(object sender, RoutedEventArgs e)
|
private void HomeOptionsBarAddPlaylistButton_Checked(object sender, RoutedEventArgs e)
|
||||||
@@ -236,7 +232,7 @@ namespace VDownload.Views.Home
|
|||||||
HomeOptionsBarSearchingStatusControl.Content = HomeOptionsBarSearchingStatusProgressRing;
|
HomeOptionsBarSearchingStatusControl.Content = HomeOptionsBarSearchingStatusProgressRing;
|
||||||
|
|
||||||
// Parse url
|
// Parse url
|
||||||
(PlaylistSource Type, string ID) source = Source.GetPlaylistSource(e.Phrase);
|
(PlaylistSource Type, string ID) source = Source.GetPlaylistSource(e.Url);
|
||||||
|
|
||||||
// Check url
|
// Check url
|
||||||
if (source.Type == PlaylistSource.Null)
|
if (source.Type == PlaylistSource.Null)
|
||||||
@@ -256,7 +252,7 @@ namespace VDownload.Views.Home
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
await playlistService.GetMetadataAsync(SearchingCancellationToken.Token);
|
await playlistService.GetMetadataAsync(SearchingCancellationToken.Token);
|
||||||
await playlistService.GetVideosAsync(e.Count, SearchingCancellationToken.Token);
|
await playlistService.GetVideosAsync(e.VideosCount, SearchingCancellationToken.Token);
|
||||||
}
|
}
|
||||||
catch (OperationCanceledException)
|
catch (OperationCanceledException)
|
||||||
{
|
{
|
||||||
@@ -290,7 +286,7 @@ namespace VDownload.Views.Home
|
|||||||
catch (WebException wex)
|
catch (WebException wex)
|
||||||
{
|
{
|
||||||
HomeOptionsBarSearchingStatusControl.Content = HomeOptionsBarSearchingStatusErrorImage;
|
HomeOptionsBarSearchingStatusControl.Content = HomeOptionsBarSearchingStatusErrorImage;
|
||||||
if (wex.Response == null)
|
if (!NetworkHelper.Instance.ConnectionInformation.IsInternetAvailable)
|
||||||
{
|
{
|
||||||
ContentDialog internetAccessErrorDialog = new ContentDialog
|
ContentDialog internetAccessErrorDialog = new ContentDialog
|
||||||
{
|
{
|
||||||
@@ -311,64 +307,81 @@ namespace VDownload.Views.Home
|
|||||||
HomeOptionBarAndAddingPanelRow.Height = new GridLength(1, GridUnitType.Star);
|
HomeOptionBarAndAddingPanelRow.Height = new GridLength(1, GridUnitType.Star);
|
||||||
HomeTasksListRow.Height = new GridLength(0);
|
HomeTasksListRow.Height = new GridLength(0);
|
||||||
|
|
||||||
|
// Open adding panel
|
||||||
HomePlaylistAddingPanel addingPanel = new HomePlaylistAddingPanel(playlistService);
|
HomePlaylistAddingPanel addingPanel = new HomePlaylistAddingPanel(playlistService);
|
||||||
addingPanel.PlaylistAddRequest += HomeVideoAddingPanel_PlayListAddRequest;
|
addingPanel.TasksAddingRequested += HomeTasksAddingRequest;
|
||||||
HomeAddingPanel.Content = addingPanel;
|
HomeAddingPanel.Content = addingPanel;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ADD PLAYLIST REQUEST FROM PLAYLIST ADDING PANEL
|
|
||||||
private void HomeVideoAddingPanel_PlayListAddRequest(object sender, PlaylistAddEventArgs e)
|
// TASK ADDING REQUEST
|
||||||
|
private void HomeTasksAddingRequest(object sender, TasksAddingRequestedEventArgs e)
|
||||||
{
|
{
|
||||||
// Replace placeholder
|
// Replace placeholder
|
||||||
HomeTasksListPlaceCurrent.Content = HomeTasksList;
|
HomeTasksListCurrentParent.Content = HomeTasksList;
|
||||||
|
|
||||||
// Uncheck video button
|
// Uncheck button
|
||||||
HomeOptionsBarAddPlaylistButton.IsChecked = false;
|
switch (e.RequestSource)
|
||||||
|
{
|
||||||
|
case TaskAddingRequestSource.Video: HomeOptionsBarAddVideoButton.IsChecked = false; break;
|
||||||
|
case TaskAddingRequestSource.Playlist: HomeOptionsBarAddPlaylistButton.IsChecked = false; break;
|
||||||
|
}
|
||||||
|
|
||||||
// Create video tasks
|
// Create video tasks
|
||||||
foreach (var video in e.Videos)
|
foreach (TaskData taskData in e.TaskData)
|
||||||
{
|
{
|
||||||
HomeTaskPanel taskPanel = new HomeTaskPanel(video.VideoService, video.MediaType, video.Stream, video.TrimStart, video.TrimEnd, video.Filename, video.Extension, video.Location, video.Schedule);
|
HomeTaskPanel taskPanel = new HomeTaskPanel(taskData);
|
||||||
|
|
||||||
taskPanel.TaskRemovingRequested += (s, a) =>
|
taskPanel.TaskRemovingRequested += (s, a) =>
|
||||||
{
|
{
|
||||||
// Remove task from tasks lists
|
// Remove task from tasks lists
|
||||||
TaskPanelsList.Remove(taskPanel);
|
TasksList.Remove(taskPanel);
|
||||||
HomeTasksList.Children.Remove(taskPanel);
|
HomeTasksList.Children.Remove(taskPanel);
|
||||||
if (TaskPanelsList.Count <= 0) HomeTasksListPlaceCurrent.Content = HomeTasksListPlaceholder;
|
if (TasksList.Count <= 0) HomeTasksListCurrentParent.Content = HomeTasksListPlaceholder;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Add task to tasks lists
|
// Add task to tasks lists
|
||||||
HomeTasksList.Children.Add(taskPanel);
|
HomeTasksList.Children.Add(taskPanel);
|
||||||
TaskPanelsList.Add(taskPanel);
|
TasksList.Add(taskPanel);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// ADDING BUTTONS UNCHECKED
|
// TASK ADDING CANCELLED
|
||||||
private void HomeOptionsBarAddingButtons_Unchecked(object sender, RoutedEventArgs e)
|
private void HomeSearchingCancelled()
|
||||||
{
|
{
|
||||||
// Cancel searching operations
|
// Cancel searching operations
|
||||||
SearchingCancellationToken.Cancel();
|
SearchingCancellationToken.Cancel();
|
||||||
SearchingCancellationToken = new CancellationTokenSource();
|
SearchingCancellationToken = new CancellationTokenSource();
|
||||||
|
|
||||||
|
// Set grid dimensions
|
||||||
HomeOptionBarAndAddingPanelRow.Height = GridLength.Auto;
|
HomeOptionBarAndAddingPanelRow.Height = GridLength.Auto;
|
||||||
HomeTasksListRow.Height = new GridLength(1, GridUnitType.Star);
|
HomeTasksListRow.Height = new GridLength(1, GridUnitType.Star);
|
||||||
|
|
||||||
|
// Clear panels
|
||||||
HomeAddingPanel.Content = null;
|
HomeAddingPanel.Content = null;
|
||||||
HomeOptionsBarAddingControl.Content = null;
|
HomeOptionsBarAddingControl.Content = null;
|
||||||
HomeOptionsBarSearchingStatusControl.Content = null;
|
HomeOptionsBarSearchingStatusControl.Content = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ADDING BUTTONS UNCHECKED
|
||||||
|
private void HomeOptionsBarAddingButtons_Unchecked(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
HomeSearchingCancelled();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// DOWNLOAD ALL BUTTON CLICKED
|
// DOWNLOAD ALL BUTTON CLICKED
|
||||||
private async void HomeOptionsBarDownloadAllButton_Click(object sender, RoutedEventArgs e)
|
private async void HomeOptionsBarDownloadAllButton_Click(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
HomeTaskPanel[] idleTasks = TaskPanelsList.Where((HomeTaskPanel video) => video.TaskStatus == Core.Enums.TaskStatus.Idle).ToArray();
|
HomeTaskPanel[] idleTasks = TasksList.Where((HomeTaskPanel video) => video.Status == Core.Enums.TaskStatus.Idle).ToArray();
|
||||||
if (idleTasks.Count() > 0)
|
if (idleTasks.Count() > 0)
|
||||||
{
|
{
|
||||||
bool delay = (bool)Config.GetValue("delay_task_when_queued_task_starts_on_metered_network");
|
bool delay = (bool)Config.GetValue("delay_task_when_queued_task_starts_on_metered_network");
|
||||||
|
if (NetworkHelper.Instance.ConnectionInformation.IsInternetOnMeteredConnection)
|
||||||
|
{
|
||||||
ContentDialogResult dialogResult = await new ContentDialog
|
ContentDialogResult dialogResult = await new ContentDialog
|
||||||
{
|
{
|
||||||
Title = ResourceLoader.GetForCurrentView().GetString("HomeDownloadAllButtonMeteredConnectionDialogTitle"),
|
Title = ResourceLoader.GetForCurrentView().GetString("HomeDownloadAllButtonMeteredConnectionDialogTitle"),
|
||||||
@@ -383,10 +396,11 @@ namespace VDownload.Views.Home
|
|||||||
case ContentDialogResult.Secondary: delay = false; break;
|
case ContentDialogResult.Secondary: delay = false; break;
|
||||||
case ContentDialogResult.None: return;
|
case ContentDialogResult.None: return;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
foreach (HomeTaskPanel videoPanel in idleTasks)
|
foreach (HomeTaskPanel videoPanel in idleTasks)
|
||||||
{
|
{
|
||||||
await Task.Delay(50);
|
await Task.Delay(10);
|
||||||
|
|
||||||
#pragma warning disable CS4014
|
#pragma warning disable CS4014
|
||||||
videoPanel.Start(delay);
|
videoPanel.Start(delay);
|
||||||
@@ -404,7 +418,7 @@ namespace VDownload.Views.Home
|
|||||||
// WAIT IN QUEUE
|
// WAIT IN QUEUE
|
||||||
public static async Task WaitInQueue(bool delayWhenOnMeteredConnection, CancellationToken token)
|
public static async Task WaitInQueue(bool delayWhenOnMeteredConnection, CancellationToken token)
|
||||||
{
|
{
|
||||||
while ((TaskPanelsList.Where((HomeTaskPanel video) => video.TaskStatus == Core.Enums.TaskStatus.InProgress).Count() >= (int)Config.GetValue("max_active_video_task") || (delayWhenOnMeteredConnection && NetworkHelper.Instance.ConnectionInformation.IsInternetOnMeteredConnection)) && !token.IsCancellationRequested)
|
while ((TasksList.Where((HomeTaskPanel task) => task.Status == Core.Enums.TaskStatus.InProgress).Count() >= (int)Config.GetValue("max_active_video_task") || (delayWhenOnMeteredConnection && NetworkHelper.Instance.ConnectionInformation.IsInternetOnMeteredConnection)) && !token.IsCancellationRequested)
|
||||||
{
|
{
|
||||||
await Task.Delay(100);
|
await Task.Delay(100);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,7 +23,7 @@
|
|||||||
<TextBox x:Name="HomeOptionsBarAddPlaylistControlUrlTextBox" x:Uid="HomeOptionsBarAddPlaylistControlUrlTextBox" Grid.Column="0" VerticalAlignment="Center"/>
|
<TextBox x:Name="HomeOptionsBarAddPlaylistControlUrlTextBox" x:Uid="HomeOptionsBarAddPlaylistControlUrlTextBox" Grid.Column="0" VerticalAlignment="Center"/>
|
||||||
|
|
||||||
<!-- MAX VIDEOS NUMBERBOX -->
|
<!-- MAX VIDEOS NUMBERBOX -->
|
||||||
<muxc:NumberBox x:Name="HomeOptionsBarAddPlaylistControlMaxVideosNumberBox" Grid.Column="1" VerticalAlignment="Center" SpinButtonPlacementMode="Compact" Minimum="0" Value="{x:Bind DefaultMaxPlaylistVideos}"/>
|
<muxc:NumberBox x:Name="HomeOptionsBarAddPlaylistControlMaxVideosNumberBox" Grid.Column="1" VerticalAlignment="Center" SpinButtonPlacementMode="Compact" Minimum="0" Value="{x:Bind DefaultMaxPlaylistVideos}" LostFocus="HomeOptionsBarAddPlaylistControlMaxVideosNumberBox_LostFocus"/>
|
||||||
|
|
||||||
<!-- SEARCH BUTTON-->
|
<!-- SEARCH BUTTON-->
|
||||||
<Button x:Name="HomeOptionsBarAddPlaylistControlSearchButton" x:Uid="HomeOptionsBarAddPlaylistControlSearchButton" Grid.Column="2" Click="HomeOptionsBarAddPlaylistControlSearchButton_Click"/>
|
<Button x:Name="HomeOptionsBarAddPlaylistControlSearchButton" x:Uid="HomeOptionsBarAddPlaylistControlSearchButton" Grid.Column="2" Click="HomeOptionsBarAddPlaylistControlSearchButton_Click"/>
|
||||||
|
|||||||
@@ -30,6 +30,12 @@ namespace VDownload.Views.Home
|
|||||||
|
|
||||||
#region EVENT HANDLERS
|
#region EVENT HANDLERS
|
||||||
|
|
||||||
|
// NUMBERBOX FOCUS LOST
|
||||||
|
private void HomeOptionsBarAddPlaylistControlMaxVideosNumberBox_LostFocus(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
if (double.IsNaN(HomeOptionsBarAddPlaylistControlMaxVideosNumberBox.Value)) HomeOptionsBarAddPlaylistControlMaxVideosNumberBox.Value = DefaultMaxPlaylistVideos;
|
||||||
|
}
|
||||||
|
|
||||||
// SEARCH BUTTON CLICKED
|
// SEARCH BUTTON CLICKED
|
||||||
private void HomeOptionsBarAddPlaylistControlSearchButton_Click(object sender, RoutedEventArgs e)
|
private void HomeOptionsBarAddPlaylistControlSearchButton_Click(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
@@ -39,8 +45,8 @@ namespace VDownload.Views.Home
|
|||||||
// Invoke search button event handlers
|
// Invoke search button event handlers
|
||||||
PlaylistSearchEventArgs args = new PlaylistSearchEventArgs
|
PlaylistSearchEventArgs args = new PlaylistSearchEventArgs
|
||||||
{
|
{
|
||||||
Phrase = HomeOptionsBarAddPlaylistControlUrlTextBox.Text,
|
Url = HomeOptionsBarAddPlaylistControlUrlTextBox.Text,
|
||||||
Count = int.Parse(HomeOptionsBarAddPlaylistControlMaxVideosNumberBox.Text),
|
VideosCount = int.Parse(HomeOptionsBarAddPlaylistControlMaxVideosNumberBox.Text),
|
||||||
};
|
};
|
||||||
SearchButtonClicked?.Invoke(this, args);
|
SearchButtonClicked?.Invoke(this, args);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ namespace VDownload.Views.Home
|
|||||||
// Invoke search button event handlers
|
// Invoke search button event handlers
|
||||||
VideoSearchEventArgs args = new VideoSearchEventArgs
|
VideoSearchEventArgs args = new VideoSearchEventArgs
|
||||||
{
|
{
|
||||||
Phrase = HomeOptionsBarAddVideoControlUrlTextBox.Text
|
Url = HomeOptionsBarAddVideoControlUrlTextBox.Text
|
||||||
};
|
};
|
||||||
SearchButtonClicked?.Invoke(this, args);
|
SearchButtonClicked?.Invoke(this, args);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ using VDownload.Core.Enums;
|
|||||||
using VDownload.Core.EventArgs;
|
using VDownload.Core.EventArgs;
|
||||||
using VDownload.Core.Interfaces;
|
using VDownload.Core.Interfaces;
|
||||||
using VDownload.Core.Services;
|
using VDownload.Core.Services;
|
||||||
|
using VDownload.Core.Structs;
|
||||||
using Windows.ApplicationModel.Resources;
|
using Windows.ApplicationModel.Resources;
|
||||||
using Windows.Foundation;
|
using Windows.Foundation;
|
||||||
using Windows.Foundation.Collections;
|
using Windows.Foundation.Collections;
|
||||||
@@ -44,20 +45,20 @@ namespace VDownload.Views.Home
|
|||||||
Name = PlaylistService.Name;
|
Name = PlaylistService.Name;
|
||||||
|
|
||||||
// Add videos to list and search mins and maxes
|
// Add videos to list and search mins and maxes
|
||||||
MinViews = PlaylistService.Videos[0].Views;
|
MinViews = PlaylistService.Videos[0].Metadata.Views;
|
||||||
MaxViews = PlaylistService.Videos[0].Views;
|
MaxViews = PlaylistService.Videos[0].Metadata.Views;
|
||||||
MinDate = PlaylistService.Videos[0].Date;
|
MinDate = PlaylistService.Videos[0].Metadata.Date;
|
||||||
MaxDate = PlaylistService.Videos[0].Date;
|
MaxDate = PlaylistService.Videos[0].Metadata.Date;
|
||||||
MinDuration = PlaylistService.Videos[0].Duration;
|
MinDuration = PlaylistService.Videos[0].Metadata.Duration;
|
||||||
MaxDuration = PlaylistService.Videos[0].Duration;
|
MaxDuration = PlaylistService.Videos[0].Metadata.Duration;
|
||||||
foreach (IVideoService video in PlaylistService.Videos)
|
foreach (IVideoService video in PlaylistService.Videos)
|
||||||
{
|
{
|
||||||
if (video.Views < MinViews) MinViews = video.Views;
|
if (video.Metadata.Views < MinViews) MinViews = video.Metadata.Views;
|
||||||
if (video.Views > MaxViews) MaxViews = video.Views;
|
if (video.Metadata.Views > MaxViews) MaxViews = video.Metadata.Views;
|
||||||
if (video.Date < MinDate) MinDate = video.Date;
|
if (video.Metadata.Date < MinDate) MinDate = video.Metadata.Date;
|
||||||
if (video.Date > MaxDate) MaxDate = video.Date;
|
if (video.Metadata.Date > MaxDate) MaxDate = video.Metadata.Date;
|
||||||
if (video.Duration < MinDuration) MinDuration = video.Duration;
|
if (video.Metadata.Duration < MinDuration) MinDuration = video.Metadata.Duration;
|
||||||
if (video.Duration > MaxDuration) MaxDuration = video.Duration;
|
if (video.Metadata.Duration > MaxDuration) MaxDuration = video.Metadata.Duration;
|
||||||
HomePlaylistAddingPanelVideoPanel videoPanel = new HomePlaylistAddingPanelVideoPanel(video);
|
HomePlaylistAddingPanelVideoPanel videoPanel = new HomePlaylistAddingPanelVideoPanel(video);
|
||||||
videoPanel.DeleteRequested += (s, a) =>
|
videoPanel.DeleteRequested += (s, a) =>
|
||||||
{
|
{
|
||||||
@@ -190,14 +191,7 @@ namespace VDownload.Views.Home
|
|||||||
int.TryParse(maxSegments.ElementAtOrDefault(2), out int maxHours);
|
int.TryParse(maxSegments.ElementAtOrDefault(2), out int maxHours);
|
||||||
TimeSpan maxDuration = new TimeSpan(maxHours, maxMinutes, maxSeconds);
|
TimeSpan maxDuration = new TimeSpan(maxHours, maxMinutes, maxSeconds);
|
||||||
|
|
||||||
List<HomePlaylistAddingPanelVideoPanel> allVideos = new List<HomePlaylistAddingPanelVideoPanel>();
|
// Title and author regex
|
||||||
foreach (HomePlaylistAddingPanelVideoPanel videoPanel in HomePlaylistAddingPanelVideosList.Children) allVideos.Add(videoPanel);
|
|
||||||
foreach (HomePlaylistAddingPanelVideoPanel videoPanel in HiddenVideos) allVideos.Add(videoPanel);
|
|
||||||
HomePlaylistAddingPanelVideosList.Children.Clear();
|
|
||||||
HiddenVideos.Clear();
|
|
||||||
|
|
||||||
foreach (HomePlaylistAddingPanelVideoPanel videoPanel in allVideos)
|
|
||||||
{
|
|
||||||
Regex titleRegex = new Regex("");
|
Regex titleRegex = new Regex("");
|
||||||
Regex authorRegex = new Regex("");
|
Regex authorRegex = new Regex("");
|
||||||
try
|
try
|
||||||
@@ -206,16 +200,28 @@ namespace VDownload.Views.Home
|
|||||||
authorRegex = new Regex(HomePlaylistAddingPanelFilterAuthorTextBox.Text);
|
authorRegex = new Regex(HomePlaylistAddingPanelFilterAuthorTextBox.Text);
|
||||||
}
|
}
|
||||||
catch (ArgumentException) { }
|
catch (ArgumentException) { }
|
||||||
if (!titleRegex.IsMatch(videoPanel.VideoService.Title)) HiddenVideos.Add(videoPanel);
|
|
||||||
else if (!authorRegex.IsMatch(videoPanel.VideoService.Author)) HiddenVideos.Add(videoPanel);
|
List<HomePlaylistAddingPanelVideoPanel> allVideos = new List<HomePlaylistAddingPanelVideoPanel>();
|
||||||
else if (HomePlaylistAddingPanelFilterMinViewsNumberBox.Value > videoPanel.VideoService.Views) HiddenVideos.Add(videoPanel);
|
foreach (HomePlaylistAddingPanelVideoPanel videoPanel in HomePlaylistAddingPanelVideosList.Children) allVideos.Add(videoPanel);
|
||||||
else if (HomePlaylistAddingPanelFilterMaxViewsNumberBox.Value < videoPanel.VideoService.Views) HiddenVideos.Add(videoPanel);
|
foreach (HomePlaylistAddingPanelVideoPanel videoPanel in HiddenVideos) allVideos.Add(videoPanel);
|
||||||
else if (HomePlaylistAddingPanelFilterMinDateDatePicker.Date > videoPanel.VideoService.Date) HiddenVideos.Add(videoPanel);
|
HomePlaylistAddingPanelVideosList.Children.Clear();
|
||||||
else if (HomePlaylistAddingPanelFilterMaxDateDatePicker.Date < videoPanel.VideoService.Date) HiddenVideos.Add(videoPanel);
|
HiddenVideos.Clear();
|
||||||
else if (minDuration > videoPanel.VideoService.Duration) HiddenVideos.Add(videoPanel);
|
|
||||||
else if (maxDuration < videoPanel.VideoService.Duration) HiddenVideos.Add(videoPanel);
|
foreach (HomePlaylistAddingPanelVideoPanel videoPanel in allVideos)
|
||||||
|
{
|
||||||
|
if (
|
||||||
|
!titleRegex.IsMatch(videoPanel.VideoService.Metadata.Title) ||
|
||||||
|
!authorRegex.IsMatch(videoPanel.VideoService.Metadata.Author) ||
|
||||||
|
HomePlaylistAddingPanelFilterMinViewsNumberBox.Value > videoPanel.VideoService.Metadata.Views ||
|
||||||
|
HomePlaylistAddingPanelFilterMaxViewsNumberBox.Value < videoPanel.VideoService.Metadata.Views ||
|
||||||
|
HomePlaylistAddingPanelFilterMinDateDatePicker.Date > videoPanel.VideoService.Metadata.Date ||
|
||||||
|
HomePlaylistAddingPanelFilterMaxDateDatePicker.Date < videoPanel.VideoService.Metadata.Date ||
|
||||||
|
minDuration > videoPanel.VideoService.Metadata.Duration ||
|
||||||
|
maxDuration < videoPanel.VideoService.Metadata.Duration
|
||||||
|
) HiddenVideos.Add(videoPanel);
|
||||||
else HomePlaylistAddingPanelVideosList.Children.Add(videoPanel);
|
else HomePlaylistAddingPanelVideosList.Children.Add(videoPanel);
|
||||||
}
|
}
|
||||||
|
|
||||||
HomePlaylistAddingPanelFilterHeaderCountTextBlock.Text = HiddenVideos.Count + DeletedVideos.Count > 0 ? $"{ResourceLoader.GetForCurrentView().GetString("HomePlaylistAddingPanelFilterHeaderCountTextBlockPrefix")}: {HiddenVideos.Count + DeletedVideos.Count}" : "";
|
HomePlaylistAddingPanelFilterHeaderCountTextBlock.Text = HiddenVideos.Count + DeletedVideos.Count > 0 ? $"{ResourceLoader.GetForCurrentView().GetString("HomePlaylistAddingPanelFilterHeaderCountTextBlockPrefix")}: {HiddenVideos.Count + DeletedVideos.Count}" : "";
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -327,18 +333,22 @@ namespace VDownload.Views.Home
|
|||||||
|
|
||||||
TimeSpan parsedTimeSpan = new TimeSpan(hours, minutes, seconds);
|
TimeSpan parsedTimeSpan = new TimeSpan(hours, minutes, seconds);
|
||||||
|
|
||||||
if (parsedTimeSpan < MinDuration && parsedTimeSpan > MaxDuration)
|
if (parsedTimeSpan < MinDuration || parsedTimeSpan > MaxDuration)
|
||||||
{
|
{
|
||||||
if (Math.Floor(MaxDuration.TotalHours) > 0) HomePlaylistAddingPanelFilterMinDurationTextBox.Text += $"{Math.Floor(MinDuration.TotalHours)}:";
|
string newMinDuration = "";
|
||||||
if (Math.Floor(MaxDuration.TotalMinutes) > 0) HomePlaylistAddingPanelFilterMinDurationTextBox.Text += Math.Floor(MaxDuration.TotalHours) > 0 ? $"{MinDuration.Minutes:00}:" : $"{MinDuration.Minutes}:";
|
if (Math.Floor(MaxDuration.TotalHours) > 0) newMinDuration += $"{Math.Floor(MinDuration.TotalHours)}:";
|
||||||
HomePlaylistAddingPanelFilterMinDurationTextBox.Text += Math.Floor(MaxDuration.TotalMinutes) > 0 ? $"{MinDuration.Seconds:00}" : $"{MinDuration.Seconds}";
|
if (Math.Floor(MaxDuration.TotalMinutes) > 0) newMinDuration += Math.Floor(MaxDuration.TotalHours) > 0 ? $"{MinDuration.Minutes:00}:" : $"{MinDuration.Minutes}:";
|
||||||
|
newMinDuration += Math.Floor(MaxDuration.TotalMinutes) > 0 ? $"{MinDuration.Seconds:00}" : $"{MinDuration.Seconds}";
|
||||||
|
HomePlaylistAddingPanelFilterMinDurationTextBox.Text = newMinDuration;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (Math.Floor(MaxDuration.TotalHours) > 0) HomePlaylistAddingPanelFilterMinDurationTextBox.Text += $"{Math.Floor(MinDuration.TotalHours)}:";
|
string newMinDuration = "";
|
||||||
if (Math.Floor(MaxDuration.TotalMinutes) > 0) HomePlaylistAddingPanelFilterMinDurationTextBox.Text += Math.Floor(MaxDuration.TotalHours) > 0 ? $"{MinDuration.Minutes:00}:" : $"{MinDuration.Minutes}:";
|
if (Math.Floor(MaxDuration.TotalHours) > 0) newMinDuration += $"{Math.Floor(MinDuration.TotalHours)}:";
|
||||||
HomePlaylistAddingPanelFilterMinDurationTextBox.Text += Math.Floor(MaxDuration.TotalMinutes) > 0 ? $"{MinDuration.Seconds:00}" : $"{MinDuration.Seconds}";
|
if (Math.Floor(MaxDuration.TotalMinutes) > 0) newMinDuration += Math.Floor(MaxDuration.TotalHours) > 0 ? $"{MinDuration.Minutes:00}:" : $"{MinDuration.Minutes}:";
|
||||||
|
newMinDuration += Math.Floor(MaxDuration.TotalMinutes) > 0 ? $"{MinDuration.Seconds:00}" : $"{MinDuration.Seconds}";
|
||||||
|
HomePlaylistAddingPanelFilterMinDurationTextBox.Text = newMinDuration;
|
||||||
}
|
}
|
||||||
FilterChanged();
|
FilterChanged();
|
||||||
}
|
}
|
||||||
@@ -355,18 +365,22 @@ namespace VDownload.Views.Home
|
|||||||
|
|
||||||
TimeSpan parsedTimeSpan = new TimeSpan(hours, minutes, seconds);
|
TimeSpan parsedTimeSpan = new TimeSpan(hours, minutes, seconds);
|
||||||
|
|
||||||
if (parsedTimeSpan < MinDuration && parsedTimeSpan > MaxDuration)
|
if (parsedTimeSpan < MinDuration || parsedTimeSpan > MaxDuration)
|
||||||
{
|
{
|
||||||
if (Math.Floor(MaxDuration.TotalHours) > 0) HomePlaylistAddingPanelFilterMaxDurationTextBox.Text += $"{Math.Floor(MaxDuration.TotalHours)}:";
|
string newMaxDuration = "";
|
||||||
if (Math.Floor(MaxDuration.TotalMinutes) > 0) HomePlaylistAddingPanelFilterMaxDurationTextBox.Text += Math.Floor(MaxDuration.TotalHours) > 0 ? $"{MaxDuration.Minutes:00}:" : $"{MaxDuration.Minutes}:";
|
if (Math.Floor(MaxDuration.TotalHours) > 0) newMaxDuration += $"{Math.Floor(MaxDuration.TotalHours)}:";
|
||||||
HomePlaylistAddingPanelFilterMaxDurationTextBox.Text += Math.Floor(MaxDuration.TotalMinutes) > 0 ? $"{MaxDuration.Seconds:00}" : $"{MaxDuration.Seconds}";
|
if (Math.Floor(MaxDuration.TotalMinutes) > 0) newMaxDuration += Math.Floor(MaxDuration.TotalHours) > 0 ? $"{MaxDuration.Minutes:00}:" : $"{MaxDuration.Minutes}:";
|
||||||
|
newMaxDuration += Math.Floor(MaxDuration.TotalMinutes) > 0 ? $"{MaxDuration.Seconds:00}" : $"{MaxDuration.Seconds}";
|
||||||
|
HomePlaylistAddingPanelFilterMaxDurationTextBox.Text = newMaxDuration;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (Math.Floor(MaxDuration.TotalHours) > 0) HomePlaylistAddingPanelFilterMaxDurationTextBox.Text += $"{Math.Floor(MaxDuration.TotalHours)}:";
|
string newMaxDuration = "";
|
||||||
if (Math.Floor(MaxDuration.TotalMinutes) > 0) HomePlaylistAddingPanelFilterMaxDurationTextBox.Text += Math.Floor(MaxDuration.TotalHours) > 0 ? $"{MaxDuration.Minutes:00}:" : $"{MaxDuration.Minutes}:";
|
if (Math.Floor(MaxDuration.TotalHours) > 0) newMaxDuration += $"{Math.Floor(MaxDuration.TotalHours)}:";
|
||||||
HomePlaylistAddingPanelFilterMaxDurationTextBox.Text += Math.Floor(MaxDuration.TotalMinutes) > 0 ? $"{MaxDuration.Seconds:00}" : $"{MaxDuration.Seconds}";
|
if (Math.Floor(MaxDuration.TotalMinutes) > 0) newMaxDuration += Math.Floor(MaxDuration.TotalHours) > 0 ? $"{MaxDuration.Minutes:00}:" : $"{MaxDuration.Minutes}:";
|
||||||
|
newMaxDuration += Math.Floor(MaxDuration.TotalMinutes) > 0 ? $"{MaxDuration.Seconds:00}" : $"{MaxDuration.Seconds}";
|
||||||
|
HomePlaylistAddingPanelFilterMaxDurationTextBox.Text = newMaxDuration;
|
||||||
}
|
}
|
||||||
FilterChanged();
|
FilterChanged();
|
||||||
}
|
}
|
||||||
@@ -385,6 +399,7 @@ namespace VDownload.Views.Home
|
|||||||
DeletedVideos.Clear();
|
DeletedVideos.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// SOURCE BUTTON CLICKED
|
// SOURCE BUTTON CLICKED
|
||||||
private async void HomePlaylistAddingPanelSourceButton_Click(object sender, RoutedEventArgs e)
|
private async void HomePlaylistAddingPanelSourceButton_Click(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
@@ -395,15 +410,33 @@ namespace VDownload.Views.Home
|
|||||||
// ADD BUTTON CLICKED
|
// ADD BUTTON CLICKED
|
||||||
private void HomePlaylistAddingPanelAddButton_Click(object sender, RoutedEventArgs e)
|
private void HomePlaylistAddingPanelAddButton_Click(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
var videos = new List<(IVideoService VideoService, MediaType MediaType, IBaseStream Stream, TimeSpan TrimStart, TimeSpan TrimEnd, string Filename, MediaFileExtension Extension, StorageFolder Location, double Schedule)>();
|
// Pack tasks data
|
||||||
|
List<TaskData> taskDataList = new List<TaskData>();
|
||||||
foreach (HomePlaylistAddingPanelVideoPanel videoPanel in HomePlaylistAddingPanelVideosList.Children)
|
foreach (HomePlaylistAddingPanelVideoPanel videoPanel in HomePlaylistAddingPanelVideosList.Children)
|
||||||
{
|
{
|
||||||
videos.Add((videoPanel.VideoService, videoPanel.MediaType, videoPanel.Stream, videoPanel.TrimStart, videoPanel.TrimEnd, videoPanel.Filename, videoPanel.Extension, videoPanel.Location, videoPanel.Schedule));
|
TaskData taskData = new TaskData
|
||||||
}
|
{
|
||||||
PlaylistAddEventArgs eventArgs = new PlaylistAddEventArgs { Videos = videos.ToArray() };
|
VideoService = videoPanel.VideoService,
|
||||||
PlaylistAddRequest?.Invoke(this, eventArgs);
|
MediaType = videoPanel.MediaType,
|
||||||
|
Stream = videoPanel.Stream,
|
||||||
|
TrimStart = videoPanel.TrimStart,
|
||||||
|
TrimEnd = videoPanel.TrimEnd,
|
||||||
|
Filename = videoPanel.Filename,
|
||||||
|
Extension = videoPanel.Extension,
|
||||||
|
Location = videoPanel.Location,
|
||||||
|
Schedule = videoPanel.Schedule,
|
||||||
|
};
|
||||||
|
taskDataList.Add(taskData);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Request tasks adding
|
||||||
|
TasksAddingRequestedEventArgs eventArgs = new TasksAddingRequestedEventArgs
|
||||||
|
{
|
||||||
|
TaskData = taskDataList.ToArray(),
|
||||||
|
RequestSource = TaskAddingRequestSource.Playlist
|
||||||
|
};
|
||||||
|
TasksAddingRequested?.Invoke(this, eventArgs);
|
||||||
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
@@ -411,7 +444,7 @@ namespace VDownload.Views.Home
|
|||||||
|
|
||||||
#region EVENT HANDLERS
|
#region EVENT HANDLERS
|
||||||
|
|
||||||
public event EventHandler<PlaylistAddEventArgs> PlaylistAddRequest;
|
public event EventHandler<TasksAddingRequestedEventArgs> TasksAddingRequested;
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -87,9 +87,9 @@
|
|||||||
<cc:SettingControl x:Uid="HomePlaylistAddingVideoPanelTrimSettingControl" Icon="{ThemeResource TrimIcon}">
|
<cc:SettingControl x:Uid="HomePlaylistAddingVideoPanelTrimSettingControl" Icon="{ThemeResource TrimIcon}">
|
||||||
<cc:SettingControl.SettingContent>
|
<cc:SettingControl.SettingContent>
|
||||||
<StackPanel Orientation="Horizontal" Spacing="5">
|
<StackPanel Orientation="Horizontal" Spacing="5">
|
||||||
<TextBox x:Name="HomePlaylistAddingVideoPanelTrimStartTextBox" ex:TextBoxExtensions.CustomMask="{x:Bind VideoService.Duration, Converter={StaticResource TimeSpanToTextBoxMaskElementsConverter}}" ex:TextBoxExtensions.Mask="{x:Bind VideoService.Duration, Converter={StaticResource TimeSpanToTextBoxMaskConverter}}" TextChanged="HomePlaylistAddingVideoPanelTrimStartTextBox_TextChanged"/>
|
<TextBox x:Name="HomePlaylistAddingVideoPanelTrimStartTextBox" ex:TextBoxExtensions.CustomMask="{x:Bind VideoService.Metadata.Duration, Converter={StaticResource TimeSpanToTextBoxMaskElementsConverter}}" ex:TextBoxExtensions.Mask="{x:Bind VideoService.Metadata.Duration, Converter={StaticResource TimeSpanToTextBoxMaskConverter}}" TextChanged="HomePlaylistAddingVideoPanelTrimStartTextBox_TextChanged"/>
|
||||||
<TextBlock VerticalAlignment="Center" Text="-"/>
|
<TextBlock VerticalAlignment="Center" Text="-"/>
|
||||||
<TextBox x:Name="HomePlaylistAddingVideoPanelTrimEndTextBox" ex:TextBoxExtensions.CustomMask="{x:Bind VideoService.Duration, Converter={StaticResource TimeSpanToTextBoxMaskElementsConverter}}" ex:TextBoxExtensions.Mask="{x:Bind VideoService.Duration, Converter={StaticResource TimeSpanToTextBoxMaskConverter}}" TextChanged="HomePlaylistAddingVideoPanelTrimEndTextBox_TextChanged"/>
|
<TextBox x:Name="HomePlaylistAddingVideoPanelTrimEndTextBox" ex:TextBoxExtensions.CustomMask="{x:Bind VideoService.Metadata.Duration, Converter={StaticResource TimeSpanToTextBoxMaskElementsConverter}}" ex:TextBoxExtensions.Mask="{x:Bind VideoService.Metadata.Duration, Converter={StaticResource TimeSpanToTextBoxMaskConverter}}" TextChanged="HomePlaylistAddingVideoPanelTrimEndTextBox_TextChanged"/>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</cc:SettingControl.SettingContent>
|
</cc:SettingControl.SettingContent>
|
||||||
</cc:SettingControl>
|
</cc:SettingControl>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
@@ -9,6 +10,7 @@ using System.Threading.Tasks;
|
|||||||
using VDownload.Core.Enums;
|
using VDownload.Core.Enums;
|
||||||
using VDownload.Core.Interfaces;
|
using VDownload.Core.Interfaces;
|
||||||
using VDownload.Core.Services;
|
using VDownload.Core.Services;
|
||||||
|
using VDownload.Core.Structs;
|
||||||
using Windows.ApplicationModel.Resources;
|
using Windows.ApplicationModel.Resources;
|
||||||
using Windows.Foundation;
|
using Windows.Foundation;
|
||||||
using Windows.Foundation.Collections;
|
using Windows.Foundation.Collections;
|
||||||
@@ -46,13 +48,13 @@ namespace VDownload.Views.Home
|
|||||||
VideoService = videoService;
|
VideoService = videoService;
|
||||||
|
|
||||||
// Set metadata
|
// Set metadata
|
||||||
ThumbnailImage = VideoService.Thumbnail != null ? new BitmapImage { UriSource = VideoService.Thumbnail } : (BitmapImage)ImagesRes["UnknownThumbnailImage"];
|
ThumbnailImage = VideoService.Metadata.Thumbnail != null ? new BitmapImage { UriSource = VideoService.Metadata.Thumbnail } : (BitmapImage)ImagesRes["UnknownThumbnailImage"];
|
||||||
SourceImage = new BitmapIcon { UriSource = new Uri($"ms-appx:///Assets/Sources/{VideoService.GetType().Namespace.Split(".").Last()}.png"), ShowAsMonochrome = false };
|
SourceImage = new BitmapIcon { UriSource = new Uri($"ms-appx:///Assets/Sources/{VideoService.GetType().Namespace.Split(".").Last()}.png"), ShowAsMonochrome = false };
|
||||||
Title = VideoService.Title;
|
Title = VideoService.Metadata.Title;
|
||||||
Author = VideoService.Author;
|
Author = VideoService.Metadata.Author;
|
||||||
Views = VideoService.Views.ToString();
|
Views = VideoService.Metadata.Views.ToString();
|
||||||
Date = VideoService.Date.ToString(CultureInfo.InstalledUICulture.DateTimeFormat.ShortDatePattern);
|
Date = VideoService.Metadata.Date.ToString(CultureInfo.InstalledUICulture.DateTimeFormat.ShortDatePattern);
|
||||||
Duration = $"{(Math.Floor(VideoService.Duration.TotalHours) > 0 ? $"{Math.Floor(VideoService.Duration.TotalHours):0}:" : "")}{VideoService.Duration.Minutes:00}:{VideoService.Duration.Seconds:00}";
|
Duration = $"{(Math.Floor(VideoService.Metadata.Duration.TotalHours) > 0 ? $"{Math.Floor(VideoService.Metadata.Duration.TotalHours):0}:" : "")}{VideoService.Metadata.Duration.Minutes:00}:{VideoService.Metadata.Duration.Seconds:00}";
|
||||||
|
|
||||||
// Set media type
|
// Set media type
|
||||||
foreach (string mediaType in Enum.GetNames(typeof(MediaType)))
|
foreach (string mediaType in Enum.GetNames(typeof(MediaType)))
|
||||||
@@ -62,37 +64,39 @@ namespace VDownload.Views.Home
|
|||||||
HomePlaylistAddingVideoPanelMediaTypeSettingControlComboBox.SelectedIndex = (int)Config.GetValue("default_media_type");
|
HomePlaylistAddingVideoPanelMediaTypeSettingControlComboBox.SelectedIndex = (int)Config.GetValue("default_media_type");
|
||||||
|
|
||||||
// Set quality
|
// Set quality
|
||||||
foreach (IBaseStream stream in VideoService.BaseStreams)
|
foreach (BaseStream stream in VideoService.BaseStreams)
|
||||||
{
|
{
|
||||||
HomePlaylistAddingVideoPanelQualitySettingControlComboBox.Items.Add($"{stream.Height}p{(stream.FrameRate > 0 ? stream.FrameRate.ToString() : "N/A")}");
|
HomePlaylistAddingVideoPanelQualitySettingControlComboBox.Items.Add($"{stream.Height}p{(stream.FrameRate > 0 ? stream.FrameRate.ToString() : "N/A")}");
|
||||||
}
|
}
|
||||||
HomePlaylistAddingVideoPanelQualitySettingControlComboBox.SelectedIndex = 0;
|
HomePlaylistAddingVideoPanelQualitySettingControlComboBox.SelectedIndex = 0;
|
||||||
|
|
||||||
// Set trim start
|
// Set trim start
|
||||||
if (Math.Floor(VideoService.Duration.TotalHours) > 0) HomePlaylistAddingVideoPanelTrimStartTextBox.Text += $"{new string('0', Math.Floor(VideoService.Duration.TotalHours).ToString().Length)}:";
|
TrimStart = new TimeSpan(0);
|
||||||
if (Math.Floor(VideoService.Duration.TotalMinutes) > 0) HomePlaylistAddingVideoPanelTrimStartTextBox.Text += Math.Floor(VideoService.Duration.TotalHours) > 0 ? "00:" : $"{new string('0', VideoService.Duration.Minutes.ToString().Length)}:";
|
if (Math.Floor(VideoService.Metadata.Duration.TotalHours) > 0) HomePlaylistAddingVideoPanelTrimStartTextBox.Text += $"{new string('0', Math.Floor(VideoService.Metadata.Duration.TotalHours).ToString().Length)}:";
|
||||||
HomePlaylistAddingVideoPanelTrimStartTextBox.Text += Math.Floor(VideoService.Duration.TotalMinutes) > 0 ? "00" : $"{new string('0', VideoService.Duration.Seconds.ToString().Length)}";
|
if (Math.Floor(VideoService.Metadata.Duration.TotalMinutes) > 0) HomePlaylistAddingVideoPanelTrimStartTextBox.Text += Math.Floor(VideoService.Metadata.Duration.TotalHours) > 0 ? "00:" : $"{new string('0', VideoService.Metadata.Duration.Minutes.ToString().Length)}:";
|
||||||
|
HomePlaylistAddingVideoPanelTrimStartTextBox.Text += Math.Floor(VideoService.Metadata.Duration.TotalMinutes) > 0 ? "00" : $"{new string('0', VideoService.Metadata.Duration.Seconds.ToString().Length)}";
|
||||||
|
|
||||||
// Set trim end
|
// Set trim end
|
||||||
if (Math.Floor(VideoService.Duration.TotalHours) > 0) HomePlaylistAddingVideoPanelTrimEndTextBox.Text += $"{Math.Floor(VideoService.Duration.TotalHours)}:";
|
TrimEnd = VideoService.Metadata.Duration;
|
||||||
if (Math.Floor(VideoService.Duration.TotalMinutes) > 0) HomePlaylistAddingVideoPanelTrimEndTextBox.Text += Math.Floor(VideoService.Duration.TotalHours) > 0 ? $"{VideoService.Duration.Minutes:00}:" : $"{VideoService.Duration.Minutes}:";
|
if (Math.Floor(VideoService.Metadata.Duration.TotalHours) > 0) HomePlaylistAddingVideoPanelTrimEndTextBox.Text += $"{Math.Floor(VideoService.Metadata.Duration.TotalHours)}:";
|
||||||
HomePlaylistAddingVideoPanelTrimEndTextBox.Text += Math.Floor(VideoService.Duration.TotalMinutes) > 0 ? $"{VideoService.Duration.Seconds:00}" : $"{VideoService.Duration.Seconds}";
|
if (Math.Floor(VideoService.Metadata.Duration.TotalMinutes) > 0) HomePlaylistAddingVideoPanelTrimEndTextBox.Text += Math.Floor(VideoService.Metadata.Duration.TotalHours) > 0 ? $"{VideoService.Metadata.Duration.Minutes:00}:" : $"{VideoService.Metadata.Duration.Minutes}:";
|
||||||
|
HomePlaylistAddingVideoPanelTrimEndTextBox.Text += Math.Floor(VideoService.Metadata.Duration.TotalMinutes) > 0 ? $"{VideoService.Metadata.Duration.Seconds:00}" : $"{VideoService.Metadata.Duration.Seconds}";
|
||||||
|
|
||||||
// Set filename
|
// Set filename
|
||||||
string temporaryFilename = (string)Config.GetValue("default_filename");
|
string temporaryFilename = (string)Config.GetValue("default_filename");
|
||||||
Dictionary<string, string> filenameStandardTemplates = new Dictionary<string, string>()
|
Dictionary<string, string> filenameStandardTemplates = new Dictionary<string, string>()
|
||||||
{
|
{
|
||||||
{ "<title>", VideoService.Title },
|
{ "<title>", VideoService.Metadata.Title },
|
||||||
{ "<author>", VideoService.Author },
|
{ "<author>", VideoService.Metadata.Author },
|
||||||
{ "<views>", VideoService.Views.ToString() },
|
{ "<views>", VideoService.Metadata.Views.ToString() },
|
||||||
{ "<id>", VideoService.ID },
|
{ "<id>", VideoService.ID },
|
||||||
};
|
};
|
||||||
foreach (KeyValuePair<string, string> template in filenameStandardTemplates) temporaryFilename = temporaryFilename.Replace(template.Key, template.Value);
|
foreach (KeyValuePair<string, string> template in filenameStandardTemplates) temporaryFilename = temporaryFilename.Replace(template.Key, template.Value);
|
||||||
Dictionary<Regex, IFormattable> filenameFormatTemplates = new Dictionary<Regex, IFormattable>()
|
Dictionary<Regex, IFormattable> filenameFormatTemplates = new Dictionary<Regex, IFormattable>()
|
||||||
{
|
{
|
||||||
{ new Regex(@"<date_pub:(?<format>.*)>"), VideoService.Date },
|
{ new Regex(@"<date_pub:(?<format>.*)>"), VideoService.Metadata.Date },
|
||||||
{ new Regex(@"<date_now:(?<format>.*)>"), DateTime.Now },
|
{ new Regex(@"<date_now:(?<format>.*)>"), DateTime.Now },
|
||||||
{ new Regex(@"<duration:(?<format>.*)>"), VideoService.Duration },
|
{ new Regex(@"<duration:(?<format>.*)>"), VideoService.Metadata.Duration },
|
||||||
};
|
};
|
||||||
foreach (KeyValuePair<Regex, IFormattable> template in filenameFormatTemplates) foreach (Match templateMatch in template.Key.Matches(temporaryFilename)) temporaryFilename = temporaryFilename.Replace(templateMatch.Value, template.Value.ToString(templateMatch.Groups["format"].Value, null));
|
foreach (KeyValuePair<Regex, IFormattable> template in filenameFormatTemplates) foreach (Match templateMatch in template.Key.Matches(temporaryFilename)) temporaryFilename = temporaryFilename.Replace(templateMatch.Value, template.Value.ToString(templateMatch.Groups["format"].Value, null));
|
||||||
foreach (char c in System.IO.Path.GetInvalidFileNameChars()) temporaryFilename = temporaryFilename.Replace(c, ' ');
|
foreach (char c in System.IO.Path.GetInvalidFileNameChars()) temporaryFilename = temporaryFilename.Replace(c, ' ');
|
||||||
@@ -142,7 +146,7 @@ namespace VDownload.Views.Home
|
|||||||
|
|
||||||
// VIDEO OPTIONS
|
// VIDEO OPTIONS
|
||||||
public MediaType MediaType { get; set; }
|
public MediaType MediaType { get; set; }
|
||||||
public IBaseStream Stream { get; set; }
|
public BaseStream Stream { get; set; }
|
||||||
public TimeSpan TrimStart { get; set; }
|
public TimeSpan TrimStart { get; set; }
|
||||||
public TimeSpan TrimEnd { get; set; }
|
public TimeSpan TrimEnd { get; set; }
|
||||||
public string Filename { get; set; }
|
public string Filename { get; set; }
|
||||||
@@ -204,15 +208,15 @@ namespace VDownload.Views.Home
|
|||||||
|
|
||||||
TimeSpan parsedTimeSpan = new TimeSpan(hours, minutes, seconds);
|
TimeSpan parsedTimeSpan = new TimeSpan(hours, minutes, seconds);
|
||||||
|
|
||||||
if (parsedTimeSpan < VideoService.Duration && parsedTimeSpan > new TimeSpan(0)) TrimStart = parsedTimeSpan;
|
if (parsedTimeSpan < VideoService.Metadata.Duration && parsedTimeSpan > new TimeSpan(0)) TrimStart = parsedTimeSpan;
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
TrimStart = new TimeSpan(0);
|
TrimStart = new TimeSpan(0);
|
||||||
|
|
||||||
string newText = string.Empty;
|
string newText = string.Empty;
|
||||||
if (Math.Floor(VideoService.Duration.TotalHours) > 0) newText += $"{new string('0', Math.Floor(VideoService.Duration.TotalHours).ToString().Length)}:";
|
if (Math.Floor(VideoService.Metadata.Duration.TotalHours) > 0) newText += $"{new string('0', Math.Floor(VideoService.Metadata.Duration.TotalHours).ToString().Length)}:";
|
||||||
if (Math.Floor(VideoService.Duration.TotalMinutes) > 0) newText += Math.Floor(VideoService.Duration.TotalHours) > 0 ? "00:" : $"{new string('0', VideoService.Duration.Minutes.ToString().Length)}:";
|
if (Math.Floor(VideoService.Metadata.Duration.TotalMinutes) > 0) newText += Math.Floor(VideoService.Metadata.Duration.TotalHours) > 0 ? "00:" : $"{new string('0', VideoService.Metadata.Duration.Minutes.ToString().Length)}:";
|
||||||
newText += Math.Floor(VideoService.Duration.TotalMinutes) > 0 ? "00" : $"{new string('0', VideoService.Duration.Seconds.ToString().Length)}";
|
newText += Math.Floor(VideoService.Metadata.Duration.TotalMinutes) > 0 ? "00" : $"{new string('0', VideoService.Metadata.Duration.Seconds.ToString().Length)}";
|
||||||
|
|
||||||
if (newText != HomePlaylistAddingVideoPanelTrimStartTextBox.Text) HomePlaylistAddingVideoPanelTrimStartTextBox.Text = newText;
|
if (newText != HomePlaylistAddingVideoPanelTrimStartTextBox.Text) HomePlaylistAddingVideoPanelTrimStartTextBox.Text = newText;
|
||||||
}
|
}
|
||||||
@@ -231,15 +235,15 @@ namespace VDownload.Views.Home
|
|||||||
|
|
||||||
TimeSpan parsedTimeSpan = new TimeSpan(hours, minutes, seconds);
|
TimeSpan parsedTimeSpan = new TimeSpan(hours, minutes, seconds);
|
||||||
|
|
||||||
if (parsedTimeSpan < VideoService.Duration && parsedTimeSpan > new TimeSpan(0)) TrimEnd = parsedTimeSpan;
|
if (parsedTimeSpan < VideoService.Metadata.Duration && parsedTimeSpan > new TimeSpan(0)) TrimEnd = parsedTimeSpan;
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
TrimEnd = VideoService.Duration;
|
TrimEnd = VideoService.Metadata.Duration;
|
||||||
|
|
||||||
string newText = string.Empty;
|
string newText = string.Empty;
|
||||||
if (Math.Floor(VideoService.Duration.TotalHours) > 0) newText += $"{Math.Floor(VideoService.Duration.TotalHours)}:";
|
if (Math.Floor(VideoService.Metadata.Duration.TotalHours) > 0) newText += $"{Math.Floor(VideoService.Metadata.Duration.TotalHours)}:";
|
||||||
if (Math.Floor(VideoService.Duration.TotalMinutes) > 0) newText += Math.Floor(VideoService.Duration.TotalHours) > 0 ? $"{TrimEnd.Minutes:00}:" : $"{TrimEnd.Minutes}:";
|
if (Math.Floor(VideoService.Metadata.Duration.TotalMinutes) > 0) newText += Math.Floor(VideoService.Metadata.Duration.TotalHours) > 0 ? $"{TrimEnd.Minutes:00}:" : $"{TrimEnd.Minutes}:";
|
||||||
newText += Math.Floor(VideoService.Duration.TotalMinutes) > 0 ? $"{TrimEnd.Seconds:00}" : $"{TrimEnd.Seconds}";
|
newText += Math.Floor(VideoService.Metadata.Duration.TotalMinutes) > 0 ? $"{TrimEnd.Seconds:00}" : $"{TrimEnd.Seconds}";
|
||||||
|
|
||||||
if (newText != HomePlaylistAddingVideoPanelTrimEndTextBox.Text) HomePlaylistAddingVideoPanelTrimEndTextBox.Text = newText;
|
if (newText != HomePlaylistAddingVideoPanelTrimEndTextBox.Text) HomePlaylistAddingVideoPanelTrimEndTextBox.Text = newText;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,8 +39,8 @@
|
|||||||
<ColumnDefinition Width="*"/>
|
<ColumnDefinition Width="*"/>
|
||||||
<ColumnDefinition Width="Auto"/>
|
<ColumnDefinition Width="Auto"/>
|
||||||
</Grid.ColumnDefinitions>
|
</Grid.ColumnDefinitions>
|
||||||
<TextBlock Grid.Column="0" FontSize="18" VerticalAlignment="Center" FontWeight="SemiBold" Text="{x:Bind Title}"/>
|
<TextBlock Grid.Column="0" FontSize="18" VerticalAlignment="Center" FontWeight="SemiBold" Text="{x:Bind Data.VideoService.Metadata.Title}"/>
|
||||||
<TextBlock Grid.Column="1" FontSize="12" VerticalAlignment="Bottom" FontWeight="Light" Text="{x:Bind Author}" Margin="0,0,0,2"/>
|
<TextBlock Grid.Column="1" FontSize="12" VerticalAlignment="Bottom" FontWeight="Light" Text="{x:Bind Data.VideoService.Metadata.Author}" Margin="0,0,0,2"/>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid Grid.Row="1" Grid.RowSpan="2" Grid.Column="1" ColumnSpacing="5">
|
<Grid Grid.Row="1" Grid.RowSpan="2" Grid.Column="1" ColumnSpacing="5">
|
||||||
<Grid.RowDefinitions>
|
<Grid.RowDefinitions>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using Microsoft.Toolkit.Uwp.Notifications;
|
using Microsoft.Toolkit.Uwp.Connectivity;
|
||||||
|
using Microsoft.Toolkit.Uwp.Notifications;
|
||||||
using System;
|
using System;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
@@ -7,8 +8,8 @@ using System.Net;
|
|||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using VDownload.Core.Enums;
|
using VDownload.Core.Enums;
|
||||||
using VDownload.Core.Interfaces;
|
|
||||||
using VDownload.Core.Services;
|
using VDownload.Core.Services;
|
||||||
|
using VDownload.Core.Structs;
|
||||||
using Windows.ApplicationModel.ExtendedExecution;
|
using Windows.ApplicationModel.ExtendedExecution;
|
||||||
using Windows.ApplicationModel.Resources;
|
using Windows.ApplicationModel.Resources;
|
||||||
using Windows.Storage;
|
using Windows.Storage;
|
||||||
@@ -24,8 +25,8 @@ namespace VDownload.Views.Home
|
|||||||
{
|
{
|
||||||
#region CONSTANTS
|
#region CONSTANTS
|
||||||
|
|
||||||
private readonly ResourceDictionary IconsRes = new ResourceDictionary { Source = new Uri("ms-appx:///Resources/Icons.xaml") };
|
private static readonly ResourceDictionary IconsRes = new ResourceDictionary { Source = new Uri("ms-appx:///Resources/Icons.xaml") };
|
||||||
private readonly ResourceDictionary ImagesRes = new ResourceDictionary { Source = new Uri("ms-appx:///Resources/Images.xaml") };
|
private static readonly ResourceDictionary ImagesRes = new ResourceDictionary { Source = new Uri("ms-appx:///Resources/Images.xaml") };
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
@@ -33,40 +34,36 @@ namespace VDownload.Views.Home
|
|||||||
|
|
||||||
#region CONSTRUCTORS
|
#region CONSTRUCTORS
|
||||||
|
|
||||||
public HomeTaskPanel(IVideoService videoService, MediaType mediaType, IBaseStream stream, TimeSpan trimStart, TimeSpan trimEnd, string filename, MediaFileExtension extension, StorageFolder location, double schedule)
|
public HomeTaskPanel(TaskData taskData)
|
||||||
{
|
{
|
||||||
this.InitializeComponent();
|
this.InitializeComponent();
|
||||||
|
|
||||||
// Set video status
|
// Set task data
|
||||||
TaskStatus = Core.Enums.TaskStatus.Idle;
|
Data = taskData;
|
||||||
|
|
||||||
// Set video service object
|
// Set video status
|
||||||
VideoService = videoService;
|
Status = Core.Enums.TaskStatus.Idle;
|
||||||
|
|
||||||
// Create video cancellation token
|
// Create video cancellation token
|
||||||
CancellationTokenSource = new CancellationTokenSource();
|
CancellationTokenSource = new CancellationTokenSource();
|
||||||
|
|
||||||
// Set video options
|
// Set thumbnail image
|
||||||
MediaType = mediaType;
|
ThumbnailImage = Data.VideoService.Metadata.Thumbnail != null ? new BitmapImage { UriSource = Data.VideoService.Metadata.Thumbnail } : (BitmapImage)ImagesRes["UnknownThumbnailImage"];
|
||||||
Stream = stream;
|
|
||||||
TrimStart = trimStart;
|
|
||||||
TrimEnd = trimEnd;
|
|
||||||
Filename = filename;
|
|
||||||
Extension = extension;
|
|
||||||
Location = location;
|
|
||||||
Schedule = schedule;
|
|
||||||
|
|
||||||
// Set metadata
|
// Set source icon
|
||||||
ThumbnailImage = VideoService.Thumbnail != null ? new BitmapImage { UriSource = VideoService.Thumbnail } : (BitmapImage)ImagesRes["UnknownThumbnailImage"];
|
SourceImage = new BitmapIcon { UriSource = new Uri($"ms-appx:///Assets/Sources/{Data.VideoService.GetType().Namespace.Split(".").Last()}.png"), ShowAsMonochrome = false };
|
||||||
SourceImage = new BitmapIcon { UriSource = new Uri($"ms-appx:///Assets/Sources/{VideoService.GetType().Namespace.Split(".").Last()}.png"), ShowAsMonochrome = false };
|
|
||||||
Title = VideoService.Title;
|
// Set duration
|
||||||
Author = VideoService.Author;
|
TimeSpan newDuration = Data.TrimEnd.Subtract(Data.TrimStart);
|
||||||
TimeSpan newDuration = TrimEnd.Subtract(TrimStart);
|
Duration = TimeSpanCustomFormat.ToOptTHBaseMMSS(newDuration);
|
||||||
Duration += $"{(Math.Floor(newDuration.TotalHours) > 0 ? $"{Math.Floor(newDuration.TotalHours):0}:" : "")}{newDuration.Minutes:00}:{newDuration.Seconds:00}";
|
if (Data.VideoService.Metadata.Duration > newDuration) Duration += $" ({TimeSpanCustomFormat.ToOptTHBaseMMSS(Data.TrimStart, Data.TrimEnd)} - {TimeSpanCustomFormat.ToOptTHBaseMMSS(Data.TrimEnd, Data.TrimStart)})";
|
||||||
if (VideoService.Duration > newDuration) Duration += $" ({(Math.Floor(TrimStart.TotalHours) > 0 || Math.Floor(TrimEnd.TotalHours) > 0 ? $"{Math.Floor(TrimStart.TotalHours):0}:" : "")}{TrimStart.Minutes:00}:{TrimStart.Seconds:00} - {(Math.Floor(TrimStart.TotalHours) > 0 || Math.Floor(TrimEnd.TotalHours) > 0 ? $"{Math.Floor(TrimEnd.TotalHours):0}:" : "")}{TrimEnd.Minutes:00}:{TrimEnd.Seconds:00})";
|
|
||||||
MediaTypeQuality += ResourceLoader.GetForCurrentView().GetString($"MediaType{MediaType}Text");
|
// Set media type
|
||||||
if (MediaType != MediaType.OnlyAudio) MediaTypeQuality += $" ({Stream.Height}p{(Stream.FrameRate > 0 ? Stream.FrameRate.ToString() : "N/A")})";
|
MediaTypeQuality += ResourceLoader.GetForCurrentView().GetString($"MediaType{Data.MediaType}Text");
|
||||||
File += $@"{(Location != null ? Location.Path : $@"{UserDataPaths.GetDefault().Downloads}\VDownload")}\{Filename}.{Extension.ToString().ToLower()}";
|
if (Data.MediaType != MediaType.OnlyAudio) MediaTypeQuality += $" ({Data.Stream.Height}p{(Data.Stream.FrameRate > 0 ? Data.Stream.FrameRate.ToString() : "N/A")})";
|
||||||
|
|
||||||
|
// Set file
|
||||||
|
File += $@"{(Data.Location != null ? Data.Location.Path : $@"{UserDataPaths.GetDefault().Downloads}\VDownload")}\{Data.Filename}.{Data.Extension.ToString().ToLower()}";
|
||||||
|
|
||||||
// Set state controls
|
// Set state controls
|
||||||
HomeTaskPanelStateIcon.Source = (SvgImageSource)IconsRes["StateIdleIcon"];
|
HomeTaskPanelStateIcon.Source = (SvgImageSource)IconsRes["StateIdleIcon"];
|
||||||
@@ -80,26 +77,16 @@ namespace VDownload.Views.Home
|
|||||||
|
|
||||||
#region PROPERTIES
|
#region PROPERTIES
|
||||||
|
|
||||||
// VIDEO STATUS
|
// TASK STATUS
|
||||||
public Core.Enums.TaskStatus TaskStatus { get; set; }
|
public Core.Enums.TaskStatus Status { get; set; }
|
||||||
|
|
||||||
// TASK CANCELLATION TOKEN
|
// TASK CANCELLATION TOKEN
|
||||||
public CancellationTokenSource CancellationTokenSource { get; set; }
|
public CancellationTokenSource CancellationTokenSource { get; set; }
|
||||||
|
|
||||||
// VIDEO SERVICE
|
// TASK DATA
|
||||||
private IVideoService VideoService { get; set; }
|
private TaskData Data { get; set; }
|
||||||
|
|
||||||
// VIDEO OPTIONS
|
// TASK PANEL DATA
|
||||||
private MediaType MediaType { get; set; }
|
|
||||||
private IBaseStream Stream { get; set; }
|
|
||||||
private TimeSpan TrimStart { get; set; }
|
|
||||||
private TimeSpan TrimEnd { get; set; }
|
|
||||||
private string Filename { get; set; }
|
|
||||||
private MediaFileExtension Extension { get; set; }
|
|
||||||
private StorageFolder Location { get; set; }
|
|
||||||
private double Schedule { get; set; }
|
|
||||||
|
|
||||||
// VIDEO PANEL DATA
|
|
||||||
private ImageSource ThumbnailImage { get; set; }
|
private ImageSource ThumbnailImage { get; set; }
|
||||||
private IconElement SourceImage { get; set; }
|
private IconElement SourceImage { get; set; }
|
||||||
private string Title { get; set; }
|
private string Title { get; set; }
|
||||||
@@ -123,12 +110,12 @@ namespace VDownload.Views.Home
|
|||||||
CancellationTokenSource = new CancellationTokenSource();
|
CancellationTokenSource = new CancellationTokenSource();
|
||||||
|
|
||||||
// Scheduling
|
// Scheduling
|
||||||
if (Schedule > 0)
|
if (Data.Schedule > 0)
|
||||||
{
|
{
|
||||||
DateTime ScheduledDateTime = DateTime.Now.AddMinutes(Schedule);
|
DateTime ScheduledDateTime = DateTime.Now.AddMinutes(Data.Schedule);
|
||||||
|
|
||||||
// Set task status
|
// Set task status
|
||||||
TaskStatus = Core.Enums.TaskStatus.Scheduled;
|
Status = Core.Enums.TaskStatus.Scheduled;
|
||||||
|
|
||||||
// Set state controls
|
// Set state controls
|
||||||
HomeTaskPanelStateIcon.Source = (SvgImageSource)IconsRes["StateScheduledIcon"];
|
HomeTaskPanelStateIcon.Source = (SvgImageSource)IconsRes["StateScheduledIcon"];
|
||||||
@@ -139,20 +126,19 @@ namespace VDownload.Views.Home
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Set task status
|
// Set task status
|
||||||
TaskStatus = Core.Enums.TaskStatus.Waiting;
|
Status = Core.Enums.TaskStatus.Waiting;
|
||||||
|
|
||||||
// Set state controls
|
// Set state controls
|
||||||
HomeTaskPanelStateIcon.Source = (SvgImageSource)IconsRes["StateWaitingIcon"];
|
HomeTaskPanelStateIcon.Source = (SvgImageSource)IconsRes["StateWaitingIcon"];
|
||||||
HomeTaskPanelStateText.Text = ResourceLoader.GetForCurrentView().GetString("HomeTaskPanelStateTextWaiting");
|
HomeTaskPanelStateText.Text = ResourceLoader.GetForCurrentView().GetString("HomeTaskPanelStateTextWaiting");
|
||||||
HomeTaskPanelStateProgressBar.Visibility = Visibility.Visible;
|
HomeTaskPanelStateProgressBar.Visibility = Visibility.Visible;
|
||||||
HomeTaskPanelStateProgressBar.IsIndeterminate = true;
|
|
||||||
|
|
||||||
// Wait in queue
|
// Wait in queue
|
||||||
await HomeMain.WaitInQueue(delayWhenOnMeteredConnection, CancellationTokenSource.Token);
|
await HomeMain.WaitInQueue(delayWhenOnMeteredConnection, CancellationTokenSource.Token);
|
||||||
if (!CancellationTokenSource.IsCancellationRequested)
|
if (!CancellationTokenSource.IsCancellationRequested)
|
||||||
{
|
{
|
||||||
// Set task status
|
// Set task status
|
||||||
TaskStatus = Core.Enums.TaskStatus.InProgress;
|
Status = Core.Enums.TaskStatus.InProgress;
|
||||||
|
|
||||||
// Get task unique ID
|
// Get task unique ID
|
||||||
string uniqueID = TaskId.Get();
|
string uniqueID = TaskId.Get();
|
||||||
@@ -176,30 +162,17 @@ namespace VDownload.Views.Home
|
|||||||
Stopwatch taskStopwatch = Stopwatch.StartNew();
|
Stopwatch taskStopwatch = Stopwatch.StartNew();
|
||||||
|
|
||||||
// Set progress event handlers
|
// Set progress event handlers
|
||||||
VideoService.DownloadingStarted += (s, a) =>
|
Data.VideoService.DownloadingProgressChanged += (s, a) =>
|
||||||
{
|
{
|
||||||
HomeTaskPanelStateIcon.Source = (SvgImageSource)IconsRes["StateDownloadingIcon"];
|
HomeTaskPanelStateIcon.Source = (SvgImageSource)IconsRes["StateDownloadingIcon"];
|
||||||
HomeTaskPanelStateText.Text = $"{ResourceLoader.GetForCurrentView().GetString("HomeTaskPanelStateTextDownloading")} (0%)";
|
HomeTaskPanelStateText.Text = $"{ResourceLoader.GetForCurrentView().GetString("HomeTaskPanelStateTextDownloading")} ({Math.Round(a.Progress)}%)";
|
||||||
HomeTaskPanelStateProgressBar.IsIndeterminate = false;
|
HomeTaskPanelStateProgressBar.Value = a.Progress;
|
||||||
HomeTaskPanelStateProgressBar.Value = 0;
|
|
||||||
};
|
};
|
||||||
VideoService.DownloadingProgressChanged += (s, a) =>
|
Data.VideoService.ProcessingProgressChanged += (s, a) =>
|
||||||
{
|
|
||||||
HomeTaskPanelStateIcon.Source = (SvgImageSource)IconsRes["StateDownloadingIcon"];
|
|
||||||
HomeTaskPanelStateText.Text = $"{ResourceLoader.GetForCurrentView().GetString("HomeTaskPanelStateTextDownloading")} ({a.ProgressPercentage}%)";
|
|
||||||
HomeTaskPanelStateProgressBar.Value = a.ProgressPercentage;
|
|
||||||
};
|
|
||||||
VideoService.ProcessingStarted += (s, a) =>
|
|
||||||
{
|
{
|
||||||
HomeTaskPanelStateIcon.Source = (SvgImageSource)IconsRes["StateProcessingIcon"];
|
HomeTaskPanelStateIcon.Source = (SvgImageSource)IconsRes["StateProcessingIcon"];
|
||||||
HomeTaskPanelStateText.Text = $"{ResourceLoader.GetForCurrentView().GetString("HomeTaskPanelStateTextProcessing")} (0%)";
|
HomeTaskPanelStateText.Text = $"{ResourceLoader.GetForCurrentView().GetString("HomeTaskPanelStateTextProcessing")} ({Math.Round(a.Progress)}%)";
|
||||||
HomeTaskPanelStateProgressBar.Value = 0;
|
HomeTaskPanelStateProgressBar.Value = a.Progress;
|
||||||
};
|
|
||||||
VideoService.ProcessingProgressChanged += (s, a) =>
|
|
||||||
{
|
|
||||||
HomeTaskPanelStateIcon.Source = (SvgImageSource)IconsRes["StateProcessingIcon"];
|
|
||||||
HomeTaskPanelStateText.Text = $"{ResourceLoader.GetForCurrentView().GetString("HomeTaskPanelStateTextProcessing")} ({a.ProgressPercentage}%)";
|
|
||||||
HomeTaskPanelStateProgressBar.Value = a.ProgressPercentage;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Request extended session
|
// Request extended session
|
||||||
@@ -208,7 +181,7 @@ namespace VDownload.Views.Home
|
|||||||
|
|
||||||
// Start task
|
// Start task
|
||||||
CancellationTokenSource.Token.ThrowIfCancellationRequested();
|
CancellationTokenSource.Token.ThrowIfCancellationRequested();
|
||||||
StorageFile tempOutputFile = await VideoService.DownloadAndTranscodeAsync(tempFolder, Stream, Extension, MediaType, TrimStart, TrimEnd, CancellationTokenSource.Token);
|
StorageFile tempOutputFile = await Data.VideoService.DownloadAndTranscodeAsync(tempFolder, Data.Stream, Data.Extension, Data.MediaType, Data.TrimStart, Data.TrimEnd, CancellationTokenSource.Token);
|
||||||
|
|
||||||
// Dispose session
|
// Dispose session
|
||||||
session.Dispose();
|
session.Dispose();
|
||||||
@@ -222,9 +195,9 @@ namespace VDownload.Views.Home
|
|||||||
HomeTaskPanelStateProgressBar.IsIndeterminate = true;
|
HomeTaskPanelStateProgressBar.IsIndeterminate = true;
|
||||||
|
|
||||||
// Move to output location
|
// Move to output location
|
||||||
StorageFile outputFile;
|
string filename = $"{Data.Filename}.{Data.Extension.ToString().ToLower()}";
|
||||||
if (Location != null) outputFile = await Location.CreateFileAsync($"{Filename}.{Extension.ToString().ToLower()}", (bool)Config.GetValue("replace_output_file_if_exists") ? CreationCollisionOption.ReplaceExisting : CreationCollisionOption.GenerateUniqueName);
|
CreationCollisionOption collisionOption = (bool)Config.GetValue("replace_output_file_if_exists") ? CreationCollisionOption.ReplaceExisting : CreationCollisionOption.GenerateUniqueName;
|
||||||
else outputFile = await DownloadsFolder.CreateFileAsync($"{Filename}.{Extension.ToString().ToLower()}", (bool)Config.GetValue("replace_output_file_if_exists") ? CreationCollisionOption.ReplaceExisting : CreationCollisionOption.GenerateUniqueName);
|
StorageFile outputFile = await (Data.Location != null ? Data.Location.CreateFileAsync(filename, collisionOption): DownloadsFolder.CreateFileAsync(filename, collisionOption));
|
||||||
await tempOutputFile.MoveAndReplaceAsync(outputFile);
|
await tempOutputFile.MoveAndReplaceAsync(outputFile);
|
||||||
|
|
||||||
// Stop stopwatch
|
// Stop stopwatch
|
||||||
@@ -270,7 +243,7 @@ namespace VDownload.Views.Home
|
|||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
// Set video status
|
// Set video status
|
||||||
TaskStatus = Core.Enums.TaskStatus.Idle;
|
Status = Core.Enums.TaskStatus.Idle;
|
||||||
|
|
||||||
// Change icon
|
// Change icon
|
||||||
HomeTaskPanelStartStopButton.Icon = new SymbolIcon(Symbol.Download);
|
HomeTaskPanelStartStopButton.Icon = new SymbolIcon(Symbol.Download);
|
||||||
@@ -313,16 +286,18 @@ namespace VDownload.Views.Home
|
|||||||
private async void HomeTaskPanelSourceButton_Click(object sender, RoutedEventArgs e)
|
private async void HomeTaskPanelSourceButton_Click(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
// Launch the website
|
// Launch the website
|
||||||
await Windows.System.Launcher.LaunchUriAsync(VideoService.VideoUrl);
|
await Windows.System.Launcher.LaunchUriAsync(Data.VideoService.VideoUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
// START STOP BUTTON CLICKED
|
// START STOP BUTTON CLICKED
|
||||||
private async void HomeTaskPanelStartStopButton_Click(object sender, RoutedEventArgs e)
|
private async void HomeTaskPanelStartStopButton_Click(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
if (TaskStatus == Core.Enums.TaskStatus.InProgress || TaskStatus == Core.Enums.TaskStatus.Waiting || TaskStatus == Core.Enums.TaskStatus.Scheduled) CancellationTokenSource.Cancel();
|
if (Status == Core.Enums.TaskStatus.InProgress || Status == Core.Enums.TaskStatus.Waiting || Status == Core.Enums.TaskStatus.Scheduled) CancellationTokenSource.Cancel();
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
bool delay = (bool)Config.GetValue("delay_task_when_queued_task_starts_on_metered_network");
|
bool delay = (bool)Config.GetValue("delay_task_when_queued_task_starts_on_metered_network");
|
||||||
|
if (NetworkHelper.Instance.ConnectionInformation.IsInternetOnMeteredConnection)
|
||||||
|
{
|
||||||
ContentDialogResult dialogResult = await new ContentDialog
|
ContentDialogResult dialogResult = await new ContentDialog
|
||||||
{
|
{
|
||||||
Title = ResourceLoader.GetForCurrentView().GetString("HomeTaskPanelTaskStartMeteredConnectionDialogTitle"),
|
Title = ResourceLoader.GetForCurrentView().GetString("HomeTaskPanelTaskStartMeteredConnectionDialogTitle"),
|
||||||
@@ -337,6 +312,7 @@ namespace VDownload.Views.Home
|
|||||||
case ContentDialogResult.Secondary: delay = false; break;
|
case ContentDialogResult.Secondary: delay = false; break;
|
||||||
case ContentDialogResult.None: return;
|
case ContentDialogResult.None: return;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
await Start(delay);
|
await Start(delay);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -344,7 +320,7 @@ namespace VDownload.Views.Home
|
|||||||
// REMOVE BUTTON CLICKED
|
// REMOVE BUTTON CLICKED
|
||||||
private void HomeTaskPanelRemoveButton_Click(object sender, RoutedEventArgs e)
|
private void HomeTaskPanelRemoveButton_Click(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
if (TaskStatus == Core.Enums.TaskStatus.InProgress || TaskStatus == Core.Enums.TaskStatus.Waiting || TaskStatus == Core.Enums.TaskStatus.Scheduled) CancellationTokenSource.Cancel();
|
if (Status == Core.Enums.TaskStatus.InProgress || Status == Core.Enums.TaskStatus.Waiting || Status == Core.Enums.TaskStatus.Scheduled) CancellationTokenSource.Cancel();
|
||||||
TaskRemovingRequested?.Invoke(this, EventArgs.Empty);
|
TaskRemovingRequested?.Invoke(this, EventArgs.Empty);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -217,9 +217,9 @@
|
|||||||
<cc:SettingControl x:Uid="HomeVideoAddingTrimSettingControl" Icon="{ThemeResource TrimIcon}">
|
<cc:SettingControl x:Uid="HomeVideoAddingTrimSettingControl" Icon="{ThemeResource TrimIcon}">
|
||||||
<cc:SettingControl.SettingContent>
|
<cc:SettingControl.SettingContent>
|
||||||
<StackPanel Orientation="Horizontal" Spacing="5">
|
<StackPanel Orientation="Horizontal" Spacing="5">
|
||||||
<TextBox x:Name="HomeVideoAddingTrimStartTextBox" ex:TextBoxExtensions.CustomMask="{x:Bind VideoService.Duration, Converter={StaticResource TimeSpanToTextBoxMaskElementsConverter}}" ex:TextBoxExtensions.Mask="{x:Bind VideoService.Duration, Converter={StaticResource TimeSpanToTextBoxMaskConverter}}" TextChanged="HomeVideoAddingTrimStartTextBox_TextChanged"/>
|
<TextBox x:Name="HomeVideoAddingTrimStartTextBox" ex:TextBoxExtensions.CustomMask="{x:Bind VideoService.Metadata.Duration, Converter={StaticResource TimeSpanToTextBoxMaskElementsConverter}}" ex:TextBoxExtensions.Mask="{x:Bind VideoService.Metadata.Duration, Converter={StaticResource TimeSpanToTextBoxMaskConverter}}" TextChanged="HomeVideoAddingTrimStartTextBox_TextChanged"/>
|
||||||
<TextBlock VerticalAlignment="Center" Text="-"/>
|
<TextBlock VerticalAlignment="Center" Text="-"/>
|
||||||
<TextBox x:Name="HomeVideoAddingTrimEndTextBox" ex:TextBoxExtensions.CustomMask="{x:Bind VideoService.Duration, Converter={StaticResource TimeSpanToTextBoxMaskElementsConverter}}" ex:TextBoxExtensions.Mask="{x:Bind VideoService.Duration, Converter={StaticResource TimeSpanToTextBoxMaskConverter}}" TextChanged="HomeVideoAddingTrimEndTextBox_TextChanged"/>
|
<TextBox x:Name="HomeVideoAddingTrimEndTextBox" ex:TextBoxExtensions.CustomMask="{x:Bind VideoService.Metadata.Duration, Converter={StaticResource TimeSpanToTextBoxMaskElementsConverter}}" ex:TextBoxExtensions.Mask="{x:Bind VideoService.Metadata.Duration, Converter={StaticResource TimeSpanToTextBoxMaskConverter}}" TextChanged="HomeVideoAddingTrimEndTextBox_TextChanged"/>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</cc:SettingControl.SettingContent>
|
</cc:SettingControl.SettingContent>
|
||||||
</cc:SettingControl>
|
</cc:SettingControl>
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ using VDownload.Core.Enums;
|
|||||||
using VDownload.Core.EventArgs;
|
using VDownload.Core.EventArgs;
|
||||||
using VDownload.Core.Interfaces;
|
using VDownload.Core.Interfaces;
|
||||||
using VDownload.Core.Services;
|
using VDownload.Core.Services;
|
||||||
|
using VDownload.Core.Structs;
|
||||||
using Windows.ApplicationModel.Resources;
|
using Windows.ApplicationModel.Resources;
|
||||||
using Windows.Storage;
|
using Windows.Storage;
|
||||||
using Windows.Storage.AccessCache;
|
using Windows.Storage.AccessCache;
|
||||||
@@ -39,13 +40,13 @@ namespace VDownload.Views.Home
|
|||||||
VideoService = videoService;
|
VideoService = videoService;
|
||||||
|
|
||||||
// Set metadata
|
// Set metadata
|
||||||
ThumbnailImage = VideoService.Thumbnail != null ? new BitmapImage { UriSource = VideoService.Thumbnail } : (BitmapImage)ImagesRes["UnknownThumbnailImage"];
|
ThumbnailImage = VideoService.Metadata.Thumbnail != null ? new BitmapImage { UriSource = VideoService.Metadata.Thumbnail } : (BitmapImage)ImagesRes["UnknownThumbnailImage"];
|
||||||
SourceImage = new BitmapIcon { UriSource = new Uri($"ms-appx:///Assets/Sources/{VideoService.GetType().Namespace.Split(".").Last()}.png"), ShowAsMonochrome = false };
|
SourceImage = new BitmapIcon { UriSource = new Uri($"ms-appx:///Assets/Sources/{VideoService.GetType().Namespace.Split(".").Last()}.png"), ShowAsMonochrome = false };
|
||||||
Title = VideoService.Title;
|
Title = VideoService.Metadata.Title;
|
||||||
Author = VideoService.Author;
|
Author = VideoService.Metadata.Author;
|
||||||
Views = VideoService.Views.ToString();
|
Views = VideoService.Metadata.Views.ToString();
|
||||||
Date = VideoService.Date.ToString(CultureInfo.InstalledUICulture.DateTimeFormat.ShortDatePattern);
|
Date = VideoService.Metadata.Date.ToString(CultureInfo.InstalledUICulture.DateTimeFormat.ShortDatePattern);
|
||||||
Duration = $"{(Math.Floor(VideoService.Duration.TotalHours) > 0 ? $"{Math.Floor(VideoService.Duration.TotalHours):0}:" : "")}{VideoService.Duration.Minutes:00}:{VideoService.Duration.Seconds:00}";
|
Duration = TimeSpanCustomFormat.ToOptTHBaseMMSS(VideoService.Metadata.Duration);
|
||||||
|
|
||||||
// Set media type
|
// Set media type
|
||||||
foreach (string mediaType in Enum.GetNames(typeof(MediaType)))
|
foreach (string mediaType in Enum.GetNames(typeof(MediaType)))
|
||||||
@@ -55,42 +56,40 @@ namespace VDownload.Views.Home
|
|||||||
HomeVideoAddingMediaTypeSettingControlComboBox.SelectedIndex = (int)Config.GetValue("default_media_type");
|
HomeVideoAddingMediaTypeSettingControlComboBox.SelectedIndex = (int)Config.GetValue("default_media_type");
|
||||||
|
|
||||||
// Set quality
|
// Set quality
|
||||||
foreach (IBaseStream stream in VideoService.BaseStreams)
|
foreach (BaseStream stream in VideoService.BaseStreams)
|
||||||
{
|
{
|
||||||
HomeVideoAddingQualitySettingControlComboBox.Items.Add($"{stream.Height}p{(stream.FrameRate > 0 ? stream.FrameRate.ToString() : "N/A")}");
|
HomeVideoAddingQualitySettingControlComboBox.Items.Add($"{stream.Height}p{(stream.FrameRate > 0 ? stream.FrameRate.ToString() : "N/A")}");
|
||||||
}
|
}
|
||||||
HomeVideoAddingQualitySettingControlComboBox.SelectedIndex = 0;
|
HomeVideoAddingQualitySettingControlComboBox.SelectedIndex = 0;
|
||||||
|
|
||||||
// Set trim start
|
// Set trim start
|
||||||
if (Math.Floor(VideoService.Duration.TotalHours) > 0) HomeVideoAddingTrimStartTextBox.Text += $"{new string('0', Math.Floor(VideoService.Duration.TotalHours).ToString().Length)}:";
|
TrimStart = TimeSpan.Zero;
|
||||||
if (Math.Floor(VideoService.Duration.TotalMinutes) > 0) HomeVideoAddingTrimStartTextBox.Text += Math.Floor(VideoService.Duration.TotalHours) > 0 ? "00:" : $"{new string('0', VideoService.Duration.Minutes.ToString().Length)}:";
|
HomeVideoAddingTrimStartTextBox.Text = TimeSpanCustomFormat.ToOptTHMMBaseSS(TrimStart, VideoService.Metadata.Duration);
|
||||||
HomeVideoAddingTrimStartTextBox.Text += Math.Floor(VideoService.Duration.TotalMinutes) > 0 ? "00" : $"{new string('0', VideoService.Duration.Seconds.ToString().Length)}";
|
|
||||||
|
|
||||||
// Set trim end
|
// Set trim end
|
||||||
if (Math.Floor(VideoService.Duration.TotalHours) > 0) HomeVideoAddingTrimEndTextBox.Text += $"{Math.Floor(VideoService.Duration.TotalHours)}:";
|
TrimEnd = VideoService.Metadata.Duration;
|
||||||
if (Math.Floor(VideoService.Duration.TotalMinutes) > 0) HomeVideoAddingTrimEndTextBox.Text += Math.Floor(VideoService.Duration.TotalHours) > 0 ? $"{VideoService.Duration.Minutes:00}:" : $"{VideoService.Duration.Minutes}:";
|
HomeVideoAddingTrimStartTextBox.Text = TimeSpanCustomFormat.ToOptTHMMBaseSS(TrimEnd);
|
||||||
HomeVideoAddingTrimEndTextBox.Text += Math.Floor(VideoService.Duration.TotalMinutes) > 0 ? $"{VideoService.Duration.Seconds:00}" : $"{VideoService.Duration.Seconds}";
|
|
||||||
|
|
||||||
// Set filename
|
// Set filename
|
||||||
string temporaryFilename = (string)Config.GetValue("default_filename");
|
string temporaryFilename = (string)Config.GetValue("default_filename");
|
||||||
Dictionary<string, string> filenameStandardTemplates = new Dictionary<string, string>()
|
Dictionary<string, string> filenameStandardTemplates = new Dictionary<string, string>()
|
||||||
{
|
{
|
||||||
{ "<title>", VideoService.Title },
|
{ "<title>", VideoService.Metadata.Title },
|
||||||
{ "<author>", VideoService.Author },
|
{ "<author>", VideoService.Metadata.Author },
|
||||||
{ "<views>", VideoService.Views.ToString() },
|
{ "<views>", VideoService.Metadata.Views.ToString() },
|
||||||
{ "<id>", VideoService.ID },
|
{ "<id>", VideoService.ID },
|
||||||
};
|
};
|
||||||
foreach (KeyValuePair<string, string> template in filenameStandardTemplates) temporaryFilename = temporaryFilename.Replace(template.Key, template.Value);
|
foreach (KeyValuePair<string, string> template in filenameStandardTemplates) temporaryFilename = temporaryFilename.Replace(template.Key, template.Value);
|
||||||
Dictionary<Regex, IFormattable> filenameFormatTemplates = new Dictionary<Regex, IFormattable>()
|
Dictionary<Regex, IFormattable> filenameFormatTemplates = new Dictionary<Regex, IFormattable>()
|
||||||
{
|
{
|
||||||
{ new Regex(@"<date_pub:(?<format>.*)>"), VideoService.Date },
|
{ new Regex(@"<date_pub:(?<format>.*)>"), VideoService.Metadata.Date },
|
||||||
{ new Regex(@"<date_now:(?<format>.*)>"), DateTime.Now },
|
{ new Regex(@"<date_now:(?<format>.*)>"), DateTime.Now },
|
||||||
{ new Regex(@"<duration:(?<format>.*)>"), VideoService.Duration },
|
{ new Regex(@"<duration:(?<format>.*)>"), VideoService.Metadata.Duration },
|
||||||
};
|
};
|
||||||
foreach (KeyValuePair<Regex, IFormattable> template in filenameFormatTemplates) foreach (Match templateMatch in template.Key.Matches(temporaryFilename)) temporaryFilename = temporaryFilename.Replace(templateMatch.Value, template.Value.ToString(templateMatch.Groups["format"].Value, null));
|
foreach (KeyValuePair<Regex, IFormattable> template in filenameFormatTemplates) foreach (Match templateMatch in template.Key.Matches(temporaryFilename)) temporaryFilename = temporaryFilename.Replace(templateMatch.Value, template.Value.ToString(templateMatch.Groups["format"].Value, null));
|
||||||
foreach (char c in System.IO.Path.GetInvalidFileNameChars()) temporaryFilename = temporaryFilename.Replace(c, ' ');
|
foreach (char c in System.IO.Path.GetInvalidFileNameChars()) temporaryFilename = temporaryFilename.Replace(c, ' ');
|
||||||
HomeVideoAddingFilenameTextBox.Text = temporaryFilename;
|
|
||||||
Filename = temporaryFilename;
|
Filename = temporaryFilename;
|
||||||
|
HomeVideoAddingFilenameTextBox.Text = Filename;
|
||||||
|
|
||||||
// Set location
|
// Set location
|
||||||
if (!(bool)Config.GetValue("custom_media_location") && StorageApplicationPermissions.FutureAccessList.ContainsItem("last_media_location"))
|
if (!(bool)Config.GetValue("custom_media_location") && StorageApplicationPermissions.FutureAccessList.ContainsItem("last_media_location"))
|
||||||
@@ -135,7 +134,7 @@ namespace VDownload.Views.Home
|
|||||||
|
|
||||||
// VIDEO OPTIONS
|
// VIDEO OPTIONS
|
||||||
private MediaType MediaType { get; set; }
|
private MediaType MediaType { get; set; }
|
||||||
private IBaseStream Stream { get; set; }
|
private BaseStream Stream { get; set; }
|
||||||
private TimeSpan TrimStart { get; set; }
|
private TimeSpan TrimStart { get; set; }
|
||||||
private TimeSpan TrimEnd { get; set; }
|
private TimeSpan TrimEnd { get; set; }
|
||||||
private string Filename { get; set; }
|
private string Filename { get; set; }
|
||||||
@@ -197,15 +196,15 @@ namespace VDownload.Views.Home
|
|||||||
|
|
||||||
TimeSpan parsedTimeSpan = new TimeSpan(hours, minutes, seconds);
|
TimeSpan parsedTimeSpan = new TimeSpan(hours, minutes, seconds);
|
||||||
|
|
||||||
if (parsedTimeSpan < VideoService.Duration && parsedTimeSpan > new TimeSpan(0)) TrimStart = parsedTimeSpan;
|
if (parsedTimeSpan < VideoService.Metadata.Duration && parsedTimeSpan > new TimeSpan(0)) TrimStart = parsedTimeSpan;
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
TrimStart = new TimeSpan(0);
|
TrimStart = new TimeSpan(0);
|
||||||
|
|
||||||
string newText = string.Empty;
|
string newText = string.Empty;
|
||||||
if (Math.Floor(VideoService.Duration.TotalHours) > 0) newText += $"{new string('0', Math.Floor(VideoService.Duration.TotalHours).ToString().Length)}:";
|
if (Math.Floor(VideoService.Metadata.Duration.TotalHours) > 0) newText += $"{new string('0', Math.Floor(VideoService.Metadata.Duration.TotalHours).ToString().Length)}:";
|
||||||
if (Math.Floor(VideoService.Duration.TotalMinutes) > 0) newText += Math.Floor(VideoService.Duration.TotalHours) > 0 ? "00:" : $"{new string('0', VideoService.Duration.Minutes.ToString().Length)}:";
|
if (Math.Floor(VideoService.Metadata.Duration.TotalMinutes) > 0) newText += Math.Floor(VideoService.Metadata.Duration.TotalHours) > 0 ? "00:" : $"{new string('0', VideoService.Metadata.Duration.Minutes.ToString().Length)}:";
|
||||||
newText += Math.Floor(VideoService.Duration.TotalMinutes) > 0 ? "00" : $"{new string('0', VideoService.Duration.Seconds.ToString().Length)}";
|
newText += Math.Floor(VideoService.Metadata.Duration.TotalMinutes) > 0 ? "00" : $"{new string('0', VideoService.Metadata.Duration.Seconds.ToString().Length)}";
|
||||||
|
|
||||||
if (newText != HomeVideoAddingTrimStartTextBox.Text) HomeVideoAddingTrimStartTextBox.Text = newText;
|
if (newText != HomeVideoAddingTrimStartTextBox.Text) HomeVideoAddingTrimStartTextBox.Text = newText;
|
||||||
}
|
}
|
||||||
@@ -224,15 +223,15 @@ namespace VDownload.Views.Home
|
|||||||
|
|
||||||
TimeSpan parsedTimeSpan = new TimeSpan(hours, minutes, seconds);
|
TimeSpan parsedTimeSpan = new TimeSpan(hours, minutes, seconds);
|
||||||
|
|
||||||
if (parsedTimeSpan < VideoService.Duration && parsedTimeSpan > new TimeSpan(0)) TrimEnd = parsedTimeSpan;
|
if (parsedTimeSpan < VideoService.Metadata.Duration && parsedTimeSpan > new TimeSpan(0)) TrimEnd = parsedTimeSpan;
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
TrimEnd = VideoService.Duration;
|
TrimEnd = VideoService.Metadata.Duration;
|
||||||
|
|
||||||
string newText = string.Empty;
|
string newText = string.Empty;
|
||||||
if (Math.Floor(VideoService.Duration.TotalHours) > 0) newText += $"{Math.Floor(VideoService.Duration.TotalHours)}:";
|
if (Math.Floor(VideoService.Metadata.Duration.TotalHours) > 0) newText += $"{Math.Floor(VideoService.Metadata.Duration.TotalHours)}:";
|
||||||
if (Math.Floor(VideoService.Duration.TotalMinutes) > 0) newText += Math.Floor(VideoService.Duration.TotalHours) > 0 ? $"{TrimEnd.Minutes:00}:" : $"{TrimEnd.Minutes}:";
|
if (Math.Floor(VideoService.Metadata.Duration.TotalMinutes) > 0) newText += Math.Floor(VideoService.Metadata.Duration.TotalHours) > 0 ? $"{TrimEnd.Minutes:00}:" : $"{TrimEnd.Minutes}:";
|
||||||
newText += Math.Floor(VideoService.Duration.TotalMinutes) > 0 ? $"{TrimEnd.Seconds:00}" : $"{TrimEnd.Seconds}";
|
newText += Math.Floor(VideoService.Metadata.Duration.TotalMinutes) > 0 ? $"{TrimEnd.Seconds:00}" : $"{TrimEnd.Seconds}";
|
||||||
|
|
||||||
if (newText != HomeVideoAddingTrimEndTextBox.Text) HomeVideoAddingTrimEndTextBox.Text = newText;
|
if (newText != HomeVideoAddingTrimEndTextBox.Text) HomeVideoAddingTrimEndTextBox.Text = newText;
|
||||||
}
|
}
|
||||||
@@ -267,12 +266,14 @@ namespace VDownload.Views.Home
|
|||||||
// LOCATION BROWSE BUTTON CLICKED
|
// LOCATION BROWSE BUTTON CLICKED
|
||||||
private async void HomeVideoAddingLocationBrowseButton_Click(object sender, RoutedEventArgs e)
|
private async void HomeVideoAddingLocationBrowseButton_Click(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
|
// Create location picker
|
||||||
FolderPicker picker = new FolderPicker
|
FolderPicker picker = new FolderPicker
|
||||||
{
|
{
|
||||||
SuggestedStartLocation = PickerLocationId.Downloads
|
SuggestedStartLocation = PickerLocationId.Downloads
|
||||||
};
|
};
|
||||||
picker.FileTypeFilter.Add("*");
|
picker.FileTypeFilter.Add("*");
|
||||||
|
|
||||||
|
// Select location
|
||||||
StorageFolder selectedFolder = await picker.PickSingleFolderAsync();
|
StorageFolder selectedFolder = await picker.PickSingleFolderAsync();
|
||||||
|
|
||||||
if (selectedFolder != null)
|
if (selectedFolder != null)
|
||||||
@@ -288,6 +289,7 @@ namespace VDownload.Views.Home
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// SOURCE BUTTON CLICKED
|
// SOURCE BUTTON CLICKED
|
||||||
public async void HomeVideoAddingPanelSourceButton_Click(object sender, RoutedEventArgs e)
|
public async void HomeVideoAddingPanelSourceButton_Click(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
@@ -298,7 +300,8 @@ namespace VDownload.Views.Home
|
|||||||
// ADD BUTTON CLICKED
|
// ADD BUTTON CLICKED
|
||||||
public void HomeVideoAddingPanelAddButton_Click(object sender, RoutedEventArgs e)
|
public void HomeVideoAddingPanelAddButton_Click(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
VideoAddEventArgs args = new VideoAddEventArgs
|
// Pack task data
|
||||||
|
TaskData taskData = new TaskData
|
||||||
{
|
{
|
||||||
VideoService = VideoService,
|
VideoService = VideoService,
|
||||||
MediaType = MediaType,
|
MediaType = MediaType,
|
||||||
@@ -310,7 +313,14 @@ namespace VDownload.Views.Home
|
|||||||
Location = Location,
|
Location = Location,
|
||||||
Schedule = Schedule,
|
Schedule = Schedule,
|
||||||
};
|
};
|
||||||
VideoAddRequest?.Invoke(this, args);
|
|
||||||
|
// Request task adding
|
||||||
|
TasksAddingRequestedEventArgs eventArgs = new TasksAddingRequestedEventArgs
|
||||||
|
{
|
||||||
|
TaskData = new TaskData[] { taskData },
|
||||||
|
RequestSource = TaskAddingRequestSource.Video
|
||||||
|
};
|
||||||
|
TasksAddingRequested?.Invoke(this, eventArgs);
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
@@ -319,7 +329,7 @@ namespace VDownload.Views.Home
|
|||||||
|
|
||||||
#region EVENT HANDLERS
|
#region EVENT HANDLERS
|
||||||
|
|
||||||
public event EventHandler<VideoAddEventArgs> VideoAddRequest;
|
public event EventHandler<TasksAddingRequestedEventArgs> TasksAddingRequested;
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,13 +13,8 @@ using Windows.UI.Xaml.Input;
|
|||||||
using Windows.UI.Xaml.Media;
|
using Windows.UI.Xaml.Media;
|
||||||
using Windows.UI.Xaml.Navigation;
|
using Windows.UI.Xaml.Navigation;
|
||||||
|
|
||||||
// The Blank Page item template is documented at https://go.microsoft.com/fwlink/?LinkId=234238
|
|
||||||
|
|
||||||
namespace VDownload.Views.Settings
|
namespace VDownload.Views.Settings
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// An empty page that can be used on its own or navigated to within a Frame.
|
|
||||||
/// </summary>
|
|
||||||
public sealed partial class SettingsMain : Page
|
public sealed partial class SettingsMain : Page
|
||||||
{
|
{
|
||||||
public SettingsMain()
|
public SettingsMain()
|
||||||
|
|||||||
@@ -15,10 +15,9 @@
|
|||||||
</Page.Resources>
|
</Page.Resources>
|
||||||
|
|
||||||
|
|
||||||
<Grid Padding="20">
|
<Grid Padding="20" RowSpacing="20">
|
||||||
<Grid.RowDefinitions>
|
<Grid.RowDefinitions>
|
||||||
<RowDefinition Height="Auto"/>
|
<RowDefinition Height="Auto"/>
|
||||||
<RowDefinition Height="20"/>
|
|
||||||
<RowDefinition/>
|
<RowDefinition/>
|
||||||
</Grid.RowDefinitions>
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
@@ -26,7 +25,7 @@
|
|||||||
<TextBlock x:Name="SourcesMainPageHeaderText" x:Uid="SourcesMainPageHeaderText" Grid.Row="0" FontSize="28" FontWeight="SemiBold"/>
|
<TextBlock x:Name="SourcesMainPageHeaderText" x:Uid="SourcesMainPageHeaderText" Grid.Row="0" FontSize="28" FontWeight="SemiBold"/>
|
||||||
|
|
||||||
<!-- CONTENT -->
|
<!-- CONTENT -->
|
||||||
<StackPanel Grid.Row="2" Spacing="10">
|
<StackPanel Grid.Row="1" Spacing="10">
|
||||||
<cc:SettingControl x:Name="SourcesTwitchSettingControl" x:Uid="SourcesTwitchSettingControl" Grid.Row="0" Icon="{StaticResource TwitchIcon}" Title="Twitch"> <!-- Twitch -->
|
<cc:SettingControl x:Name="SourcesTwitchSettingControl" x:Uid="SourcesTwitchSettingControl" Grid.Row="0" Icon="{StaticResource TwitchIcon}" Title="Twitch"> <!-- Twitch -->
|
||||||
<cc:SettingControl.SettingContent>
|
<cc:SettingControl.SettingContent>
|
||||||
<Button x:Name="SourcesTwitchLoginButton" IsEnabled="False" Click="SourcesTwitchLoginButton_Click"/>
|
<Button x:Name="SourcesTwitchLoginButton" IsEnabled="False" Click="SourcesTwitchLoginButton_Click"/>
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
using Microsoft.UI.Xaml.Controls;
|
using Microsoft.Toolkit.Uwp.Connectivity;
|
||||||
|
using Microsoft.UI.Xaml.Controls;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using Windows.ApplicationModel.Resources;
|
using Windows.ApplicationModel.Resources;
|
||||||
using Windows.UI.WindowManagement;
|
using Windows.UI.WindowManagement;
|
||||||
using Windows.UI.Xaml;
|
using Windows.UI.Xaml;
|
||||||
@@ -25,36 +27,54 @@ namespace VDownload.Views.Sources
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
#region EVENT HANDLERS
|
#region MAIN EVENT HANDLERS VOIDS
|
||||||
|
|
||||||
// NAVIGATED TO THIS PAGE (Check all services)
|
// ON NAVIGATED TO THIS PAGE (Check all services)
|
||||||
protected override async void OnNavigatedTo(NavigationEventArgs e)
|
protected override async void OnNavigatedTo(NavigationEventArgs e)
|
||||||
{
|
{
|
||||||
// Check Twitch
|
Task[] checkingTasks = new Task[1];
|
||||||
|
|
||||||
|
checkingTasks[0] = CheckTwitch();
|
||||||
|
|
||||||
|
await Task.WhenAll(checkingTasks);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region TWITCH
|
||||||
|
|
||||||
|
// CHECK TWITCH LOGIN AT START
|
||||||
|
private async Task CheckTwitch()
|
||||||
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
string twitchAccessToken = await Core.Services.Sources.Twitch.Auth.ReadAccessTokenAsync();
|
string twitchAccessToken = await Core.Services.Sources.Twitch.Helpers.Auth.ReadAccessTokenAsync();
|
||||||
(bool IsValid, string Login, DateTime? ExpirationDate) twitchAccessTokenValidation = await Core.Services.Sources.Twitch.Auth.ValidateAccessTokenAsync(twitchAccessToken);
|
#pragma warning disable IDE0042 // Deconstruct variable declaration
|
||||||
if (twitchAccessToken != null && twitchAccessTokenValidation.IsValid)
|
(bool IsValid, string Login, DateTime? ExpirationDate) twitchAccessTokenValidation = await Core.Services.Sources.Twitch.Helpers.Auth.ValidateAccessTokenAsync(twitchAccessToken);
|
||||||
|
#pragma warning restore IDE0042 // Deconstruct variable declaration
|
||||||
|
if (twitchAccessTokenValidation.IsValid)
|
||||||
{
|
{
|
||||||
SourcesTwitchSettingControl.Description = $"{ResourceLoader.GetForCurrentView().GetString("SourcesTwitchSettingControlDescriptionLoggedIn")} {twitchAccessTokenValidation.Login}";
|
SourcesTwitchSettingControl.Description = $"{ResourceLoader.GetForCurrentView().GetString("SourcesTwitchSettingControlDescriptionLoggedIn")} {twitchAccessTokenValidation.Login}";
|
||||||
SourcesTwitchLoginButton.Content = ResourceLoader.GetForCurrentView().GetString("SourcesTwitchLoginButtonTextLoggedIn");
|
SourcesTwitchLoginButton.Content = ResourceLoader.GetForCurrentView().GetString("SourcesTwitchLoginButtonTextLoggedIn");
|
||||||
SourcesTwitchLoginButton.IsEnabled = true;
|
SourcesTwitchLoginButton.IsEnabled = true;
|
||||||
}
|
}
|
||||||
else if (twitchAccessToken == null || !twitchAccessTokenValidation.IsValid)
|
else
|
||||||
{
|
{
|
||||||
if (twitchAccessToken != null) await Core.Services.Sources.Twitch.Auth.DeleteAccessTokenAsync();
|
if (twitchAccessToken != null) await Core.Services.Sources.Twitch.Helpers.Auth.DeleteAccessTokenAsync();
|
||||||
SourcesTwitchSettingControl.Description = ResourceLoader.GetForCurrentView().GetString("SourcesTwitchSettingControlDescriptionNotLoggedIn");
|
SourcesTwitchSettingControl.Description = ResourceLoader.GetForCurrentView().GetString("SourcesTwitchSettingControlDescriptionNotLoggedIn");
|
||||||
SourcesTwitchLoginButton.Content = ResourceLoader.GetForCurrentView().GetString("SourcesTwitchLoginButtonTextNotLoggedIn");
|
SourcesTwitchLoginButton.Content = ResourceLoader.GetForCurrentView().GetString("SourcesTwitchLoginButtonTextNotLoggedIn");
|
||||||
SourcesTwitchLoginButton.IsEnabled = true;
|
SourcesTwitchLoginButton.IsEnabled = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (WebException wex)
|
catch (WebException)
|
||||||
{
|
{
|
||||||
if (wex.Response == null)
|
if (!NetworkHelper.Instance.ConnectionInformation.IsInternetAvailable)
|
||||||
{
|
{
|
||||||
SourcesTwitchSettingControl.Description = ResourceLoader.GetForCurrentView().GetString("SourcesTwitchSettingControlDescriptionInternetConnectionError");
|
SourcesTwitchSettingControl.Description = ResourceLoader.GetForCurrentView().GetString("SourcesTwitchSettingControlDescriptionInternetConnectionError");
|
||||||
SourcesTwitchLoginButton.Content = ResourceLoader.GetForCurrentView().GetString("SourcesTwitchLoginButtonTextNotLoggedIn");
|
SourcesTwitchLoginButton.Content = ResourceLoader.GetForCurrentView().GetString("SourcesTwitchLoginButtonTextNotLoggedIn");
|
||||||
|
SourcesTwitchLoginButton.IsEnabled = false;
|
||||||
}
|
}
|
||||||
else throw;
|
else throw;
|
||||||
}
|
}
|
||||||
@@ -65,15 +85,15 @@ namespace VDownload.Views.Sources
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
string accessToken = await Core.Services.Sources.Twitch.Auth.ReadAccessTokenAsync();
|
string accessToken = await Core.Services.Sources.Twitch.Helpers.Auth.ReadAccessTokenAsync();
|
||||||
var accessTokenValidation = await Core.Services.Sources.Twitch.Auth.ValidateAccessTokenAsync(accessToken);
|
var accessTokenValidation = await Core.Services.Sources.Twitch.Helpers.Auth.ValidateAccessTokenAsync(accessToken);
|
||||||
if (accessToken != null && accessTokenValidation.IsValid)
|
if (accessTokenValidation.IsValid)
|
||||||
{
|
{
|
||||||
// Revoke access token
|
// Revoke access token
|
||||||
await Core.Services.Sources.Twitch.Auth.RevokeAccessTokenAsync(accessToken);
|
await Core.Services.Sources.Twitch.Helpers.Auth.RevokeAccessTokenAsync(accessToken);
|
||||||
|
|
||||||
// Delete access token
|
// Delete access token
|
||||||
await Core.Services.Sources.Twitch.Auth.DeleteAccessTokenAsync();
|
await Core.Services.Sources.Twitch.Helpers.Auth.DeleteAccessTokenAsync();
|
||||||
|
|
||||||
// Update Twitch SettingControl
|
// Update Twitch SettingControl
|
||||||
SourcesTwitchSettingControl.Description = ResourceLoader.GetForCurrentView().GetString("SourcesTwitchSettingControlDescriptionNotLoggedIn");
|
SourcesTwitchSettingControl.Description = ResourceLoader.GetForCurrentView().GetString("SourcesTwitchSettingControlDescriptionNotLoggedIn");
|
||||||
@@ -85,21 +105,22 @@ namespace VDownload.Views.Sources
|
|||||||
AppWindow TwitchAuthWindow = await AppWindow.TryCreateAsync();
|
AppWindow TwitchAuthWindow = await AppWindow.TryCreateAsync();
|
||||||
TwitchAuthWindow.Title = "Twitch Authentication";
|
TwitchAuthWindow.Title = "Twitch Authentication";
|
||||||
|
|
||||||
|
#pragma warning disable CS8305 // Type is for evaluation purposes only and is subject to change or removal in future updates.
|
||||||
WebView2 TwitchAuthWebView = new WebView2();
|
WebView2 TwitchAuthWebView = new WebView2();
|
||||||
await TwitchAuthWebView.EnsureCoreWebView2Async();
|
await TwitchAuthWebView.EnsureCoreWebView2Async();
|
||||||
TwitchAuthWebView.Source = Core.Services.Sources.Twitch.Auth.AuthorizationUrl;
|
TwitchAuthWebView.Source = Core.Services.Sources.Twitch.Helpers.Auth.AuthorizationUrl;
|
||||||
ElementCompositionPreview.SetAppWindowContent(TwitchAuthWindow, TwitchAuthWebView);
|
ElementCompositionPreview.SetAppWindowContent(TwitchAuthWindow, TwitchAuthWebView);
|
||||||
|
|
||||||
// NavigationStarting event (only when redirected)
|
// NavigationStarting event (only when redirected)
|
||||||
TwitchAuthWebView.NavigationStarting += async (s, a) =>
|
TwitchAuthWebView.NavigationStarting += async (s, a) =>
|
||||||
{
|
{
|
||||||
if (new Uri(a.Uri).Host == Core.Services.Sources.Twitch.Auth.RedirectUrl.Host)
|
if (new Uri(a.Uri).Host == Core.Services.Sources.Twitch.Helpers.Auth.RedirectUrl.Host)
|
||||||
{
|
{
|
||||||
// Close window
|
// Close window
|
||||||
await TwitchAuthWindow.CloseAsync();
|
await TwitchAuthWindow.CloseAsync();
|
||||||
|
|
||||||
// Get response
|
// Get response
|
||||||
string response = a.Uri.Replace(Core.Services.Sources.Twitch.Auth.RedirectUrl.OriginalString, "");
|
string response = a.Uri.Replace(Core.Services.Sources.Twitch.Helpers.Auth.RedirectUrl.OriginalString, "");
|
||||||
|
|
||||||
if (response[1] == '#')
|
if (response[1] == '#')
|
||||||
{
|
{
|
||||||
@@ -107,10 +128,10 @@ namespace VDownload.Views.Sources
|
|||||||
accessToken = response.Split('&')[0].Replace("/#access_token=", "");
|
accessToken = response.Split('&')[0].Replace("/#access_token=", "");
|
||||||
|
|
||||||
// Check token
|
// Check token
|
||||||
accessTokenValidation = await Core.Services.Sources.Twitch.Auth.ValidateAccessTokenAsync(accessToken);
|
accessTokenValidation = await Core.Services.Sources.Twitch.Helpers.Auth.ValidateAccessTokenAsync(accessToken);
|
||||||
|
|
||||||
// Save token
|
// Save token
|
||||||
await Core.Services.Sources.Twitch.Auth.SaveAccessTokenAsync(accessToken);
|
await Core.Services.Sources.Twitch.Helpers.Auth.SaveAccessTokenAsync(accessToken);
|
||||||
|
|
||||||
// Update Twitch SettingControl
|
// Update Twitch SettingControl
|
||||||
SourcesTwitchSettingControl.Description = $"{ResourceLoader.GetForCurrentView().GetString("SourcesTwitchSettingControlDescriptionLoggedIn")} {accessTokenValidation.Login}";
|
SourcesTwitchSettingControl.Description = $"{ResourceLoader.GetForCurrentView().GetString("SourcesTwitchSettingControlDescriptionLoggedIn")} {accessTokenValidation.Login}";
|
||||||
@@ -151,11 +172,12 @@ namespace VDownload.Views.Sources
|
|||||||
TwitchAuthWebView.CoreWebView2.CookieManager.DeleteAllCookies();
|
TwitchAuthWebView.CoreWebView2.CookieManager.DeleteAllCookies();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
#pragma warning restore CS8305 // Type is for evaluation purposes only and is subject to change or removal in future updates.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (WebException wex)
|
catch (WebException wex)
|
||||||
{
|
{
|
||||||
if (wex.Response == null)
|
if (!NetworkHelper.Instance.ConnectionInformation.IsInternetAvailable)
|
||||||
{
|
{
|
||||||
SourcesTwitchSettingControl.Description = ResourceLoader.GetForCurrentView().GetString("SourcesTwitchSettingControlDescriptionInternetConnectionError");
|
SourcesTwitchSettingControl.Description = ResourceLoader.GetForCurrentView().GetString("SourcesTwitchSettingControlDescriptionInternetConnectionError");
|
||||||
SourcesTwitchLoginButton.Content = ResourceLoader.GetForCurrentView().GetString("SourcesTwitchLoginButtonTextNotLoggedIn");
|
SourcesTwitchLoginButton.Content = ResourceLoader.GetForCurrentView().GetString("SourcesTwitchLoginButtonTextNotLoggedIn");
|
||||||
|
|||||||
@@ -8,7 +8,11 @@
|
|||||||
mc:Ignorable="d"
|
mc:Ignorable="d"
|
||||||
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
|
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
|
||||||
|
|
||||||
<Grid>
|
<Grid Padding="20" RowSpacing="20">
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
<RowDefinition/>
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
</Grid>
|
</Grid>
|
||||||
</Page>
|
</Page>
|
||||||
|
|||||||
@@ -13,13 +13,8 @@ using Windows.UI.Xaml.Input;
|
|||||||
using Windows.UI.Xaml.Media;
|
using Windows.UI.Xaml.Media;
|
||||||
using Windows.UI.Xaml.Navigation;
|
using Windows.UI.Xaml.Navigation;
|
||||||
|
|
||||||
// The Blank Page item template is documented at https://go.microsoft.com/fwlink/?LinkId=234238
|
|
||||||
|
|
||||||
namespace VDownload.Views.Subscriptions
|
namespace VDownload.Views.Subscriptions
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// An empty page that can be used on its own or navigated to within a Frame.
|
|
||||||
/// </summary>
|
|
||||||
public sealed partial class SubscriptionsMain : Page
|
public sealed partial class SubscriptionsMain : Page
|
||||||
{
|
{
|
||||||
public SubscriptionsMain()
|
public SubscriptionsMain()
|
||||||
|
|||||||
Reference in New Issue
Block a user