Subtitles SUP file: import PNGs via XML

Comments

Qbrick wrote on 9/15/2023, 3:34 PM

@jetdv on the image you can see that:

:01 is converted to ,010

:22 is converted to ,220

Can you fix it please? :)

jetdv wrote on 9/15/2023, 3:46 PM

There is no image in your post...

Here's the original file on my system:

  <Events>
    <Event InTC="00:02:15:04" OutTC="00:02:17:05" Forced="False">
      <Graphic Width="360" Height="70" X="180" Y="356">Subpictures_20_exp_0001_0.png</Graphic>
    </Event>
    <Event InTC="00:02:17:13" OutTC="00:02:18:23" Forced="False">
      <Graphic Width="349" Height="26" X="184" Y="399">Subpictures_20_exp_0002_0.png</Graphic>
    </Event>
    <Event InTC="00:02:19:04" OutTC="00:02:20:23" Forced="False">
      <Graphic Width="247" Height="76" X="236" Y="355">Subpictures_20_exp_0003_0.png</Graphic>
    </Event>
    <Event InTC="00:02:21:04" OutTC="00:02:23:11" Forced="False">
      <Graphic Width="274" Height="76" X="222" Y="355">Subpictures_20_exp_0004_0.png</Graphic>
    </Event>
    <Event InTC="00:02:23:19" OutTC="00:02:25:03" Forced="False">
      <Graphic Width="319" Height="25" X="199" Y="400">Subpictures_20_exp_0005_0.png</Graphic>
    </Event>
    <Event InTC="00:02:25:08" OutTC="00:02:27:16" Forced="False">
      <Graphic Width="416" Height="32" X="151" Y="399">Subpictures_20_exp_0006_0.png</Graphic>
    </Event>

Here's the new output file on my system:

  <Events>
    <Event InTC="00:00:07;11" OutTC="00:00:25;21" Forced="False">
      <Graphic Width="360" Height="70" X="180" Y="356">Subpictures_20_exp_0001_0.png</Graphic>
    </Event>
    <Event InTC="00:00:33;05" OutTC="00:00:50;01" Forced="False">
      <Graphic Width="349" Height="26" X="184" Y="399">Subpictures_20_exp_0002_0.png</Graphic>
    </Event>
    <Event InTC="00:02:19;04" OutTC="00:02:20;23" Forced="False">
      <Graphic Width="247" Height="76" X="236" Y="355">Subpictures_20_exp_0003_0.png</Graphic>
    </Event>
    <Event InTC="00:02:21;04" OutTC="00:02:23;11" Forced="False">
      <Graphic Width="274" Height="76" X="222" Y="355">Subpictures_20_exp_0004_0.png</Graphic>
    </Event>
    <Event InTC="00:02:23;19" OutTC="00:02:25;03" Forced="False">
      <Graphic Width="319" Height="25" X="199" Y="400">Subpictures_20_exp_0005_0.png</Graphic>
    </Event>
    <Event InTC="00:02:25;08" OutTC="00:02:27;16" Forced="False">
      <Graphic Width="416" Height="32" X="151" Y="399">Subpictures_20_exp_0006_0.png</Graphic>
    </Event>

If you're seeing something different, you might want to look at your timecode format on the timeline:

Qbrick wrote on 9/15/2023, 4:06 PM

"... you might want to look at your timecode format on the timeline" <-- you're right! thanks!

@jetdv last fix and will be correct 100%

At left the imported XML, at right the exported XML

Why they are different? They must be identical on export. :)

jetdv wrote on 9/15/2023, 4:15 PM

@Qbrick In the file you sent me...

<Format VideoFormat="576i" FrameRate="25" DropFrame="False"/>

So I matched that perfectly. I don't know that information on export because we're not looking at the original file and that information is not even imported as it wasn't used. I even asked about this and you said:

@Qbrick, will this always be the same?

<Format VideoFormat="576i" FrameRate="25" DropFrame="False"/>

