script-splitter/Program.cs
adam 70e17dffad works
and can even run in another directory!

now I need to really really nail down a format.
2024-09-22 23:47:54 -04:00

282 lines
12 KiB
C#

using System.Collections.Concurrent;
using System.Data;
using System.Diagnostics;
using System.Net;
using System.Reflection.Metadata.Ecma335;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices.Marshalling;
using System.Text;
using System.Text.RegularExpressions;
using System.Timers;
using System.Xml.Schema;
using Newtonsoft.Json;
namespace placeholdervo
{
public partial class Program
{
[GeneratedRegex("^(\\[|#)")]
private static partial Regex NonTextLine();
public static Config conf;
private static string workingDir = "./";
private static HttpClient hc;
private static ConcurrentQueue<string> titleCardLines = new ConcurrentQueue<string>();
private static ConcurrentQueue<string> noteLines = new ConcurrentQueue<string>();
private static ConcurrentDictionary<string, string> filePickups = new ConcurrentDictionary<string, string>();
private static CancellationToken scriptFileCancellationToken;
///<summary>mostly just to give myself the reminder about paths</summary>
///<param name="src">the *complete path* of the awaited file. you might want conf.sync_dropoff</param>
///<param name="dest">the *complete path* to move the file to.</param>
private static async void awaitFile(string src, string dest)
{
filePickups[src] = dest;
}
private static async void filePickup()
{
while(true)
{
foreach(var srcKey in filePickups.Keys)
{
//Console.WriteLine($"hoping for {srcKey} (would move it to {filePickups[srcKey]})");
if(File.Exists(srcKey))
{
Console.WriteLine($"found {srcKey}");
string dest;
if (filePickups.TryRemove(srcKey, out dest))
{
File.Move(srcKey, dest, true);
}
else
{
Console.Error.WriteLine($"Failed to remove {srcKey} from filepickups. Unrecoverable, I think.");
Environment.Exit(-1);
}
}
}
if(scriptFileCancellationToken.IsCancellationRequested)
{
//Console.WriteLine($"Cancellation requested. but I'm using it in more of a \"there won't be more work, but finish what you're doing\" kind of way.");
if(filePickups.IsEmpty)
{
Console.WriteLine("and we got nothing left, so we quit.");
return;
}
}
System.Threading.Thread.Sleep(TimeSpan.FromSeconds(5));
}
}
private static async void FFStuff()
{
var cardCount = 0;
var magnitude = 1;
Console.WriteLine($"{titleCardLines.Count} title card lines");
magnitude = (int)Math.Log10(titleCardLines.Count);
Directory.CreateDirectory(conf.titlecards_dest);
foreach(var tcL in titleCardLines)
{
Console.WriteLine("titlecard commanding");
await Process.Start(conf.titlecard_command, tcL).WaitForExitAsync();
cardCount++;
File.Move("tc.png", Path.Combine(conf.titlecards_dest, $"tc{cardCount.ToString($"D{magnitude}")}.png"), true);
}
Console.WriteLine("titlecards done");
if(noteLines != null)
{
cardCount = 0;
Console.WriteLine($"{noteLines?.Count} note lines");
magnitude = (int)Math.Log10(noteLines?.Count ?? 1);
Directory.CreateDirectory(conf.notes_dest);
foreach(var nL in noteLines)
{
Console.WriteLine("note commanding");
await Process.Start(conf.note_command, nL).WaitForExitAsync();
cardCount++;
File.Move("note.png", Path.Combine(conf.notes_dest, $"note{cardCount.ToString($"D{magnitude}")}.png"), true);
}
}
}
public static async Task Main(string[] args)
{
conf = JsonConvert.DeserializeObject<Config>(
File.ReadAllText(
AppDomain.CurrentDomain.BaseDirectory + "appsettings.json"))
?? new Config();
hc = new HttpClient(){ BaseAddress = new Uri(conf.speech_service)};
var taskHeap = new List<Task>();
var scriptPath = args?.FirstOrDefault();
if(string.IsNullOrWhiteSpace(scriptPath))
{
scriptPath = "./script.md";
if(!File.Exists(scriptPath))
{
scriptPath = "./script.txt";
if(!File.Exists(scriptPath))
{
Console.Error.WriteLine("no script found.");
return;
}
}
}
var scriptBasename = Path.GetFileNameWithoutExtension(scriptPath);
workingDir = Path.GetDirectoryName(scriptPath);
var cancelSource = new CancellationTokenSource();
scriptFileCancellationToken = cancelSource.Token;
var lines = File.ReadAllLines(scriptPath)
.Append("\n").Append("\n");
var primaryVOLines = new List<string>();
var titlecardUnprocessed = new List<string>();
var directiveLines = new List<string>();
var altVOLines = new List<string>();
foreach(var l in lines)
{
if(l.StartsWith('#'))
{
titlecardUnprocessed.Add(l);
}
else if (l.StartsWith('['))
{
directiveLines.Add(l);
}
else
{
//as opposed to Environment.NewLine, which will be running on linux, and thus \n. ms speech api cooperates more with \r\n.
l.ReplaceLineEndings("\r\n");
primaryVOLines.Add(l);
}
}
var scriptStripped = $"{scriptBasename}-primaryVO.txt";
File.WriteAllLines(scriptStripped, primaryVOLines);
taskHeap.Add(ttsVO(scriptStripped, Path.Combine(conf.VODropoff, $"primaryVO")));
var titleLevels = new List<int>(){0};
var titleIncrementDepth = 0;
foreach(var l in titlecardUnprocessed)
{
titleIncrementDepth = 1;
var thisString = l;
while(thisString.StartsWith('#'))
{
thisString = thisString.Substring(1);
if(!thisString.StartsWith('#'))
{
if(titleLevels.Count < titleIncrementDepth)
{
titleLevels = [.. titleLevels, 0];
}
titleLevels[titleIncrementDepth-1]++;
if(titleLevels.Count > titleIncrementDepth)
{
titleLevels.RemoveRange(titleIncrementDepth, titleLevels.Count - titleIncrementDepth);
}
var TitleCardText = thisString.Trim();
if(titleLevels.Count > 1)
{
TitleCardText = string.Join('.', titleLevels[1..]) + ": " + TitleCardText;
}
Console.WriteLine($"title card: {TitleCardText}");
titleCardLines.Enqueue(TitleCardText);
}
else
{
titleIncrementDepth++;
}
}
}
foreach(var l in directiveLines)
{
var thisLine = l.Trim();
if(!thisLine.Contains(']'))
{
Console.Error.WriteLine("malformed directive line: ");
Console.WriteLine(thisLine);
continue;
}
if (thisLine.EndsWith(']'))
{
thisLine = thisLine.Trim('[', ']');
if (Uri.TryCreate(thisLine, UriKind.Absolute, out Uri asUri)
&& (asUri.Scheme == Uri.UriSchemeHttp || asUri.Scheme == Uri.UriSchemeHttps))
{
Console.WriteLine($"uri: {asUri}. ship off to yt-dlp or project Ose?");
}
else
{
//just a directive, we can't do anything here
Console.WriteLine($"mere directive: {thisLine}");
}
}
else
{
thisLine = thisLine.TrimStart('[');
var directiveName = thisLine[..thisLine.IndexOf(']')];
var parameterText = thisLine[(thisLine.IndexOf(']') + 1) ..].Trim();
if(directiveName == "note")
{
noteLines.Enqueue(parameterText);
Console.WriteLine($"on screen note: {parameterText}");
}
else
{
altVOLines.Add(parameterText);
Console.WriteLine($"alt VA: {parameterText}");
}
}
}
if(altVOLines?.Count > 0)
{
var altVOscript = $"{scriptBasename}-altVO.txt";
File.WriteAllLines(altVOscript, altVOLines);
taskHeap.Add(ttsVO(altVOscript, Path.Combine(conf.VODropoff, $"altVO")));
}
// execute them all
taskHeap.Add(Task.Run(FFStuff));
taskHeap.Add(Task.Run(filePickup));
Console.WriteLine("sleeping for 1 sec before requesting cancellation");
System.Threading.Thread.Sleep(TimeSpan.FromSeconds(1));
cancelSource.Cancel();
Task.WaitAll([.. taskHeap]);
Console.WriteLine("kbai");
}
private static async Task ttsVO(string scriptPath, string destPath)
{
HttpContent fileStreamContent = new StreamContent(File.OpenRead(scriptPath));
using (var formData = new MultipartFormDataContent())
{
formData.Add(fileStreamContent, "file1", "file1");
var response = await hc.PostAsync("/speak", formData);
if (!response.IsSuccessStatusCode)
{
Console.Error.WriteLine($"{response.StatusCode} - {response.ReasonPhrase} - {await response.Content.ReadAsStringAsync()}");
}
else
{
var fname = (await response.Content.ReadAsStringAsync()).Trim('[').Trim(']').Trim('"');
Console.WriteLine($"success; {fname}."); //"success" might be a strong word; it's successfully submitted. Its decided what filename it will, eventually, give it.
var ext = Path.GetExtension(fname);
Directory.CreateDirectory(conf.VODropoff);
awaitFile(Path.Combine(conf.sync_dropoff, fname), destPath + $"{ext}");
}
}
}
}
}