From 89e625943d925b5546e6b7338643c4589e80f287 Mon Sep 17 00:00:00 2001 From: Daniel Wolf Date: Thu, 3 Dec 2015 23:04:31 +0100 Subject: [PATCH] Created import script for Sony Vegas --- extras/VegasImport/.gitignore | 1 + extras/VegasImport/Import Rhubarb.cs | 228 ++++++++++++++++++++ extras/VegasImport/Import Rhubarb.cs.config | 4 + extras/VegasImport/README.md | 3 + 4 files changed, 236 insertions(+) create mode 100644 extras/VegasImport/.gitignore create mode 100644 extras/VegasImport/Import Rhubarb.cs create mode 100644 extras/VegasImport/Import Rhubarb.cs.config create mode 100644 extras/VegasImport/README.md diff --git a/extras/VegasImport/.gitignore b/extras/VegasImport/.gitignore new file mode 100644 index 0000000..5d86297 --- /dev/null +++ b/extras/VegasImport/.gitignore @@ -0,0 +1 @@ +/Project diff --git a/extras/VegasImport/Import Rhubarb.cs b/extras/VegasImport/Import Rhubarb.cs new file mode 100644 index 0000000..7461760 --- /dev/null +++ b/extras/VegasImport/Import Rhubarb.cs @@ -0,0 +1,228 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Drawing; +using System.Drawing.Design; +using System.Globalization; +using System.IO; +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; + +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) { + // Load XML file + if (!File.Exists(config.XmlFile)) { + throw new Exception("XML file does not exist."); + } + XmlDocument xmlDocument = new XmlDocument(); + xmlDocument.Load(config.XmlFile); + + // Determine image file names + XmlNodeList mouthCueElements = xmlDocument.SelectNodes("//mouthCue"); + List shapeNames = new List(); + foreach (XmlElement mouthCueElement in mouthCueElements) { + if (!shapeNames.Contains(mouthCueElement.InnerText)) { + shapeNames.Add(mouthCueElement.InnerText); + } + } + Dictionary imageFileNames = GetImageFileNames(config.OneImageFile, shapeNames.ToArray()); + + // Create new project + bool promptSave = !config.DiscardChanges; + bool showDialog = false; + Project project = new Project(promptSave, showDialog); + + // Set frame rate + if (config.FrameRate < 0.1 || config.FrameRate > 100) { + throw new Exception("Invalid frame rate."); + } + project.Video.FrameRate = config.FrameRate; + + // 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) { + Timecode start = GetTimecode(mouthCueElement.Attributes["start"]); + Timecode length = GetTimecode(mouthCueElement.Attributes["duration"]); + VideoEvent videoEvent = videoTrack.AddVideoEvent(start, length); + Media imageMedia = new Media(imageFileNames[mouthCueElement.InnerText]); + videoEvent.AddTake(imageMedia.GetVideoStreamByIndex(0)); + } + + // Add audio track with original sound file + AudioTrack audioTrack = vegas.Project.AddAudioTrack(); + Media audioMedia = new Media(xmlDocument.SelectSingleNode("//soundFile").InnerText); + AudioEvent audioEvent = audioTrack.AddAudioEvent(new Timecode(0), audioMedia.Length); + audioEvent.AddTake(audioMedia.GetAudioStreamByIndex(0)); + } + + private static Timecode GetTimecode(XmlAttribute valueAttribute) { + double seconds = Double.Parse(valueAttribute.Value, CultureInfo.InvariantCulture); + return Timecode.FromSeconds(seconds); + } + + private Dictionary GetImageFileNames(string oneImageFile, string[] shapeNames) { + if (oneImageFile == null) { + throw new Exception("Image file name not set."); + } + Regex nameRegex = new Regex(@"(?<=-)([^-]*)(?=\.[^.]+$)"); + if (!nameRegex.IsMatch(oneImageFile)) { + throw new Exception("Image file name doesn't have expected format."); + } + + Dictionary result = new Dictionary(); + foreach (string shapeName in shapeNames) { + string imageFileName = nameRegex.Replace(oneImageFile, shapeName); + if (!File.Exists(imageFileName)) { + throw new Exception(string.Format("Image file '{0}' not found.", imageFileName)); + } + result[shapeName] = imageFileName; + } + return result; + } + +} + +public class Config { + + private string xmlFile; + private string oneImageFile; + private double frameRate = 100; + private bool discardChanges = false; + + [DisplayName("XML File")] + [Description("An XML file generated by Rhubarb Lip Sync.")] + [Editor(typeof(XmlFileEditor), typeof(UITypeEditor))] + public string XmlFile { + get { return xmlFile; } + set { xmlFile = value; } + } + + [DisplayName("One image file")] + [Description("Any image file out of the set of image files representing the mouth chart.")] + [Editor(typeof(FileNameEditor), typeof(UITypeEditor))] + public string OneImageFile { + get { return oneImageFile; } + set { oneImageFile = value; } + } + + [DisplayName("Frame rate")] + [Description("The frame rate for the new project.")] + public double FrameRate { + get { return frameRate; } + set { frameRate = value; } + } + + [DisplayName("Discard Changes")] + [Description("Discard all changes to the current project without prompting to save.")] + public bool DiscardChanges { + get { return discardChanges; } + set { discardChanges = value; } + } + + private static string ConfigFileName { + get { + string folder = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); + return Path.Combine(folder, "VegasRhubarbScriptSettings.xml"); + } + } + + public static Config Load() { + XmlSerializer serializer = new XmlSerializer(typeof(Config)); + using (FileStream file = File.OpenRead(ConfigFileName)) { + return (Config) serializer.Deserialize(file); + } + } + + 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 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 = "Import 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/VegasImport/Import Rhubarb.cs.config new file mode 100644 index 0000000..430cb18 --- /dev/null +++ b/extras/VegasImport/Import Rhubarb.cs.config @@ -0,0 +1,4 @@ + + + System.Design.dll + \ No newline at end of file diff --git a/extras/VegasImport/README.md b/extras/VegasImport/README.md new file mode 100644 index 0000000..049bdf2 --- /dev/null +++ b/extras/VegasImport/README.md @@ -0,0 +1,3 @@ +# 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