576i seems odd... In searching, it appears that is the "number of scan lines" so it would be a 720x576 project. At least initially, I'm assuming that line could just be hard coded in.

Not worry about that :)

The important thing Is to be able move subtitles on timeline and change their duration. Then export them on the XML with these new values.

Nothing more :)

But you can change these lines to be whatever you want them to be:

            lang.SetAttribute("Code", "eng");
            format.SetAttribute("VideoFormat", "576i");
            format.SetAttribute("FrameRate", "25");
            format.SetAttribute("DropFrame", "False");

 

Qbrick wrote on 9/15/2023, 4:25 PM

@Qbrick In the file you sent me...

<Format VideoFormat="576i" FrameRate="25" DropFrame="False"/>

So I matched that perfectly. I don't know that information on export because we're not looking at the original file and that information is not even imported as it wasn't used. I even asked about this and you said:

@Qbrick, will this always be the same?

<Format VideoFormat="576i" FrameRate="25" DropFrame="False"/>

576i seems odd... In searching, it appears that is the "number of scan lines" so it would be a 720x576 project. At least initially, I'm assuming that line could just be hard coded in.

Not worry about that :)

The important thing Is to be able move subtitles on timeline and change their duration. Then export them on the XML with these new values.

Nothing more :)

But you can change these lines to be whatever you want them to be:

            lang.SetAttribute("Code", "eng");
            format.SetAttribute("VideoFormat", "576i");
            format.SetAttribute("FrameRate", "25");
            format.SetAttribute("DropFrame", "False");

 

