Skip to content

Commit ddb6473

Browse files
committed
1.22
1 parent ccad2d9 commit ddb6473

File tree

9 files changed

+278
-40
lines changed

9 files changed

+278
-40
lines changed

TwitchDownloaderWPF/App.config

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,12 @@
8484
<setting name="BackgroundColorA" serializeAs="String">
8585
<value>255</value>
8686
</setting>
87+
<setting name="OAuth" serializeAs="String">
88+
<value />
89+
</setting>
90+
<setting name="EncodeCFR" serializeAs="String">
91+
<value>False</value>
92+
</setting>
8793
</TwitchDownloader.Properties.Settings>
8894
</userSettings>
8995
</configuration>

TwitchDownloaderWPF/ChatRoot.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ public class Emoticon2
4848
public class Message
4949
{
5050
public string body { get; set; }
51+
public int bits_spent { get; set; }
5152
public List<Fragment> fragments { get; set; }
5253
public bool is_action { get; set; }
5354
public List<UserBadge> user_badges { get; set; }

TwitchDownloaderWPF/InfoHelper.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,13 +37,13 @@ public static async Task<JObject> GetVideoInfo(int videoId)
3737
}
3838
}
3939

40-
public static async Task<JObject> GetVideoToken(int videoId)
40+
public static async Task<JObject> GetVideoToken(int videoId, string authToken)
4141
{
4242
using (WebClient client = new WebClient())
4343
{
4444
client.Encoding = Encoding.UTF8;
4545
client.Headers.Add("Client-ID", "kimne78kx3ncx6brgo4mv6wki5h1ko");
46-
string response = await client.DownloadStringTaskAsync(String.Format("https://api.twitch.tv/api/vods/{0}/access_token", videoId));
46+
string response = await client.DownloadStringTaskAsync(String.Format("https://api.twitch.tv/api/vods/{0}/access_token{1}", videoId, (authToken == "" ? "" : "?oauth_token=" + authToken)));
4747
JObject result = JObject.Parse(response);
4848
return result;
4949
}

TwitchDownloaderWPF/PageChatRender.xaml.cs

Lines changed: 149 additions & 28 deletions
Large diffs are not rendered by default.

TwitchDownloaderWPF/PageVodDownload.xaml

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,12 +52,14 @@
5252
<TextBox x:Name="textUrl" Margin="3" MinWidth="200" MaxWidth="400"/>
5353
<Button x:Name="btnGetInfo" Margin="3" MinWidth="50" Content="Get Info" Click="btnGetInfo_Click"/>
5454
</WrapPanel>
55-
<StackPanel Grid.Row="2" Grid.Column="2" Grid.ColumnSpan="2" Margin="0,20,20,0" Orientation="Horizontal" HorizontalAlignment="Center">
55+
<StackPanel Grid.Row="2" Grid.Column="2" Grid.ColumnSpan="2" Margin="0,15,20,0" Orientation="Horizontal" HorizontalAlignment="Center">
5656
<StackPanel HorizontalAlignment="Left">
5757
<TextBlock Text="Length:" HorizontalAlignment="Right"/>
5858
<TextBlock Text="Quality:" HorizontalAlignment="Right" Margin="0,10,0,0"/>
5959
<TextBlock Text="Crop Video:" HorizontalAlignment="Right" Margin="0,14,0,0"/>
6060
<TextBlock Text="Download Threads:" HorizontalAlignment="Right" Margin="0,30,0,0"/>
61+
<TextBlock HorizontalAlignment="Right" Margin="0,12,0,0">OAuth (optional) <Hyperlink NavigateUri="https://www.youtube.com/watch?v=1MBsUoFGuls" RequestNavigate="Hyperlink_RequestNavigate" ToolTipService.ShowDuration="30000"><Hyperlink.ToolTip>Only required for sub only VODs. All 3rd party OAuth tokens will not work. Click to see YouTube video on how to get OAuth token.</Hyperlink.ToolTip>(?)</Hyperlink>:</TextBlock>
62+
<TextBlock HorizontalAlignment="Right" Margin="0,12,0,0">Re-encode to CFR <Hyperlink ToolTipService.ShowDuration="30000"><Hyperlink.ToolTip>Fixes de-sync in Adobe Premiere, will take significantly more time and may result in quality loss.</Hyperlink.ToolTip>(?)</Hyperlink>:</TextBlock>
6163
</StackPanel>
6264
<StackPanel>
6365
<TextBlock x:Name="labelLength" Text="0:0:0" Margin="5,0,0,0"/>
@@ -77,11 +79,13 @@
7779
<xctk:IntegerUpDown Margin="3,-1,0,0" Value="0" Name="numEndMinute" />
7880
<xctk:IntegerUpDown Margin="3,-1,0,0" Value="0" Name="numEndSecond" />
7981
</StackPanel>
80-
<xctk:IntegerUpDown IsEnabled="False" Margin="2,5,0,0" Value="10" Width="40" x:Name="numDownloadThreads" HorizontalAlignment="Left" ValueChanged="numDownloadThreads_ValueChanged" />
82+
<xctk:IntegerUpDown Margin="2,5,0,0" Value="10" Width="40" x:Name="numDownloadThreads" HorizontalAlignment="Left" ValueChanged="numDownloadThreads_ValueChanged" />
83+
<TextBox x:Name="textOauth" Margin="0,8,3,3" MinWidth="200" MaxWidth="400" TextChanged="textOauth_TextChanged"/>
84+
<CheckBox x:Name="checkCFR" Margin="0,2,0,0" Checked="checkCFR_Changed" Unchecked="checkCFR_Changed"></CheckBox>
8185
</StackPanel>
8286
</StackPanel>
8387
</StackPanel>
84-
<Button x:Name="btnDownload" Grid.Row="3" Grid.Column="2" Grid.ColumnSpan="2" Content="Download" Height="50" Width="100" Click="btnDownload_Click" Margin="0,0,0,20" VerticalAlignment="Bottom"/>
88+
<Button x:Name="btnDownload" Grid.Row="3" Grid.Column="2" Grid.ColumnSpan="2" Content="Download" Height="50" Width="100" Click="btnDownload_Click" Margin="0,0,0,10" VerticalAlignment="Bottom"/>
8589
<!-- RIGHT -->
8690
<StackPanel Grid.Column="4" Grid.Row="2" Grid.RowSpan="2">
8791
<TextBlock Text="Log:"/>

TwitchDownloaderWPF/PageVodDownload.xaml.cs

Lines changed: 81 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using System;
44
using System.Collections.Generic;
55
using System.ComponentModel;
6+
using System.Diagnostics;
67
using System.Drawing;
78
using System.Globalization;
89
using System.Linq;
@@ -27,6 +28,7 @@
2728
using Xabe.FFmpeg;
2829
using WpfAnimatedGif;
2930
using TwitchDownloader.Properties;
31+
using Xabe.FFmpeg.Events;
3032

3133
namespace TwitchDownloaderWPF
3234
{
@@ -55,7 +57,11 @@ private void SetEnabled(bool isEnabled)
5557
numEndMinute.IsEnabled = isEnabled;
5658
numEndSecond.IsEnabled = isEnabled;
5759
btnDownload.IsEnabled = isEnabled;
58-
numDownloadThreads.IsEnabled = isEnabled;
60+
}
61+
private void Hyperlink_RequestNavigate(object sender, RequestNavigateEventArgs e)
62+
{
63+
Process.Start(new ProcessStartInfo(e.Uri.AbsoluteUri));
64+
e.Handled = true;
5965
}
6066

6167
private async void btnGetInfo_Click(object sender, RoutedEventArgs e)
@@ -67,7 +73,7 @@ private async void btnGetInfo_Click(object sender, RoutedEventArgs e)
6773
try
6874
{
6975
Task<JObject> taskInfo = InfoHelper.GetVideoInfo(videoId);
70-
Task<JObject> taskAccessToken = InfoHelper.GetVideoToken(videoId);
76+
Task<JObject> taskAccessToken = InfoHelper.GetVideoToken(videoId, textOauth.Text);
7177
await Task.WhenAll(taskInfo, taskAccessToken);
7278
string thumbUrl = taskInfo.Result["data"][0]["thumbnail_url"].ToString().Replace("%{width}", 512.ToString()).Replace("%{height}", 290.ToString());
7379
Task<BitmapImage> thumbImage = InfoHelper.GetThumb(thumbUrl);
@@ -278,14 +284,67 @@ private void BackgroundDownloadManager_DoWork(object sender, DoWorkEventArgs e)
278284
catch { }
279285
}
280286
}
281-
282-
(sender as BackgroundWorker).ReportProgress(0, "Finalizing MP4 (3/3)");
287+
288+
bool isVFR = false;
289+
if (options.encode_cfr)
290+
{
291+
var process = new Process
292+
{
293+
StartInfo =
294+
{
295+
FileName = "ffmpeg.exe",
296+
Arguments = $"-i \"" + Path.Combine(downloadFolder, "output.ts") + "\" -vf vfrdet -f null -",
297+
UseShellExecute = false,
298+
CreateNoWindow = true,
299+
RedirectStandardInput = true,
300+
RedirectStandardOutput = true,
301+
RedirectStandardError = true
302+
}
303+
};
304+
string output = "";
305+
process.ErrorDataReceived += delegate(object o, DataReceivedEventArgs args)
306+
{
307+
if (args.Data != null && args.Data.Contains("Parsed_vfrdet"))
308+
{
309+
output += args.Data;
310+
}
311+
};
312+
process.Start();
313+
process.BeginErrorReadLine();
314+
process.BeginOutputReadLine();
315+
process.WaitForExit();
316+
double VFR = double.Parse(output.Substring(output.IndexOf("VFR:") + 4, 8));
317+
if (VFR == 0.0)
318+
{
319+
AppendLog("Constant framerate detected, no need to re-encode");
320+
}
321+
else
322+
{
323+
isVFR = true;
324+
AppendLog("Detected variable framerate, re-encoding");
325+
}
326+
}
327+
328+
if (isVFR)
329+
(sender as BackgroundWorker).ReportProgress(0, "Re-encoding MP4 (3/3)");
330+
else
331+
(sender as BackgroundWorker).ReportProgress(0, "Finalizing MP4 (3/3)");
283332
string outputConvert = options.filename;
284333
Task<IMediaInfo> info = MediaInfo.Get(Path.Combine(downloadFolder, "output.ts"));
285334
Task.WaitAll(info);
286335
double seekTime = options.crop_begin;
287-
double seekDuration = info.Result.Duration.TotalSeconds - seekTime - options.crop_end;
288-
Task<IConversionResult> conversionResult = Conversion.New().Start(String.Format("-y -i \"{0}\" -ss {1} -analyzeduration {2} -t {3} -avoid_negative_ts make_zero -vcodec copy \"{4}\"", Path.Combine(downloadFolder, "output.ts"), seekTime.ToString(), int.MaxValue, seekDuration.ToString(), outputConvert));
336+
double seekDuration = Math.Round(info.Result.Duration.TotalSeconds - seekTime - options.crop_end);
337+
Task<IConversionResult> conversionResult = null;
338+
if (isVFR)
339+
{
340+
int newFps = (int)Math.Ceiling(info.Result.VideoStreams.First().FrameRate);
341+
conversionResult = Conversion.New().Start(String.Format("-y -i \"{0}\" -ss {1} -analyzeduration {2} -t {3} -crf 20 -filter:v fps=fps={4} \"{5}\"", Path.Combine(downloadFolder, "output.ts"), seekTime.ToString(), int.MaxValue, seekDuration.ToString(), newFps, outputConvert));
342+
}
343+
else
344+
{
345+
conversionResult = Conversion.New().Start(String.Format("-y -i \"{0}\" -ss {1} -analyzeduration {2} -t {3} -avoid_negative_ts make_zero -acodec copy -vcodec copy \"{4}\"", Path.Combine(downloadFolder, "output.ts"), seekTime.ToString(), int.MaxValue, seekDuration.ToString(), outputConvert));
346+
}
347+
289348
Task.WaitAll(conversionResult);
290349
if (Directory.Exists(downloadFolder))
291350
DeleteDirectory(downloadFolder);
@@ -477,6 +536,8 @@ private void Page_Initialized(object sender, EventArgs e)
477536
SetEnabled(false);
478537
WebRequest.DefaultWebProxy = null;
479538
numDownloadThreads.Value = Settings.Default.VodDownloadThreads;
539+
textOauth.Text = Settings.Default.OAuth;
540+
checkCFR.IsChecked = Settings.Default.EncodeCFR;
480541
}
481542

