Skip to content

Commit

Permalink
Ability to embed twitch badges and bit emotes (#437)
Browse files Browse the repository at this point in the history
* add ability to embed twitch badges and bit emotes

* render using the cached ones!

* add --offline argument for CLI

* script to update old chat json files

* Renamed 'emotes' json property to 'embededData', renamed '--embed-emotes' to '--embed-images', update README, complete some TODOs, typos

* Typos, added some comments

* GetImage can fail, put in try catch

* use status codes in the catch statements

Co-authored-by: ScrubN <72096833+ScrubN@users.noreply.github.com>
Co-authored-by: Lewis Pardo <fizzohd@gmail.com>
  • Loading branch information
3 people committed Nov 20, 2022
1 parent 7603ec9 commit ef143bb
Show file tree
Hide file tree
Showing 19 changed files with 573 additions and 140 deletions.
10 changes: 5 additions & 5 deletions TwitchDownloaderCLI/Modes/Arguments/ChatDownloadArgs.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,16 @@ public class ChatDownloadArgs
[Option('e', "ending", HelpText = "Time in seconds to crop ending.")]
public int CropEndingTime { get; set; }

[Option('E', "embed-emotes", Default = false, HelpText = "Embed emotes into the chat download.")]
public bool EmbedEmotes { get; set; }
[Option('E', "embed-images", Default = false, HelpText = "Embed first party emotes, badges, and cheermotes into the chat download for offline rendering.")]
public bool EmbedData { get; set; }

[Option("bttv", Default = true, HelpText = "Enable BTTV embedding in chat download. Requires -E / --embed-emotes!")]
[Option("bttv", Default = true, HelpText = "Enable BTTV embedding in chat download. Requires -E / --embed-images!")]
public bool? BttvEmotes { get; set; }

[Option("ffz", Default = true, HelpText = "Enable FFZ embedding in chat download. Requires -E / --embed-emotes!")]
[Option("ffz", Default = true, HelpText = "Enable FFZ embedding in chat download. Requires -E / --embed-images!")]
public bool? FfzEmotes { get; set; }

[Option("stv", Default = true, HelpText = "Enable 7tv embedding in chat download. Requires -E / --embed-emotes!")]
[Option("stv", Default = true, HelpText = "Enable 7tv embedding in chat download. Requires -E / --embed-images!")]
public bool? StvEmotes { get; set; }

[Option("timestamp", Default = false, HelpText = "Enable timestamps for .txt chat downloads.")]
Expand Down
33 changes: 33 additions & 0 deletions TwitchDownloaderCLI/Modes/Arguments/ChatDownloadUpdaterArgs.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
using CommandLine;

namespace TwitchDownloaderCLI.Modes.Arguments
{

[Verb("chatupdate", HelpText = "Updates the embeded emotes, badges, and bits of a chat download.")]
public class ChatDownloadUpdaterArgs
{
[Option('i', "input", Required = true, HelpText = "Path to input file. Valid extensions are json")]
public string InputFile { get; set; }

[Option('o', "output", Required = true, HelpText = "Path to output file. Extension should match the input.")]
public string OutputFile { get; set; }

[Option('E', "embed-missing", Default = true, HelpText = "Embed missing emotes, badges, and bits. Already embedded images will be untouched")]
public bool EmbedMissing { get; set; }

[Option('U', "update-old", Default = false, HelpText = "Update old emotes, badges, and bits to the current. All embedded images will be overwritten")]
public bool UpdateOldEmbeds { get; set; }

[Option("bttv", Default = true, HelpText = "Enable BTTV embedding in chat download.")]
public bool BttvEmotes { get; set; }

[Option("ffz", Default = true, HelpText = "Enable FFZ embedding in chat download.")]
public bool FfzEmotes { get; set; }

[Option("stv", Default = true, HelpText = "Enable 7tv embedding in chat download.")]
public bool StvEmotes { get; set; }

[Option("temp-path", Default = "", HelpText = "Path to temporary folder to use for cache.")]
public string TempFolder { get; set; }
}
}
3 changes: 3 additions & 0 deletions TwitchDownloaderCLI/Modes/Arguments/ChatRenderArgs.cs
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,9 @@ public class ChatRenderArgs
[Option("badge-filter", Default = 0, HelpText = "Bitmask of types of Chat Badges to filter out. Add the numbers of the types of badges you want to filter. For example, 6 = no broadcaster or moderator badges.\r\nKey: Other = 1, Broadcaster = 2, Moderator = 4, VIP = 8, Subscriber = 16, Predictions = 32, NoAudio/NoVideo = 64, PrimeGaming = 128")]
public int BadgeFilterMask { get; set; }

[Option("offline", Default = false, HelpText = "Render completely offline, using only resources embedded emotes, badges, and bits in the input json.")]
public bool Offline { get; set; }

[Option("ffmpeg-path", HelpText = "Path to ffmpeg executable.")]
public string FfmpegPath { get; set; }

Expand Down
2 changes: 1 addition & 1 deletion TwitchDownloaderCLI/Modes/DownloadChat.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ internal static void Download(ChatDownloadArgs inputOptions)
CropEnding = inputOptions.CropEndingTime > 0.0,
CropEndingTime = inputOptions.CropEndingTime,
Timestamp = inputOptions.Timestamp,
EmbedEmotes = inputOptions.EmbedEmotes,
EmbedData = inputOptions.EmbedData,
Filename = inputOptions.OutputFile,
TimeFormat = inputOptions.TimeFormat,
ConnectionCount = inputOptions.ChatConnections,
Expand Down
170 changes: 170 additions & 0 deletions TwitchDownloaderCLI/Modes/DownloadChatUpdater.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.Json;
using System.Threading.Tasks;
using TwitchDownloaderCLI.Modes.Arguments;
using TwitchDownloaderCore;
using TwitchDownloaderCore.Options;
using TwitchDownloaderCore.TwitchObjects;

namespace TwitchDownloaderCLI.Modes
{
internal class DownloadChatUpdater
{
internal static void Update(ChatDownloadUpdaterArgs inputOptions)
{
DownloadFormat inFormat = Path.GetExtension(inputOptions.InputFile)!.ToLower() switch
{
".json" => DownloadFormat.Json,
".html" => DownloadFormat.Html,
".htm" => DownloadFormat.Html,
_ => DownloadFormat.Text
};
DownloadFormat outFormat = Path.GetExtension(inputOptions.OutputFile)!.ToLower() switch
{
".json" => DownloadFormat.Json,
".html" => DownloadFormat.Html,
".htm" => DownloadFormat.Html,
_ => DownloadFormat.Text
};
// Check that both input and output are json
if (inFormat != DownloadFormat.Json || outFormat != DownloadFormat.Json)
{
Console.WriteLine("[ERROR] - {0} format must be be json!", inFormat != DownloadFormat.Json ? "Input" : "Output");
Environment.Exit(1);
}
if (!File.Exists(inputOptions.InputFile))
{
Console.WriteLine("[ERROR] - Input file does not exist!");
Environment.Exit(1);
}
if (!inputOptions.EmbedMissing && !inputOptions.UpdateOldEmbeds)
{
Console.WriteLine("[ERROR] - Please enable either EmbedMissingEmotes or UpdateOldEmotes");
Environment.Exit(1);
}

// Read in the old input file
ChatRoot chatRoot = Task.Run(() => ChatRenderer.ParseJsonStatic(inputOptions.InputFile)).Result;
if (chatRoot.streamer == null)
{
chatRoot.streamer = new Streamer();
chatRoot.streamer.id = int.Parse(chatRoot.comments.First().channel_id);
chatRoot.streamer.name = Task.Run(() => TwitchHelper.GetStreamerName(chatRoot.streamer.id)).Result;
}
if (chatRoot.embeddedData == null)
{
chatRoot.embeddedData = new EmbeddedData();
}

string cacheFolder = Path.Combine(string.IsNullOrWhiteSpace(inputOptions.TempFolder) ? Path.GetTempPath() : inputOptions.TempFolder, "TwitchDownloader", "chatupdatecache");

// Clear working directory if it already exists
if (Directory.Exists(cacheFolder))
Directory.Delete(cacheFolder, true);

// Thirdparty emotes
if (chatRoot.embeddedData.thirdParty == null || inputOptions.UpdateOldEmbeds)
{
chatRoot.embeddedData.thirdParty = new List<EmbedEmoteData>();
}
Console.WriteLine("Input third party emote count: " + chatRoot.embeddedData.thirdParty.Count);
List<TwitchEmote> thirdPartyEmotes = new List<TwitchEmote>();
thirdPartyEmotes = Task.Run(() => TwitchHelper.GetThirdPartyEmotes(chatRoot.streamer.id, cacheFolder, bttv: inputOptions.BttvEmotes, ffz: inputOptions.FfzEmotes, stv: inputOptions.StvEmotes, embeddedData: chatRoot.embeddedData)).Result;
foreach (TwitchEmote emote in thirdPartyEmotes)
{
EmbedEmoteData newEmote = new EmbedEmoteData();
newEmote.id = emote.Id;
newEmote.imageScale = emote.ImageScale;
newEmote.data = emote.ImageData;
newEmote.name = emote.Name;
newEmote.width = emote.Width / emote.ImageScale;
newEmote.height = emote.Height / emote.ImageScale;
chatRoot.embeddedData.thirdParty.Add(newEmote);
}
Console.WriteLine("Output third party emote count: " + chatRoot.embeddedData.thirdParty.Count);

// Firstparty emotes
if (chatRoot.embeddedData.firstParty == null || inputOptions.UpdateOldEmbeds)
{
chatRoot.embeddedData.firstParty = new List<EmbedEmoteData>();
}
Console.WriteLine("Input first party emote count: " + chatRoot.embeddedData.firstParty.Count);
List<TwitchEmote> firstPartyEmotes = new List<TwitchEmote>();
firstPartyEmotes = Task.Run(() => TwitchHelper.GetEmotes(chatRoot.comments, cacheFolder, embeddedData: chatRoot.embeddedData)).Result;
foreach (TwitchEmote emote in firstPartyEmotes)
{
EmbedEmoteData newEmote = new EmbedEmoteData();
newEmote.id = emote.Id;
newEmote.imageScale = emote.ImageScale;
newEmote.data = emote.ImageData;
newEmote.width = emote.Width / emote.ImageScale;
newEmote.height = emote.Height / emote.ImageScale;
chatRoot.embeddedData.firstParty.Add(newEmote);
}
Console.WriteLine("Output third party emote count: " + chatRoot.embeddedData.firstParty.Count);

// Twitch badges
if (chatRoot.embeddedData.twitchBadges == null || inputOptions.UpdateOldEmbeds)
{
chatRoot.embeddedData.twitchBadges = new List<EmbedChatBadge>();
}
Console.WriteLine("Input twitch badge count: " + chatRoot.embeddedData.twitchBadges.Count);
List<ChatBadge> twitchBadges = new List<ChatBadge>();
twitchBadges = Task.Run(() => TwitchHelper.GetChatBadges(chatRoot.streamer.id, cacheFolder, embeddedData: chatRoot.embeddedData)).Result;
foreach (ChatBadge badge in twitchBadges)
{
EmbedChatBadge newBadge = new EmbedChatBadge();
newBadge.name = badge.Name;
newBadge.versions = badge.VersionsData;
chatRoot.embeddedData.twitchBadges.Add(newBadge);
}
Console.WriteLine("Output twitch badge count: " + chatRoot.embeddedData.twitchBadges.Count);

// Twitch bits / cheers
if (chatRoot.embeddedData.twitchBits == null || inputOptions.UpdateOldEmbeds)
{
chatRoot.embeddedData.twitchBits = new List<EmbedCheerEmote>();
}
Console.WriteLine("Input twitch bit count: " + chatRoot.embeddedData.twitchBits.Count);
List<CheerEmote> twitchBits = new List<CheerEmote>();
twitchBits = Task.Run(() => TwitchHelper.GetBits(cacheFolder, chatRoot.streamer.id.ToString(), embeddedData: chatRoot.embeddedData)).Result;
foreach (CheerEmote bit in twitchBits)
{
EmbedCheerEmote newBit = new EmbedCheerEmote();
newBit.prefix = bit.prefix;
newBit.tierList = new Dictionary<int, EmbedEmoteData>();
foreach (KeyValuePair<int, TwitchEmote> emotePair in bit.tierList)
{
EmbedEmoteData newEmote = new EmbedEmoteData();
newEmote.id = emotePair.Value.Id;
newEmote.imageScale = emotePair.Value.ImageScale;
newEmote.data = emotePair.Value.ImageData;
newEmote.name = emotePair.Value.Name;
newEmote.width = emotePair.Value.Width / emotePair.Value.ImageScale;
newEmote.height = emotePair.Value.Height / emotePair.Value.ImageScale;
newBit.tierList.Add(emotePair.Key, newEmote);
}
chatRoot.embeddedData.twitchBits.Add(newBit);
}
Console.WriteLine("Input twitch bit count: " + chatRoot.embeddedData.twitchBits.Count);

// Finally save the output to file!
// TODO: maybe in the future we could also export as HTML here too?
if (outFormat == DownloadFormat.Json)
{
using (TextWriter writer = File.CreateText(inputOptions.OutputFile))
{
var serializer = new Newtonsoft.Json.JsonSerializer();
serializer.Serialize(writer, chatRoot);
}
}

// Clear our working directory, it's highly unlikely we would reuse it anyways
if (Directory.Exists(cacheFolder))
Directory.Delete(cacheFolder, true);
}
}
}
3 changes: 2 additions & 1 deletion TwitchDownloaderCLI/Modes/RenderChat.cs
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,8 @@ internal static void Render(ChatRenderArgs inputOptions)
TempFolder = inputOptions.TempFolder,
SubMessages = (bool)inputOptions.SubMessages,
ChatBadges = (bool)inputOptions.ChatBadges,
Timestamp = inputOptions.Timestamp
Timestamp = inputOptions.Timestamp,
Offline = (bool)inputOptions.Offline,
};