My mistake :(

"Don't worry about that :)"

meant

"don't change these 3 values inside Vegas, but import them from xml, so you can export them identical on new xml"

In fact, every XML I'll import will have 3 different values, depends on film...

Anyway thanks for these 2 fantastic scripts! I think is they are the only solution on internet to manage SUP file without OCR them..

Qbrick wrote on 9/15/2023, 4:41 PM

@jetdv I'll try to modify you're export script to export Code, Videoformat, FrameRate as the original file

Thanks for you're support/effort! 💯💯💯🎊🎉🎁🎊🏅🏅🏅

jetdv wrote on 9/15/2023, 4:55 PM

@Qbrick The "VideoFormat" and "FrameRate" could be pulled from the current project settings if your current project is set to match. The "language code" - that would have to be imported from the original file and stored somewhere.

Change this section and that will make it match the current project:

            XmlElement format = AddChild(desc, "Format");
            VideoFieldOrder fo = myVegas.Project.Video.FieldOrder;
            string fieldorder = "i";
            if (fo == VideoFieldOrder.ProgressiveScan)
            {
                fieldorder = "p";
            }
            string vFormat = myVegas.Project.Video.Height.ToString() + fieldorder;
            format.SetAttribute("VideoFormat", vFormat);
            format.SetAttribute("FrameRate", myVegas.Project.Video.FrameRate.ToString());

            format.SetAttribute("DropFrame", "False");

 

jetdv wrote on 9/15/2023, 4:59 PM

With that change I just got this:

<Format VideoFormat="1080p" FrameRate="29.97003" DropFrame="False" />

Now all you have to worry about is changing the language.

Qbrick wrote on 9/16/2023, 5:51 PM

EXPORT SCRIPT:

using System;
using System.Collections.Generic;
using System.Collections;
using System.IO;
using System.Text;
using System.Windows.Forms;
using System.Globalization;
using System.Drawing;
using System.Runtime;
using System.Xml;
using System.Xml.XPath;
using ScriptPortal.Vegas;
namespace Test_Script
{
    public class Class1
    {
        public Vegas myVegas;
        private string[] filenames;
        private string basePath;
        private static System.Text.Encoding myCharacterEncoding = System.Text.Encoding.UTF8;
        public void Main(Vegas vegas)
        {
            myVegas = vegas;
            // open input XML for get 3 values
            MessageBox.Show("Selext the XML to get language, videoFormat, framerate, dropFrame");
            OpenFileDialog ofd = new OpenFileDialog();
            ofd.Filter = "Import Files (*.XML)|*.xml|All Files (*.*)|*.*";
            ofd.Multiselect = false;
            ofd.Title = "Select the file to import";
            ofd.InitialDirectory = "C:\\Users\\xxx\\Documents";
            DialogResult dr = ofd.ShowDialog();
            if (dr == System.Windows.Forms.DialogResult.OK)
            {
                filenames = ofd.FileNames;
                foreach (string file in filenames)
                {
                    processImportFile(file);
                    myVegas.UpdateUI();
                }
            }
        }
        private void processImportFile(string myFile)
        {
            XPathNavigator nav;
            XPathDocument docNav;
            docNav = new XPathDocument(myFile);
            nav = docNav.CreateNavigator();
            String language = "string(//Description/Language/@Code)";
            String videoFormat = "string(//Description/Format/@VideoFormat)";
            String frameRate = "string(//Description/Format/@FrameRate)";
            String dropFrame = "string(//Description/Format/@DropFrame)";
            string languageS = nav.Evaluate(language).ToString();
            string videoFormatS = nav.Evaluate(videoFormat).ToString();
            string frameRateS = nav.Evaluate(frameRate).ToString();
            string dropFrameS = nav.Evaluate(dropFrame).ToString();
             
                       
            // give name of new XML
            MessageBox.Show("Selext the name of new XML");
            SaveFileDialog svd = new SaveFileDialog();
            svd.InitialDirectory = "C:\\Users\\xxx\\Documents";
            svd.Filter = "Save Files (*.XML)|*.xml|All Files (*.*)|*.*";
            svd.FilterIndex = 1;
            svd.RestoreDirectory = true;
            if (svd.ShowDialog() == DialogResult.OK)
            {
                foreach (Track myTrack in myVegas.Project.Tracks)
                {
                    if (myTrack.Selected && myTrack.IsVideo())
                    {
                        ExportXML(svd.FileName, myTrack, languageS, videoFormatS, frameRateS, dropFrameS);
                        break;
                    }
                }
            }
        }
        private void ExportXML(string myFile, Track myTrack, string language, string videoFormat, string frameRate, string dropFrame)
        {
            XmlDocument doc = new XmlDocument();
            XmlProcessingInstruction xmlPI = doc.CreateProcessingInstruction("xml", "version=\"1.0\" encoding=\"UTF-8\"");
            doc.AppendChild(xmlPI);
            var root = doc.CreateElement("BDN");
            root.SetAttribute("Version", "0.93");
            //root.SetAttribute("xmlns", "http://www/org/2007");
            root.SetAttribute("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance");
            root.SetAttribute("noNamespaceSchemaLocation", "http://www.w3.org/2001/XMLSchema-instance", "BD-03-006-0093b BDN File Format.xsd");
            doc.AppendChild(root);
            //Description section
            XmlElement desc = AddChild(root, "Description");
            XmlElement name = AddChild(desc, "Name");
            name.SetAttribute("Title", Path.GetFileNameWithoutExtension(myFile));
            name.SetAttribute("Content", "");
            XmlElement lang = AddChild(desc, "Language");
            lang.SetAttribute("Code", language);
            XmlElement format = AddChild(desc, "Format");
            format.SetAttribute("VideoFormat", videoFormat);
            format.SetAttribute("FrameRate", frameRate);
            format.SetAttribute("DropFrame", dropFrame);
            XmlElement ev1 = AddChild(desc, "Events");
            ev1.SetAttribute("Type", "Graphic");
            ev1.SetAttribute("FirstEventInTC", myTrack.Events[0].Start.ToString());
            ev1.SetAttribute("LastEventOutTC", myTrack.Events[myTrack.Events.Count - 1].End.ToString());
            ev1.SetAttribute("NumberofEvents", myTrack.Events.Count.ToString());
            //All Events
            XmlElement ev2 = AddChild(root, "Events");
            foreach (VideoEvent evnt in myTrack.Events)
            {
                XmlElement oneev = AddChild(ev2, "Event");
                oneev.SetAttribute("InTC", evnt.Start.ToString());
                oneev.SetAttribute("OutTC", evnt.End.ToString());
                oneev.SetAttribute("Forced", "False");
                XmlElement graphic = ChildString(oneev, "Graphic", Path.GetFileName(evnt.ActiveTake.Media.FilePath));
                VideoStream vs = evnt.ActiveTake.Media.GetVideoStreamByIndex(0);
                graphic.SetAttribute("Width", vs.Width.ToString());
                graphic.SetAttribute("Height", vs.Height.ToString());
                VideoMotionKeyframe kf = evnt.VideoMotion.Keyframes[0];
                float X = myVegas.Project.Video.Width / 2 - kf.Center.X;
                float Y = myVegas.Project.Video.Height / 2 - kf.Center.Y;
                graphic.SetAttribute("X", X.ToString());
                graphic.SetAttribute("Y", Y.ToString());
            }
            XmlTextWriter writer = new XmlTextWriter(myFile, myCharacterEncoding);
            writer.Formatting = Formatting.Indented;
            writer.Indentation = 2;
            writer.IndentChar = ' ';
            doc.WriteTo(writer);
            writer.Close();
        }
        private static XmlElement AddChild(XmlElement parent, String childName)
        {
            return ChildString(parent, childName, null);
        }
        private static XmlElement ChildString(XmlElement parent, String childName, String childValue)
        {
            XmlElement child = parent.OwnerDocument.CreateElement(childName);
            parent.AppendChild(child);
            if (null != childValue)
                child.InnerText = childValue.ToString();
            return child;
        }
    }
    public class EntryPoint
    {
        public void FromVegas(Vegas vegas)
        {
            Test_Script.Class1 test = new Test_Script.Class1();
            test.Main(vegas);
        }
    }
}

Many thanks to @jetdv yet !!! You rule! :)

