1.0-dev10 (Code cleaning)
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
namespace VDownload.Core.Enums
|
||||
{
|
||||
public enum VideoStatus
|
||||
public enum TaskStatus
|
||||
{
|
||||
Idle,
|
||||
Waiting,
|
||||
8
VDownload.Core/EventArgs/PlaylistSearchEventArgs.cs
Normal file
8
VDownload.Core/EventArgs/PlaylistSearchEventArgs.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
namespace VDownload.Core.EventArgs
|
||||
{
|
||||
public class PlaylistSearchEventArgs : System.EventArgs
|
||||
{
|
||||
public string Phrase { get; set; }
|
||||
public int Count { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -4,13 +4,13 @@ using VDownload.Core.Interfaces;
|
||||
using VDownload.Core.Objects;
|
||||
using Windows.Storage;
|
||||
|
||||
namespace VDownload.Core.EventArgsObjects
|
||||
namespace VDownload.Core.EventArgs
|
||||
{
|
||||
public class VideoAddEventArgs : EventArgs
|
||||
public class VideoAddEventArgs : System.EventArgs
|
||||
{
|
||||
public IVideoService VideoService { get; set; }
|
||||
public MediaType MediaType { get; set; }
|
||||
public Stream Stream { get; set; }
|
||||
public IBaseStream Stream { get; set; }
|
||||
public TimeSpan TrimStart { get; set; }
|
||||
public TimeSpan TrimEnd { get; set; }
|
||||
public string Filename { get; set; }
|
||||
7
VDownload.Core/EventArgs/VideoSearchEventArgs.cs
Normal file
7
VDownload.Core/EventArgs/VideoSearchEventArgs.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
namespace VDownload.Core.EventArgs
|
||||
{
|
||||
public class VideoSearchEventArgs : System.EventArgs
|
||||
{
|
||||
public string Phrase { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace VDownload.Core.EventArgsObjects
|
||||
{
|
||||
public class PlaylistSearchEventArgs : EventArgs
|
||||
{
|
||||
public string Phrase { get; set; }
|
||||
public int Count { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace VDownload.Core.EventArgsObjects
|
||||
{
|
||||
public class VideoSearchEventArgs : EventArgs
|
||||
{
|
||||
public string Phrase { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
using System;
|
||||
using VDownload.Core.Enums;
|
||||
|
||||
namespace VDownload.Core.Interfaces
|
||||
{
|
||||
public interface IAStream
|
||||
{
|
||||
#region PROPERTIES
|
||||
|
||||
Uri Url { get; }
|
||||
bool IsChunked { get; }
|
||||
StreamType StreamType { get; }
|
||||
int AudioBitrate { get; }
|
||||
string AudioCodec { get; }
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,19 +1,21 @@
|
||||
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 IVStream
|
||||
public interface IBaseStream
|
||||
{
|
||||
#region PROPERTIES
|
||||
|
||||
Uri Url { get; }
|
||||
bool IsChunked { get; }
|
||||
StreamType StreamType { get; }
|
||||
int Width { get; }
|
||||
int Height { get; }
|
||||
int FrameRate { get; }
|
||||
string VideoCodec { get; }
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -7,6 +7,7 @@ namespace VDownload.Core.Interfaces
|
||||
{
|
||||
#region PROPERTIES
|
||||
|
||||
// PLAYLIST PROPERTIES
|
||||
string ID { get; }
|
||||
string Name { get; }
|
||||
|
||||
@@ -16,8 +17,10 @@ namespace VDownload.Core.Interfaces
|
||||
|
||||
#region METHODS
|
||||
|
||||
// GET PLAYLIST METADATA
|
||||
Task GetMetadataAsync(CancellationToken cancellationToken = default);
|
||||
|
||||
// GET VIDEOS FROM PLAYLIST
|
||||
Task GetVideosAsync(int numberOfVideos, CancellationToken cancellationToken = default);
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -3,7 +3,6 @@ using System.ComponentModel;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using VDownload.Core.Enums;
|
||||
using VDownload.Core.Objects;
|
||||
using Windows.Storage;
|
||||
|
||||
namespace VDownload.Core.Interfaces
|
||||
@@ -21,7 +20,7 @@ namespace VDownload.Core.Interfaces
|
||||
TimeSpan Duration { get; }
|
||||
long Views { get; }
|
||||
Uri Thumbnail { get; }
|
||||
Stream[] Streams { get; }
|
||||
IBaseStream[] BaseStreams { get; }
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -36,14 +35,7 @@ namespace VDownload.Core.Interfaces
|
||||
Task GetStreamsAsync(CancellationToken cancellationToken = default);
|
||||
|
||||
// DOWNLOAD VIDEO
|
||||
Task<StorageFile> DownloadAndTranscodeAsync(StorageFolder downloadingFolder, Stream audioVideoStream, MediaFileExtension extension, MediaType mediaType, TimeSpan trimStart, TimeSpan trimEnd, CancellationToken cancellationToken = default);
|
||||
Task<StorageFile> DownloadAndTranscodeAsync(StorageFolder downloadingFolder, Stream audioVideoStream, MediaFileExtension extension, MediaType mediaType, CancellationToken cancellationToken = default);
|
||||
Task<StorageFile> DownloadAndTranscodeAsync(StorageFolder downloadingFolder, IAStream audioStream, IVStream videoStream, VideoFileExtension extension, TimeSpan trimStart, TimeSpan trimEnd, CancellationToken cancellationToken = default);
|
||||
Task<StorageFile> DownloadAndTranscodeAsync(StorageFolder downloadingFolder, IAStream audioStream, IVStream videoStream, VideoFileExtension extension, CancellationToken cancellationToken = default);
|
||||
Task<StorageFile> DownloadAndTranscodeAsync(StorageFolder downloadingFolder, IAStream audioStream, AudioFileExtension extension, TimeSpan trimStart, TimeSpan trimEnd, CancellationToken cancellationToken = default);
|
||||
Task<StorageFile> DownloadAndTranscodeAsync(StorageFolder downloadingFolder, IAStream audioStream, AudioFileExtension extension, CancellationToken cancellationToken = default);
|
||||
Task<StorageFile> DownloadAndTranscodeAsync(StorageFolder downloadingFolder, IVStream videoStream, VideoFileExtension extension, TimeSpan trimStart, TimeSpan trimEnd, CancellationToken cancellationToken = default);
|
||||
Task<StorageFile> DownloadAndTranscodeAsync(StorageFolder downloadingFolder, IVStream videoStream, VideoFileExtension extension, CancellationToken cancellationToken = default);
|
||||
Task<StorageFile> DownloadAndTranscodeAsync(StorageFolder downloadingFolder, IBaseStream baseStream, MediaFileExtension extension, MediaType mediaType, TimeSpan trimStart, TimeSpan trimEnd, CancellationToken cancellationToken = default);
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ using VDownload.Core.Interfaces;
|
||||
|
||||
namespace VDownload.Core.Objects
|
||||
{
|
||||
public class Stream : IVStream, IAStream
|
||||
public class Stream : IBaseStream
|
||||
{
|
||||
#region CONSTRUCTORS
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using VDownload.Core.Enums;
|
||||
using Windows.Media.Editing;
|
||||
using Windows.Storage;
|
||||
|
||||
@@ -15,7 +15,7 @@ namespace VDownload.Core.Services
|
||||
{
|
||||
public class MediaProcessor
|
||||
{
|
||||
#region CONSTRUCTOR
|
||||
#region CONSTRUCTORS
|
||||
|
||||
public MediaProcessor(StorageFile outputFile, TimeSpan trimStart, TimeSpan trimEnd)
|
||||
{
|
||||
@@ -40,10 +40,11 @@ namespace VDownload.Core.Services
|
||||
|
||||
#region STANDARD METHODS
|
||||
|
||||
// SINGLE AUDIO & VIDEO FILE PROCESSING
|
||||
public async Task Run(StorageFile audioVideoInputFile, MediaFileExtension extension, MediaType mediaType, CancellationToken cancellationToken = default)
|
||||
{
|
||||
// Invoke ProcessingStarted event
|
||||
ProcessingStarted?.Invoke(this, EventArgs.Empty);
|
||||
ProcessingStarted?.Invoke(this, System.EventArgs.Empty);
|
||||
|
||||
// Init transcoder
|
||||
MediaTranscoder mediaTranscoder = new MediaTranscoder
|
||||
@@ -55,31 +56,31 @@ namespace VDownload.Core.Services
|
||||
};
|
||||
|
||||
// Start transcoding operation
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
using (IRandomAccessStream outputFileOpened = await OutputFile.OpenAsync(FileAccessMode.ReadWrite))
|
||||
{
|
||||
PrepareTranscodeResult transcodingPreparated = await mediaTranscoder.PrepareStreamTranscodeAsync(await audioVideoInputFile.OpenAsync(FileAccessMode.Read), outputFileOpened, await GetMediaEncodingProfile(audioVideoInputFile, extension, mediaType));
|
||||
IAsyncActionWithProgress<double> transcodingTask = transcodingPreparated.TranscodeAsync();
|
||||
try
|
||||
{
|
||||
await transcodingTask.AsTask(cancellationToken, new Progress<double>((percent) => { ProcessingProgressChanged(this, new ProgressChangedEventArgs((int)Math.Round(percent), null)); }));
|
||||
await outputFileOpened.FlushAsync();
|
||||
}
|
||||
catch (TaskCanceledException) { }
|
||||
await transcodingTask.AsTask(cancellationToken, new Progress<double>((percent) => { ProcessingProgressChanged(this, new ProgressChangedEventArgs((int)Math.Round(percent), null)); }));
|
||||
await outputFileOpened.FlushAsync();
|
||||
transcodingTask.Close();
|
||||
}
|
||||
|
||||
// Invoke ProcessingCompleted event
|
||||
ProcessingCompleted?.Invoke(this, EventArgs.Empty);
|
||||
ProcessingCompleted?.Invoke(this, System.EventArgs.Empty);
|
||||
}
|
||||
|
||||
// SEPARATE AUDIO & VIDEO FILES PROCESSING
|
||||
public async Task Run(StorageFile audioFile, StorageFile videoFile, VideoFileExtension extension, CancellationToken cancellationToken = default)
|
||||
{
|
||||
// Invoke ProcessingStarted event
|
||||
ProcessingStarted?.Invoke(this, EventArgs.Empty);
|
||||
ProcessingStarted?.Invoke(this, System.EventArgs.Empty);
|
||||
|
||||
// Init editor
|
||||
MediaComposition mediaEditor = new MediaComposition();
|
||||
|
||||
// Add media files
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
Task<MediaClip> getVideoFileTask = MediaClip.CreateFromFileAsync(videoFile).AsTask();
|
||||
Task<BackgroundAudioTrack> getAudioFileTask = BackgroundAudioTrack.CreateFromFileAsync(audioFile).AsTask();
|
||||
await Task.WhenAll(getVideoFileTask, getAudioFileTask);
|
||||
@@ -97,11 +98,14 @@ namespace VDownload.Core.Services
|
||||
// Start rendering operation
|
||||
var renderOperation = mediaEditor.RenderToFileAsync(OutputFile, (MediaTrimmingPreference)Config.GetValue("media_editing_algorithm"), await GetMediaEncodingProfile(videoFile, audioFile, (MediaFileExtension)extension, MediaType.AudioVideo));
|
||||
renderOperation.Progress += (info, progress) => { ProcessingProgressChanged(this, new ProgressChangedEventArgs((int)Math.Round(progress), null)); };
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
await renderOperation.AsTask(cancellationToken);
|
||||
|
||||
// Invoke ProcessingCompleted event
|
||||
ProcessingCompleted?.Invoke(this, EventArgs.Empty);
|
||||
ProcessingCompleted?.Invoke(this, System.EventArgs.Empty);
|
||||
}
|
||||
|
||||
// SINGLE AUDIO OR VIDEO FILES PROCESSING
|
||||
public async Task Run(StorageFile audioFile, AudioFileExtension extension, CancellationToken cancellationToken = default) { await Run(audioFile, (MediaFileExtension)extension, MediaType.OnlyAudio, cancellationToken); }
|
||||
public async Task Run(StorageFile videoFile, VideoFileExtension extension, CancellationToken cancellationToken = default) { await Run(videoFile, (MediaFileExtension)extension, MediaType.OnlyVideo, cancellationToken); }
|
||||
|
||||
|
||||
@@ -1,9 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using System.Text.RegularExpressions;
|
||||
using VDownload.Core.Enums;
|
||||
|
||||
namespace VDownload.Core.Services
|
||||
@@ -12,6 +7,7 @@ namespace VDownload.Core.Services
|
||||
{
|
||||
#region CONSTANTS
|
||||
|
||||
// VIDEO SOURCES REGULAR EXPRESSIONS
|
||||
private static readonly (Regex Regex, VideoSource Type)[] VideoSources = new (Regex Regex, VideoSource Type)[]
|
||||
{
|
||||
(new Regex(@"^https://www.twitch.tv/videos/(?<id>\d+)"), VideoSource.TwitchVod),
|
||||
@@ -19,6 +15,7 @@ namespace VDownload.Core.Services
|
||||
(new Regex(@"^https://clips.twitch.tv/(?<id>[^?]+)"), VideoSource.TwitchClip),
|
||||
};
|
||||
|
||||
// PLAYLIST SOURCES REGULAR EXPRESSIONS
|
||||
private static readonly (Regex Regex, PlaylistSource Type)[] PlaylistSources = new (Regex Regex, PlaylistSource Type)[]
|
||||
{
|
||||
(new Regex(@"^https://www.twitch.tv/(?<id>[^?]+)"), PlaylistSource.TwitchChannel),
|
||||
@@ -30,6 +27,7 @@ namespace VDownload.Core.Services
|
||||
|
||||
#region METHODS
|
||||
|
||||
// GET VIDEO SOURCE
|
||||
public static (VideoSource Type, string ID) GetVideoSource(string url)
|
||||
{
|
||||
foreach ((Regex Regex, VideoSource Type) Source in VideoSources)
|
||||
@@ -40,6 +38,7 @@ namespace VDownload.Core.Services
|
||||
return (VideoSource.Null, null);
|
||||
}
|
||||
|
||||
// GET PLAYLIST SOURCE
|
||||
public static (PlaylistSource Type, string ID) GetPlaylistSource(string url)
|
||||
{
|
||||
foreach ((Regex Regex, PlaylistSource Type) Source in PlaylistSources)
|
||||
|
||||
@@ -65,7 +65,7 @@ namespace VDownload.Core.Services.Sources.Twitch
|
||||
StorageFile authDataFile = await authDataFolder.CreateFileAsync("Twitch.auth", CreationCollisionOption.ReplaceExisting);
|
||||
|
||||
// Save data
|
||||
FileIO.WriteTextAsync(authDataFile, accessToken);
|
||||
await FileIO.WriteTextAsync(authDataFile, accessToken);
|
||||
}
|
||||
|
||||
// DELETE ACCESS TOKEN
|
||||
|
||||
@@ -40,14 +40,13 @@ namespace VDownload.Core.Services.Sources.Twitch
|
||||
// GET CHANNEL METADATA
|
||||
public async Task GetMetadataAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
// Set cancellation token
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
// Get access token
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
string accessToken = await Auth.ReadAccessTokenAsync();
|
||||
if (accessToken == null) throw new TwitchAccessTokenNotFoundException();
|
||||
|
||||
// Check access token
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
var twitchAccessTokenValidation = await Auth.ValidateAccessTokenAsync(accessToken);
|
||||
if (!twitchAccessTokenValidation.IsValid) throw new TwitchAccessTokenNotValidException();
|
||||
|
||||
@@ -58,6 +57,7 @@ namespace VDownload.Core.Services.Sources.Twitch
|
||||
|
||||
// Get response
|
||||
client.QueryString.Add("login", ID);
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
JToken response = JObject.Parse(await client.DownloadStringTaskAsync("https://api.twitch.tv/helix/users"))["data"][0];
|
||||
|
||||
// Set parameters
|
||||
@@ -68,10 +68,8 @@ namespace VDownload.Core.Services.Sources.Twitch
|
||||
// GET CHANNEL VIDEOS
|
||||
public async Task GetVideosAsync(int numberOfVideos, CancellationToken cancellationToken = default)
|
||||
{
|
||||
// Set cancellation token
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
// Get access token
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
string accessToken = await Auth.ReadAccessTokenAsync();
|
||||
if (accessToken == null) throw new TwitchAccessTokenNotFoundException();
|
||||
|
||||
@@ -88,6 +86,7 @@ namespace VDownload.Core.Services.Sources.Twitch
|
||||
do
|
||||
{
|
||||
// Check access token
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
var twitchAccessTokenValidation = await Auth.ValidateAccessTokenAsync(accessToken);
|
||||
if (!twitchAccessTokenValidation.IsValid) throw new TwitchAccessTokenNotValidException();
|
||||
|
||||
@@ -104,6 +103,7 @@ namespace VDownload.Core.Services.Sources.Twitch
|
||||
client.QueryString.Add("first", count.ToString());
|
||||
client.QueryString.Add("after", pagination);
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
JToken response = JObject.Parse(await client.DownloadStringTaskAsync("https://api.twitch.tv/helix/videos"));
|
||||
|
||||
pagination = (string)response["pagination"]["cursor"];
|
||||
|
||||
@@ -48,7 +48,7 @@ namespace VDownload.Core.Services.Sources.Twitch
|
||||
public TimeSpan Duration { get; private set; }
|
||||
public long Views { get; private set; }
|
||||
public Uri Thumbnail { get; private set; }
|
||||
public Stream[] Streams { get; private set; }
|
||||
public IBaseStream[] BaseStreams { get; private set; }
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -59,14 +59,13 @@ namespace VDownload.Core.Services.Sources.Twitch
|
||||
// GET CLIP METADATA
|
||||
public async Task GetMetadataAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
// Set cancellation token
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
// Get access token
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
string accessToken = await Auth.ReadAccessTokenAsync();
|
||||
if (accessToken == null) throw new TwitchAccessTokenNotFoundException();
|
||||
|
||||
// Check access token
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
var twitchAccessTokenValidation = await Auth.ValidateAccessTokenAsync(accessToken);
|
||||
if (!twitchAccessTokenValidation.IsValid) throw new TwitchAccessTokenNotValidException();
|
||||
|
||||
@@ -93,14 +92,12 @@ namespace VDownload.Core.Services.Sources.Twitch
|
||||
|
||||
public async Task GetStreamsAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
// Set cancellation token
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
// Create client
|
||||
WebClient client = new WebClient { Encoding = Encoding.UTF8 };
|
||||
client.Headers.Add("Client-ID", Auth.GQLApiClientID);
|
||||
|
||||
// Get video streams
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
JToken[] response = JArray.Parse(await client.UploadStringTaskAsync("https://gql.twitch.tv/gql", "[{\"operationName\":\"VideoAccessToken_Clip\",\"variables\":{\"slug\":\"" + ID + "\"},\"extensions\":{\"persistedQuery\":{\"version\":1,\"sha256Hash\":\"36b89d2507fce29e5ca551df756d27c1cfe079e2609642b4390aa4c35796eb11\"}}}]"))[0]["data"]["clip"]["videoQualities"].ToArray();
|
||||
|
||||
// Init streams list
|
||||
@@ -126,60 +123,55 @@ namespace VDownload.Core.Services.Sources.Twitch
|
||||
}
|
||||
|
||||
// Set Streams parameter
|
||||
Streams = streams.ToArray();
|
||||
BaseStreams = streams.ToArray();
|
||||
}
|
||||
|
||||
public async Task<StorageFile> DownloadAndTranscodeAsync(StorageFolder downloadingFolder, Stream audioVideoStream, MediaFileExtension extension, MediaType mediaType, TimeSpan trimStart, TimeSpan trimEnd, CancellationToken cancellationToken = default)
|
||||
public async Task<StorageFile> DownloadAndTranscodeAsync(StorageFolder downloadingFolder, IBaseStream baseStream, MediaFileExtension extension, MediaType mediaType, TimeSpan trimStart, TimeSpan trimEnd, CancellationToken cancellationToken = default)
|
||||
{
|
||||
// Set cancellation token
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
// Invoke DownloadingStarted event
|
||||
DownloadingStarted?.Invoke(this, EventArgs.Empty);
|
||||
DownloadingStarted?.Invoke(this, System.EventArgs.Empty);
|
||||
|
||||
// Create client
|
||||
WebClient client = new WebClient();
|
||||
client.Headers.Add("Client-Id", Auth.GQLApiClientID);
|
||||
|
||||
// Get video GQL access token
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
JToken videoAccessToken = JArray.Parse(await client.UploadStringTaskAsync("https://gql.twitch.tv/gql", "[{\"operationName\":\"VideoAccessToken_Clip\",\"variables\":{\"slug\":\"" + ID + "\"},\"extensions\":{\"persistedQuery\":{\"version\":1,\"sha256Hash\":\"36b89d2507fce29e5ca551df756d27c1cfe079e2609642b4390aa4c35796eb11\"}}}]"))[0]["data"]["clip"]["playbackAccessToken"];
|
||||
|
||||
|
||||
// Download
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
StorageFile rawFile = await downloadingFolder.CreateFileAsync("raw.mp4");
|
||||
using (client = new WebClient())
|
||||
{
|
||||
client.DownloadProgressChanged += (s, a) => { DownloadingProgressChanged(this, new ProgressChangedEventArgs(a.ProgressPercentage, null)); };
|
||||
client.QueryString.Add("sig", (string)videoAccessToken["signature"]);
|
||||
client.QueryString.Add("token", HttpUtility.UrlEncode((string)videoAccessToken["value"]));
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
using (cancellationToken.Register(client.CancelAsync))
|
||||
{
|
||||
await client.DownloadFileTaskAsync(audioVideoStream.Url, rawFile.Path);
|
||||
await client.DownloadFileTaskAsync(baseStream.Url, rawFile.Path);
|
||||
}
|
||||
}
|
||||
DownloadingCompleted?.Invoke(this, EventArgs.Empty);
|
||||
DownloadingCompleted?.Invoke(this, System.EventArgs.Empty);
|
||||
|
||||
// Processing
|
||||
StorageFile outputFile = rawFile;
|
||||
if (extension != MediaFileExtension.MP4 || mediaType != MediaType.AudioVideo || trimStart > new TimeSpan(0) || trimEnd < Duration)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
outputFile = await downloadingFolder.CreateFileAsync($"transcoded.{extension.ToString().ToLower()}");
|
||||
MediaProcessor mediaProcessor = new MediaProcessor(outputFile, trimStart, trimEnd);
|
||||
mediaProcessor.ProcessingStarted += ProcessingStarted;
|
||||
mediaProcessor.ProcessingProgressChanged += ProcessingProgressChanged;
|
||||
mediaProcessor.ProcessingCompleted += ProcessingCompleted;
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
await mediaProcessor.Run(rawFile, extension, mediaType, cancellationToken);
|
||||
}
|
||||
|
||||
// Return output file
|
||||
return outputFile;
|
||||
}
|
||||
public async Task<StorageFile> DownloadAndTranscodeAsync(StorageFolder downloadingFolder, Stream audioVideoStream, MediaFileExtension extension, MediaType mediaType, CancellationToken cancellationToken = default) { return await DownloadAndTranscodeAsync(downloadingFolder, audioVideoStream, extension, mediaType, new TimeSpan(0), Duration, cancellationToken); }
|
||||
public async Task<StorageFile> DownloadAndTranscodeAsync(StorageFolder downloadingFolder, IAStream audioStream, IVStream videoStream, VideoFileExtension extension, TimeSpan trimStart, TimeSpan trimEnd, CancellationToken cancellationToken = default) { throw new NotImplementedException("Twitch Clip download service doesn't support separate video and audio streams"); }
|
||||
public async Task<StorageFile> DownloadAndTranscodeAsync(StorageFolder downloadingFolder, IAStream audioStream, IVStream videoStream, VideoFileExtension extension, CancellationToken cancellationToken = default) { return await DownloadAndTranscodeAsync(downloadingFolder, audioStream, videoStream, extension, new TimeSpan(0), Duration, cancellationToken); }
|
||||
public async Task<StorageFile> DownloadAndTranscodeAsync(StorageFolder downloadingFolder, IAStream audioStream, AudioFileExtension extension, TimeSpan trimStart, TimeSpan trimEnd, CancellationToken cancellationToken = default) { throw new NotImplementedException("Twitch Clip download service doesn't support separate video and audio streams"); }
|
||||
public async Task<StorageFile> DownloadAndTranscodeAsync(StorageFolder downloadingFolder, IAStream audioStream, AudioFileExtension extension, CancellationToken cancellationToken = default) { return await DownloadAndTranscodeAsync(downloadingFolder, audioStream, extension, new TimeSpan(0), Duration, cancellationToken); }
|
||||
public async Task<StorageFile> DownloadAndTranscodeAsync(StorageFolder downloadingFolder, IVStream videoStream, VideoFileExtension extension, TimeSpan trimStart, TimeSpan trimEnd, CancellationToken cancellationToken = default) { throw new NotImplementedException("Twitch Clip download service doesn't support separate video and audio streams"); }
|
||||
public async Task<StorageFile> DownloadAndTranscodeAsync(StorageFolder downloadingFolder, IVStream videoStream, VideoFileExtension extension, CancellationToken cancellationToken = default) { return await DownloadAndTranscodeAsync(downloadingFolder, videoStream, extension, new TimeSpan(0), Duration, cancellationToken); }
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Text.RegularExpressions;
|
||||
@@ -51,7 +50,7 @@ namespace VDownload.Core.Services.Sources.Twitch
|
||||
public TimeSpan Duration { get; private set; }
|
||||
public long Views { get; private set; }
|
||||
public Uri Thumbnail { get; private set; }
|
||||
public Stream[] Streams { get; private set; }
|
||||
public IBaseStream[] BaseStreams { get; private set; }
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -62,14 +61,13 @@ namespace VDownload.Core.Services.Sources.Twitch
|
||||
// GET VOD METADATA
|
||||
public async Task GetMetadataAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
// Set cancellation token
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
// Get access token
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
string accessToken = await Auth.ReadAccessTokenAsync();
|
||||
if (accessToken == null) throw new TwitchAccessTokenNotFoundException();
|
||||
|
||||
// Check access token
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
var twitchAccessTokenValidation = await Auth.ValidateAccessTokenAsync(accessToken);
|
||||
if (!twitchAccessTokenValidation.IsValid) throw new TwitchAccessTokenNotValidException();
|
||||
|
||||
@@ -80,6 +78,7 @@ namespace VDownload.Core.Services.Sources.Twitch
|
||||
|
||||
// Get response
|
||||
client.QueryString.Add("id", ID);
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
JToken response = JObject.Parse(await client.DownloadStringTaskAsync("https://api.twitch.tv/helix/videos")).GetValue("data")[0];
|
||||
|
||||
// Set parameters
|
||||
@@ -102,17 +101,16 @@ namespace VDownload.Core.Services.Sources.Twitch
|
||||
// GET VOD STREAMS
|
||||
public async Task GetStreamsAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
// Set cancellation token
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
// Create client
|
||||
WebClient client = new WebClient();
|
||||
client.Headers.Add("Client-Id", Auth.GQLApiClientID);
|
||||
|
||||
// Get video GQL access token
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
JToken videoAccessToken = JObject.Parse(await client.UploadStringTaskAsync("https://gql.twitch.tv/gql", "{\"operationName\":\"PlaybackAccessToken_Template\",\"query\":\"query PlaybackAccessToken_Template($login: String!, $isLive: Boolean!, $vodID: ID!, $isVod: Boolean!, $playerType: String!) { streamPlaybackAccessToken(channelName: $login, params: {platform: \\\"web\\\", playerBackend: \\\"mediaplayer\\\", playerType: $playerType}) @include(if: $isLive) { value signature __typename } videoPlaybackAccessToken(id: $vodID, params: {platform: \\\"web\\\", playerBackend: \\\"mediaplayer\\\", playerType: $playerType}) @include(if: $isVod) { value signature __typename }}\",\"variables\":{\"isLive\":false,\"login\":\"\",\"isVod\":true,\"vodID\":\"" + ID + "\",\"playerType\":\"embed\"}}"))["data"]["videoPlaybackAccessToken"];
|
||||
|
||||
// Get video streams
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
string[] response = (await client.DownloadStringTaskAsync($"http://usher.twitch.tv/vod/{ID}?nauth={videoAccessToken["value"]}&nauthsig={videoAccessToken["signature"]}&allow_source=true&player=twitchweb")).Split("\n");
|
||||
|
||||
// Init streams list
|
||||
@@ -147,74 +145,64 @@ namespace VDownload.Core.Services.Sources.Twitch
|
||||
}
|
||||
|
||||
// Set Streams parameter
|
||||
Streams = streams.ToArray();
|
||||
BaseStreams = streams.ToArray();
|
||||
}
|
||||
|
||||
// DOWNLOAD AND TRANSCODE VOD
|
||||
public async Task<StorageFile> DownloadAndTranscodeAsync(StorageFolder downloadingFolder, Stream audioVideoStream, MediaFileExtension extension, MediaType mediaType, TimeSpan trimStart, TimeSpan trimEnd, CancellationToken cancellationToken = default)
|
||||
public async Task<StorageFile> DownloadAndTranscodeAsync(StorageFolder downloadingFolder, IBaseStream baseStream, MediaFileExtension extension, MediaType mediaType, TimeSpan trimStart, TimeSpan trimEnd, CancellationToken cancellationToken = default)
|
||||
{
|
||||
// Invoke DownloadingStarted event
|
||||
if (!cancellationToken.IsCancellationRequested) DownloadingStarted?.Invoke(this, EventArgs.Empty);
|
||||
DownloadingStarted?.Invoke(this, System.EventArgs.Empty);
|
||||
|
||||
// Get video chunks
|
||||
List<(Uri ChunkUrl, TimeSpan ChunkDuration)> chunksList = null;
|
||||
if (!cancellationToken.IsCancellationRequested) chunksList = await ExtractChunksFromM3U8Async(audioVideoStream.Url);
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
List<(Uri ChunkUrl, TimeSpan ChunkDuration)> chunksList = await ExtractChunksFromM3U8Async(baseStream.Url, cancellationToken);
|
||||
|
||||
// Passive trim
|
||||
if ((bool)Config.GetValue("twitch_vod_passive_trim")) (trimStart, trimEnd) = PassiveVideoTrim(chunksList, trimStart, trimEnd, Duration);
|
||||
|
||||
// Download
|
||||
StorageFile rawFile = null;
|
||||
if (!cancellationToken.IsCancellationRequested)
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
StorageFile rawFile = await downloadingFolder.CreateFileAsync("raw.ts");
|
||||
|
||||
float chunksDownloaded = 0;
|
||||
|
||||
Task<byte[]> downloadTask;
|
||||
Task writeTask;
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
downloadTask = DownloadChunkAsync(chunksList[0].ChunkUrl);
|
||||
await downloadTask;
|
||||
for (int i = 1; i < chunksList.Count; i++)
|
||||
{
|
||||
rawFile = await downloadingFolder.CreateFileAsync("raw.ts");
|
||||
float chunksDownloaded = 0;
|
||||
|
||||
Task<byte[]> downloadTask;
|
||||
Task writeTask;
|
||||
|
||||
downloadTask = DownloadChunkAsync(chunksList[0].ChunkUrl);
|
||||
await downloadTask;
|
||||
for (int i = 1; i < chunksList.Count && !cancellationToken.IsCancellationRequested; i++)
|
||||
{
|
||||
writeTask = WriteChunkToFileAsync(rawFile, downloadTask.Result);
|
||||
downloadTask = DownloadChunkAsync(chunksList[i].ChunkUrl);
|
||||
await Task.WhenAll(writeTask, downloadTask);
|
||||
DownloadingProgressChanged(this, new ProgressChangedEventArgs((int)Math.Round(++chunksDownloaded * 100 / chunksList.Count), null));
|
||||
}
|
||||
if (!cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
await WriteChunkToFileAsync(rawFile, downloadTask.Result);
|
||||
DownloadingProgressChanged(this, new ProgressChangedEventArgs((int)Math.Round(++chunksDownloaded * 100 / chunksList.Count), null));
|
||||
|
||||
DownloadingCompleted?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
writeTask = WriteChunkToFileAsync(rawFile, downloadTask.Result);
|
||||
downloadTask = DownloadChunkAsync(chunksList[i].ChunkUrl);
|
||||
await Task.WhenAll(writeTask, downloadTask);
|
||||
DownloadingProgressChanged(this, new ProgressChangedEventArgs((int)Math.Round(++chunksDownloaded * 100 / chunksList.Count), null));
|
||||
}
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
await WriteChunkToFileAsync(rawFile, downloadTask.Result);
|
||||
DownloadingProgressChanged(this, new ProgressChangedEventArgs((int)Math.Round(++chunksDownloaded * 100 / chunksList.Count), null));
|
||||
|
||||
DownloadingCompleted?.Invoke(this, System.EventArgs.Empty);
|
||||
|
||||
|
||||
// Processing
|
||||
StorageFile outputFile = null;
|
||||
if (!cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
outputFile = await downloadingFolder.CreateFileAsync($"transcoded.{extension.ToString().ToLower()}");
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
StorageFile outputFile = await downloadingFolder.CreateFileAsync($"transcoded.{extension.ToString().ToLower()}");
|
||||
|
||||
MediaProcessor mediaProcessor = new MediaProcessor(outputFile, trimStart, trimEnd);
|
||||
mediaProcessor.ProcessingStarted += ProcessingStarted;
|
||||
mediaProcessor.ProcessingProgressChanged += ProcessingProgressChanged;
|
||||
mediaProcessor.ProcessingCompleted += ProcessingCompleted;
|
||||
await mediaProcessor.Run(rawFile, extension, mediaType, cancellationToken);
|
||||
}
|
||||
MediaProcessor mediaProcessor = new MediaProcessor(outputFile, trimStart, trimEnd);
|
||||
mediaProcessor.ProcessingStarted += ProcessingStarted;
|
||||
mediaProcessor.ProcessingProgressChanged += ProcessingProgressChanged;
|
||||
mediaProcessor.ProcessingCompleted += ProcessingCompleted;
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
await mediaProcessor.Run(rawFile, extension, mediaType, cancellationToken);
|
||||
|
||||
|
||||
// Return output file
|
||||
return outputFile;
|
||||
}
|
||||
public async Task<StorageFile> DownloadAndTranscodeAsync(StorageFolder downloadingFolder, Stream audioVideoStream, MediaFileExtension extension, MediaType mediaType, CancellationToken cancellationToken = default) { return await DownloadAndTranscodeAsync(downloadingFolder, audioVideoStream, extension, mediaType, new TimeSpan(0), Duration, cancellationToken); }
|
||||
public async Task<StorageFile> DownloadAndTranscodeAsync(StorageFolder downloadingFolder, IAStream audioStream, IVStream videoStream, VideoFileExtension extension, TimeSpan trimStart, TimeSpan trimEnd, CancellationToken cancellationToken = default) { throw new NotImplementedException("Twitch VOD download service doesn't support separate video and audio streams"); }
|
||||
public async Task<StorageFile> DownloadAndTranscodeAsync(StorageFolder downloadingFolder, IAStream audioStream, IVStream videoStream, VideoFileExtension extension, CancellationToken cancellationToken = default) { return await DownloadAndTranscodeAsync(downloadingFolder, audioStream, videoStream, extension, new TimeSpan(0), Duration, cancellationToken); }
|
||||
public async Task<StorageFile> DownloadAndTranscodeAsync(StorageFolder downloadingFolder, IAStream audioStream, AudioFileExtension extension, TimeSpan trimStart, TimeSpan trimEnd, CancellationToken cancellationToken = default) { throw new NotImplementedException("Twitch VOD download service doesn't support separate video and audio streams"); }
|
||||
public async Task<StorageFile> DownloadAndTranscodeAsync(StorageFolder downloadingFolder, IAStream audioStream, AudioFileExtension extension, CancellationToken cancellationToken = default) { return await DownloadAndTranscodeAsync(downloadingFolder, audioStream, extension, new TimeSpan(0), Duration, cancellationToken); }
|
||||
public async Task<StorageFile> DownloadAndTranscodeAsync(StorageFolder downloadingFolder, IVStream videoStream, VideoFileExtension extension, TimeSpan trimStart, TimeSpan trimEnd, CancellationToken cancellationToken = default) { throw new NotImplementedException("Twitch VOD download service doesn't support separate video and audio streams"); }
|
||||
public async Task<StorageFile> DownloadAndTranscodeAsync(StorageFolder downloadingFolder, IVStream videoStream, VideoFileExtension extension, CancellationToken cancellationToken = default) { return await DownloadAndTranscodeAsync(downloadingFolder, videoStream, extension, new TimeSpan(0), Duration, cancellationToken); }
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -223,14 +211,16 @@ namespace VDownload.Core.Services.Sources.Twitch
|
||||
#region LOCAL METHODS
|
||||
|
||||
// GET CHUNKS DATA FROM M3U8 PLAYLIST
|
||||
private static async Task<List<(Uri ChunkUrl, TimeSpan ChunkDuration)>> ExtractChunksFromM3U8Async(Uri streamUrl)
|
||||
private static async Task<List<(Uri ChunkUrl, TimeSpan ChunkDuration)>> ExtractChunksFromM3U8Async(Uri streamUrl, CancellationToken cancellationToken = default)
|
||||
{
|
||||
// Create client
|
||||
WebClient client = new WebClient();
|
||||
client.Headers.Add("Client-Id", Auth.GQLApiClientID);
|
||||
|
||||
// Get playlist
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
string response = await client.DownloadStringTaskAsync(streamUrl);
|
||||
|
||||
// Create dictionary
|
||||
List<(Uri ChunkUrl, TimeSpan ChunkDuration)> chunks = new List<(Uri ChunkUrl, TimeSpan ChunkDuration)>();
|
||||
|
||||
@@ -273,11 +263,12 @@ namespace VDownload.Core.Services.Sources.Twitch
|
||||
}
|
||||
|
||||
// DOWNLOAD CHUNK
|
||||
private static async Task<byte[]> DownloadChunkAsync(Uri chunkUrl)
|
||||
private static async Task<byte[]> DownloadChunkAsync(Uri chunkUrl, CancellationToken cancellationToken = default)
|
||||
{
|
||||
int retriesCount = 0;
|
||||
while ((bool)Config.GetValue("twitch_vod_downloading_chunk_retry_after_error") && retriesCount < (int)Config.GetValue("twitch_vod_downloading_chunk_max_retries"))
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
try
|
||||
{
|
||||
using (WebClient client = new WebClient())
|
||||
|
||||
@@ -1,38 +1,56 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace VDownload.Core.Services
|
||||
{
|
||||
public class TaskId
|
||||
{
|
||||
// VARIABLES
|
||||
#region CONSTANTS
|
||||
|
||||
// RANDOM
|
||||
private static readonly Random Random = new Random();
|
||||
private static readonly char[] CharsID = "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890".ToCharArray();
|
||||
private static readonly int LengthID = 10;
|
||||
|
||||
// ID SETTINGS
|
||||
private static readonly char[] IDChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890".ToCharArray();
|
||||
private static readonly int IDLength = 10;
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region PROPERTIES
|
||||
|
||||
// USED IDS LIST
|
||||
private static readonly List<string> UsedIDs = new List<string>();
|
||||
|
||||
// METHOD
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region METHODS
|
||||
|
||||
// GET TASK ID
|
||||
public static string Get()
|
||||
{
|
||||
string id;
|
||||
do
|
||||
{
|
||||
id = "";
|
||||
while (id.Length < LengthID)
|
||||
while (id.Length < IDLength)
|
||||
{
|
||||
id += CharsID[Random.Next(0, CharsID.Length)];
|
||||
id += IDChars[Random.Next(0, IDChars.Length)];
|
||||
}
|
||||
} while (UsedIDs.Contains(id));
|
||||
UsedIDs.Add(id);
|
||||
return id;
|
||||
}
|
||||
|
||||
// DISPOSE TASK ID
|
||||
public static void Dispose(string id)
|
||||
{
|
||||
UsedIDs.Remove(id);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -127,16 +127,15 @@
|
||||
<Compile Include="Enums\StreamType.cs" />
|
||||
<Compile Include="Enums\VideoFileExtension.cs" />
|
||||
<Compile Include="Enums\VideoSource.cs" />
|
||||
<Compile Include="Enums\VideoStatus.cs" />
|
||||
<Compile Include="EventArgsObjects\VideoAddEventArgs.cs" />
|
||||
<Compile Include="EventArgsObjects\VideoSearchEventArgs.cs" />
|
||||
<Compile Include="EventArgsObjects\PlaylistSearchEventArgs.cs" />
|
||||
<Compile Include="Enums\TaskStatus.cs" />
|
||||
<Compile Include="EventArgs\VideoAddEventArgs.cs" />
|
||||
<Compile Include="EventArgs\VideoSearchEventArgs.cs" />
|
||||
<Compile Include="EventArgs\PlaylistSearchEventArgs.cs" />
|
||||
<Compile Include="Exceptions\TwitchAccessTokenNotFoundException.cs" />
|
||||
<Compile Include="Exceptions\TwitchAccessTokenNotValidException.cs" />
|
||||
<Compile Include="Interfaces\IAStream.cs" />
|
||||
<Compile Include="Interfaces\IBaseStream.cs" />
|
||||
<Compile Include="Interfaces\IPlaylistService.cs" />
|
||||
<Compile Include="Interfaces\IVideoService.cs" />
|
||||
<Compile Include="Interfaces\IVStream.cs" />
|
||||
<Compile Include="Objects\Stream.cs" />
|
||||
<Compile Include="Services\Config.cs" />
|
||||
<Compile Include="Services\MediaProcessor.cs" />
|
||||
|
||||
Reference in New Issue
Block a user