EDL export script, help needed!

There was a question on the forum on how to export EDL files from the VSE.

I personally think Blender is really missing the option to export EDL (CMX 3600) from the VSE. If Blender was able to export EDL files you would be able to import your edits into Davinci Resolve for color grading or DAWs(using a file converter) for audio editing. My ultimate dream is that most open sourced video-related softwares will be able to use this format. ShotCut and Flowblade has implemented EDL export. Kdenlive-folks are considering it and Natron-folks are considering adding EDL import.

An EDL file is basically just a text file:

001  10_sec   AA/V  C        00:00:00:00 00:00:01:00 00:00:00:00 00:00:01:00
* FROM CLIP NAME: 10_sec.mov
111^^22222222^^3333^^4444^555^66666666666^77777777777^88888888888^99999999999

The number of characters for each column must be exact. More info on the format: http://www.scottsimmons.tv/blog/2006/10/12/how-to-read-an-edl/

I have no prior experience with Python(I’ve coded in Amiga basic, 3ds max script, wxBasic, Avisynth script and a bit of AutoIt script), but the Python syntax is driving me nuts.

At the moment I have batFINGER’s script to extract the clip info(fades and dissolves missing): http://blender.stackexchange.com/questions/53330/how-to-get-a-clips-source-media-starting-and-ending-frame-numbers


import bpycontext = bpy.context
scene = context.scene
vse = scene.sequence_editor

for strip in vse.sequences_all:
    # Edit Strip Panel
    print("-" * 72)
    print(strip.name)
    print(strip.type)
    # extend for other strip types.
    if strip.type in ['MOVIE']:
        print(strip.filepath)
    elif strip.type in ['SOUND']:
        print(strip.sound.filepath)
    print(strip.channel)
    print(strip.frame_start)
    print(strip.frame_final_duration)
    # Trim Duration (soft)
    print(strip.frame_offset_start)     print(strip.frame_offset_end)

And Campbell Barton’s TimeCode functions to convert the frame numbers to SMPTE timecode strings: https://git.blender.org/gitweb/gitweb.cgi/blender-addons.git/blob_plain/HEAD:/io_sequencer_edl/parse_edl.py

And between the TimeCode functions there is a function to almost generate the above mentioned EDL line called: repr


    def __repr__(self):
        txt = "num: %d, " % self.number
        txt += "reel: %s, " % self.reel
        txt += "edit_type: "
        txt += EditDecision.edit_flags_to_text(self.edit_type) + ", "

        txt += "trans_type: "
        for item, val in TRANSITION_DICT.items():
            if val == self.transition_type:
                txt += item + ", "
                break

        txt += "m2: "
        if self.m2:
            txt += "%g" % float(self.m2.fps)
            txt += "
	"
            txt += self.m2.data
        else:
            txt += "nil"

        txt += ", "
        txt += "recIn: " + str(self.recIn) + ", "
        txt += "recOut: " + str(self.recOut) + ", "
        txt += "srcIn: " + str(self.srcIn) + ", "
        txt += "srcOut: " + str(self.srcOut) + ", "

return txt

(The string needs to be corrected a bit. Ex. the columns shouldn’t be separated by commas and each item should have an exact number of characters/spaces etc.)

However I’m that ignorant, that I can’t figure out how to call the functions to convert the frames into SMPTE timecode strings and I can’t figure out how to feed that information into the repr function and save each line to a text file.

Can anyone help me to merge these scripts into a draft script, so I can tinker with it from there, until ex. Resolve can import it successfully?

That would be an helpful add-on, Tin2Tin.

Yes, I think so. I hope someone will step up and give us a hand.

Here’s a python script to save the EDL in the correct format: https://github.com/wrzwicky/Lightworks-Converter/blob/master/edl.py

So what is needed now is to extract the rest of the data from the sequencer like dissolves, convert ex. frames to smpte and merge all of the scripts together.

#!/usr/bin/python3
"""
edl.py -- Classes to help create Edit Description List ("EDL") files.

Copyright (C) 2015 William R. Zwicky <[email protected]>

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.
"""

