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.
This commit is contained in:
Daniel Wolf 2016-03-03 22:29:51 +01:00
parent 04ca644cca
commit 4ac159dc4c
7 changed files with 347 additions and 15 deletions

View File

@ -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<EventType, List<TimedEvent>> timedEvents = ParseLogFile(config);
// Add markers/regions
foreach (EventType eventType in timedEvents.Keys) {
foreach (Visualization visualization in config.Visualizations) {
if (visualization.EventType != eventType) continue;
List<TimedEvent> 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<TimedEvent> FilterEvents(List<TimedEvent> timedEvents, Regex filterRegex) {
if (filterRegex == null) return timedEvents;
StringBuilder stringBuilder = new StringBuilder();
Dictionary<int, TimedEvent> timedEventsByCharPosition = new Dictionary<int, TimedEvent>();
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<TimedEvent> result = new List<TimedEvent>();
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<EventType, List<TimedEvent>> ParseLogFile(Config config) {
string[] lines = File.ReadAllLines(config.LogFile);
Regex structuredLogLine = new Regex(@"##(\w+)\[(\d*\.\d*)-(\d*\.\d*)\]: (.*)");
Dictionary<EventType, List<TimedEvent>> timedEvents = new Dictionary<EventType, List<TimedEvent>>();
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<TimedEvent>();
}
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<Visualization> visualizations = new List<Visualization>();
[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<Visualization> Visualizations {
get { return visualizations; }
set { visualizations = value; }
}
[Browsable(false)]
public Visualization[] VisualizationArray {
get { return visualizations.ToArray(); }
set { visualizations = new List<Visualization>(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: '<AO>(?=<T>)' 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);
}
}
}

View File

@ -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,15 +143,19 @@ 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() {
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() {

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8" ?>
<ScriptSettings>
<AssemblyReference>System.Design.dll</AssemblyReference>
</ScriptSettings>

View File

@ -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 `<Vegas installation directory>\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.

View File

@ -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 `<Vegas installation directory>\Script Menu`. When you restart Vegas, you'll find a new menu item Tools > Scripting > Import Rhubarb.