482543
private void numDownloadThreads_ValueChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
@@ -487,6 +548,18 @@ private void numDownloadThreads_ValueChanged(object sender, RoutedPropertyChange
487548
Settings.Default.Save();
488549
}
489550
}
551+
552+
private void textOauth_TextChanged(object sender, TextChangedEventArgs e)
553+
{
554+
Settings.Default.OAuth = textOauth.Text;
555+
Settings.Default.Save();
556+
}
557+
558+
private void checkCFR_Changed(object sender, RoutedEventArgs e)
559+
{
560+
Settings.Default.EncodeCFR = (bool)checkCFR.IsChecked;
561+
Settings.Default.Save();
562+
}
490563
}
491564
}
492565
public class ProgressReport
@@ -516,6 +589,7 @@ public class DownloadOptions
516589
public TimeSpan cropped_end_time { get; set; }
517590
public double crop_end { get; set; }
518591
public int download_threads { get; set; }
592+
public bool encode_cfr { get; set; }
519593
public DownloadOptions()
520594
{
521595

@@ -535,5 +609,6 @@ public void UpdateValues(PageVodDownload currentPage)
535609
crop_begin = 0.0;
536610
crop_end = 0.0;
537611
download_threads = (int)currentPage.numDownloadThreads.Value;
612+
encode_cfr = (bool)currentPage.checkCFR.IsChecked;
538613
}
539614
}