jetdv wrote on 9/17/2023, 7:00 AM

@Qbrick, glad I could help. I was trying to eliminate the need to look at the original file but that's certainly a way to find the original information.

And this still can be eliminated by actually reading that information on import and storing it in the project but would take some more work to accomplish.

Qbrick wrote on 9/18/2023, 2:47 PM

@jetdv the import script works very good.

Anyway Vegas is usless until it load all PNG.

For a 2 hours film it take a lot of time.

Is there a way to load PNG in a asynchrony way?

May be we have to write a extension in Vegas? :)

jetdv wrote on 9/18/2023, 2:53 PM

It took my system almost a minute to load the 692 test images you sent. There's no real way to make VEGAS load them faster other than to have a faster machine. But the export process is basically instantaneous!

Qbrick wrote on 9/18/2023, 3:13 PM

@jetdv in your opinion ProgressWorker Class can help us to avoid Vegas freezes until entire loading PNG process is completed?

My project is 1614 PNG.. I'm on a i7-2700k - 12GB RAM - SSD

jetdv wrote on 9/18/2023, 3:18 PM

@Qbrick, to be honest, I'm not sure. I've never tried implementing that. I've always tried my best to make sure the code was as fast as possible. For example, originally Excalibur processing the multi-cam switches to get the master timeline was very slow so I totally rewrote the code and made it exponentially faster. When I've started a process of that nature, I've always just waited until it finished. I've seen bad things happen sometimes when I try to go to fast.

jetdv wrote on 9/18/2023, 3:55 PM

@Qbrick Based on this, it would appear to not be a viable option:

* The most important restriction of using ProgressWorker objects is
you cannot directly manipulate the Vegas object or project data on
the worker thread. Depending on the task, this can be a serious
limitation.

You could always add a form with a progress bar to indicate the percentage done. To do that, you'd need to initially read the "NumberOfEvents" number and then count them as they are added to the timeline and calculate the percentage (count/NumberOfEvents*100).