class EDLBlock:
    def __init__(self):
        self.id = 0
        """Num, 3 digits, officially 001-999. Non-num makes row a comment."""
        self.reel = None
        """Reference to media file or tape.
           Officially: 4-digit num, optional B; or BL for black,
           or AX for aux source.
           Unofficially: any string."""
        self.channels = None
        """A=audio1,V=video1,B=A+V,A2=audio2,A2/V=A2+V,AA=A1+A2,AA/V=A1+A2+V"""
        self.transition = None
        """C=cut,
           D=dissolve,
           Wxxx=wipe type xxx,
           KB=key background,
           K=key foreground,
           KO=key foreground mask"""
        self.transDur = None
        """3-digit duration, in frames, or lone F"""
        self.srcIn = None
        """timecode (hh:mm:ss:ff)"""
        self.srcOut = None
        """timecode (hh:mm:ss:ff)"""
        self.recIn = None
        """timecode (hh:mm:ss:ff)"""
        self.recOut = None
        """timecode (hh:mm:ss:ff). Either out-time or duration.
           Ignored on read; clip length is srcOut-srcIn."""

class EDL(list):
    def __init__(self):
        self.title = None
        self.dropframe = True
        self.reels = {}
        #self.edits = []

    def load(filename):
        pass

    def savePremiere(self):
        # CMX 3600:
        #   111^^222^^3333^^4444^555^666666666666^777777777777^888888888888^999999999999^
        # Old Lightworks converter:
        #   003  E00706EU  V     D    030 00:00:26:29 00:00:32:10 00:00:01:02 00:00:07:13
        #   111^^22222222^^3333^^4444^555^66666666666^77777777777^88888888888^99999999999
        # Export from Premiere:
        # 003  AX       AA    C        00:00:00:10 00:02:03:24 00:00:53:25 00:02:57:09
        # * FROM CLIP NAME: Ep6_Sc2 - Elliot tries again with Tiff.mp4

        if not not self.title:
            print("TITLE: ", self.title)
        if self.dropframe:
            print("FCM: DROP FRAME")
        else:
            print("FCM: NON DROP FRAME")
        print()
        
        for block in self:
            s = "%03d  %-8s  %-4s  %-4s %03s %-11s %-11s %-11s %-11s" \
                % (block.id, block.reel, block.channels,
                   block.transition, block.transDur,
                   block.srcIn, block.srcOut, block.recIn, block.recOut)
            print(s)



# TITLE: title
# FCM: DROP FRAME | NON DROP FRAME

if __name__ == '__main__':
    e = EDL()
    e.title = "Test script"
    b = EDLBlock()
    b.id = 2
    b.reel = "7x432"
    b.channels = "B"
    e.append(b)
    b = EDLBlock()
    b.id = 3
    b.reel = "9b777"
    b.channels = "AA/V"
    e.append(b)
     e.savePremiere()

Here’s how far I am atm. Correct the export path in the last line of the script to test it. I haven’t found out how to pull information on crossfades and fades. Any help much appreciated.

import bpy
import os


"""
export_edl.py -- Export of Edit Description List ("EDL") files.


This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.


This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.


You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.
"""


