-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathProgram.cs
More file actions
188 lines (184 loc) · 8.2 KB
/
Copy pathProgram.cs
File metadata and controls
188 lines (184 loc) · 8.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
namespace vevodl {
class Program {
static string ROOTDIR = Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location);
static void Main(string[] args) {
while(true) {
foreach ((string ISRC, string Version) in Ask<bool>("Search for an ISRC", AskSettings.None) ? ISRCDB.Search(Ask<string>("Artist"), Ask<string>("Title")) : new List<(string, string)> { (Ask<string>("ISRC", AskSettings.Uppercase, "ISRC Code", @"^[A-Z]{2}-?\w{3}-?\d{2}-?\d{5}$"), string.Empty) }) {
#region Attempt to Query the ISRC to VEVO
if (!VEVO.Query(ISRC)) {
continue;
}
#endregion
#region Download HLS Catalogue to MKV File
string Filename = VEVO.Artist + " - " + VEVO.Title + (Version != string.Empty ? " [" + VEVO.Sanitize(Version) + "]" : string.Empty) + " [" + ISRC + "]";
string HLSCatalogue = VEVO.HLSCatalogue;
Logger.Info(" :=: Downloading " + ISRC + " as \"" + Filename + ".mkv\" :=:");
#region Subtitles
if (!string.IsNullOrEmpty(VEVO.Subtitle)) {
#region Download Subtitle M3U8 File as a VTT
DownloadM3U8(VEVO.Subtitle, "vtt");
#endregion
#region Fix VTT Subtitle
// For some reason the VTT has a ton of garbage not parsed properly with ffmpeg, no idea who to fault but they manually need to be removed.
// "WEBVTT FILE" line has an odd character in front of it, so a .EndsWith is needed.
// The timestamp line may be specific to each video im not sure.
File.WriteAllLines("temp/.vtt", File.ReadAllLines("temp/.vtt", Encoding.UTF8).Select(l => l.Trim()).Where(l => l != "WEBVTT" && !l.EndsWith("WEBVTT FILE") && l != "X-TIMESTAMP-MAP=MPEGTS:900000,LOCAL:00:00:00.000"), Encoding.UTF8);
#endregion
#region Convert VTT to SRT
if (RunEXE("SubtitleEdit.exe", "/convert \"temp/.vtt\" srt /overwrite") != 0) {
Logger.Error("Failed to convert the VTT subtitles to SRT, ignoring subtitles and continuing without them!");
if (File.Exists("temp/.vtt")) {
File.Delete("temp/.vtt");
}
if (File.Exists("temp/.srt")) {
File.Delete("temp/.srt");
}
}
#endregion
}
#endregion
#region TS (Video/Audio)
DownloadM3U8(VEVO.TS);
#endregion
#region Mux everything into an MKV
if (RunEXE("mkvmerge.exe", "--output \"" + Path.Combine(ROOTDIR, Filename.Replace("/", "-") + ".mkv") + "\" \"" + Path.Combine(ROOTDIR, "temp", ".ts") + "\" --default-track 0:false " + (File.Exists("temp/.chapters") ? "--chapters \"" + Path.Combine(ROOTDIR, "temp", ".chapters") + "\"" : string.Empty) + " " + (File.Exists("temp/.srt") ? "--sub-charset 0:UTF-8 \"" + Path.Combine(ROOTDIR, "temp", ".srt") + "\"" : string.Empty)) != 0) {
return;
}
// Cleanup files no longer needed
// todo: setup files in such a way to be multi-threaded supported and not conflict with other downloads at same time
File.Delete("temp/.ts");
File.Delete("temp/.srt");
File.Delete("temp/.chapters");
#endregion
Console.ForegroundColor = ConsoleColor.Green;
Console.WriteLine("Downloaded!");
Console.ResetColor();
#endregion
}
}
}
[Flags]
public enum AskSettings {
None = 0,
Uppercase = 1,
Lowercase = 2,
UniqueOnly = 4
}
public static T Ask<T>(string Question, AskSettings Settings = AskSettings.None, string AnswerName = "answer", string MatchPattern = null) {
try {
bool IsYesNo = typeof(T) == typeof(bool);
Console.Write("[" + Question + (IsYesNo ? "? (y/n)" : string.Empty) + "]: ");
object Answer = IsYesNo ? ((T)Convert.ChangeType(Console.ReadKey().Key == ConsoleKey.Y, typeof(T))) : (object)Console.ReadLine();
if (MatchPattern != null) {
if (typeof(T) != typeof(string)) {
Logger.Error("Code attempted to use a MatchPattern Question with a non string return type, this can't be done, oops.");
return default(T);
}
string AnswerAsStr = (string)Convert.ChangeType(Answer, typeof(string));
if (Settings.HasFlag(AskSettings.UniqueOnly)) {
AnswerAsStr = new string(AnswerAsStr.Distinct().ToArray());
}
if (Settings.HasFlag(AskSettings.Uppercase)) {
AnswerAsStr = AnswerAsStr.ToUpperInvariant();
} else if (Settings.HasFlag(AskSettings.Lowercase)) {
AnswerAsStr = AnswerAsStr.ToLowerInvariant();
}
if (!Regex.Match(AnswerAsStr, MatchPattern).Success) {
Logger.Error("\"" + AnswerAsStr + "\" is an invalid " + AnswerName + "...");
return Ask<T>(Question, Settings, AnswerName, MatchPattern);
}
}
return (T)Convert.ChangeType(Answer, typeof(T));
} finally {
DeleteCurrentLine();
}
}
public static void DeleteConsoleLines(int Lines) {
for (int i = 0; i < Lines; i++) {
Console.SetCursorPosition(0, Console.CursorTop - 1);
Console.Write(new string(' ', Console.WindowWidth));
Console.SetCursorPosition(0, Console.CursorTop - 1);
}
}
public static void DeleteCurrentLine() {
Console.SetCursorPosition(0, Console.CursorTop); // Set cursor to start of current line
Console.Write(new string(' ', Console.WindowWidth)); // Fill the entire line with spaces
Console.SetCursorPosition(0, Console.CursorTop - 1); // Set cursor to start of previous line
}
private static int RunEXE(string exePath, string args) {
//todo: find a more convenient better looking way to ignore all output and windows then using redirect like this
Process p = new Process {
StartInfo = new ProcessStartInfo(Path.Combine(ROOTDIR, "tools", exePath)) {
Arguments = args,
UseShellExecute = false,
CreateNoWindow = false,
RedirectStandardError = true,
RedirectStandardInput = true,
RedirectStandardOutput = true
}
};
p.Start();
p.BeginOutputReadLine();
p.BeginErrorReadLine();
p.WaitForExit();
if (p.ExitCode != 0) {
Logger.Error(Path.GetFileName(exePath) + " closed with an error code :( (Something unexpected went wrong)");
}
return p.ExitCode;
}
private static void DownloadM3U8(string URL, string Type = "ts") {
foreach (string dir in new[] { "temp", "temp/seg" }) {
Directory.CreateDirectory(dir);
}
string M3U8 = null;
using (WebClient WC = new WebClient()) {
// Get M3U8 File's Contents via GET
M3U8 = WC.DownloadString(URL);
}
// Download Segments and replace the M3U8 File Content's segment paths to the local downloaded relative paths
ConcurrentDictionary<string, string> segMap = new ConcurrentDictionary<string, string>();
Parallel.ForEach(
Regex.Matches(M3U8, "#EXTINF:.*\\s(.*)").Cast<Match>().Select(x => x.Groups[1].Value).Distinct(),
seg => {
string fn = "temp/seg/" + seg.GetHashCode().ToString().Replace("-", "m") + ".ts";
bool downloaded = false;
while (!downloaded) {
try {
// Cannot reference an already instanciated WebClient here as WebClient doesnt support I/O (Cant do multi-threaded operations)
new WebClient().DownloadFile((!seg.StartsWith("http") ? URL.Substring(0, URL.LastIndexOf('/') + 1) : string.Empty) + seg, fn);
segMap.TryAdd(seg, fn.Replace("temp/", string.Empty));
downloaded = true;
} catch (Exception ex) {
Logger.Error("Failed while downloading \"" + seg + "\", Retrying, Error Message: " + ex.Message);
}
}
}
);
foreach (KeyValuePair<string, string> seg in segMap) {
M3U8 = M3U8.Replace(seg.Key, seg.Value);
}
// Write new M3U8 content to a file so FFMPEG can read it
File.WriteAllText("temp/" + Type + ".m3u8", M3U8);
// Run FFMPEG on the new M3U8 to let it compile them all as a single Matroska format file dealing with crap like timing, order, timecodes, audio, muxing, and shit.
if (RunEXE("ffmpeg.exe", "-protocol_whitelist file,http,https,tcp,tls,crypto -allowed_extensions ALL -y -hide_banner -i \"temp/" + Type + ".m3u8\" -c copy \"temp/." + Type + "\"") != 0) {
return;
}
// Delete now un-needed data
foreach (string dir in new[] { "temp/seg" }) {
Directory.Delete(dir, true);
}
File.Delete("temp/" + Type + ".m3u8");
}
}
}