380 lines
12 KiB
Ruby
Executable File
380 lines
12 KiB
Ruby
Executable File
# 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
|
|
|
|
|
|
|
|
|
|
|
|
|