# Convert timecode # Read EDL FIle input # REF # 000001 JH_ENDSCENE_PT1_ALT1.AVI V C 01:00:05:09 01:00:13:14 01:00:00:00 01:00:09:20 path = File.expand_path $0 path = File.dirname(path) require "#{ENV['RS_TOOLSROOT']}/lib/pipeline/util/string" DEBUG = true class Time attr_reader :SourceIN attr_writer :SourceIN attr_reader :SourceOUT attr_writer :SourceOUT attr_reader :EditIN attr_writer :EditIN attr_reader :EditOUT attr_writer :EditOUT attr_reader :SourceDuration attr_writer :SourceDuration def initialize() @SourceIN = Timeformat.new() @SourceOUT = Timeformat.new() @EditIN = Timeformat.new() @EditOUT = Timeformat.new() @EditDuration = 0 end class Timeformat attr_reader :Time attr_writer :Time attr_reader :Frames attr_writer :Frames def initialize() @Time = 0 @Frames = 0 end end end class EDLEntry attr_reader :Index attr_writer :Index attr_reader :Name attr_writer :Name attr_reader :AudioVideo attr_writer :AudioVideo attr_reader :Cut attr_writer :Cut attr_reader :TimeCode attr_writer :TimeCode attr_reader :MediaType attr_writer :MediaType attr_reader :RangeType attr_writer :RangeType def initialize(tokens) @Index = tokens[0].to_i @Name = filterFilename(tokens[1]) @MediaType = filterMediaType(tokens[1]) @AudioVideo = tokens[2] @Cut = tokens[3] @RangeType = "RANGE" timecode = Time.new() timecode.SourceIN.Time = tokens[tokens.length-4] timecode.SourceIN.Frames = retStartRange(timecode.SourceIN.Time).to_i timecode.SourceOUT.Time = tokens[tokens.length-3] timecode.SourceOUT.Frames = retEndRange(timecode.SourceOUT.Time).to_i timecode.EditIN.Time = tokens[tokens.length-2] timecode.EditIN.Frames = retStartRange(timecode.EditIN.Time).to_i timecode.EditOUT.Time = tokens[tokens.length-1] timecode.EditOUT.Frames = retEndRange(timecode.EditOUT.Time).to_i timecode.SourceDuration = timecode.SourceOUT.Frames - timecode.SourceIN.Frames+1 @TimeCode = timecode end def filterFilename(rawName) if rawName.include? "$" finalName = rawName.split('$')[0] elsif finalName = rawName.split('.')[0] else finalName = rawName end return finalName end def filterMediaType(filename) if filename.include? "." mediaType = filename.split('.')[1] if mediaType.length < 3 mediaType = "MOV" #Default media type in case some is cut off by avid end elsif filename.include? "$" mediaType = "MOV" #Default media type if we have a $ but no . else mediaType = "NONE" #Shouldn't ever come up, but just covering everything end return mediaType end def retStartRange(sTimecode) timeArray = retTimeArray(sTimecode) returnTimeFrames(timeArray) end def retEndRange(sTimecode) timeArray = retTimeArray(sTimecode) returnTimeFrames(timeArray)-1 end private def retTimeArray(sTimecode) timearray = Array.new() tokens = sTimecode.split(':') (0..tokens.size-1).each { |i| timearray.push(tokens[i].to_i) } timearray end # return the time from the time array def returnTimeFrames(aTimeArray) #if aTimeArray.length != 4 then fHours = 0#aTimeArray[0]*60*60*29.97 fMins = aTimeArray[1]*60*30 fSeconds = aTimeArray[2]*30 fFrames = aTimeArray[3] #@log.Message("#{fHours} #{fMins} #{fSeconds} #{fFrames}") # Total mTotalFrames = fHours + fMins + fSeconds + fFrames # THis is tha start oe end #puts mTotalFrames mDropFrames = ((mTotalFrames) + 500) / 1000 puts mTotalFrames #@log.Message("Total drop frames = #{mDropFrames}") #@log.Message("Inintal frames = #{mTotalFrames}") return mTotalFrames+mDropFrames end end class EDL # Constructors attr_reader :TITLE attr_writer :TITLE attr_reader :FCM attr_writer :FCM attr_reader :REELLIST attr_writer :REELLIST def initialize() @REELLIST = Array.new() end end class EDLParser def initialize(log) @log = log @previousEntry = nil @extraEntry = nil @rangeTypeNames = ["LEADIN", "LEADOUT", "INGAME", "MULTISTART", "BLENDOUT", "FADEIN", "FADEOUT", "BLACK"] end def readEDL(filename) #file edlEntry = EDL.new() backupFrames = 0 backupDuration = 0 fadeinDuration = 0 blackDuration = 0 justDidFakeEditFix = false File::open( filename, 'r' ) do |fp| fp.each do |line| # Skip empty lines next if 0 == line.size tokens = line.split(' ') if line.starts_with( 'TITLE' ) then edlEntry.TITLE = tokens[1] elsif line.starts_with( 'FCM' ) then edlEntry.FCM = tokens[1] elsif line.starts_with( '00' ) and tokens.length > 7 then # check we have a avi/mov referenced in teh Entry fileTokens = tokens[1].split('.') if fileTokens.length < 2 fileTokens = tokens[1].split('$') end if fileTokens.length == 2 then if tokens[2].starts_with( 'V' ) then """ =========================== NIGHTMARE EDL RANGING LOGIC 4.0 =========================== By Blake Buck There is probably a much smarter way of doing this, but I've just continued adding checks and exceptions for all the various special case scenarios that seem to keep coming up. Latest Updates 4/7/14: Added fadein/fadeout/black range detection, and updated exceptions. =========================================================================================== """ # CHECK IF THIS IS THE FIRST ENTRY # If setup properly in Avid, the first clip will never be a LEADIN/MULTI/BLENDOUT if @previousEntry == nil currentType = tokens[1].split('.')[0] # CHECK FOR FADEIN if currentType == "FADEIN" tempEntry = EDLEntry.new(tokens) fadeinDuration = tempEntry.TimeCode.SourceDuration next end # IF WE HAVE A FADEIN - Create new extra clip first with correct ranges and type if fadeinDuration != 0 fadeEntry = EDLEntry.new(tokens) fadeEntry.TimeCode.SourceIN.Frames = fadeEntry.TimeCode.SourceIN.Frames - fadeinDuration fadeEntry.TimeCode.SourceOUT.Frames = fadeEntry.TimeCode.SourceIN.Frames + fadeinDuration - 1 fadeEntry.TimeCode.SourceDuration = fadeinDuration fadeEntry.RangeType = "FADEIN" edlEntry.REELLIST.push(fadeEntry) fadeinDuration = 0 end # THEN CREATE OUR FIRST 'REAL' RANGE newEntry = EDLEntry.new(tokens) #We create a new entry @previousEntry = newEntry #And set it to previous entry edlEntry.REELLIST.push(newEntry) else # CHECKS IF THIS RANGE IS A 'LABEL RANGE' (LEADIN/MULTI/BLENDOUT) currentName = tokens[1].split('.')[0] tempEntry = EDLEntry.new(tokens) #create a temp version of the range without actually adding it to the REELLIST # CHECK FOR A FADEOUT TO FADEIN EXCEPTION - Set durations for fade and 'black' frames # I believe we will always have at least 1 frame of 'black' between fadeouts and fadeins. if (@previousEntry.RangeType == "FADEOUT") and currentName == ("FADEIN") fadeinDuration = tempEntry.TimeCode.SourceDuration blackDuration = tempEntry.TimeCode.EditIN.Frames - @previousEntry.TimeCode.EditOUT.Frames justDidFakeEditFix = false # NEW LABEL EXCEPTION TO FAKE EDIT FIX (This is way to goddamn complicated.) # If we just did a fake edit fix, but now know it was a sequential new label, we correct the old # ranges (technically 2 ranges back now) and create a new range using the saved @extraEntry elsif (@rangeTypeNames.include? currentName) and (justDidFakeEditFix == true) @previousEntry.TimeCode.SourceOUT.Frames = backupFrames @previousEntry.TimeCode.SourceDuration = backupDuration @extraEntry.RangeType = currentName #And updated the rangetype to the correct label edlEntry.REELLIST.push(@extraEntry) @previousEntry = @extraEntry justDidFakeEditFix = false # IF THIS IS A REGULAR LABEL (and not a fake edit exception) elsif (@rangeTypeNames.include? currentName) and (justDidFakeEditFix == false) @previousEntry.RangeType = currentName #We change the type of the previous entry justDidFakeEditFix = false # RANGE IS A REGULAR RANGE AND NOT A LABEL # CHECK IF WE HAVE A 'FAKE EDIT' (2 immediately sequential 'regular' ranges) elsif (@previousEntry.RangeType == "RANGE") and (tempEntry.RangeType == "RANGE") and ((@previousEntry.TimeCode.SourceOUT.Frames + 1) == (tempEntry.TimeCode.SourceIN.Frames)) backupFrames = @previousEntry.TimeCode.SourceOUT.Frames backupDuration = @previousEntry.TimeCode.SourceDuration @extraEntry = tempEntry @previousEntry.TimeCode.SourceOUT.Frames = tempEntry.TimeCode.SourceOUT.Frames @previousEntry.TimeCode.SourceDuration = @previousEntry.TimeCode.SourceDuration + tempEntry.TimeCode.SourceDuration justDidFakeEditFix = true else #OR WE JUST HAVE A VALID NEW RANGE # CHECK FOR BLACK RANGES FIRST if blackDuration != 0 blackEntry = EDLEntry.new(tokens) blackEntry.TimeCode.SourceIN.Frames = blackEntry.TimeCode.SourceIN.Frames - blackDuration - fadeinDuration blackEntry.TimeCode.SourceOUT.Frames = blackEntry.TimeCode.SourceIN.Frames + blackDuration - 1 blackEntry.TimeCode.SourceDuration = blackDuration blackEntry.RangeType = "BLACK" edlEntry.REELLIST.push(blackEntry) blackDuration = 0 end # CHECK FOR FADEIN RANGES NEXT if fadeinDuration != 0 fadeEntry = EDLEntry.new(tokens) fadeEntry.TimeCode.SourceIN.Frames = fadeEntry.TimeCode.SourceIN.Frames - fadeinDuration fadeEntry.TimeCode.SourceOUT.Frames = fadeEntry.TimeCode.SourceIN.Frames + fadeinDuration - 1 fadeEntry.TimeCode.SourceDuration = fadeinDuration fadeEntry.RangeType = "FADEIN" edlEntry.REELLIST.push(fadeEntry) fadeinDuration = 0 end # THEN CREATE A BASIC CLIP (Regardless if fadein / black ranges are created or not) newEntry = EDLEntry.new(tokens) #Create a new entry @previousEntry = newEntry #Set it to previous entry edlEntry.REELLIST.push(newEntry) justDidFakeEditFix = false end end end end end end end # DEbug Output if DEBUG then puts edlEntry.REELLIST.each do |entry| tab = "\t" if entry.Name.length < 24 then tab = "\t\t" end puts "#{entry.Name}#{tab}#{entry.TimeCode.SourceIN.Frames}\t#{entry.TimeCode.SourceOUT.Frames}\t#{entry.TimeCode.SourceDuration}\t#{entry.RangeType}" end end return edlEntry end end #require 'RSG.Base.dll' #require 'RSG.Base.Configuration.dll' #require 'RSG.Base.Windows.dll' #require 'RSG.SourceControl.Perforce' # Core #include RSG::Base::Configuration #include RSG::Base::Logging #include RSG::Base::Logging::Universal #include RSG::Base::OS #include RSG::SourceControl::Perforce #----------------------------------------------------------------------------- # Entry-Point #----------------------------------------------------------------------------- #if ( __FILE__ == $0 ) then #g_Options = OS::Options::new( OPTIONS ) #Need to enable the initlaize on the TOOLS NG Branch # LogFactory.Initialize() # LogFactory.CreateApplicationConsoleLogTarget( ) # @log = LogFactory.ApplicationLog # @log.message("Using Server: #{"cheese"}") # filename = 'X:/rdr3/assets/non_final/cutscene/EDL/UTP1MCS1.EDL' #TRV5INT.EDL # parser = EDLParser.new{ } # parser.readEDL(filename) # end