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; }
}
}
}