FileNameGenerator.cs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Globalization;
  4. using System.IO;
  5. using System.Linq;
  6. using System.Text.RegularExpressions;
  7. using UnityEditor.Recorder.Input;
  8. using UnityEngine;
  9. using UnityEngine.SceneManagement;
  10. namespace UnityEditor.Recorder
  11. {
  12. internal class Wildcard
  13. {
  14. readonly string m_Pattern;
  15. readonly string m_Label;
  16. readonly Func<RecordingSession, string> m_Resolver;
  17. public string pattern { get { return m_Pattern; } }
  18. public string label { get { return m_Label; } }
  19. internal Wildcard(string pattern, Func<RecordingSession, string> resolver, string info = null)
  20. {
  21. m_Pattern = pattern;
  22. m_Label = m_Pattern;
  23. if (info != null)
  24. m_Label += " " + info;
  25. m_Resolver = resolver;
  26. }
  27. internal string Resolve(RecordingSession session)
  28. {
  29. return m_Resolver == null ? string.Empty : m_Resolver(session);
  30. }
  31. }
  32. /// <summary>
  33. /// Helper class for default Wildcards that you can use when constructing the output file name of a Recorder
  34. /// (see <see cref="RecorderSettings.OutputFile"/>).
  35. /// </summary>
  36. public static class DefaultWildcard
  37. {
  38. /// <summary>
  39. /// The Recorder name.
  40. /// </summary>
  41. public static readonly string Recorder = GeneratePattern("Recorder");
  42. /// <summary>
  43. /// The time the recording session started (in the 00h00m format).
  44. /// </summary>
  45. public static readonly string Time = GeneratePattern("Time");
  46. /// <summary>
  47. /// The take number (which is incremented every time a new session is started).
  48. /// </summary>
  49. public static readonly string Take = GeneratePattern("Take");
  50. /// <summary>
  51. /// The date when the recording session started (in the yyyy-MM-dd format).
  52. /// </summary>
  53. public static readonly string Date = GeneratePattern("Date");
  54. /// <summary>
  55. /// The name of the current Unity Project.
  56. /// </summary>
  57. public static readonly string Project = GeneratePattern("Project");
  58. /// <summary>
  59. /// The product name from the build settings (a combination of the Unity Project name and the output file extension).
  60. /// </summary>
  61. public static readonly string Product = GeneratePattern("Product");
  62. /// <summary>
  63. /// The name of the current Unity Scene.
  64. /// </summary>
  65. public static readonly string Scene = GeneratePattern("Scene");
  66. /// <summary>
  67. /// The output resolution in pixels.
  68. /// </summary>
  69. public static readonly string Resolution = GeneratePattern("Resolution");
  70. /// <summary>
  71. /// The current frame ID (a four-digit zero-padded number).
  72. /// </summary>
  73. public static readonly string Frame = GeneratePattern("Frame");
  74. /// <summary>
  75. /// The file extension of the output format.
  76. /// </summary>
  77. public static readonly string Extension = GeneratePattern("Extension");
  78. public static string GeneratePattern(string tag)
  79. {
  80. return "<" + tag + ">";
  81. }
  82. }
  83. [Serializable]
  84. public class FileNameGenerator
  85. {
  86. static string s_ProjectName;
  87. [SerializeField] OutputPath m_Path = new OutputPath();
  88. [SerializeField] string m_FileName = DefaultWildcard.Recorder;
  89. readonly List<Wildcard> m_Wildcards;
  90. internal IEnumerable<Wildcard> wildcards
  91. {
  92. get { return m_Wildcards; }
  93. }
  94. internal void FromPath(string str)
  95. {
  96. str = SanitizePath(str);
  97. var i = str.LastIndexOf('/');
  98. if (i != -1 && i < str.Length - 1)
  99. {
  100. m_FileName = str.Substring(i + 1);
  101. if (i == 0)
  102. {
  103. m_Path.root = OutputPath.Root.Absolute;
  104. m_Path.leaf = "/";
  105. }
  106. else
  107. {
  108. str = str.Substring(0, i);
  109. m_Path = OutputPath.FromPath(str);
  110. }
  111. }
  112. else
  113. {
  114. m_FileName = str;
  115. m_Path.root = OutputPath.Root.Absolute;
  116. m_Path.leaf = string.Empty;
  117. }
  118. }
  119. internal string ToPath()
  120. {
  121. var path = m_Path.GetFullPath();
  122. if (!string.IsNullOrEmpty(path))
  123. path += "/";
  124. return SanitizePath(path + SanitizeFilename(m_FileName));
  125. }
  126. /// <summary>
  127. /// Stores the default set of tags that make up the output file name.
  128. /// </summary>
  129. public string FileName {
  130. get { return m_FileName; }
  131. set { m_FileName = value; }
  132. }
  133. /// <summary>
  134. /// Indicates the root location the paths are relative to.
  135. /// </summary>
  136. public OutputPath.Root Root
  137. {
  138. get { return m_Path.root; }
  139. set { m_Path.root = value; }
  140. }
  141. /// <summary>
  142. /// Indicates the filename part of the full path (without the extension).
  143. /// </summary>
  144. public string Leaf
  145. {
  146. get { return m_Path.leaf; }
  147. set { m_Path.leaf = value; }
  148. }
  149. /// <summary>
  150. /// Use this property to ensure that the generated file is saved in the Assets folder.
  151. /// </summary>
  152. public bool ForceAssetsFolder
  153. {
  154. get { return m_Path.forceAssetsFolder; }
  155. set { m_Path.forceAssetsFolder = value; }
  156. }
  157. readonly RecorderSettings m_RecorderSettings;
  158. internal FileNameGenerator(RecorderSettings recorderSettings)
  159. {
  160. m_RecorderSettings = recorderSettings;
  161. m_Wildcards = new List<Wildcard>
  162. {
  163. new Wildcard(DefaultWildcard.Recorder, RecorderResolver),
  164. new Wildcard(DefaultWildcard.Time, TimeResolver),
  165. new Wildcard(DefaultWildcard.Take, TakeResolver),
  166. new Wildcard(DefaultWildcard.Date, DateResolver),
  167. new Wildcard(DefaultWildcard.Project, ProjectNameResolver),
  168. new Wildcard(DefaultWildcard.Product, ProductNameResolver),
  169. new Wildcard(DefaultWildcard.Scene, SceneResolver),
  170. new Wildcard(DefaultWildcard.Resolution, ResolutionResolver),
  171. new Wildcard(DefaultWildcard.Frame, FrameResolver),
  172. new Wildcard(DefaultWildcard.Extension, ExtensionResolver)
  173. };
  174. }
  175. /// <summary>
  176. /// Adds a tag and the corresponding callback to resolve it.
  177. /// </summary>
  178. /// <param name="tag">The tag string.</param>
  179. /// <param name="resolver">Callback invoked to replace the tag with custom content.</param>
  180. public void AddWildcard(string tag, Func<RecordingSession, string> resolver)
  181. {
  182. m_Wildcards.Add(new Wildcard(tag, resolver));
  183. }
  184. string RecorderResolver(RecordingSession session)
  185. {
  186. return m_RecorderSettings.name;
  187. }
  188. static string TimeResolver(RecordingSession session)
  189. {
  190. var date = session != null ? session.sessionStartTS : DateTime.Now;
  191. return string.Format("{0:HH}h{1:mm}m", date, date);
  192. }
  193. string TakeResolver(RecordingSession session)
  194. {
  195. return m_RecorderSettings.Take.ToString("000");
  196. }
  197. static string DateResolver(RecordingSession session)
  198. {
  199. var date = session != null ? session.sessionStartTS : DateTime.Now;
  200. return date.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture); // ISO 8601
  201. }
  202. string ExtensionResolver(RecordingSession session)
  203. {
  204. return m_RecorderSettings.Extension;
  205. }
  206. string ResolutionResolver(RecordingSession session)
  207. {
  208. var input = m_RecorderSettings.InputsSettings.FirstOrDefault() as ImageInputSettings;
  209. if (input == null)
  210. return "NA";
  211. return input.OutputWidth + "x" + input.OutputHeight;
  212. }
  213. static string SceneResolver(RecordingSession session)
  214. {
  215. return SceneManager.GetActiveScene().name;
  216. }
  217. static string FrameResolver(RecordingSession session)
  218. {
  219. var i = session != null ? session.frameIndex : 0;
  220. return i.ToString("0000");
  221. }
  222. static string ProjectNameResolver(RecordingSession session)
  223. {
  224. if (string.IsNullOrEmpty(s_ProjectName))
  225. {
  226. var parts = Application.dataPath.Split('/');
  227. s_ProjectName = parts[parts.Length - 2];
  228. }
  229. return s_ProjectName;
  230. }
  231. static string ProductNameResolver(RecordingSession session)
  232. {
  233. return PlayerSettings.productName;
  234. }
  235. /// <summary>
  236. /// Builds an absolute path from the list of configured output file tags replaced by the RecordingSession.
  237. /// </summary>
  238. /// <param name="session">The Recorder session used to replace the tags.</param>
  239. /// <returns>An absolute path towards a file.</returns>
  240. public string BuildAbsolutePath(RecordingSession session)
  241. {
  242. var fullPath = ApplyWildcards(ToPath(), session) + "." + ExtensionResolver(session);
  243. string drive = null;
  244. if (Application.platform == RuntimePlatform.WindowsEditor)
  245. {
  246. if (fullPath.Length > 2 && char.IsLetter(fullPath[0]) && fullPath[1] == ':' && fullPath[2] == '/')
  247. {
  248. drive = fullPath.Substring(0, 2);
  249. fullPath = fullPath.Substring(3);
  250. }
  251. }
  252. fullPath = string.Join(Path.DirectorySeparatorChar.ToString(), fullPath.Split('/').Select(s =>
  253. Path.GetInvalidFileNameChars().Aggregate(s, (current, c) => current.Replace(c.ToString(), string.Empty))).ToArray());
  254. if (!string.IsNullOrEmpty(drive))
  255. fullPath = drive.ToUpper() + Path.DirectorySeparatorChar + fullPath;
  256. return fullPath;
  257. }
  258. /// <summary>
  259. /// Creates the directory structure containing the output file from the list of tags and a RecordingSession.
  260. /// </summary>
  261. /// <param name="session">The Recorder session.</param>
  262. public void CreateDirectory(RecordingSession session)
  263. {
  264. var path = ApplyWildcards(m_Path.GetFullPath(), session);
  265. if(!string.IsNullOrEmpty(path) && !Directory.Exists(path))
  266. Directory.CreateDirectory(path);
  267. }
  268. internal static string SanitizeFilename(string filename)
  269. {
  270. filename = filename.Replace("\\", "");
  271. filename = Regex.Replace(filename, "/", "");
  272. return filename;
  273. }
  274. /// <summary>
  275. /// Makes the output file path compliant with any OS (replacing any "\" by "/").
  276. /// </summary>
  277. /// <param name="fullPath"></param>
  278. /// <returns>The full path with slashes "/" as file separators.</returns>
  279. public static string SanitizePath(string fullPath)
  280. {
  281. fullPath = fullPath.Replace("\\", "/");
  282. fullPath = Regex.Replace(fullPath, "/+", "/");
  283. return fullPath;
  284. }
  285. string ApplyWildcards(string str, RecordingSession session)
  286. {
  287. if (string.IsNullOrEmpty(str))
  288. return string.Empty;
  289. foreach (var w in wildcards)
  290. str = str.Replace(w.pattern, w.Resolve(session));
  291. return str;
  292. }
  293. }
  294. }