# TimeCode class by Campbell Barton
class TimeCode:
    """
    Simple timecode class
    also supports conversion from other time strings used by EDL
    """
    __slots__ = (
        "fps",
        "hours",
        "minutes",
        "seconds",
        "frame",
    )


    def __init__(self, data, fps):
        self.fps = fps
        if type(data) == str:
            self.from_string(data)
            frame = self.as_frame()
            self.from_frame(frame)
        else:
            self.from_frame(data)


    def from_string(self, text):
        # hh:mm:ss:ff
        # No dropframe support yet


        if text.lower().endswith("mps"):  # 5.2mps
            return self.from_frame(int(float(text[:-3]) * self.fps))
        elif text.lower().endswith("s"):  # 5.2s
            return self.from_frame(int(float(text[:-1]) * self.fps))
        elif text.isdigit():  # 1234
            return self.from_frame(int(text))
        elif ":" in text:  # hh:mm:ss:ff
            text = text.replace(";", ":").replace(",", ":").replace(".", ":")
            text = text.split(":")


            self.hours = int(text[0])
            self.minutes = int(text[1])
            self.seconds = int(text[2])
            self.frame = int(text[3])
            return self
        else:
            print("ERROR: could not convert this into timecode %r" % text)
            return self


    def from_frame(self, frame):


        if frame < 0:
            frame = -frame
            neg = True
        else:
            neg = False


        fpm = 60 * self.fps
        fph = 60 * fpm


        if frame < fph:
            self.hours = 0
        else:
            self.hours = int(frame / fph)
            frame = frame % fph


        if frame < fpm:
            self.minutes = 0
        else:
            self.minutes = int(frame / fpm)
            frame = frame % fpm


        if frame < self.fps:
            self.seconds = 0
        else:
            self.seconds = int(frame / self.fps)
            frame = frame % self.fps


        self.frame = frame


        if neg:
            self.frame = -self.frame
            self.seconds = -self.seconds
            self.minutes = -self.minutes
            self.hours = -self.hours


        return self


    def as_frame(self):
        abs_frame = self.frame
        abs_frame += self.seconds * self.fps
        abs_frame += self.minutes * 60 * self.fps
        abs_frame += self.hours * 60 * 60 * self.fps


        return abs_frame


    def as_string(self):
        self.from_frame(int(self))
        return "%.2d:%.2d:%.2d:%.2d" % (self.hours, self.minutes, self.seconds, self.frame)


    def __repr__(self):
        return self.as_string()


    # Numeric stuff, may as well have this
    def __neg__(self):
        return TimeCode(-int(self), self.fps)


    def __int__(self):
        return self.as_frame()


    def __sub__(self, other):
        return TimeCode(int(self) - int(other), self.fps)


    def __add__(self, other):
        return TimeCode(int(self) + int(other), self.fps)


    def __mul__(self, other):
        return TimeCode(int(self) * int(other), self.fps)


    def __div__(self, other):
        return TimeCode(int(self) // int(other), self.fps)


    def __abs__(self):
        return TimeCode(abs(int(self)), self.fps)


    def __iadd__(self, other):
        return self.from_frame(int(self) + int(other))


    def __imul__(self, other):
        return self.from_frame(int(self) * int(other))


    def __idiv__(self, other):
        return self.from_frame(int(self) // int(other))
# end timecode


#EDLBlock Copyright (C) 2015 William R. Zwicky <[email protected]>
class EDLBlock:
    def __init__(self):
        self.id = 0
        """Num, 3 digits, officially 001-999. Non-num makes row a comment."""
        self.reel = None
        """Reference to media file or tape.
           Officially: 4-digit num, optional B; or BL for black,
           or AX for aux source.
           Unofficially: any string."""
        self.channels = None
        """A=audio1,V=video1,B=A+V,A2=audio2,A2/V=A2+V,AA=A1+A2,AA/V=A1+A2+V"""
        self.transition = None
        """C=cut,
           D=dissolve,
           Wxxx=wipe type xxx,
           KB=key background,
           K=key foreground,
           KO=key foreground mask"""
        self.transDur = None
        """3-digit duration, in frames, or lone F"""
        self.srcIn = None
        """timecode (hh:mm:ss:ff)"""
        self.srcOut = None
        """timecode (hh:mm:ss:ff)"""
        self.recIn = None
        """timecode (hh:mm:ss:ff)"""
        self.recOut = None
        """timecode (hh:mm:ss:ff). Either out-time or duration.
           Ignored on read; clip length is srcOut-srcIn."""
        self.file = None
        """filename and extention, but no path"""          


class EDL(list):
    def __init__(self):
        self.title = None
        self.dropframe = False
        self.reels = {}
        #self.edits = []


    def load(filename):
        pass


    def savePremiere(self):
        # CMX 3600:
        #   111^^222^^3333^^4444^555^666666666666^777777777777^888888888888^999999999999^
        # Old Lightworks converter:
        #   003  E00706EU  V     D    030 00:00:26:29 00:00:32:10 00:00:01:02 00:00:07:13
        #   111^^22222222^3333^4444^555^^66666666666^77777777777^88888888888^99999999999
        # Export from Premiere:
        # 003  AX       AA    C        00:00:00:10 00:02:03:24 00:00:53:25 00:02:57:09
        # * FROM CLIP NAME: Ep6_Sc2 - Elliot tries again with Tiff.mp4
        s=""
        if not not self.title:
            s="TITLE: " + self.title+"
"
        if self.dropframe:
            s += "FCM: DROP FRAME"+"
"
        else:
            s += "FCM: NON DROP FRAME"+"

"


        for block in self:
            s += "%03d  %-8s %-4s %-4s %03s  %-11s %-11s %-11s %-11s" \
                % (block.id, block.reel, block.channels,
                   block.transition, block.transDur,
                   block.srcIn, block.srcOut, block.recIn, block.recOut)+"
* FROM CLIP NAME: " + block.file + "
"
        print(s)
        return(s)




context = bpy.context
scene = context.scene
vse = scene.sequence_editor


edl_fps=scene.render.fps
id_count=1


e = EDL()
e.title = "Test script"
e.dropframe=False


for strip in vse.sequences_all:
#    # Edit Strip Panel
#    #print("-" * 72)
#    print(strip.name)
#    print(strip.type)
#    # extend for other strip types.
#    if strip.type in ['MOVIE']:
#        print(strip.filepath)
#    elif strip.type in ['SOUND']:
#        print(strip.sound.filepath)
#    print(strip.channel)
#    print(strip.frame_start)
#    print(strip.frame_final_duration)
#    # Trim Duration (soft)
#    print(strip.frame_offset_start)
#    print(strip.frame_offset_end)
    
    b = EDLBlock()
    b.id = id_count
    id_count=id_count+1
    if strip.type in ['MOVIE']:
        reelname = bpy.path.basename(strip.filepath)
        b.file=reelname
        reelname = os.path.splitext(reelname)[0]        
        b.reel = ((reelname+"        ")[0:8])                
        b.channels = "V   "
    elif strip.type in ['SOUND']:
        reelname = bpy.path.basename(strip.sound.filepath)
        b.file=reelname
        reelname = os.path.splitext(reelname)[0]        
        b.reel = ((reelname+"        ")[0:8])        
        b.channels = "A   " 
    #elif strip.type in ['CROSS']:            
        
    b.transition = "C   "
    b.transDur = "   "
    b.srcIn = TimeCode(strip.frame_offset_start,edl_fps)
    b.srcOut = TimeCode(strip.frame_offset_start+strip.frame_final_duration,edl_fps)
    b.recIn = TimeCode(strip.frame_final_start,edl_fps)
    b.recOut = TimeCode(strip.frame_final_end,edl_fps)                
    e.append(b)


#write to a file
file_handle = open("C:/Users/User/Desktop/mytest.edl","w")
file_handle.write(e.savePremiere())
file_handle.close()



Got a working prototype now. Davinci Resolve reads the edl-files.
Copy/paste it into the script editor and change the save-path in the end of the script to something meaningful for you.

import bpy
import os




"""
export_edl.py -- Export of Edit Description List ("EDL") files.


This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.


This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.


You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.
"""


# TimeCode class by Campbell Barton
class TimeCode:
    """
    Simple timecode class
    also supports conversion from other time strings used by EDL
    """
    __slots__ = (
        "fps",
        "hours",
        "minutes",
        "seconds",
        "frame",
    )


    def __init__(self, data, fps):
        self.fps = fps
        if type(data) == str:
            self.from_string(data)
            frame = self.as_frame()
            self.from_frame(frame)
        else:
            self.from_frame(data)


    def from_string(self, text):
        # hh:mm:ss:ff
        # No dropframe support yet


        if text.lower().endswith("mps"):  # 5.2mps
            return self.from_frame(int(float(text[:-3]) * self.fps))
        elif text.lower().endswith("s"):  # 5.2s
            return self.from_frame(int(float(text[:-1]) * self.fps))
        elif text.isdigit():  # 1234
            return self.from_frame(int(text))
        elif ":" in text:  # hh:mm:ss:ff
            text = text.replace(";", ":").replace(",", ":").replace(".", ":")
            text = text.split(":")


            self.hours = int(text[0])
            self.minutes = int(text[1])
            self.seconds = int(text[2])
            self.frame = int(text[3])
            return self
        else:
            print("ERROR: could not convert this into timecode %r" % text)
            return self


    def from_frame(self, frame):


        if frame < 0:
            frame = -frame
            neg = True
        else:
            neg = False


        fpm = 60 * self.fps
        fph = 60 * fpm


        if frame < fph:
            self.hours = 0
        else:
            self.hours = int(frame / fph)
            frame = frame % fph


        if frame < fpm:
            self.minutes = 0
        else:
            self.minutes = int(frame / fpm)
            frame = frame % fpm


        if frame < self.fps:
            self.seconds = 0
        else:
            self.seconds = int(frame / self.fps)
            frame = frame % self.fps


        self.frame = frame


        if neg:
            self.frame = -self.frame
            self.seconds = -self.seconds
            self.minutes = -self.minutes
            self.hours = -self.hours


        return self


    def as_frame(self):
        abs_frame = self.frame
        abs_frame += self.seconds * self.fps
        abs_frame += self.minutes * 60 * self.fps
        abs_frame += self.hours * 60 * 60 * self.fps


        return abs_frame


    def as_string(self):
        self.from_frame(int(self))
        return "%.2d:%.2d:%.2d:%.2d" % (self.hours, self.minutes, self.seconds, self.frame)


    def __repr__(self):
        return self.as_string()


    # Numeric stuff, may as well have this
    def __neg__(self):
        return TimeCode(-int(self), self.fps)


    def __int__(self):
        return self.as_frame()


    def __sub__(self, other):
        return TimeCode(int(self) - int(other), self.fps)


    def __add__(self, other):
        return TimeCode(int(self) + int(other), self.fps)


    def __mul__(self, other):
        return TimeCode(int(self) * int(other), self.fps)


    def __div__(self, other):
        return TimeCode(int(self) // int(other), self.fps)


    def __abs__(self):
        return TimeCode(abs(int(self)), self.fps)


    def __iadd__(self, other):
        return self.from_frame(int(self) + int(other))


    def __imul__(self, other):
        return self.from_frame(int(self) * int(other))


    def __idiv__(self, other):
        return self.from_frame(int(self) // int(other))
# end timecode


#EDLBlock Copyright (C) 2015 William R. Zwicky <[email protected]>
class EDLBlock:
    def __init__(self):
        self.id = 0
        """Num, 3 digits, officially 001-999. Non-num makes row a comment."""
        self.reel = None
        """Reference to media file or tape.
           Officially: 4-digit num, optional B; or BL for black,
           or AX for aux source.
           Unofficially: any string."""
        self.channels = None
        """A=audio1,V=video1,B=A+V,A2=audio2,A2/V=A2+V,AA=A1+A2,AA/V=A1+A2+V"""
        self.transition = None
        """C=cut,
           D=dissolve,
           Wxxx=wipe type xxx,
           KB=key background,
           K=key foreground,
           KO=key foreground mask"""
        self.transDur = None
        """3-digit duration, in frames, or lone F"""
        self.srcIn = None
        """timecode (hh:mm:ss:ff)"""
        self.srcOut = None
        """timecode (hh:mm:ss:ff)"""
        self.recIn = None
        """timecode (hh:mm:ss:ff)"""
        self.recOut = None
        """timecode (hh:mm:ss:ff). Either out-time or duration.
           Ignored on read; clip length is srcOut-srcIn."""
        self.file = None
        """filename and extention, but no path"""          

#EDL Class - Copyright (C) 2015 William R. Zwicky <[email protected]>
class EDL(list):
    def __init__(self):
        self.title = None
        self.dropframe = False
        self.reels = {}
        #self.edits = []


    def load(filename):
        pass


    def savePremiere(self):
        # CMX 3600:
        #   111^^222^^3333^^4444^555^666666666666^777777777777^888888888888^999999999999^
        # Old Lightworks converter:
        #   003  E00706EU  V     D    030 00:00:26:29 00:00:32:10 00:00:01:02 00:00:07:13
        #   111^^22222222^3333^4444^555^^66666666666^77777777777^88888888888^99999999999
        # Export from Premiere:
        # 003  AX       AA    C        00:00:00:10 00:02:03:24 00:00:53:25 00:02:57:09
        # * FROM CLIP NAME: Ep6_Sc2 - Elliot tries again with Tiff.mp4
        s=""
        if not not self.title:
            s="TITLE: " + self.title+"
"
        if self.dropframe:
            s += "FCM: DROP FRAME"+"
"
        else:
            s += "FCM: NON DROP FRAME"+"

"


        for block in self:
            s += "%03d  %-8s %-4s %-4s %03s  %-11s %-11s %-11s %-11s" \
                % (block.id, block.reel, block.channels,
                   block.transition, block.transDur,
                   block.srcIn, block.srcOut, block.recIn, block.recOut)+"
* FROM CLIP NAME: " + block.file + "
"
        print(s)
        return(s)


context = bpy.context
scene = context.scene
vse = scene.sequence_editor


edl_fps=scene.render.fps
id_count=1


e = EDL()
e.title = "Test script"
e.dropframe=False


for strip in vse.sequences_all:
    b = EDLBlock()
    b.id = id_count
    id_count=id_count+1
    if strip.type in ['MOVIE']:
        reelname = bpy.path.basename(strip.filepath)
        b.file=reelname
        reelname = os.path.splitext(reelname)[0]        
        b.reel = ((reelname+"        ")[0:8])                
        b.channels = "V   "
    elif strip.type in ['SOUND']:
        reelname = bpy.path.basename(strip.sound.filepath)
        b.file=reelname
        reelname = os.path.splitext(reelname)[0]        
        b.reel = ((reelname+"        ")[0:8])        
        b.channels = "A   " 
    #elif strip.type in ['CROSS']:            
        
    b.transition = "C   "
    b.transDur = "   "
    b.srcIn = TimeCode(strip.frame_offset_start,edl_fps)
    b.srcOut = TimeCode(strip.frame_offset_start+strip.frame_final_duration,edl_fps)
    b.recIn = TimeCode(strip.frame_final_start,edl_fps)
    b.recOut = TimeCode(strip.frame_final_end,edl_fps)                
    e.append(b)


#write to a file
file_handle = open("C:/Users/User/Desktop/EDLBUG/mytest.edl","w")
file_handle.write(e.savePremiere())
file_handle.close()



I have a couple of questions:

  • How to pull information from cross dissolves and fades about which clips are affected.
  • How do I change the order of the reading of the clips in the sequencer? It seems to be going from the lower right corner to the top left and I need it to go in the opposite direction. (Ex. Lwks needs clips to be added from left to right).
  • How do I make it save an edl file pr. track?
  • Any pointers on how I turn it into a script which can be accessed from the File menu?

Get the latest ‘EDL export’ code here: http://codepad.org/TQ7HAQo8
(Correct the output path in the end of the script)
Now crossfades with video has been added.
Having other elements than video/audio/crossfades may crash the script.

I need help to figure out how to read sequencer data from the top left corner to the lower right corner?
As the script is now clip data is read/written in the order, the clips has been added to the timeline and not their locations.

Now the script exports VSE edits fine to Davinci Resolve, but only video and audio clips. I can’t find a way to pull the ‘inside clip’ in and out points of transitions and reached a dead end:


http://blender.stackexchange.com/questions/76589/how-to-access-the-values-of-the-next-item-of-a-list-in-a-for-loop?noredirect=1&lq=1

If I can get the transitions solved, the next thing is to figure out how to add the script to the File > Export menu.

Here’s the latest code:
http://codepad.org/CA30FHUm
(Correct the output path in the end of the script)

The EDL export script now supports export of movie, sound and cross dissolve clips. Tested in Resolve and Lightworks. Drop-frame framerates not supported.

How to: In the VSE collect your movie clips in one track and the sound clips in as few tracks as possible and run the script.

Please test the attached file, by running it in the script window. I still need to figure out how to let it run from the File > Export menu. Any help appreciated.

Attachments

io_export_sequencer_edl.zip (4.39 KB)

Have you tried round tripping a VSE cut back to the VSE using Campbells EDL importer?

Yes, the timings are correct, but not imported cross dissolves(the opacity changes are over the entire clip and not only in the transition area).

However the exported dissolves are working correctly in Lightworks and Davinci Resolve.

Maybe some API chances has caused the import to change? I’m only taking my first baby steps into python land, so I not the right person to figure that out.

Also the import edl script seems imprecise: https://github.com/jliljebl/flowblade/issues/252

I’m using Campbell’s timecode functions so that imprecision might also be in the exporter, but I haven’t tested with longer strips.

I’m trying to add the script as an add-on to the User Preferences, but I get an error:

I haven’t got a clue what this means?

Here’s the latest script:
io_export_sequencer_edl.zip (4.53 KB)

Sorry I can’t help with code pointers but have you tried getting advice at Blender stack exchange? I have found it to be very responsive.

Concerning transitions the ones supported by the EDL script are the ones made as a clip between clips in the same track and not ones with keyframed opacity.

Another consideration is to let the script export an EDL file pr. track. EDL files only supports one video track and four audio tracks and prefers only one video and one audio track.

Typical EDLs contain at least 2 tracks of audio, to support stereo. Of course Blender displays stereo strips as a single track (+pan). EDL do not support panning, it is implicit in 2 track approach.

The type of dissolve for a single track video is called a horizontal transition (stacked strips would use vertical transitions). I don’t recall when the the transition starts? At the end of strip A? Applications often let you vary the transition start e.g… Start, Middle, End or variable.
I guess you’ve seen these guides?
http://www.edlmax.com/edlmaxhelp/edl/maxguide.html
http://www.scottsimmons.tv/blog/2006/10/12/how-to-read-an-edl/

Also Apple introduced an XML format that is human readable (unlike the AVID version), that could be more useful.

I wonder if an EDL could be reconstructed in the compositor? I suppose its just writing frame offsets into Image/Movie nodes with transitions driven by frame function?

My interest in EDL is to use this simple format for getting projects in/out of all the free open sourced video/audio related apps out there.

Conversion:
Mlt2edl(conversion): https://eyeframeconverter.wordpress.com/mlt2edl/
(Mlt is the “engine” of ShotCut, Flowblade, Kdenlive and OpenShot)

Implemented:
Shotcut (Export): https://forum.shotcut.org/t/mlt2edl-script-error-fix/55
Flowblade (Export): https://github.com/jliljebl/flowblade/issues/252
Blender (Import/almost Export)

Some interest:
Natron (Import): https://github.com/MrKepzie/Natron/issues/1170

Not so much interest:
Ardour (Import): https://community.ardour.org/node/13913
Audacity (Import): http://forum.audacityteam.org/viewtopic.php?f=56&t=91810&p=307134&hilit=lof#p307134
http://forum.audacityteam.org/viewtopic.php?f=56&t=81123

Not open sourced, but free:
Davinci Resolve: (import/export)
Lightworks: (Import. Export not supported in free version. Script to convert Lwks projects to EDL: https://github.com/wrzwicky/Lightworks-Converter )

All payware NLEs support EDL, like Avid, Premiere and Final Cut.

Now the EDL exporter will export one edl file pr. track. In Davinci Resolve you can right click on the first imported edl file in the media pool and select import ‘edl in new track’. This way you can import all tracks one by one.

(One strange bug in Resolve is that the beginning of the timeline is created by the first imported clip, so add a clip at frame 00:00:00:00 before exporting from Blender in the first track. Another Resolve problem is that it ignores audio in edl files…)

I still haven’t found out why the script can’t register as a proper add-on - using the user preferences - however after you first run the script, you’ll be able to run it from menu: File > Export > Timeline (.edl)

Attachments

io_export_sequencer_edl_single_tracks_tracks.zip (4.84 KB)

Yes I think that the Audio issue in Resolve is intentional, as there are paid for converters that will transfer legacy FCP XML and edl etc. to new FCPX format.

Not sure Resolve would limit something like that. Their EDL export is also very sparse, so it proberly just not a priority for them. Edl is proberly mostly used in Resolve as a way to reconform edits. Where you render the timeline to one file and then import an edl of the videotrack to add all the cuts to the timeline for colorgrading.

Did anyone test the script yet?