#nullable enable using Newtonsoft.Json; using Newtonsoft.Json.Converters; using Newtonsoft.Json.Linq; using T3.Core.Video; using T3.Editor.Gui.UiHelpers; using T3.Editor.UiModel; using T3.Editor.UiModel.ProjectHandling; namespace T3.Editor.Gui.Windows.RenderExport; internal sealed class RenderSettings { public static readonly RenderSettings Defaults = new(); /// /// Returns the render settings for the focused composition, initializing from defaults /// (with legacy path migration) if none exist yet. /// public static RenderSettings Current { get { var symbolUi = ProjectView.Focused?.CompositionInstance?.Symbol.GetSymbolUi(); if (symbolUi == null) return Defaults; if (symbolUi.RenderSettings != null) return symbolUi.RenderSettings; // First access for this composition — initialize with defaults + legacy paths var settings = new RenderSettings(); #pragma warning disable CS0612, CS0618 // Obsolete var legacy = UserSettings.Config; if (!string.IsNullOrEmpty(legacy.RenderVideoFilePath)) settings.VideoFilePath = legacy.RenderVideoFilePath; if (!string.IsNullOrEmpty(legacy.RenderSequenceFilePath)) settings.SequenceFilePath = legacy.RenderSequenceFilePath; if (!string.IsNullOrEmpty(legacy.RenderSequenceFileName)) settings.SequenceFileName = legacy.RenderSequenceFileName; if (!string.IsNullOrEmpty(legacy.RenderSequencePrefix)) settings.SequencePrefix = legacy.RenderSequencePrefix; #pragma warning restore CS0612, CS0618 // Don't flag as modified here: this getter runs from mere UI reads (e.g. the output window's // render tooltip), which must not dirty the composition. Actual changes to the settings are // flagged by the render window and render process. symbolUi.RenderSettings = settings; return settings; } } [JsonConverter(typeof(StringEnumConverter))] public TimeReferences TimeReference; public float StartInBars; public float EndInBars = 8f; public float FrameRate = 60f; public int OverrideMotionBlurSamples = -1; [JsonConverter(typeof(StringEnumConverter))] public RenderModes RenderMode = RenderModes.Video; [JsonConverter(typeof(StringEnumConverter))] public VideoExportCodec VideoCodec = VideoExportCodec.H264; public int Bitrate = 25_000_000; public bool AutoIncrementVersionNumber = true; public bool CreateSubFolder = true; public bool AutoIncrementSubFolder = true; public bool ExportAudio = true; [JsonConverter(typeof(StringEnumConverter))] public ScreenshotWriter.FileFormats FileFormat; [JsonConverter(typeof(StringEnumConverter))] public TimeRanges TimeRange = TimeRanges.Custom; // Continuous-capture options — only meaningful when TimeRange == Continuous. [JsonConverter(typeof(StringEnumConverter))] public ContinuousCaptureClock ContinuousClock = ContinuousCaptureClock.Realtime; [JsonConverter(typeof(StringEnumConverter))] public ContinuousFrameRateMode ContinuousFrameRate = ContinuousFrameRateMode.FixedFps; public float ResolutionFactor = 1f; // Render paths — persisted per-project in .t3ui public string VideoFilePath = "./Render/render-v01.mp4"; public string SequenceFilePath = "./ImageSequence/"; public string SequenceFileName = "v01"; public string SequencePrefix = "render"; public void CopyFrom(RenderSettings other) { TimeReference = other.TimeReference; StartInBars = other.StartInBars; EndInBars = other.EndInBars; FrameRate = other.FrameRate; OverrideMotionBlurSamples = other.OverrideMotionBlurSamples; RenderMode = other.RenderMode; VideoCodec = other.VideoCodec; Bitrate = other.Bitrate; AutoIncrementVersionNumber = other.AutoIncrementVersionNumber; CreateSubFolder = other.CreateSubFolder; AutoIncrementSubFolder = other.AutoIncrementSubFolder; ExportAudio = other.ExportAudio; FileFormat = other.FileFormat; TimeRange = other.TimeRange; ContinuousClock = other.ContinuousClock; ContinuousFrameRate = other.ContinuousFrameRate; ResolutionFactor = other.ResolutionFactor; VideoFilePath = other.VideoFilePath; SequenceFilePath = other.SequenceFilePath; SequenceFileName = other.SequenceFileName; SequencePrefix = other.SequencePrefix; } public RenderSettings Clone() { var clone = new RenderSettings(); clone.CopyFrom(this); return clone; } #region Serialization internal void WriteToJson(JsonTextWriter writer) { writer.WritePropertyName("RenderExport"); writer.WriteRawValue(JsonConvert.SerializeObject(this, Formatting.Indented)); } internal static RenderSettings? ReadFromJson(JToken parentToken) { var token = parentToken["RenderExport"]; if (token == null) return null; return token.ToObject(); } #endregion internal enum RenderModes { Video, ImageSequence } internal enum TimeReferences { Bars, Seconds, Frames } internal enum TimeRanges { Custom, Loop, Soundtrack, /// Open-ended recording: capture starts immediately and runs until the user stops it, with no /// predetermined end. See for how playback time is driven. Continuous, } /// What drives playback time during a capture. internal enum ContinuousCaptureClock { /// Leave playback under live/user control and grab whatever the output shows each frame /// (OBS/VJ-style). Best for live performance and audio-reactive/interactive content. Realtime, /// Step playback forward at the target FPS like a normal render, but without a fixed end — /// frame-perfect even when encoding is slower than realtime. Deterministic, } /// The frame-rate written for a capture. internal enum ContinuousFrameRateMode { /// Write at the settings' FPS; in realtime grab, pace via a wall-clock accumulator. FixedFps, /// Variable frame rate timestamped by wall-clock. Reserved — not yet implemented. Variable, } internal readonly struct QualityLevel { internal QualityLevel(double bits, string title, string description) { MinBitsPerPixelSecond = bits; Title = title; Description = description; } internal readonly double MinBitsPerPixelSecond; internal readonly string Title; internal readonly string Description; } }