Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
e091865
added otiodiff script
cato-o Jun 9, 2025
619ca8b
added command line arg and function framework for otiodiff
cato-o Jul 30, 2025
ab4ecac
ported otiodiff into console scripts and added otiodiff as an option …
cato-o Aug 6, 2025
814c261
updated file output of otiodiff to use output of otiotool
cato-o Aug 6, 2025
2eaae82
comments cleanup
cato-o Aug 7, 2025
d6efa99
added track type to processTracks and makeTrack to support timelines …
cato-o Aug 7, 2025
b4cf7ef
added clipDB to hold all sorted clips and added check for clips that …
cato-o Aug 8, 2025
8978296
sort out moved clips in db and make new moved track with new color in…
cato-o Aug 11, 2025
cf3288f
removed move classification if move within same track
cato-o Aug 11, 2025
db1022b
added handling of unmatched extra tracks back in
cato-o Aug 11, 2025
9a2831d
reorder audio timeline display order to have B on top
cato-o Aug 12, 2025
8cc3ad8
added overall summary stats and did some code cleanup
cato-o Aug 12, 2025
5899116
added unit test file for otiodiff and ported existing tests over
cato-o Aug 13, 2025
ad82f25
code organization and adding comments
cato-o Aug 13, 2025
4d6a751
renamed otiodif.py to otiodiff.py, fixed bugs, and added todo's
cato-o Aug 14, 2025
16d0fff
added input otio error handling/warnings, added video/audio tracks ex…
cato-o Aug 14, 2025
64ea08e
changed ClipData init to pull info from source OTIO clip, updated ins…
cato-o Aug 15, 2025
fb26df8
added otiodiff description and example to otiotool description, fixed…
cato-o Aug 15, 2025
b40e417
added docstrings, comments, and updated variable names
cato-o Aug 15, 2025
f86d739
fixed timeline output bug and return empty timeline instead of None
cato-o Aug 15, 2025
2debbb6
switched color in makeOtio to use OTIO Color rather than strings, add…
cato-o Aug 16, 2025
e7972f4
made color an optional parameter in addMarker and added examples for …
cato-o Aug 16, 2025
7e862e6
added edit tests
cato-o Aug 18, 2025
d34ef9d
Merge remote-tracking branch 'upstream/main' into otiodiff
cato-o Aug 18, 2025
c1bf13b
added variables to specify color-coding, and updated formatting
cato-o Aug 18, 2025
15d88f8
fixed trackA to use unchanged clips from timeline A instead of timeli…
cato-o Aug 19, 2025
afa7ba4
padded track set that have less tracks with empty tracks for matched …
cato-o Aug 20, 2025
22e8692
added ability to specify comparisons by name or by full_name for clip…
cato-o Aug 20, 2025
704a4f9
Added docstrings for clipData
cato-o Aug 20, 2025
cae61a4
added todo's
cato-o Aug 21, 2025
cb34d06
Code cleanup
cato-o Aug 22, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file.
130 changes: 130 additions & 0 deletions src/py-opentimelineio/opentimelineio/console/otiodiff/clipData.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
import opentimelineio as otio

# TODO: clip comparable??? ClipInfo
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's go with ClipInfo

# source clip or clip ref?

class ClipData:
"""ClipData holds information from an OTIO clip that's necessary for
comparing differences. It also keeps some information associated with
the clip after comparisons are made, such as a matched ClipData and a note
about what has changed.

source_clip = original OTIO clip the ClipData represents
full_name = full name of source_clip
name and version splits full_name on space
ex: full_name: clipA version1, name: clipA, version: version1
"""
def __init__(self, source_clip, track_num, note=None):
self.full_name = source_clip.name
self.name, self.version = self.splitFullName(source_clip)
self.media_ref = source_clip.media_reference
self.source_range = source_clip.source_range
self.timeline_range = source_clip.trimmed_range_in_parent()
self.track_num = track_num
self.source_clip = source_clip
self.note = note
self.matched_clipData = None

# split full name into name of clip and version by white space
# uses structure of "clipA v1" where clipA is the name and v1 is the version
def splitFullName(self, clip):
"""Split full name into name and version by space. Returns None for
version if full name contains no spaces."""
parts = clip.name.split(" ")
shortName = parts[0]
version = parts[1] if len(parts) > 1 else None

return shortName, version

def printData(self):
"""Prints to console all parameters of ClipData"""
print("name: ", self.name)
print("version: ", self.version)
print("media ref: ", self.media_ref)
print("source start time: ", self.source_range.start_time.value,
" duration: ", self.source_range.duration.value)
print("timeline start time:", self.timeline_range.start_time.value,
" duration: ", self.timeline_range.duration.value)
if (self.note != ""):
print("note: ", self.note)
print("source clip: ", self.source.name)

def sameName(self, cA):
"""Compare names and returns if they are the same"""
if (self.name.lower() == cA.name.lower()):
return True
else:
return False

# note: local and source duration should always match, can assume same
def sameDuration(self, cA):
"""Compare duration within the timeline of this ClipData
against another ClipData"""
return self.timeline_range.duration.value == cA.timeline_range.duration.value


def checkSame(self, cA):
"""Check if this ClipData is the exact same as another ClipData or if
it's the same just moved along the timeline. Updates note based on edits"""
isSame = False
# check names are same
if self.sameName(cA):
# check source range is same
if (self.source_range == cA.source_range):
# check in same place on timeline
if (self.timeline_range == cA.timeline_range):
isSame = True
# check duration is same but not necessarily in same place
# on timeline
# TODO: change to else? (does the elif always run?)
elif (self.sameDuration(cA)):
# Note: currently only checks for lateral shifts, doesn't
# check for reordering of clips
isSame = True
self.note = "shifted laterally in track"
return isSame

def checkEdited(self, cA):
"""Compare 2 ClipDatas and see if they have been edited"""
isEdited = False

# Note: assumption that source range and timeline range duration always equal
# TODO: sometimes asserts get triggered, more investigation needed
# assert(self.source_range.duration.value == self.timeline_range.duration.value
# ), "clip source range and timeline range durations don't match"
# assert(cA.source_range.duration.value == cA.timeline_range.duration.value
# ), "clip source range and timeline range durations don't match"

selfDur = self.source_range.duration
cADur = cA.source_range.duration

selfSourceStart = self.source_range.start_time
cASourceStart = cA.source_range.start_time

if (self.source_range != cA.source_range):
self.note = "source range changed"
isEdited = True
deltaFramesStr = str(abs(selfDur.to_frames() - cADur.to_frames()))

if (selfDur.value == cADur.value):
self.note = "start time in source range changed"

# clip duration shorter
elif (selfDur.value < cADur.value):
self.note = "trimmed " + deltaFramesStr + " frames"

if (selfSourceStart.value == cASourceStart.value):
self.note = "trimmed tail by " + deltaFramesStr + " frames"
elif (selfSourceStart.value < cASourceStart.value):
self.note = "trimmed head by " + deltaFramesStr + " frames"

# clip duration longer
elif (selfDur.value > cADur.value):
self.note = "lengthened " + deltaFramesStr + " frames"

if (selfSourceStart.value == cASourceStart.value):
self.note = "lengthened tail by " + deltaFramesStr + " frames"
elif (selfSourceStart.value > cASourceStart.value):
self.note = "lengthened head by " + deltaFramesStr + " frames"

return isEdited
Loading