Add CBR, VBR, and CQP Bitrate Control Options and Dynamic UI Generation

This commit is contained in:
copecone committed 2026-06-01 22:13:26 +09:00
1 parent d7ca416980
commit c6356a28f0
7 files changed
+222 -33

No files matched your search

+159 -28
View File
@@ -15,16 +15,24 @@ namespace ADOFAIRenderer.UI
[SerializeField] private TMP_InputField videoFPSInput = null!;
[SerializeField] private TMP_InputField renderWidthInput = null!;
[SerializeField] private TMP_InputField renderHeightInput = null!;
[SerializeField] private TMP_InputField bitrateInput = null!;
[SerializeField] private TMP_InputField waitAfterEndInput = null!;
[SerializeField] private TMP_InputField randomSeedInput = null!;
[Header("Dropdown")]
[Header("Dropdowns")]
[SerializeField] private TMP_Dropdown codecDropdown = null!;
[Header("Toggle")]
[SerializeField] private TMP_Dropdown bitrateModeDropdown = null!;
[Header("Toggles")]
[SerializeField] private Toggle showHitTextToggle = null!;
[Header("Bitrate Dynamic UI Container")]
[SerializeField] private RectTransform bitrateContainer = null!;
// Dynamically instantiated inputs
private TMP_InputField _bitrateInput = null!;
private TMP_InputField _maxBitrateInput = null!;
private TMP_InputField _qpInput = null!;
private Button _buttonComponent = null!;
private void Start()
@@ -34,11 +42,93 @@ namespace ADOFAIRenderer.UI
RenderMod.FixEventSystem();
if (codecDropdown == null) return;
codecDropdown.ClearOptions();
if (codecDropdown != null)
{
codecDropdown.ClearOptions();
var options = System.Enum.GetNames(typeof(VideoFFmpeg.Codec)).ToList();
codecDropdown.AddOptions(options);
}
// 1. Initialize Bitrate Mode Dropdown
if (bitrateModeDropdown != null)
{
bitrateModeDropdown.ClearOptions();
var modeOptions = System.Enum.GetNames(typeof(RenderManager.BitrateControlMode)).ToList();
bitrateModeDropdown.AddOptions(modeOptions);
bitrateModeDropdown.onValueChanged.AddListener(delegate { UpdateUIByMode(); });
}
// 2. Dynamically instantiate input fields using targetFPSInput as a styling template
if (bitrateContainer != null && targetFPSInput != null)
{
// Instantiate bitrateInput
var bitrateInputObj = Instantiate(targetFPSInput.gameObject, bitrateContainer);
bitrateInputObj.name = "Dynamic_BitrateInput";
_bitrateInput = bitrateInputObj.GetComponent<TMP_InputField>();
_bitrateInput.text = "15000";
SetPlaceholderText(_bitrateInput, "비트레이트 (kbps)");
// Instantiate maxBitrateInput
var maxBitrateInputObj = Instantiate(targetFPSInput.gameObject, bitrateContainer);
maxBitrateInputObj.name = "Dynamic_MaxBitrateInput";
_maxBitrateInput = maxBitrateInputObj.GetComponent<TMP_InputField>();
_maxBitrateInput.text = "30000";
SetPlaceholderText(_maxBitrateInput, "최대 비트레이트 (kbps)");
// Instantiate qpInput
var qpInputObj = Instantiate(targetFPSInput.gameObject, bitrateContainer);
qpInputObj.name = "Dynamic_QPInput";
_qpInput = qpInputObj.GetComponent<TMP_InputField>();
_qpInput.text = "18";
SetPlaceholderText(_qpInput, "QP (0-51) / CRF");
}
// 3. Perform initial UI state updates based on default mode
UpdateUIByMode();
}
private void SetPlaceholderText(TMP_InputField inputField, string text)
{
if (inputField.placeholder == null) return;
var tmpText = inputField.placeholder.GetComponent<TMP_Text>();
var options = System.Enum.GetNames(typeof(VideoFFmpeg.Codec)).ToList();
codecDropdown.AddOptions(options);
if (tmpText)
{
tmpText.text = text;
}
}
private void UpdateUIByMode()
{
if (bitrateModeDropdown == null) return;
if (_bitrateInput == null || _maxBitrateInput == null || _qpInput == null) return;
var selectedMode = (RenderManager.BitrateControlMode)bitrateModeDropdown.value;
switch (selectedMode)
{
case RenderManager.BitrateControlMode.CBR:
_bitrateInput.gameObject.SetActive(true);
_maxBitrateInput.gameObject.SetActive(false);
_qpInput.gameObject.SetActive(false);
break;
case RenderManager.BitrateControlMode.VBR:
_bitrateInput.gameObject.SetActive(true);
_maxBitrateInput.gameObject.SetActive(true);
_qpInput.gameObject.SetActive(false);
break;
case RenderManager.BitrateControlMode.CQP:
_bitrateInput.gameObject.SetActive(false);
_maxBitrateInput.gameObject.SetActive(false);
_qpInput.gameObject.SetActive(true);
break;
}
if (bitrateContainer != null)
{
Canvas.ForceUpdateCanvases();
LayoutRebuilder.ForceRebuildLayoutImmediate(bitrateContainer);
}
}
private void Update()
@@ -74,11 +164,49 @@ namespace ADOFAIRenderer.UI
Melon<RenderMod>.Logger.Error($"올바르지 않은 Render Height 값입니다. 1에서 {SystemInfo.maxTextureSize} 사이의 정수여야 합니다.");
return;
}
if (!int.TryParse(bitrateInput.text, out var bitrate) || bitrate <= 0)
var bitrateMode = RenderManager.BitrateControlMode.CBR;
if (bitrateModeDropdown != null)
{
Melon<RenderMod>.Logger.Error("올바르지 않은 Bitrate 값입니다. 0보다 큰 정수여야 합니다.");
return;
bitrateMode = (RenderManager.BitrateControlMode)bitrateModeDropdown.value;
}
var bitrate = 0;
var maxBitrate = 0;
var qp = 18;
if (bitrateMode == RenderManager.BitrateControlMode.CBR || bitrateMode == RenderManager.BitrateControlMode.VBR)
{
if (!int.TryParse(_bitrateInput.text, out bitrate) || bitrate <= 0)
{
Melon<RenderMod>.Logger.Error("올바르지 않은 Bitrate 값입니다. 0보다 큰 정수여야 합니다.");
return;
}
}
if (bitrateMode == RenderManager.BitrateControlMode.VBR)
{
if (!int.TryParse(_maxBitrateInput.text, out maxBitrate) || maxBitrate <= 0)
{
Melon<RenderMod>.Logger.Error("올바르지 않은 Max Bitrate 값입니다. 0보다 큰 정수여야 합니다.");
return;
}
if (maxBitrate < bitrate)
{
Melon<RenderMod>.Logger.Error("Max Bitrate는 Target Bitrate보다 크거나 같아야 합니다.");
return;
}
}
if (bitrateMode == RenderManager.BitrateControlMode.CQP)
{
if (!int.TryParse(_qpInput.text, out qp) || qp < 0 || qp > 51)
{
Melon<RenderMod>.Logger.Error("올바르지 않은 QP 값입니다. 0에서 51 사이의 정수여야 합니다.");
return;
}
}
if (!double.TryParse(waitAfterEndInput.text, out var waitAfterEnd) || waitAfterEnd < 0)
{
Melon<RenderMod>.Logger.Error("올바르지 않은 Wait After End 값입니다. 0 이상의 숫자여야 합니다.");
@@ -109,18 +237,20 @@ namespace ADOFAIRenderer.UI
if (RenderManager.Instance)
{
RenderManager.Instance.Configuration = new RenderManager.RenderSettings
{
TargetFPS = targetFPS,
VideoFPS = videoFPS,
RenderWidth = renderWidth,
RenderHeight = renderHeight,
Bitrate = bitrate,
Codec = selectedCodecName,
EndWaitTime = waitAfterEnd,
ShowHitText = showHitText,
RandomSeed = parsedRandomSeed
};
RenderManager.Instance.Configuration = new RenderManager.RenderSettings(
targetFPS,
videoFPS,
renderWidth,
renderHeight,
bitrateMode,
bitrate,
maxBitrate,
qp,
selectedCodecName,
waitAfterEnd,
showHitText,
parsedRandomSeed
);
}
RenderManager.Instance!.SetupCaptureDirectory();
@@ -133,12 +263,13 @@ namespace ADOFAIRenderer.UI
if (string.IsNullOrWhiteSpace(videoFPSInput.text)) return true;
if (string.IsNullOrWhiteSpace(renderWidthInput.text)) return true;
if (string.IsNullOrWhiteSpace(renderHeightInput.text)) return true;
if (string.IsNullOrWhiteSpace(bitrateInput.text)) return true;
if (string.IsNullOrWhiteSpace(waitAfterEndInput.text)) return true;
// 드롭다운은 기본적으로 항상 무언가 선택되어 있지만(0번 항목 등),
// 만약 '선택 안함(-1)' 같은 예외 처리가 필요하다면 아래처럼 검사할 수 있습니다.
return codecDropdown.value < 0;
if (_bitrateInput != null && _bitrateInput.gameObject.activeSelf && string.IsNullOrWhiteSpace(_bitrateInput.text)) return true;
if (_maxBitrateInput != null && _maxBitrateInput.gameObject.activeSelf && string.IsNullOrWhiteSpace(_maxBitrateInput.text)) return true;
if (_qpInput != null && _qpInput.gameObject.activeSelf && string.IsNullOrWhiteSpace(_qpInput.text)) return true;
return codecDropdown.value < 0 || (bitrateModeDropdown != null && bitrateModeDropdown.value < 0);
}
}
}