Qbrick wrote on 9/19/2023, 1:25 AM

@Qbrick Based on this, it would appear to not be a viable option:

* The most important restriction of using ProgressWorker objects is
you cannot directly manipulate the Vegas object or project data on
the worker thread. Depending on the task, this can be a serious
limitation.

You could always add a form with a progress bar to indicate the percentage done. To do that, you'd need to initially read the "NumberOfEvents" number and then count them as they are added to the timeline and calculate the percentage (count/NumberOfEvents*100).

Thanks for this hint @jetdv

I have another option: set the lenght of the timeline (before start importing PNGs) at LastEventOutTC XML attribute so I can see the progress of loading..

How can I set that ? Is there a Timeline class ? :)

Qbrick wrote on 9/19/2023, 2:01 AM

@jetdv found a possible solution :)

Shorter code = instant loading!

I don't really need the resize parts...

But thanks for this because I can learn a lot from your previous code!

IMPORT SCRIPT:

using System;
using System.Collections.Generic;
using System.Collections;
using System.IO;
using System.Text;
using System.Windows.Forms;
using System.Globalization;
using System.Drawing;
using System.Runtime;
using System.Xml;
using ScriptPortal.Vegas;

namespace Test_Script
{
    public class Class1
    {
        public Vegas myVegas;
        private NumberFormatInfo myNumberFormat = NumberFormatInfo.InvariantInfo;
        private string[] filenames;
        private string clipitemFile;
        private string basePath;
        Timecode baseStartPosition;
        VideoTrack vTrack;
        AudioTrack aTrack;

        public void Main(Vegas vegas)
        {
            myVegas = vegas;

            OpenFileDialog ofd = new OpenFileDialog();
            ofd.Filter = "Import Files (*.XML)|*.xml|All Files (*.*)|*.*";
            ofd.Multiselect = false;
            ofd.Title = "Select the file to import";
            ofd.InitialDirectory = "D:\\Edward_\\SUP Test"; //Change this to where you want it to look by default
            DialogResult dr = ofd.ShowDialog();
            if (dr == System.Windows.Forms.DialogResult.OK)
            {
                filenames = ofd.FileNames;

                vTrack = new VideoTrack(myVegas.Project, 0, "Subtitles");
                myVegas.Project.Tracks.Add(vTrack);

                foreach (string file in filenames)
                {
                    baseStartPosition = myVegas.Transport.CursorPosition;
                    processImportFile(file);
                    myVegas.UpdateUI();
                }
            }
        }

        private void processImportFile(string myFile)
        {
            basePath = Path.GetDirectoryName(myFile) + Path.DirectorySeparatorChar;

            XmlDocument doc = new XmlDocument();
            doc.Load(myFile);

            XmlElement root = doc.DocumentElement;
            foreach (XmlElement child in root)
            {
                if (child.Name.ToLower() == "events")
                {
                    foreach (XmlElement child2 in child)
                    {
                        if (child2.Name.ToLower() == "event")
                        {
                            processClipItem(child2);
                        }
                    }
                }
            }
        }

        private void processClipItem(XmlElement clipitem)
        {
            string StartTC = clipitem.GetAttribute("InTC");
            string EndTC = clipitem.GetAttribute("OutTC");
            int Width;
            int Height;
            int X;
            int Y;
            string FilePath;

            foreach (XmlElement child in clipitem)
            {
                FilePath = basePath + child.InnerText;
                Width = Convert.ToInt32(child.GetAttribute("Width"));
                Height = Convert.ToInt32(child.GetAttribute("Height"));
                X = Convert.ToInt32(child.GetAttribute("X"));
                Y = Convert.ToInt32(child.GetAttribute("Y"));
                VideoEvent vEvent = addClipToTimeline(FilePath, StartTC, EndTC);
            }
        }

