using System; using System.Collections.Generic; using System.Linq; using UnityEngine; namespace UnityEditor.Recorder { /// /// Sets which source camera to use for recording (by some specific Recorders). /// [Flags] public enum ImageSource { /// /// Use the current active camera. /// ActiveCamera = 1, /// /// Use the main camera. /// MainCamera = 2, /// /// Use the first camera that matches a GameObject tag. /// TaggedCamera = 4 } /// /// Sets which frame rate type to use during recording. /// public enum FrameRatePlayback { /// /// The frame rate doesn't vary during recording, even if the actual frame rate is lower or higher. /// Constant, /// /// Use the application's frame rate, which might vary during recording. This option is not supported by all Recorders. /// Variable, } /// /// The mode that defines the way to manage the starting point and duration of the recording. /// public enum RecordMode { /// /// Record every frame between when the recording is started and when it is stopped (either using the UI or through API methods). /// Manual, /// /// Record one single frame according to the specified frame number. /// SingleFrame, /// /// Record all frames within an interval of frames according to the specified Start and End frame numbers. /// FrameInterval, /// /// Record all frames within a time interval according to the specified Start time and End time. /// TimeInterval } /// /// Main base class for a Recorder settings. /// Each Recorder needs to have its corresponding settings properly configured. /// public abstract class RecorderSettings : ScriptableObject, ISerializationCallbackReceiver { private static string s_OutputFileErrorMessage = "Recorder output file cannot be empty"; // Use the Windows value of 259 (260-1 for the NUL terminator) // (https://learn.microsoft.com/en-us/windows/win32/fileio/maximum-file-path-limitation) private int m_MaxPathLength = 259; /// /// Stores the path this Recorder will use to generate the output file. /// It can be either an absolute or a relative path. /// The file extension is automatically added. /// Wildcards such as DefaultWildcard.Time are supported. /// /// public string OutputFile { get { return fileNameGenerator.ToPath(); } set { if (string.IsNullOrEmpty(value)) throw new ArgumentException(s_OutputFileErrorMessage); fileNameGenerator.FromPath(value); } } /// /// Indicates if this Recorder is active when starting the recording. If false, the Recorder is ignored and generates no output. /// public bool Enabled { get { return enabled; } set { enabled = value; } } [SerializeField] private bool enabled = true; /// /// Stores the current Take number. Automatically incremented after each recording session. /// public int Take { get { return take; } set { if (value < 0) throw new ArgumentOutOfRangeException($"The take number must be positive"); take = value; } } [SerializeField] internal int take = 1; /// /// Stores the file extension used by this Recorder (without the dot). /// protected internal abstract string Extension { get; } [SerializeField] internal int captureEveryNthFrame = 1; [SerializeField] internal FileNameGenerator fileNameGenerator; internal bool IsOutputNameDuplicate { get; set; } /// /// The object that resolves wildcards into a final path for output files. /// public FileNameGenerator FileNameGenerator => fileNameGenerator; /// /// The mode that defines the way to manage the starting point and duration of the recording: either manually /// or within a specific time or frame interval. /// public RecordMode RecordMode { get; set; } /// /// The type of frame rate to use in the recording: constant or variable. /// public FrameRatePlayback FrameRatePlayback { get; set; } float frameRate = 30.0f; /// /// The number of recorded frames per second. In constant frame rate mode, this represent a target value, while /// in variable frame rate mode, this represents a maximum value. /// /// public float FrameRate { get => frameRate; set => frameRate = value; } /// /// The start frame of the recording. /// public int StartFrame { get; set; } /// /// The end frame of the recording. /// public int EndFrame { get; set; } /// /// The start time of the recording. /// public float StartTime { get; set; } /// /// The end time of the recording. /// public float EndTime { get; set; } /// /// Specifies whether or not to limit the frame rate when it is above the target frame rate. /// public bool CapFrameRate { get; set; } /// /// The constructor of the class. /// protected RecorderSettings() { fileNameGenerator = new FileNameGenerator() { Root = OutputPath.Root.Project, Leaf = "Recordings" }; fileNameGenerator.RecorderSettings = this; } /// /// Tests if the Recorder is correctly configured. /// /// List of errors encountered. /// True if there are no errors, False otherwise. [Obsolete("Please use methods GetErrors() and GetWarnings()")] protected internal virtual bool ValidityCheck(List errors) { var ok = true; if (InputsSettings != null) { var inputErrors = new List(); #pragma warning disable 618 var valid = InputsSettings.All(x => x.ValidityCheck(inputErrors)); #pragma warning restore 618 if (!valid) { errors.AddRange(inputErrors); ok = false; } } return ok; } /// /// Tests if the Recorder has any errors. /// /// List of errors encountered. protected internal virtual void GetErrors(List errors) { if (InputsSettings != null) { foreach (var i in InputsSettings) { var inputErrors = new List(); i.CheckForErrors(inputErrors); errors.AddRange(inputErrors); } } if (string.IsNullOrEmpty(fileNameGenerator.FileName)) { errors.Add("Missing file name"); } if (IsOutputNameDuplicate) { errors.Add("Output file name is not unique"); } if (Math.Abs(FrameRate) <= float.Epsilon) { errors.Add("Invalid frame rate"); } if (!IsPlatformSupported) { errors.Add("Current platform is not supported"); } if (this is IResolutionUser { IsOutputResolutionContradictory : true }) { errors.Add("Conflicting resolution detected. All active Recorders must have the same output resolution."); } if (fileNameGenerator.BuildAbsolutePath(null).Length > m_MaxPathLength) { errors.Add($"File path length exceeds {m_MaxPathLength} characters."); } } /// /// Tests if the Recorder has any warnings. /// /// List of warnings encountered. protected internal virtual void GetWarnings(List warnings) { if (InputsSettings != null) { foreach (var i in InputsSettings) { var inputWarnings = new List(); i.CheckForWarnings(inputWarnings); warnings.AddRange(inputWarnings); } } } /// /// Indicates if the current platform is supported (True) or not (False). /// public virtual bool IsPlatformSupported { get { return true; } } /// /// Indicates whether the input must be flipped vertically (True) or not (False) for the output format. /// /// /// Some output formats might expect the image to be vertically flipped while others expect it as it comes from the engine, /// due to different coordinate conventions. /// /// internal virtual bool NeedToFlipVerticallyForOutputFormat => false; /// /// Stores the list of Input settings required by this Recorder. /// public abstract IEnumerable InputsSettings { get; } /// /// Override this method if any post treatment needs to be done after this Recorder is duplicated in the Recorder Window. /// public virtual void OnAfterDuplicate() { } internal virtual bool IsInvalid() { return false; } protected internal virtual bool HasErrors() { var errors = new List(); GetErrors(errors); return errors.Count > 0; } internal virtual bool HasWarnings() { var warnings = new List(); var oldErrors = new List(); #pragma warning disable 618 // In the old API, errors were meant to be handled as non-blocking warnings ValidityCheck(oldErrors); #pragma warning restore 618 GetWarnings(warnings); return oldErrors.Count > 0 || warnings.Count > 0; } /// /// Validation of serialized value. /// internal virtual void OnValidate() { captureEveryNthFrame = Mathf.Max(1, captureEveryNthFrame); take = Mathf.Max(0, take); OnValidateUpgrade(); } /// /// Indicates whether the current Recorder supports Accumulation recording or not. /// /// True if the current Recorder supports Accumulation recording, False otherwise. public virtual bool IsAccumulationSupported() { return false; } /// /// Indicates the latest version of the recorder. /// This is used during the asset upgrade process to determine if the asset needs to be upgraded. /// Derived classes should override this property to provide their own latest version. /// protected virtual int LatestVersion { get => 0; } /// /// Indicates the current version of this recorder. /// This is used during the asset upgrade process to determine if the asset needs to be upgraded. /// Derived classes should override this property to provide their own version. /// protected virtual int Version { get; set; } = 0; /// /// Unity calls this method before serializing the object. /// void ISerializationCallbackReceiver.OnBeforeSerialize() { OnBeforeSerialize(); } /// /// Unity calls this method after de-serializing the object. /// void ISerializationCallbackReceiver.OnAfterDeserialize() { if (Version < LatestVersion) { OnUpgradeFromVersion(); //upgrade derived classes } OnAfterDeserialize(); } void OnEnable() { OnValidateUpgrade(); } void OnValidateUpgrade() { Version = LatestVersion; } /// /// Called before a RecorderSetting is serialized. /// protected virtual void OnBeforeSerialize() {}// Do not clean up since users can only override this /// /// Called after a RecorderSetting has been deserialized. /// protected virtual void OnAfterDeserialize() {} // Do not clean up since users can only override this /// /// Defines how to handle the upgrade of Recorder Settings created in a previous version according to their type. /// Unity automatically calls this method when loading a Recorder Setting (after deserialization) if its version is older than the current project version. /// protected virtual void OnUpgradeFromVersion() {} /// /// An interface used for classes that include Resolution information /// internal interface IResolutionUser { internal bool IsOutputResolutionContradictory { get; set; } internal int OutputWidth { get; } internal int OutputHeight { get; } internal Type ImageInputType { get; } } } }