1.0-dev7 (Video adding panel added)
This commit is contained in:
@@ -2,11 +2,11 @@
|
||||
{
|
||||
public enum AudioFileExtension
|
||||
{
|
||||
MP3 = 4,
|
||||
FLAC = 5,
|
||||
WAV = 6,
|
||||
M4A = 7,
|
||||
ALAC = 8,
|
||||
WMA = 9,
|
||||
MP3 = 3,
|
||||
FLAC = 4,
|
||||
WAV = 5,
|
||||
M4A = 6,
|
||||
ALAC = 7,
|
||||
WMA = 8,
|
||||
}
|
||||
}
|
||||
|
||||
8
VDownload.Core/Enums/DefaultLocationType.cs
Normal file
8
VDownload.Core/Enums/DefaultLocationType.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
namespace VDownload.Core.Enums
|
||||
{
|
||||
public enum DefaultLocationType
|
||||
{
|
||||
Last,
|
||||
Selected
|
||||
}
|
||||
}
|
||||
@@ -2,8 +2,8 @@
|
||||
{
|
||||
public enum MediaType
|
||||
{
|
||||
AudioVideo,
|
||||
OnlyAudio,
|
||||
OnlyVideo,
|
||||
AudioVideo = 0,
|
||||
OnlyAudio = 1,
|
||||
OnlyVideo = 2,
|
||||
}
|
||||
}
|
||||
|
||||
8
VDownload.Core/Enums/PlaylistSource.cs
Normal file
8
VDownload.Core/Enums/PlaylistSource.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
namespace VDownload.Core.Enums
|
||||
{
|
||||
public enum PlaylistSource
|
||||
{
|
||||
TwitchChannel,
|
||||
Null
|
||||
}
|
||||
}
|
||||
@@ -2,8 +2,8 @@
|
||||
{
|
||||
public enum VideoFileExtension
|
||||
{
|
||||
MP4 = 1,
|
||||
WMV = 2,
|
||||
HEVC = 3,
|
||||
MP4 = 0,
|
||||
WMV = 1,
|
||||
HEVC = 2,
|
||||
}
|
||||
}
|
||||
|
||||
9
VDownload.Core/Enums/VideoSource.cs
Normal file
9
VDownload.Core/Enums/VideoSource.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
namespace VDownload.Core.Enums
|
||||
{
|
||||
public enum VideoSource
|
||||
{
|
||||
TwitchVod,
|
||||
TwitchClip,
|
||||
Null
|
||||
}
|
||||
}
|
||||
20
VDownload.Core/EventArgsObjects/VideoAddEventArgs.cs
Normal file
20
VDownload.Core/EventArgsObjects/VideoAddEventArgs.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
using System;
|
||||
using VDownload.Core.Enums;
|
||||
using VDownload.Core.Interfaces;
|
||||
using VDownload.Core.Objects;
|
||||
using Windows.Storage;
|
||||
|
||||
namespace VDownload.Core.EventArgsObjects
|
||||
{
|
||||
public class VideoAddEventArgs : EventArgs
|
||||
{
|
||||
public IVideoService VideoService { get; set; }
|
||||
public MediaType MediaType { get; set; }
|
||||
public Stream Stream { get; set; }
|
||||
public TimeSpan TrimStart { get; set; }
|
||||
public TimeSpan TrimEnd { get; set; }
|
||||
public string Filename { get; set; }
|
||||
public MediaFileExtension Extension { get; set; }
|
||||
public StorageFolder Location { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,9 @@
|
||||
using System.Threading.Tasks;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace VDownload.Core.Interfaces
|
||||
{
|
||||
internal interface IPlaylistService
|
||||
public interface IPlaylistService
|
||||
{
|
||||
#region PROPERTIES
|
||||
|
||||
@@ -15,9 +16,9 @@ namespace VDownload.Core.Interfaces
|
||||
|
||||
#region METHODS
|
||||
|
||||
Task GetMetadataAsync();
|
||||
Task GetMetadataAsync(CancellationToken cancellationToken = default);
|
||||
|
||||
Task GetVideosAsync(int numberOfVideos);
|
||||
Task GetVideosAsync(int numberOfVideos, CancellationToken cancellationToken = default);
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
@@ -12,13 +12,16 @@ namespace VDownload.Core.Interfaces
|
||||
{
|
||||
#region PROPERTIES
|
||||
|
||||
// VIDEO PROPERTIES
|
||||
string ID { get; }
|
||||
Uri VideoUrl { get; }
|
||||
string Title { get; }
|
||||
string Author { get; }
|
||||
DateTime Date { get; }
|
||||
TimeSpan Duration { get; }
|
||||
long Views { get; }
|
||||
Uri Thumbnail { get; }
|
||||
Stream[] Streams { get; }
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -27,10 +30,10 @@ namespace VDownload.Core.Interfaces
|
||||
#region METHODS
|
||||
|
||||
// GET VIDEO METADATA
|
||||
Task GetMetadataAsync();
|
||||
Task GetMetadataAsync(CancellationToken cancellationToken = default);
|
||||
|
||||
// GET VIDEO STREAMS
|
||||
Task GetStreamsAsync();
|
||||
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);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using VDownload.Core.Enums;
|
||||
using Windows.Media.Editing;
|
||||
using Windows.Storage;
|
||||
|
||||
@@ -24,6 +25,11 @@ namespace VDownload.Core.Services
|
||||
{ "media_transcoding_use_mrfcrf444_algorithm", true },
|
||||
{ "media_editing_algorithm", (int)MediaTrimmingPreference.Fast },
|
||||
{ "default_max_playlist_videos", 0 },
|
||||
{ "default_media_type", (int)MediaType.AudioVideo },
|
||||
{ "default_filename", "[<date_pub:yyyy.MM.dd>] <title>" },
|
||||
{ "default_video_extension", (int)VideoFileExtension.MP4 },
|
||||
{ "default_audio_extension", (int)AudioFileExtension.MP3 },
|
||||
{ "default_location_type", (int)DefaultLocationType.Last },
|
||||
};
|
||||
|
||||
#endregion
|
||||
|
||||
55
VDownload.Core/Services/Source.cs
Normal file
55
VDownload.Core/Services/Source.cs
Normal file
@@ -0,0 +1,55 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using VDownload.Core.Enums;
|
||||
|
||||
namespace VDownload.Core.Services
|
||||
{
|
||||
public class Source
|
||||
{
|
||||
#region CONSTANTS
|
||||
|
||||
private static readonly (Regex Regex, VideoSource Type)[] VideoSources = new (Regex Regex, VideoSource Type)[]
|
||||
{
|
||||
(new Regex(@"^https://www.twitch.tv/videos/(?<id>\d+)"), VideoSource.TwitchVod),
|
||||
(new Regex(@"^https://www.twitch.tv/\S+/clip/(?<id>[^?]+)"), VideoSource.TwitchClip),
|
||||
(new Regex(@"^https://clips.twitch.tv/(?<id>[^?]+)"), VideoSource.TwitchClip),
|
||||
};
|
||||
|
||||
private static readonly (Regex Regex, PlaylistSource Type)[] PlaylistSources = new (Regex Regex, PlaylistSource Type)[]
|
||||
{
|
||||
(new Regex(@"^https://www.twitch.tv/(?<id>[^?]+)"), PlaylistSource.TwitchChannel),
|
||||
};
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region METHODS
|
||||
|
||||
public static (VideoSource Type, string ID) GetVideoSource(string url)
|
||||
{
|
||||
foreach ((Regex Regex, VideoSource Type) Source in VideoSources)
|
||||
{
|
||||
Match sourceMatch = Source.Regex.Match(url);
|
||||
if (sourceMatch.Success) return (Source.Type, sourceMatch.Groups["id"].Value);
|
||||
}
|
||||
return (VideoSource.Null, null);
|
||||
}
|
||||
|
||||
public static (PlaylistSource Type, string ID) GetPlaylistSource(string url)
|
||||
{
|
||||
foreach ((Regex Regex, PlaylistSource Type) Source in PlaylistSources)
|
||||
{
|
||||
Match sourceMatch = Source.Regex.Match(url);
|
||||
if (sourceMatch.Success) return (Source.Type, sourceMatch.Groups["id"].Value);
|
||||
}
|
||||
return (PlaylistSource.Null, null);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -100,9 +100,15 @@ namespace VDownload.Core.Services.Sources.Twitch
|
||||
|
||||
return (true, login, expirationDate);
|
||||
}
|
||||
catch (WebException)
|
||||
catch (WebException wex)
|
||||
{
|
||||
return (false, null, null);
|
||||
if (wex.Response != null)
|
||||
{
|
||||
JObject wexInfo = JObject.Parse(new StreamReader(wex.Response.GetResponseStream()).ReadToEnd());
|
||||
if ((int)wexInfo["status"] == 401) return (false, null, null);
|
||||
else throw;
|
||||
}
|
||||
else throw;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using VDownload.Core.Exceptions;
|
||||
using VDownload.Core.Interfaces;
|
||||
@@ -37,8 +38,11 @@ namespace VDownload.Core.Services.Sources.Twitch
|
||||
#region STANDARD METHODS
|
||||
|
||||
// GET CHANNEL METADATA
|
||||
public async Task GetMetadataAsync()
|
||||
public async Task GetMetadataAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
// Set cancellation token
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
// Get access token
|
||||
string accessToken = await Auth.ReadAccessTokenAsync();
|
||||
if (accessToken == null) throw new TwitchAccessTokenNotFoundException();
|
||||
@@ -62,8 +66,11 @@ namespace VDownload.Core.Services.Sources.Twitch
|
||||
}
|
||||
|
||||
// GET CHANNEL VIDEOS
|
||||
public async Task GetVideosAsync(int numberOfVideos)
|
||||
public async Task GetVideosAsync(int numberOfVideos, CancellationToken cancellationToken = default)
|
||||
{
|
||||
// Set cancellation token
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
// Get access token
|
||||
string accessToken = await Auth.ReadAccessTokenAsync();
|
||||
if (accessToken == null) throw new TwitchAccessTokenNotFoundException();
|
||||
|
||||
@@ -41,6 +41,7 @@ namespace VDownload.Core.Services.Sources.Twitch
|
||||
#region PROPERTIES
|
||||
|
||||
public string ID { get; private set; }
|
||||
public Uri VideoUrl { get; private set; }
|
||||
public string Title { get; private set; }
|
||||
public string Author { get; private set; }
|
||||
public DateTime Date { get; private set; }
|
||||
@@ -56,8 +57,11 @@ namespace VDownload.Core.Services.Sources.Twitch
|
||||
#region STANDARD METHODS
|
||||
|
||||
// GET CLIP METADATA
|
||||
public async Task GetMetadataAsync()
|
||||
public async Task GetMetadataAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
// Set cancellation token
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
// Get access token
|
||||
string accessToken = await Auth.ReadAccessTokenAsync();
|
||||
if (accessToken == null) throw new TwitchAccessTokenNotFoundException();
|
||||
@@ -74,7 +78,10 @@ namespace VDownload.Core.Services.Sources.Twitch
|
||||
// Get response
|
||||
client.QueryString.Add("id", ID);
|
||||
JToken response = JObject.Parse(await client.DownloadStringTaskAsync("https://api.twitch.tv/helix/clips")).GetValue("data")[0];
|
||||
|
||||
|
||||
// Create unified video url
|
||||
VideoUrl = new Uri($"https://clips.twitch.tv/{ID}");
|
||||
|
||||
// Set parameters
|
||||
Title = (string)response["title"];
|
||||
Author = (string)response["broadcaster_name"];
|
||||
@@ -84,8 +91,11 @@ namespace VDownload.Core.Services.Sources.Twitch
|
||||
Thumbnail = new Uri((string)response["thumbnail_url"]);
|
||||
}
|
||||
|
||||
public async Task GetStreamsAsync()
|
||||
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);
|
||||
|
||||
@@ -20,16 +20,8 @@ namespace VDownload.Core.Services.Sources.Twitch
|
||||
{
|
||||
#region CONSTANTS
|
||||
|
||||
// METADATA TIME FORMATS
|
||||
private static readonly string[] TimeFormats = new[]
|
||||
{
|
||||
@"h\hm\ms\s",
|
||||
@"m\ms\s",
|
||||
@"s\s",
|
||||
};
|
||||
|
||||
// STREAMS RESPONSE REGULAR EXPRESSIONS
|
||||
private static readonly Regex L2Regex = new Regex(@"^#EXT-X-STREAM-INF:BANDWIDTH=\d+,CODECS=""(?<video_codec>\S+),(?<audio_codec>\S+)"",RESOLUTION=(?<width>\d+)x(?<height>\d+),VIDEO=""\w+"",FRAME-RATE=(?<frame_rate>\d+.\d+)");
|
||||
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)");
|
||||
@@ -52,6 +44,7 @@ namespace VDownload.Core.Services.Sources.Twitch
|
||||
#region PROPERTIES
|
||||
|
||||
public string ID { get; private set; }
|
||||
public Uri VideoUrl { get; private set; }
|
||||
public string Title { get; private set; }
|
||||
public string Author { get; private set; }
|
||||
public DateTime Date { get; private set; }
|
||||
@@ -67,8 +60,11 @@ namespace VDownload.Core.Services.Sources.Twitch
|
||||
#region STANDARD METHODS
|
||||
|
||||
// GET VOD METADATA
|
||||
public async Task GetMetadataAsync()
|
||||
public async Task GetMetadataAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
// Set cancellation token
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
// Get access token
|
||||
string accessToken = await Auth.ReadAccessTokenAsync();
|
||||
if (accessToken == null) throw new TwitchAccessTokenNotFoundException();
|
||||
@@ -91,18 +87,24 @@ namespace VDownload.Core.Services.Sources.Twitch
|
||||
}
|
||||
internal void GetMetadataAsync(JToken response)
|
||||
{
|
||||
// Create unified video url
|
||||
VideoUrl = new Uri($"https://www.twitch.tv/videos/{ID}");
|
||||
|
||||
// Set parameters
|
||||
Title = ((string)response["title"]).Replace("\n", "");
|
||||
Author = (string)response["user_name"];
|
||||
Date = Convert.ToDateTime(response["created_at"]);
|
||||
Duration = TimeSpan.ParseExact((string)response["duration"], TimeFormats, null);
|
||||
Duration = ParseDuration((string)response["duration"]);
|
||||
Views = (long)response["view_count"];
|
||||
Thumbnail = (string)response["thumbnail_url"] == string.Empty ? null : new Uri((string)response["thumbnail_url"]);
|
||||
Thumbnail = (string)response["thumbnail_url"] == string.Empty ? null : new Uri(((string)response["thumbnail_url"]).Replace("%{width}", "1920").Replace("%{height}", "1080"));
|
||||
}
|
||||
|
||||
// GET VOD STREAMS
|
||||
public async Task GetStreamsAsync()
|
||||
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);
|
||||
@@ -126,7 +128,7 @@ namespace VDownload.Core.Services.Sources.Twitch
|
||||
Uri url = new Uri(response[i + 2]);
|
||||
int width = int.Parse(line2.Groups["width"].Value);
|
||||
int height = int.Parse(line2.Groups["height"].Value);
|
||||
int frameRate = (int)Math.Round(double.Parse(line2.Groups["frame_rate"].Value));
|
||||
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;
|
||||
|
||||
@@ -161,12 +163,7 @@ namespace VDownload.Core.Services.Sources.Twitch
|
||||
List<(Uri ChunkUrl, TimeSpan ChunkDuration)> chunksList = await ExtractChunksFromM3U8Async(audioVideoStream.Url);
|
||||
|
||||
// Passive trim
|
||||
if ((bool)Config.GetValue("twitch_vod_passive_trim"))
|
||||
{
|
||||
var trimResult = PassiveVideoTrim(chunksList, trimStart, trimEnd, Duration);
|
||||
trimStart = trimResult.TrimStart;
|
||||
trimEnd = trimResult.TrimEnd;
|
||||
}
|
||||
if ((bool)Config.GetValue("twitch_vod_passive_trim")) (trimStart, trimEnd) = PassiveVideoTrim(chunksList, trimStart, trimEnd, Duration);
|
||||
|
||||
// Download
|
||||
StorageFile rawFile = await downloadingFolder.CreateFileAsync("raw.ts");
|
||||
@@ -301,6 +298,20 @@ namespace VDownload.Core.Services.Sources.Twitch
|
||||
});
|
||||
}
|
||||
|
||||
// PARSE DURATION TO SECONDS
|
||||
private static TimeSpan ParseDuration(string duration)
|
||||
{
|
||||
char[] separators = { 'h', 'm', 's' };
|
||||
string[] durationParts = duration.Split(separators, StringSplitOptions.RemoveEmptyEntries).Reverse().ToArray();
|
||||
|
||||
TimeSpan timeSpan = new TimeSpan(
|
||||
durationParts.Count() > 2 ? int.Parse(durationParts[2]) : 0,
|
||||
durationParts.Count() > 1 ? int.Parse(durationParts[1]) : 0,
|
||||
int.Parse(durationParts[0]));
|
||||
|
||||
return timeSpan;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
@@ -121,10 +121,14 @@
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="Enums\AudioFileExtension.cs" />
|
||||
<Compile Include="Enums\DefaultLocationType.cs" />
|
||||
<Compile Include="Enums\MediaFileExtension.cs" />
|
||||
<Compile Include="Enums\MediaType.cs" />
|
||||
<Compile Include="Enums\PlaylistSource.cs" />
|
||||
<Compile Include="Enums\StreamType.cs" />
|
||||
<Compile Include="Enums\VideoFileExtension.cs" />
|
||||
<Compile Include="Enums\VideoSource.cs" />
|
||||
<Compile Include="EventArgsObjects\VideoAddEventArgs.cs" />
|
||||
<Compile Include="EventArgsObjects\VideoSearchEventArgs.cs" />
|
||||
<Compile Include="EventArgsObjects\PlaylistSearchEventArgs.cs" />
|
||||
<Compile Include="Exceptions\TwitchAccessTokenNotFoundException.cs" />
|
||||
@@ -136,6 +140,7 @@
|
||||
<Compile Include="Objects\Stream.cs" />
|
||||
<Compile Include="Services\Config.cs" />
|
||||
<Compile Include="Services\MediaProcessor.cs" />
|
||||
<Compile Include="Services\Source.cs" />
|
||||
<Compile Include="Services\Sources\Twitch\Auth.cs" />
|
||||
<Compile Include="Services\Sources\Twitch\Channel.cs" />
|
||||
<Compile Include="Services\Sources\Twitch\Clip.cs" />
|
||||
|
||||
Reference in New Issue
Block a user