컷별 데이터 연계

This commit is contained in:
2026-04-14 17:14:12 +09:00
parent 54c778c75d
commit e0c5f4dbfe
19 changed files with 7826 additions and 33 deletions

View File

@@ -0,0 +1,229 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
namespace Tornado3_2026Election.Services;
public sealed class KarismaSceneVariableCatalog
{
private static readonly IReadOnlyDictionary<string, KarismaSceneVariableDefinition> EmptySceneVariables =
new Dictionary<string, KarismaSceneVariableDefinition>(StringComparer.OrdinalIgnoreCase);
private readonly IReadOnlyDictionary<string, IReadOnlyDictionary<string, KarismaSceneVariableDefinition>> _scenes;
private KarismaSceneVariableCatalog(
IReadOnlyDictionary<string, IReadOnlyDictionary<string, KarismaSceneVariableDefinition>> scenes)
{
_scenes = scenes;
}
public static KarismaSceneVariableCatalog Load(LogService logService)
{
var reportPath = FindDiscoveryReportPath();
if (string.IsNullOrWhiteSpace(reportPath) || !File.Exists(reportPath))
{
logService.Warning("Karisma scene variable catalog report was not found. Falling back to runtime value heuristics.");
return new KarismaSceneVariableCatalog(
new Dictionary<string, IReadOnlyDictionary<string, KarismaSceneVariableDefinition>>(StringComparer.OrdinalIgnoreCase));
}
try
{
var scenes = ParseReport(reportPath);
logService.Info($"Karisma scene variable catalog loaded: scenes={scenes.Count} source='{reportPath}'.");
return new KarismaSceneVariableCatalog(scenes);
}
catch (Exception ex)
{
logService.Warning($"Failed to load Karisma scene variable catalog: {ex.Message}");
return new KarismaSceneVariableCatalog(
new Dictionary<string, IReadOnlyDictionary<string, KarismaSceneVariableDefinition>>(StringComparer.OrdinalIgnoreCase));
}
}
public IReadOnlyDictionary<string, KarismaSceneVariableDefinition> GetSceneVariables(string t3CutPath, string scenePath)
{
if (_scenes.Count == 0 ||
string.IsNullOrWhiteSpace(t3CutPath) ||
string.IsNullOrWhiteSpace(scenePath))
{
return EmptySceneVariables;
}
var relativePath = NormalizeRelativePath(Path.GetRelativePath(t3CutPath, scenePath));
return _scenes.TryGetValue(relativePath, out var variables)
? variables
: EmptySceneVariables;
}
private static IReadOnlyDictionary<string, IReadOnlyDictionary<string, KarismaSceneVariableDefinition>> ParseReport(string reportPath)
{
var scenes = new Dictionary<string, Dictionary<string, KarismaSceneVariableDefinition>>(StringComparer.OrdinalIgnoreCase);
string? currentScene = null;
foreach (var rawLine in File.ReadLines(reportPath, Encoding.UTF8))
{
var line = rawLine.Trim();
if (TryParseSceneHeader(line, out var sceneRelativePath))
{
currentScene = NormalizeRelativePath(sceneRelativePath);
if (!scenes.ContainsKey(currentScene))
{
scenes[currentScene] = new Dictionary<string, KarismaSceneVariableDefinition>(StringComparer.OrdinalIgnoreCase);
}
continue;
}
if (string.IsNullOrWhiteSpace(currentScene) || !line.StartsWith('|'))
{
continue;
}
var cells = SplitMarkdownRow(line);
if (cells.Count < 4 ||
string.Equals(cells[0], "Variable", StringComparison.OrdinalIgnoreCase) ||
string.Equals(cells[0], "---", StringComparison.OrdinalIgnoreCase))
{
continue;
}
var variableName = cells[0];
if (string.IsNullOrWhiteSpace(variableName))
{
continue;
}
var method = cells[1];
var payload = cells[2];
var result = cells[3];
if (!string.Equals(result, "RESULT_SUCCESS", StringComparison.OrdinalIgnoreCase))
{
continue;
}
scenes[currentScene][variableName] = new KarismaSceneVariableDefinition(
variableName,
ResolveKind(variableName, method, payload),
method,
payload);
}
return scenes.ToDictionary(
pair => pair.Key,
pair => (IReadOnlyDictionary<string, KarismaSceneVariableDefinition>)pair.Value,
StringComparer.OrdinalIgnoreCase);
}
private static bool TryParseSceneHeader(string line, out string sceneRelativePath)
{
sceneRelativePath = string.Empty;
if (!line.StartsWith("### `", StringComparison.Ordinal) || !line.EndsWith('`'))
{
return false;
}
sceneRelativePath = line.Substring(5, line.Length - 6);
return !string.IsNullOrWhiteSpace(sceneRelativePath);
}
private static List<string> SplitMarkdownRow(string line)
{
var cells = line.Split('|');
if (cells.Length <= 2)
{
return [];
}
return cells
.Skip(1)
.Take(cells.Length - 2)
.Select(cell => cell.Trim())
.ToList();
}
private static KarismaSceneVariableKind ResolveKind(string variableName, string method, string payload)
{
if (string.Equals(method, "SetCounterNumberKey", StringComparison.OrdinalIgnoreCase))
{
return KarismaSceneVariableKind.Counter;
}
if (variableName.StartsWith("\uC720\uD655\uB2F9", StringComparison.OrdinalIgnoreCase))
{
return KarismaSceneVariableKind.VideoResource;
}
if (payload.EndsWith(".vrv", StringComparison.OrdinalIgnoreCase))
{
return KarismaSceneVariableKind.VideoResource;
}
if (payload.EndsWith(".png", StringComparison.OrdinalIgnoreCase) ||
payload.EndsWith(".jpg", StringComparison.OrdinalIgnoreCase) ||
payload.EndsWith(".jpeg", StringComparison.OrdinalIgnoreCase) ||
payload.EndsWith(".webp", StringComparison.OrdinalIgnoreCase))
{
return KarismaSceneVariableKind.Image;
}
return KarismaSceneVariableKind.Text;
}
private static string? FindDiscoveryReportPath()
{
foreach (var startPath in EnumerateSearchRoots())
{
var current = startPath;
for (var depth = 0; depth < 8 && !string.IsNullOrWhiteSpace(current); depth++)
{
var candidate = Path.Combine(current, "TSCN_VARIABLE_DISCOVERY_E_DRIVE.md");
if (File.Exists(candidate))
{
return candidate;
}
current = Path.GetDirectoryName(current);
}
}
return null;
}
private static IEnumerable<string> EnumerateSearchRoots()
{
var roots = new List<string> { AppContext.BaseDirectory };
try
{
roots.Add(Directory.GetCurrentDirectory());
}
catch
{
}
return roots;
}
private static string NormalizeRelativePath(string relativePath)
{
return relativePath
.Replace('/', '\\')
.Trim();
}
}
public sealed record KarismaSceneVariableDefinition(
string Name,
KarismaSceneVariableKind Kind,
string Method,
string Payload);
public enum KarismaSceneVariableKind
{
Text,
Image,
VideoResource,
Counter
}