Python read/write MLT XML files

From reading other posts it sounds like compiling MLT for Python is non-trivial because it needs to connect to other binaries, like ffmpeg, etc.?

I only want to read/write the XML from MLT, change a few values, like Title text and it’s x/y coords, then write it to a new MLT file, then send it to melt.exe to to generate a video clip.

Or, I’ll create small projects in Shotcut then use them as a template to drive the production of video files by changing some of their information. I hope all this makes sense.

My guess is I could use any XML library for this, but maybe there’s already a library and python code somewhere I could start with? Any recommendations appreciated! Thanks!

I have been working on one behind the scenes. It’s not ready for release and I don’t have it on github yet. I will soon.

At the moment I’m using this for reading the project notes and then passing those off to a text-to-speech module, as well as the OpenCV Tracker experiment. It’s only very partially implemented and needs more functionality and tests before it can be close to releasing.

# shotcutmlt.py
import sys
import re
import argparse

from bs4 import BeautifulSoup 

class mltProjectFile():
	
    def __init__(self, mlt_path):
		
        self._mlt_path = mlt_path
		
        self._mlt_data = None

        self.read_file()

        return
        
    def read_file(self):
		
        """ Reads the data inside the xml file
        """
        
        with open(self._mlt_path, 'r') as f:
            data = f.read() 

        # Passing the stored data inside the beautifulsoup parser 
        self._mlt_data = BeautifulSoup(data, 'xml') 
		
        return

    def find_filter(self, filter_name, replace_data = None):
	
        """ Finds a Filter by name and optionally replaces
	        the value
        """
	    	    
        print('----------------------------------------------')

        a_name = bs_data.find_all('property', {'name':'shotcut:filter'})  
        print(a_name) 
        for a in a_name:
            value = a.string
            if value == 'affine':
			
                if filter_name != a.parent['id']:
                    continue
                
                print("Found affine ===>",value, a.parent['id'])
            
                t_name = a.parent.find('property', {'name':'transition.rect'})  
                print('  transition=', t_name.string) 
            
            elif value == 'resource':
                print("Found resource ===>",value)
                print("  parent=",a.parent)
			
            elif value == 'affineSizePosition':
			
                if filter_name != a.parent['id']:
                    continue
			
                print("Found affineSizePosition ===>",value, a.parent['id'])
            
                t_name = a.parent.find('property', {'name':'transition.rect'})  
                print('  transition=', t_name.string) 

                # reading transition
                transitions = {}
                transitions_temp = t_name.string.split(';')
                for t in transitions_temp:
				
                    splittimepos = t.split('=')
                    transitions[splittimepos[0]] = [float(s) for s in re.findall("[-+]?[.]?[\d]+(?:,\d\d\d)*[\.]?\d*(?:[eE][-+]?\d+)?", splittimepos[1])]
            
                for ft in transitions:
                    print(' *',ft,' - ',transitions[ft])
            
                if replace_data != None:
                    print('replacing transition')
                    t_name.string = replace_data
                    
                return t_name.string
            
            else:
                print("  name=",a['name'])
    
    def find_notes(self, replace_data = None):

        """ Finds a Filter by name and optionally replaces
	        the value
        """
	    	    
        result = None

        a_name = self._mlt_data.find_all('tractor', {'id':'tractor0'})  
        for a in a_name:
            t_name = a.parent.find('property', {'name':'shotcut:projectNote'})  
            result = t_name.string

        return result
		
    def write_file():
	
        return

    def bs_tree():

        return self._mlt_data	
    
		
if __name__ == '__main__' :

    # mlt file to process
    input_path = '../sjhau.info/napoleon-1/napoleon-1.mlt'

    mlt = mltProjectFile(input_path)

    print('----------------------------------------------')

    print(mlt.find_notes())
1 Like

Thanks David. I would have probably ended up using BeautifulSoup, so more reason to do so. Looks like you’re doing similar stuff so as soon as I’m functional I’ll share what I’ve coded. Thanks for your code, looks like it will already save me time!

1 Like

Here’s how I’m using it at the moment.

1 Like

I’m having to use a kludge because Beautiful soup, I believe, substitutes & for & when working with XML text (this Kdenlive property contains HTML). Does anyone have a better solution? THANKS! If not, this works, so can move on.

if __name__ == '__main__' :

    # mlt file to process
    input_path = r'C:\Max_Software\kdenlive\sample_mlt.kdenlive'
    with open(input_path, 'r') as f:
        data = f.read() 

    # Passing the stored data inside the beautifulsoup parser 
    mlt_xml = BeautifulSoup(data, features="xml") 

    xml_to_replace = mlt_xml.find('property', {'name':'xmldata'})
    xml_to_replace.string = xml_to_replace.string.replace("Max Rottersman", "New Name")

    # kludges because xml replaces &'s with &amp in HTML text of Kdenlive xmldata property;
    clean_mlt_xml = str(mlt_xml)
    clean_mlt_xml = str(mlt_xml).replace("&","&")
    clean_mlt_xml = str(clean_mlt_xml).replace(">",">")

    with open("C:\Max_Software\kdenlive\sample_mlt_new.kdenlive", "w") as file:
        file.write(clean_mlt_xml)
1 Like