if (renderOptions.GenerateMask && renderOptions.BackgroundColor.Alpha == 255)
Expand Down
11 changes: 5 additions & 6 deletions TwitchDownloaderCLI/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,22 +32,21 @@ static void Main(string[] args)
}

string[] preParsedArgs;
if (args.Any(x => x.Equals("-m") || x.Equals("--mode")))
if (args.Any(x => x is "-m" or "--mode" or "--embed-emotes"))
{
// Old -m/--mode syntax was used, print an info message and convert to verb syntax
Console.WriteLine("[INFO] The program has switched from --mode <mode> to verbs (like \"git <verb>\"), consider using verbs instead." +
" Run \"{0} help\" for more information.", processFileName);
preParsedArgs = PreParseArgs.Process(PreParseArgs.ConvertFromOldSyntax(args));
// A legacy syntax was used, convert to new syntax
preParsedArgs = PreParseArgs.Process(PreParseArgs.ConvertFromOldSyntax(args, processFileName));
}
else
{
preParsedArgs = PreParseArgs.Process(args);
}

Parser.Default.ParseArguments<VideoDownloadArgs, ClipDownloadArgs, ChatDownloadArgs, ChatRenderArgs, FfmpegArgs, CacheArgs>(preParsedArgs)
Parser.Default.ParseArguments<VideoDownloadArgs, ClipDownloadArgs, ChatDownloadArgs, ChatDownloadUpdaterArgs, ChatRenderArgs, FfmpegArgs, CacheArgs>(preParsedArgs)
.WithParsed<VideoDownloadArgs>(DownloadVideo.Download)
.WithParsed<ClipDownloadArgs>(DownloadClip.Download)
.WithParsed<ChatDownloadArgs>(DownloadChat.Download)
.WithParsed<ChatDownloadUpdaterArgs>(DownloadChatUpdater.Update)
.WithParsed<ChatRenderArgs>(RenderChat.Render)
.WithParsed<FfmpegArgs>(FfmpegHandler.ParseArgs)
.WithParsed<CacheArgs>(CacheHandler.ParseArgs)
Expand Down
45 changes: 38 additions & 7 deletions TwitchDownloaderCLI/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ A cross platform command line tool that can do the main functions of the GUI pro
- [Arguments for mode clipdownload](#arguments-for-mode-clipdownload)
- [Arguments for mode chatdownload](#arguments-for-mode-chatdownload)
- [Arguments for mode chatrender](#arguments-for-mode-chatrender)
- [Arguments for mode chatupdate](#arguments-for-mode-chatupdate)
- [Arguments for mode ffmpeg](#arguments-for-mode-ffmpeg)
- [Arguments for mode cache](#arguments-for-mode-cache)
- [Example commands](#example-commands)
Expand Down Expand Up @@ -73,17 +74,17 @@ Time in seconds to crop beginning. For example if I had a 10 second stream but o
**-e/-\-ending**
Time in seconds to crop ending. For example if I had a 10 second stream but only wanted the first 4 seconds of it I would use `-e 4` to end on the 4th second.

**-E/-\-embed-emotes**
(Default: false) Embeds emotes into the JSON file so in the future when an emote is removed from Twitch or a 3rd party, it will still render correctly. Useful for archival purposes, file size will be larger.
**-E/-\-embed-images**
(Default: false) Embed first party emotes, badges, and cheermotes into the download file for offline rendering. Useful for archival purposes, file size will be larger.

**-\-bttv**
(Default: true) BTTV emote embedding. Requires `-E / --embed-emotes`.
(Default: true) BTTV emote embedding. Requires `-E / --embed-images`.

**-\-ffz**
(Default: true) FFZ emote embedding. Requires `-E / --embed-emotes`.
(Default: true) FFZ emote embedding. Requires `-E / --embed-images`.

**-\-stv**
(Default: true) 7TV emote embedding. Requires `-E / --embed-emotes`.
(Default: true) 7TV emote embedding. Requires `-E / --embed-images`.

**-\-timestamp**
(Default: false) Enable timestamps
Expand Down Expand Up @@ -162,7 +163,7 @@ File the program will output to.
(Default: 0.2) Time in seconds to update chat render output.

**-\-input-args**
(Default: -framerate {fps} -f rawvideo -analyzeduration {max_int} -probesize {max_int} -pix_fmt bgra -video_size {width}x{height} -i -) Input (pass1) arguments for ffmpeg chat render.
(Default: -framerate {fps} -f rawvideo -analyzeduration {max_int} -probesize {max_int} -pix_fmt bgra -video_size {width}x{height} -i -) Input (pass1) arguments for ffmpeg chat render.

**-\-output-args**
(Default: -c:v libx264 -preset veryfast -crf 18 -pix_fmt yuv420p "{save_path}") Output (pass2) arguments for ffmpeg chat render.
Expand All @@ -182,13 +183,43 @@ Predictions = `32`,
NoAudioVisual = `64`,
PrimeGaming = `128`

**-\-offline**
Render completely offline, using only resources embedded emotes, badges, and bits in the input json.

**-\-ffmpeg-path**
Path to ffmpeg executable.

**-\-temp-path**
Path to temporary folder for cache.


## Arguments for mode chatupdate

**-i/-\-input (REQUIRED)**
Path to input file. Valid extensions are json

**-o/-\-output (REQUIRED)**
Path to output file. Extension should match the input.

**-E/-\-embed-missing**
(Default: true) Embed missing emotes, badges, and bits. Already embedded images will be untouched.

**-U/-\-update-old**
(Default: false) Update old emotes, badges, and bits to the current. All embedded images will be overwritten!

**-\-bttv**
(Default: true) Enable embedding BTTV emotes.

**-\-ffz**
(Default: true) Enable embedding FFZ emotes.

**-\-stv**
(Default: true) Enable embedding 7TV emotes.

**-\-temp-path**
Path to temporary folder for cache.


## Arguments for mode ffmpeg
Manage standalone ffmpeg

Expand Down Expand Up @@ -219,7 +250,7 @@ Download a Chat (plain text with timestamps)
TwitchDownloaderCLI chatdownload --id 612942303 --timestamp-format Relative -o chat.txt
Download a Chat (JSON with embeded emotes from Twitch and Bttv)

TwitchDownloaderCLI chatdownload --id 612942303 --embed-emotes --bttv=true --ffz=false --stv=false -o chat.json
TwitchDownloaderCLI chatdownload --id 612942303 --embed-images --bttv=true --ffz=false --stv=false -o chat.json
Render a chat with defaults

TwitchDownloaderCLI chatrender -i chat.json -o chat.mp4
Expand Down

0 comments on commit ef143bb

Please sign in to comment.