        private VideoEvent addClipToTimeline(string fileName, string StartFrame, string EndFrame)
        {
            Media media = new Media(fileName);

            Timecode startTC = Timecode.FromString(StartFrame);
            Timecode lenTC = Timecode.FromString(EndFrame) - startTC;

            MediaStream stream = media.Streams.GetItemByMediaType(MediaType.Video, 0);
            VideoEvent vEvent = new VideoEvent(startTC, lenTC);
            vTrack.Events.Add(vEvent);
            Take myNewtake = new Take(stream);
            vEvent.Takes.Add(myNewtake);
            return vEvent;
        }

    }
}

public class EntryPoint
{
    public void FromVegas(Vegas vegas)
    {
        Test_Script.Class1 test = new Test_Script.Class1();
        test.Main(vegas);
    }
}

 

jetdv wrote on 9/19/2023, 7:24 AM

@Qbrick, yes, removing the resizing and positioning code should certainly speed up the process but they won't be displayed in the correct location on the screen and the export script will need to be changed in order to get the correct "X" and "Y" locations since they are no longer being moved into the correct position.

Doing it without resizing and positioning, you can actually simplify it even further by removing:

            int Width;
            int Height; 
            int X; 
            int Y;

and

                Width = Convert.ToInt32(child.GetAttribute("Width")); 
                Height = Convert.ToInt32(child.GetAttribute("Height")); 
                X = Convert.ToInt32(child.GetAttribute("X")); 
                Y = Convert.ToInt32(child.GetAttribute("Y"));

as they were only used by the positioning and resizing code.

Qbrick wrote on 9/19/2023, 8:35 AM

@Qbrick, yes, removing the resizing and positioning code should certainly speed up the process but they won't be displayed in the correct location on the screen and the export script will need to be changed in order to get the correct "X" and "Y" locations since they are no longer being moved into the correct position.

Doing it without resizing and positioning, you can actually simplify it even further by removing:

            int Width;
            int Height; 
            int X; 
            int Y;

and

                Width = Convert.ToInt32(child.GetAttribute("Width")); 
                Height = Convert.ToInt32(child.GetAttribute("Height")); 
                X = Convert.ToInt32(child.GetAttribute("X")); 
                Y = Convert.ToInt32(child.GetAttribute("Y"));

as they were only used by the positioning and resizing code.


@jetdv I've a problem but I have also a solution to implement

Problem: When I export, X and Y are not the same as the original file.

Solution: get X and Y from the original and save these values at the end of event names.

Examples:

old way: film_exp_exp_0001_0

new way: film_exp_exp_0001_0___(742)_(384)

742 is X

384 is y

So when I export the subtitles need to read these X and Y values

jetdv wrote on 9/19/2023, 1:50 PM

@Qbrick Yes, that's what I said would happen - you no longer have your X and Y values. I provided a working solution with the option to be modified however you see fit to modify it. You might go back to the original code and look at this section:

private void AdjustEventSizePosition(VideoEvent vEvent, int Width, int Height, int X, int Y) 
{ 
    MatchAspect(vEvent); 
    RestoreSize(vEvent); 
    VideoMotionKeyframe kf = vEvent.VideoMotion.Keyframes[0]; 
    PositionImage(kf, (float)X, (float)Y); 
}

Test what happens when you comment out one or the other or both of these lines. Will the export be able to read the correct X and Y values then without doing all of the other work?

    MatchAspect(vEvent);
    RestoreSize(vEvent); 

Might be worth a test to see if it would still work to get the X and Y values without having to store them somewhere else. Or you might be able to use just the last two lines above and change the Export script to properly translate (it might already, I haven't tested it) back to the original numbers.

Qbrick wrote on 11/6/2023, 2:36 PM

Thanks for the video Jet! ;)

before and after your script you have to use https://github.com/captainayy/BDSup2SubPlusPlus/releases

mark-y wrote on 11/6/2023, 9:22 PM

My project is 1614 PNG.. I'm on a i7-2700k - 12GB RAM - SSD

Anything below 4th Generation i7 and 16GB RAM is going to be slow -- perhaps painfully so.