TwitchDownloaderWPF/Properties/Settings.Designer.cs

Lines changed: 24 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

TwitchDownloaderWPF/Properties/Settings.settings

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,5 +68,11 @@
6868
<Setting Name="BackgroundColorA" Type="System.Int32" Scope="User">
6969
<Value Profile="(Default)">255</Value>
7070
</Setting>
71+
<Setting Name="OAuth" Type="System.String" Scope="User">
72+
<Value Profile="(Default)" />
73+
</Setting>
74+
<Setting Name="EncodeCFR" Type="System.Boolean" Scope="User">
75+
<Value Profile="(Default)">False</Value>
76+
</Setting>
7177
</Settings>
7278
</SettingsFile>

TwitchDownloaderWPF/WindowPreview.xaml.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ public void Update(PageChatRender pageChatRender)
8080
SKBitmap sectionImage = new SKBitmap((int)canvasSize.Width, (int)canvasSize.Height);
8181
List<GifEmote> currentGifEmotes = new List<GifEmote>();
8282
List<SKBitmap> emoteList = new List<SKBitmap>();
83+
List<CheerEmote> cheerEmotes = new List<CheerEmote>();
8384
List<SKRect> emotePositionList = new List<SKRect>();
8485
new SKCanvas(sectionImage).Clear(renderOptions.background_color);
8586
Comment comment = new Comment();
@@ -93,7 +94,7 @@ public void Update(PageChatRender pageChatRender)
9394
if (previewComment.badges != null)
9495
sectionImage = DrawBadges(sectionImage, imageList, renderOptions, canvasSize, ref drawPos, previewComment);
9596
sectionImage = pageChatRender.DrawUsername(sectionImage, imageList, renderOptions, nameFont, userName, userColor, canvasSize, ref drawPos);
96-
sectionImage = pageChatRender.DrawMessage(sectionImage, imageList, renderOptions, currentGifEmotes, messageFont, emojiCache, chatEmotes, thirdPartyEmotes, comment, canvasSize, ref drawPos, emojiRegex, ref default_x, emoteList, emotePositionList);
97+
sectionImage = pageChatRender.DrawMessage(sectionImage, imageList, renderOptions, currentGifEmotes, messageFont, emojiCache, chatEmotes, thirdPartyEmotes, cheerEmotes, comment, canvasSize, ref drawPos, emojiRegex, ref default_x, emoteList, emotePositionList);
9798

9899

99100
int finalHeight = 0;

0 commit comments

Comments
 (0)