From 4ac159dc4c93a7b1879157d3d614e5879fcacb1a Mon Sep 17 00:00:00 2001 From: Daniel Wolf Date: Thu, 3 Mar 2016 22:29:51 +0100 Subject: [PATCH] Split import script for Sony Vegas into two `Import Rhubarb.cs` now only imports the animation. `Debug Rhubarb.cs` visualizes structured log data for debugging purposes. --- extras/{VegasImport => SonyVegas}/.gitignore | 0 extras/SonyVegas/Debug Rhubarb.cs | 327 ++++++++++++++++++ .../Debug Rhubarb.cs.config} | 0 .../Import Rhubarb.cs | 20 +- extras/SonyVegas/Import Rhubarb.cs.config | 4 + extras/SonyVegas/README.md | 8 + extras/VegasImport/README.md | 3 - 7 files changed, 347 insertions(+), 15 deletions(-) rename extras/{VegasImport => SonyVegas}/.gitignore (100%) create mode 100644 extras/SonyVegas/Debug Rhubarb.cs rename extras/{VegasImport/Import Rhubarb.cs.config => SonyVegas/Debug Rhubarb.cs.config} (100%) rename extras/{VegasImport => SonyVegas}/Import Rhubarb.cs (92%) create mode 100644 extras/SonyVegas/Import Rhubarb.cs.config create mode 100644 extras/SonyVegas/README.md delete mode 100644 extras/VegasImport/README.md diff --git a/extras/VegasImport/.gitignore b/extras/SonyVegas/.gitignore similarity index 100% rename from extras/VegasImport/.gitignore rename to extras/SonyVegas/.gitignore diff --git a/extras/SonyVegas/Debug Rhubarb.cs b/extras/SonyVegas/Debug Rhubarb.cs new file mode 100644 index 0000000..93ccbce --- /dev/null +++ b/extras/SonyVegas/Debug Rhubarb.cs @@ -0,0 +1,327 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.ComponentModel.Design; +using System.Drawing; +using System.Drawing.Design; +using System.Globalization; +using System.IO; +using System.Text; +using System.Text.RegularExpressions; +using System.Web.UI.Design; +using System.Windows.Forms; +using System.Windows.Forms.Design; +using System.Xml; +using System.Xml.Serialization; +using Sony.Vegas; +using Region = Sony.Vegas.Region; + +public class EntryPoint { + public void FromVegas(Vegas vegas) { + Config config = Config.Load(); + ImportDialog importDialog = new ImportDialog(config, delegate { Import(config, vegas); }); + importDialog.ShowDialog(); + config.Save(); + } + + private void Import(Config config, Vegas vegas) { + Project project = vegas.Project; + + // Clear markers and regions + if (config.ClearMarkers) { + project.Markers.Clear(); + } + if (config.ClearRegions) { + project.Regions.Clear(); + } + + // Load log file + if (!File.Exists(config.LogFile)) { + throw new Exception("Log file does not exist."); + } + Dictionary> timedEvents = ParseLogFile(config); + + // Add markers/regions + foreach (EventType eventType in timedEvents.Keys) { + foreach (Visualization visualization in config.Visualizations) { + if (visualization.EventType != eventType) continue; + + List filteredEvents = FilterEvents(timedEvents[eventType], visualization.Regex); + foreach (TimedEvent timedEvent in filteredEvents) { + Timecode start = Timecode.FromSeconds(timedEvent.Start); + Timecode end = Timecode.FromSeconds(timedEvent.End); + switch (visualization.VisualizationType) { + case VisualizationType.Marker: + project.Markers.Add(new Marker(start, timedEvent.Value)); + break; + case VisualizationType.Region: + project.Regions.Add(new Region(start, end, timedEvent.Value)); + break; + } + } + } + } + } + + private List FilterEvents(List timedEvents, Regex filterRegex) { + if (filterRegex == null) return timedEvents; + + StringBuilder stringBuilder = new StringBuilder(); + Dictionary timedEventsByCharPosition = new Dictionary(); + foreach (TimedEvent timedEvent in timedEvents) { + string inAngleBrackets = "<" + timedEvent.Value + ">"; + for (int charPosition = stringBuilder.Length; + charPosition < stringBuilder.Length + inAngleBrackets.Length; + charPosition++) { + timedEventsByCharPosition[charPosition] = timedEvent; + } + stringBuilder.Append(inAngleBrackets); + } + + MatchCollection matches = filterRegex.Matches(stringBuilder.ToString()); + List result = new List(); + foreach (Match match in matches) { + if (match.Length == 0) continue; + + for (int charPosition = match.Index; charPosition < match.Index + match.Length; charPosition++) { + TimedEvent matchedEvent = timedEventsByCharPosition[charPosition]; + if (!result.Contains(matchedEvent)) { + result.Add(matchedEvent); + } + } + } + return result; + } + + private static Dictionary> ParseLogFile(Config config) { + string[] lines = File.ReadAllLines(config.LogFile); + Regex structuredLogLine = new Regex(@"##(\w+)\[(\d*\.\d*)-(\d*\.\d*)\]: (.*)"); + Dictionary> timedEvents = new Dictionary>(); + foreach (string line in lines) { + Match match = structuredLogLine.Match(line); + if (!match.Success) continue; + + EventType eventType = (EventType) Enum.Parse(typeof(EventType), match.Groups[1].Value, true); + double start = double.Parse(match.Groups[2].Value, CultureInfo.InvariantCulture); + double end = double.Parse(match.Groups[3].Value, CultureInfo.InvariantCulture); + string value = match.Groups[4].Value; + + if (!timedEvents.ContainsKey(eventType)) { + timedEvents[eventType] = new List(); + } + timedEvents[eventType].Add(new TimedEvent(eventType, start, end, value)); + } + return timedEvents; + } +} + +public class TimedEvent { + private readonly EventType eventType; + private readonly double start; + private readonly double end; + private readonly string value; + + public TimedEvent(EventType eventType, double start, double end, string value) { + this.eventType = eventType; + this.start = start; + this.end = end; + this.value = value; + } + + public EventType EventType { + get { return eventType; } + } + + public double Start { + get { return start; } + } + + public double End { + get { return end; } + } + + public string Value { + get { return value; } + } +} + +public class Config { + private string logFile; + private bool clearMarkers; + private bool clearRegions; + private List visualizations = new List(); + + [DisplayName("Log File")] + [Description("A log file generated by Rhubarb Lip Sync.")] + [Editor(typeof(FileNameEditor), typeof(UITypeEditor))] + public string LogFile { + get { return logFile; } + set { logFile = value; } + } + + [DisplayName("Clear Markers")] + [Description("Clear all markers in the current project.")] + public bool ClearMarkers { + get { return clearMarkers; } + set { clearMarkers = value; } + } + + [DisplayName("Clear Regions")] + [Description("Clear all regions in the current project.")] + public bool ClearRegions { + get { return clearRegions; } + set { clearRegions = value; } + } + + [DisplayName("Visualization rules")] + [Description("Specify how to visualize various log events.")] + [Editor(typeof(CollectionEditor), typeof(UITypeEditor))] + [XmlIgnore] + public List Visualizations { + get { return visualizations; } + set { visualizations = value; } + } + + [Browsable(false)] + public Visualization[] VisualizationArray { + get { return visualizations.ToArray(); } + set { visualizations = new List(value); } + } + + private static string ConfigFileName { + get { + string folder = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); + return Path.Combine(folder, "DebugRhubarbSettings.xml"); + } + } + + public static Config Load() { + try { + XmlSerializer serializer = new XmlSerializer(typeof(Config)); + using (FileStream file = File.OpenRead(ConfigFileName)) { + return (Config) serializer.Deserialize(file); + } + } catch (Exception) { + return new Config(); + } + } + + public void Save() { + XmlSerializer serializer = new XmlSerializer(typeof(Config)); + using (StreamWriter file = File.CreateText(ConfigFileName)) { + XmlWriterSettings settings = new XmlWriterSettings(); + settings.Indent = true; + settings.IndentChars = "\t"; + using (XmlWriter writer = XmlWriter.Create(file, settings)) { + serializer.Serialize(writer, this); + } + } + } +} + +public class Visualization { + private EventType eventType; + private string regexString; + private VisualizationType visualizationType = VisualizationType.Marker; + + [DisplayName("Event Type")] + [Description("The type of event to visualize.")] + public EventType EventType { + get { return eventType; } + set { eventType = value; } + } + + [DisplayName("Regular Expression")] + [Description("A regular expression used to filter events. Leave empty to disable filtering.\nInput is a string of events in angle brackets. Example: '(?=)' finds every AO phone followed by a T phone.")] + public string RegexString { + get { return regexString; } + set { regexString = value; } + } + + [Browsable(false)] + public Regex Regex { + get { return string.IsNullOrEmpty(RegexString) ? null : new Regex(RegexString); } + } + + [DisplayName("Visualization Type")] + [Description("Specify how to visualize events.")] + public VisualizationType VisualizationType { + get { return visualizationType; } + set { visualizationType = value; } + } + + public override string ToString() { + return string.Format("{0} -> {1}", EventType, VisualizationType); + } +} + +public enum EventType { + Utterance, + Word, + Phone, + Shape +} + +public enum VisualizationType { + None, + Marker, + Region +} + +public delegate void ImportAction(); + +public class ImportDialog : Form { + private readonly Config config; + private readonly ImportAction import; + + public ImportDialog(Config config, ImportAction import) { + this.config = config; + this.import = import; + SuspendLayout(); + InitializeComponent(); + ResumeLayout(false); + } + + private void InitializeComponent() { + // Configure dialog + Text = "Debug Rhubarb"; + Size = new Size(600, 400); + Font = new Font(Font.FontFamily, 10); + + // Add property grid + PropertyGrid propertyGrid1 = new PropertyGrid(); + propertyGrid1.SelectedObject = config; + Controls.Add(propertyGrid1); + propertyGrid1.Dock = DockStyle.Fill; + + // Add button panel + FlowLayoutPanel buttonPanel = new FlowLayoutPanel(); + buttonPanel.FlowDirection = FlowDirection.RightToLeft; + buttonPanel.AutoSize = true; + buttonPanel.Dock = DockStyle.Bottom; + Controls.Add(buttonPanel); + + // Add Cancel button + Button cancelButton1 = new Button(); + cancelButton1.Text = "Cancel"; + cancelButton1.DialogResult = DialogResult.Cancel; + buttonPanel.Controls.Add(cancelButton1); + CancelButton = cancelButton1; + + // Add OK button + Button okButton1 = new Button(); + okButton1.Text = "OK"; + okButton1.Click += OkButtonClickedHandler; + buttonPanel.Controls.Add(okButton1); + AcceptButton = okButton1; + } + + private void OkButtonClickedHandler(object sender, EventArgs e) { + try { + import(); + DialogResult = DialogResult.OK; + } catch (Exception exception) { + MessageBox.Show(exception.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); + } + } +} \ No newline at end of file diff --git a/extras/VegasImport/Import Rhubarb.cs.config b/extras/SonyVegas/Debug Rhubarb.cs.config similarity index 100% rename from extras/VegasImport/Import Rhubarb.cs.config rename to extras/SonyVegas/Debug Rhubarb.cs.config diff --git a/extras/VegasImport/Import Rhubarb.cs b/extras/SonyVegas/Import Rhubarb.cs similarity index 92% rename from extras/VegasImport/Import Rhubarb.cs rename to extras/SonyVegas/Import Rhubarb.cs index 15b2699..f5a69f0 100644 --- a/extras/VegasImport/Import Rhubarb.cs +++ b/extras/SonyVegas/Import Rhubarb.cs @@ -59,14 +59,6 @@ public class EntryPoint { project.Video.FieldOrder = VideoFieldOrder.ProgressiveScan; project.Video.PixelAspectRatio = 1; - // Add markers for phones - XmlNodeList phoneElements = xmlDocument.SelectNodes("//phone"); - foreach (XmlElement phoneElement in phoneElements) { - Timecode position = GetTimecode(phoneElement.Attributes["start"]); - string label = phoneElement.InnerText; - project.Markers.Add(new Marker(position, label)); - } - // Add video track with images VideoTrack videoTrack = vegas.Project.AddVideoTrack(); foreach (XmlElement mouthCueElement in mouthCueElements) { @@ -151,14 +143,18 @@ public class Config { private static string ConfigFileName { get { string folder = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); - return Path.Combine(folder, "VegasRhubarbScriptSettings.xml"); + return Path.Combine(folder, "ImportRhubarbSettings.xml"); } } public static Config Load() { - XmlSerializer serializer = new XmlSerializer(typeof(Config)); - using (FileStream file = File.OpenRead(ConfigFileName)) { - return (Config) serializer.Deserialize(file); + try { + XmlSerializer serializer = new XmlSerializer(typeof(Config)); + using (FileStream file = File.OpenRead(ConfigFileName)) { + return (Config) serializer.Deserialize(file); + } + } catch (Exception) { + return new Config(); } } diff --git a/extras/SonyVegas/Import Rhubarb.cs.config b/extras/SonyVegas/Import Rhubarb.cs.config new file mode 100644 index 0000000..430cb18 --- /dev/null +++ b/extras/SonyVegas/Import Rhubarb.cs.config @@ -0,0 +1,4 @@ + + + System.Design.dll + \ No newline at end of file diff --git a/extras/SonyVegas/README.md b/extras/SonyVegas/README.md new file mode 100644 index 0000000..a363c30 --- /dev/null +++ b/extras/SonyVegas/README.md @@ -0,0 +1,8 @@ +# Scripts for Sony Vegas + +If you own a copy of [Sony Vegas](http://www.sonycreativesoftware.com/vegassoftware), you can use this script to visualize Rhubarb Lip Sync's output on the timeline. This can be useful for creating lip-synced videos or for debugging. + +Copy (or symlink) the files in this directory to `\Script Menu`. When you restart Vegas, you'll find two new menu items: + +* *Tools > Scripting > Import Rhubarb:* This will create a new Vegas project and add two tracks: a video track with a visualization of Rhubarb Lip-Sync's output and an audio track with the original recording. +* *Tools > Scripting > Debug Rhubarb:* This will create markers or regions on the timeline visualizing Rhubarb Lip-Sync's internal data from a log file. You can obtain a log file by redirecting `stdout`. I've written this script mainly as a debugging aid for myself; feel free to contact me if you're interested and need a more thorough explanation. \ No newline at end of file diff --git a/extras/VegasImport/README.md b/extras/VegasImport/README.md deleted file mode 100644 index 049bdf2..0000000 --- a/extras/VegasImport/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# Import into Sony Vegas - -If you own a copy of [Sony Vegas](http://www.sonycreativesoftware.com/vegassoftware), you can use this script to visualize Rhubarb Lip Sync's output on the timeline. Just copy (or symlink) `Import Rhubarb.cs` and `Import Rhubarb.cs.config` to `\Script Menu`. When you restart Vegas, you'll find a new menu item Tools > Scripting > Import Rhubarb. \ No newline at end of file