streamingle-unity-utilities/CameraAI~/Editor/AICameraABRenderWindow.cs
2026-08-03 04:42:44 +09:00

211 lines
7.2 KiB
C#

using System;
using System.IO;
using UnityEditor;
using UnityEngine;
namespace Streamingle.Editor
{
public sealed class AICameraABRenderWindow : EditorWindow
{
private string candidateADirectory = string.Empty;
private string candidateBDirectory = string.Empty;
private string outputRoot = string.Empty;
private int startFrame;
private int endFrameExclusive = 1;
private AICameraABRenderCodec codec =
AICameraABRenderCodec.MP4;
private int width = 1920;
private int height = 1080;
private Vector2 scrollPosition;
[MenuItem("Tools/Streamingle/Timeline/Render AI Camera A-B")]
public static void Open()
{
GetWindow<AICameraABRenderWindow>(
"AI Camera A-B Render");
}
private void OnEnable()
{
if (string.IsNullOrWhiteSpace(outputRoot))
{
outputRoot = Path.GetFullPath(Path.Combine(
Application.dataPath,
"..",
"Recordings",
"CWAI_AB"));
}
}
private void OnInspectorUpdate()
{
Repaint();
}
private void OnGUI()
{
scrollPosition = EditorGUILayout.BeginScrollView(scrollPosition);
EditorGUILayout.LabelField(
"AI Camera A-B Movie Render",
EditorStyles.boldLabel);
EditorGUILayout.LabelField(
"Renders two generated camera candidates against the same " +
"saved Scene, Timeline frame range, and Timeline audio.",
EditorStyles.wordWrappedLabel);
EditorGUILayout.Space();
DrawFolderField(
"Candidate A",
ref candidateADirectory,
"Select generated camera candidate A");
DrawFolderField(
"Candidate B",
ref candidateBDirectory,
"Select generated camera candidate B");
DrawFolderField(
"Output Root",
ref outputRoot,
"Select A-B movie output directory");
EditorGUILayout.Space();
EditorGUILayout.LabelField("Frame Range", EditorStyles.boldLabel);
startFrame = EditorGUILayout.IntField(
"Start Frame",
startFrame);
endFrameExclusive = EditorGUILayout.IntField(
"End Frame (Exclusive)",
endFrameExclusive);
EditorGUILayout.Space();
EditorGUILayout.LabelField("Movie", EditorStyles.boldLabel);
codec = (AICameraABRenderCodec)EditorGUILayout.EnumPopup(
"Codec",
codec);
width = EditorGUILayout.IntField("Width", width);
height = EditorGUILayout.IntField("Height", height);
EditorGUILayout.HelpBox(
"MP4 uses H.264 and requires even dimensions. WebM uses VP8. " +
"Both candidates use MainCamera and include Timeline audio.",
MessageType.Info);
EditorGUILayout.Space();
DrawActiveJob();
EditorGUILayout.Space();
var hasActive = AICameraABRenderRunner.TryGetActiveStatus(
out _,
out var phase,
out _);
var nonTerminalJob =
hasActive &&
!AICameraABRenderUtility.IsTerminalPhase(phase);
using (new EditorGUI.DisabledScope(nonTerminalJob))
{
if (GUILayout.Button("Start A-B Render", GUILayout.Height(30)))
{
StartRender();
}
}
EditorGUILayout.EndScrollView();
}
private void DrawActiveJob()
{
if (!AICameraABRenderRunner.TryGetActiveStatus(
out var jobId,
out var phase,
out var manifestPath))
{
EditorGUILayout.LabelField("No active A-B render job.");
return;
}
EditorGUILayout.LabelField("Current Job", EditorStyles.boldLabel);
EditorGUILayout.SelectableLabel(
jobId,
EditorStyles.textField,
GUILayout.Height(EditorGUIUtility.singleLineHeight));
EditorGUILayout.LabelField("Phase", phase);
EditorGUILayout.LabelField(
"Manifest",
manifestPath,
EditorStyles.wordWrappedLabel);
using (new EditorGUILayout.HorizontalScope())
{
if (GUILayout.Button("Reveal Manifest") &&
!string.IsNullOrWhiteSpace(manifestPath))
{
EditorUtility.RevealInFinder(manifestPath);
}
using (new EditorGUI.DisabledScope(
AICameraABRenderUtility.IsTerminalPhase(phase)))
{
if (GUILayout.Button("Cancel"))
{
AICameraABRenderRunner.CancelABRenderForCli(jobId);
}
}
}
}
private void StartRender()
{
try
{
var request = new AICameraABRenderRequest
{
candidateADirectory = candidateADirectory,
candidateBDirectory = candidateBDirectory,
outputRoot = outputRoot,
startFrame = startFrame,
endFrameExclusive = endFrameExclusive,
codec = codec,
width = width,
height = height
};
var started = AICameraABRenderRunner.Start(request);
Debug.Log(
$"CW-AI A/B render started. Manifest: " +
$"{started.manifestPath}");
}
catch (Exception exception)
{
Debug.LogException(exception);
EditorUtility.DisplayDialog(
"Unable to start A-B render",
exception.Message,
"OK");
}
}
private static void DrawFolderField(
string label,
ref string value,
string dialogTitle)
{
using (new EditorGUILayout.HorizontalScope())
{
value = EditorGUILayout.TextField(label, value);
if (GUILayout.Button("Browse", GUILayout.Width(70)))
{
var initialDirectory = Directory.Exists(value)
? value
: Directory.GetParent(Application.dataPath)?.FullName ??
string.Empty;
var selected = EditorUtility.OpenFolderPanel(
dialogTitle,
initialDirectory,
string.Empty);
if (!string.IsNullOrWhiteSpace(selected))
{
value = selected;
}
}
}
}
}
}