211 lines
7.2 KiB
C#
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;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|