Skip to content

Commit 7949476

Browse files
committed
added docstrings, comments, and updated variable names
1 parent 511754c commit 7949476

File tree

3 files changed

+116
-120
lines changed

3 files changed

+116
-120
lines changed

src/py-opentimelineio/opentimelineio/console/otiodiff/clipData.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ def checkSame(self, cA):
6565
# check names are same
6666
if self.sameName(cA):
6767
# check source range is same
68-
# TODO: call trimmed range instead of source range
68+
# TODO: call trimmed range instead of source range ???
6969
# TODO: make test where has null source range -> see things break, then go back and change <- low priority
7070
if(self.source_range == cA.source_range):
7171
# print(self.name, " ", self.timeline_range, " ", cA.timeline_range)

src/py-opentimelineio/opentimelineio/console/otiodiff/getDiff.py

Lines changed: 96 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,18 @@
88
from .clipData import ClipData
99
from . import makeOtio
1010

11-
#currently only handles video and audio tracks
12-
def diff(timelineA, timelineB):
13-
# TODO: put docstring here, descriptive name, most wordy descrip
11+
def diffTimelines(timelineA, timelineB):
12+
'''Diff two OTIO timelines and identify how clips on video and/or audio tracks changed from timeline A to timeline B.
13+
Return an annotated otio file with the differences and print a text summary to console.
14+
15+
Parameters:
16+
timelineA (otio.schema.Timeline()): timeline from the file you want to compare against, ex. clip1 version 1
17+
timelineB (otio.schema.Timeline()): timeline from the file you want to compare, ex. clip1 version 2
18+
19+
Returns:
20+
outputTimeline (otio.schema.Timeline()): timeline with color coded clips and marker annotations showing the
21+
differences between the input tracks with the tracks from timeline B stacked on top of timeline A
22+
'''
1423
hasVideo = False
1524
hasAudio = False
1625

@@ -25,61 +34,58 @@ def diff(timelineA, timelineB):
2534
# else:
2635
# print("no audio tracks")
2736

28-
makeTlSummary(timelineA, timelineB)
37+
makeTimelineSummary(timelineA, timelineB)
2938

30-
outputTl = None
39+
outputTimeline = None
3140
# process video tracks, audio tracks, or both
3241
if hasVideo and hasAudio:
33-
videoClipTable = processTracks(timelineA.video_tracks(), timelineB.video_tracks())
34-
audioClipTable = processTracks(timelineA.audio_tracks(), timelineB.audio_tracks())
42+
videoClipTable = categorizeClipsByTracks(timelineA.video_tracks(), timelineB.video_tracks())
43+
audioClipTable = categorizeClipsByTracks(timelineA.audio_tracks(), timelineB.audio_tracks())
3544

3645
makeSummary(videoClipTable, otio.schema.Track.Kind.Video, "perTrack")
3746
makeSummary(audioClipTable, otio.schema.Track.Kind.Audio, "summary")
3847

3948
videoTl = makeNewOtio(videoClipTable, otio.schema.Track.Kind.Video)
40-
outputTl = makeNewOtio(audioClipTable, otio.schema.Track.Kind.Audio)
49+
outputTimeline = makeNewOtio(audioClipTable, otio.schema.Track.Kind.Audio)
4150
# combine
4251
for t in videoTl.tracks:
43-
outputTl.tracks.append(copy.deepcopy(t))
52+
outputTimeline.tracks.append(copy.deepcopy(t))
4453

4554
elif hasVideo:
46-
videoClipTable = processTracks(timelineA.video_tracks(), timelineB.video_tracks())
55+
videoClipTable = categorizeClipsByTracks(timelineA.video_tracks(), timelineB.video_tracks())
4756
makeSummary(videoClipTable, otio.schema.Track.Kind.Video, "summary")
48-
outputTl = makeNewOtio(videoClipTable, otio.schema.Track.Kind.Video)
57+
outputTimeline = makeNewOtio(videoClipTable, otio.schema.Track.Kind.Video)
4958

5059
elif hasAudio:
51-
audioClipTable = processTracks(timelineA.audio_tracks(), timelineB.audio_tracks())
60+
audioClipTable = categorizeClipsByTracks(timelineA.audio_tracks(), timelineB.audio_tracks())
5261
makeSummary(audioClipTable, "Audio", "summary")
53-
outputTl = makeNewOtio(audioClipTable, otio.schema.Track.Kind.Audio)
62+
outputTimeline = makeNewOtio(audioClipTable, otio.schema.Track.Kind.Audio)
5463

5564
else:
56-
# TODO: log no vid/aud or throw
57-
pass
65+
print("No video or audio tracks found in both timelines.")
5866

5967
# Debug
6068
# origClipCount = len(timelineA.find_clips()) + len(timelineB.find_clips())
6169

6270
# print(origClipCount)
63-
# print(len(outputTl.find_clips()))
71+
# print(len(outputTimeline.find_clips()))
6472

65-
return outputTl
73+
return outputTimeline
6674

67-
def toOtio(data, path):
68-
otio.adapters.write_to_file(data, path)
69-
70-
# for debugging, put response into file
71-
def toJson(file):
72-
with open("clipDebug.json", "w") as f:
73-
f.write(file)
75+
# TODO: make nonClones a set rather than a list
76+
def findClones(clips):
77+
"""Separate the cloned ClipDatas (ones that share the same name) from the unique ClipDatas and return both
78+
79+
Paramenters:
80+
clips (list of ClipDatas): list of ClipDatas
7481
75-
def toTxt(file):
76-
with open("report.txt", "w") as f:
77-
f.write(file)
82+
Returns:
83+
clones (dictionary): dictionary of all clones in the group of ClipDatas
84+
keys: name of clone
85+
values: list of ClipDatas of that name
86+
nonClones (list): list of unique clones in group of ClipDatas\
87+
"""
7888

79-
# create a dictionary with all the cloned clips (ones that share the same truncated name)
80-
# key is the truncated name, value is a list of ClipDatas
81-
# @parameter clips, list of ClipDatas
82-
def findClones(clips):
8389
clones = {}
8490
nonClones = []
8591
names = []
@@ -98,6 +104,8 @@ def findClones(clips):
98104
return clones, nonClones
99105

100106
def sortClones(clipDatasA, clipDatasB):
107+
"""Identify cloned ClipDatas (ones that share the same name) across two groups of ClipDatas and separate from the unique
108+
ClipDatas (ones that only appear once in each group)"""
101109
# find cloned clips and separate out from unique clips
102110
clonesA, nonClonesA = findClones(clipDatasA)
103111
clonesB, nonClonesB = findClones(clipDatasB)
@@ -120,8 +128,8 @@ def sortClones(clipDatasA, clipDatasB):
120128
# clipCountB = 0
121129
return (clonesA, nonClonesA), (clonesB, nonClonesB)
122130

123-
# compare all clips that had a clone
124131
def compareClones(clonesA, clonesB):
132+
"""Compare two groups of cloned ClipDatas and categorize into added, unchanged, or deleted"""
125133
added = []
126134
unchanged = []
127135
deleted = []
@@ -160,8 +168,8 @@ def compareClones(clonesA, clonesB):
160168

161169
return added, unchanged, deleted
162170

163-
# compare all strictly unique clips
164171
def compareClips(clipDatasA, clipDatasB):
172+
"""Compare two groups of unique ClipDatas and categorize into added, edited, unchanged, and deleted"""
165173
namesA = {}
166174
namesB = {}
167175

@@ -176,6 +184,7 @@ def compareClips(clipDatasA, clipDatasB):
176184
namesB[c.name] = c
177185

178186
for cB in clipDatasB:
187+
179188
if cB.name not in namesA:
180189
added.append(cB)
181190
else:
@@ -205,29 +214,8 @@ def compareClips(clipDatasA, clipDatasB):
205214
# TODO: some can be sets instead of lists
206215
return added, edited, unchanged, deleted
207216

208-
# # clip is an otio Clip
209-
# def getTake(clip):
210-
# take = None
211-
# if(len(clip.name.split(" ")) > 1):
212-
# take = clip.name.split(" ")[1]
213-
# else:
214-
# take = None
215-
# return take
216-
217-
# TODO: change name, make comparable rep? clip comparator?
218-
# TODO: learn abt magic functions ex __eq__
219-
# def makeClipData(clip, trackNum):
220-
# cd = ClipData(clip.name.split(" ")[0],
221-
# clip.media_reference,
222-
# clip.source_range,
223-
# clip.trimmed_range_in_parent(),
224-
# trackNum,
225-
# clip,
226-
# getTake(clip))
227-
# return cd
228-
229-
# the consolidated version of processVideo and processAudio, meant to replace both
230217
def compareTracks(trackA, trackB, trackNum):
218+
"""Compare clipis in two OTIO tracks and categorize into added, edited, same, and deleted"""
231219
clipDatasA = []
232220
clipDatasB = []
233221

@@ -244,27 +232,25 @@ def compareTracks(trackA, trackB, trackNum):
244232
(clonesA, nonClonesA), (clonesB, nonClonesB) = sortClones(clipDatasA, clipDatasB)
245233

246234
# compare clips and put into categories
247-
addV = []
248-
editV = []
249-
sameV = []
250-
deleteV = []
235+
added = []
236+
edited = []
237+
unchanged = []
238+
deleted = []
251239

252240
# compare and categorize unique clips
253-
addV, editV, sameV, deleteV = compareClips(nonClonesA, nonClonesB)
241+
added, edited, unchanged, deleted = compareClips(nonClonesA, nonClonesB)
254242

255243
# compare and categorize cloned clips
256-
addCloneV, sameCloneV, deleteCloneV = compareClones(clonesA, clonesB)
257-
addV.extend(addCloneV)
258-
sameV.extend(sameCloneV)
259-
deleteV.extend(deleteCloneV)
244+
addedClone, unchangedClone, deletedClone = compareClones(clonesA, clonesB)
245+
added.extend(addedClone)
246+
unchanged.extend(unchangedClone)
247+
deleted.extend(deletedClone)
260248

261-
# SortedClipDatas = namedtuple('VideoGroup', ['add', 'edit', 'same', 'delete'])
262-
# videoGroup = SortedClipDatas(addV, editV, sameV, deleteV)
263-
264-
return addV, editV, sameV, deleteV
265-
# return videoGroup
249+
return added, edited, unchanged, deleted
266250

251+
# TODO? account for move edit, currently only identifies strictly moved
267252
def checkMoved(allDel, allAdd):
253+
"""Identify ClipDatas that have moved between different tracks"""
268254
# ones found as same = moved
269255
# ones found as edited = moved and edited
270256

@@ -290,8 +276,8 @@ def checkMoved(allDel, allAdd):
290276

291277
return newAdd, moveEdit, moved, newDel
292278

293-
# TODO? account for move edit, currently only identifies strictly moved
294279
def sortMoved(clipTable):
280+
"""Put ClipDatas that have moved between tracks into their own category and remove from their previous category"""
295281
allAdd = []
296282
allEdit = []
297283
allSame = []
@@ -323,8 +309,17 @@ def sortMoved(clipTable):
323309
return clipTable
324310

325311
def makeNewOtio(clipTable, trackType):
312+
"""Make a new annotated OTIO timeline showing the change from timeline A to timeline B, with the tracks
313+
from timeline B stacked on top of the tracks from timeline A
314+
315+
Ex. New timeline showing the differences of timeline A and B with 2 tracks each
316+
Track 2B
317+
Track 1B
318+
========
319+
Track 2A
320+
Track 1A
321+
"""
326322
newTl = otio.schema.Timeline(name="diffed")
327-
# TODO: rename into track sets
328323
tracksInA = []
329324
tracksInB = []
330325

@@ -362,15 +357,29 @@ def makeNewOtio(clipTable, trackType):
362357

363358
# TODO: rename to create bucket/cat/db/stuff; categorizeClipsByTracks + comment
364359

365-
def processTracks(tracksA, tracksB):
366-
# TODO: add docstring like this for public facing functions, otherwise comment is ok
367-
"""Return a copy of the input timelines with only tracks that match
368-
either the list of names given, or the list of track indexes given."""
369-
clipTable = {}
370-
# READ ME IMPORTANT READ MEEEEEEE clipTable structure: {1:{"add": [], "edit": [], "same": [], "delete": []}
371-
# clipTable keys are track numbers, values are dictionaries
372-
# per track dictionary keys are clip categories, values are lists of clips of that category
360+
def categorizeClipsByTracks(tracksA, tracksB):
361+
"""Compare the clips in each track in tracksB against the corresponding track in tracksA
362+
and categorize based on how they have changed. Return a dictionary table of ClipDatas
363+
categorized by added, edited, unchanged, deleted, and moved and ordered by track.
364+
365+
Parameters:
366+
tracksA (list of otio.schema.Track() elements): list of tracks from timeline A
367+
tracksB (list of otio.schema.Track() elements): list of tracks from timeline B
368+
369+
Returns:
370+
clipTable (dictionary): dictionary holding categorized ClipDatas, organized by the track number of the ClipDatas
371+
dictionary keys: track number (int)
372+
dictionary values: dictionary holding categorized ClipDatas of that track
373+
nested dictionary keys: category name (string)
374+
nested dictionary values: list of ClipDatas that fall into the category
375+
376+
ex: clipTable when tracksA and tracksB contain 3 tracks
377+
{1 : {"add": [], "edit": [], "same": [], "delete": [], "move": []}
378+
2 : {"add": [], "edit": [], "same": [], "delete": [], "move": []}
379+
3 : {"add": [], "edit": [], "same": [], "delete": []}, "move": []}
380+
"""
373381

382+
clipTable = {}
374383
# TODO? ^change to class perhaps? low priority
375384

376385
shorterTlTracks = tracksA if len(tracksA) < len(tracksB) else tracksB
@@ -428,6 +437,8 @@ def processTracks(tracksA, tracksB):
428437
return clipTable
429438

430439
def makeSummary(clipTable, trackType, mode):
440+
"""Summarize what clips got changed and how they changed and print to console."""
441+
431442
print(trackType.upper(), "CLIPS")
432443
print("===================================")
433444
print(" Overview Summary ")
@@ -464,10 +475,11 @@ def makeSummary(clipTable, trackType, mode):
464475
print(cat.upper(), ":", len(clipGroup[cat]))
465476
if cat != "same":
466477
for i in clipGroup[cat]:
467-
print(i.name)
478+
print(i.name + ": " + i.note) if i.note is not None else print(i.name)
468479
print("")
469480

470-
def makeTlSummary(timelineA, timelineB):
481+
def makeTimelineSummary(timelineA, timelineB):
482+
"""Summarize information about the two timelines compared and print to console."""
471483
print("Comparing Timeline B:", timelineB.name, "vs")
472484
print(" Timeline A:", timelineA.name)
473485
print("")
@@ -494,31 +506,12 @@ def makeTlSummary(timelineA, timelineB):
494506
print("")
495507

496508
''' ======= Notes =======
497-
maybe can make use of algorithms.filter.filter_composition
498-
499-
# a test using python difflib, prob not useful
500-
# # find deltas of 2 files and print into html site
501-
# d = HtmlDiff(wrapcolumn=100)
502-
# diff = d.make_file(file1.splitlines(), file2.splitlines(), context=True)
503-
# with open("diff.html", "w", encoding="utf-8") as f:
504-
# f.write(diff)
505-
506-
# s = SequenceMatcher(None, file1, file2)
507-
# print(s.quick_ratio())
508-
509-
# each one in new check with each one in old
510-
# if everything matches, unchanged <- can't just check with first instance because might have added one before it
511-
# if everything matches except for timeline position-> moved
512-
# if length doesn't match, look for ordering? or just classify as added/deleted
513-
# if counts of old and new dif then def add/deleted
514-
515-
516509
Test shot simple:
517-
python ./src/getDif.py /Users/yingjiew/Documents/testDifFiles/h150_104a.105j_2025.04.04_ANIM-flat.otio /Users/yingjiew/Documents/testDifFiles/150_104a.105jD_2025.06.27-flat.otio
510+
/Users/yingjiew/Documents/testDifFiles/h150_104a.105j_2025.04.04_ANIM-flat.otio /Users/yingjiew/Documents/testDifFiles/150_104a.105jD_2025.06.27-flat.otio
518511
519512
Test seq matching edit's skywalker:
520-
python ./src/getDif.py /Users/yingjiew/Folio/casa/Dream_EP101_2024.02.09_Skywalker_v3.0_ChangeNotes.Relinked.01.otio /Users/yingjiew/Folio/casa/Dream_EP101_2024.02.23_Skywalker_v4.0_ChangeNotes.otio
513+
/Users/yingjiew/Folio/casa/Dream_EP101_2024.02.09_Skywalker_v3.0_ChangeNotes.Relinked.01.otio /Users/yingjiew/Folio/casa/Dream_EP101_2024.02.23_Skywalker_v4.0_ChangeNotes.otio
521514
522515
Test shot multitrack:
523-
python ./src/getDif.py /Users/yingjiew/Folio/edit-dept/More_OTIO/i110_BeliefSystem_2022.07.28_BT3.otio /Users/yingjiew/Folio/edit-dept/More_OTIO/i110_BeliefSystem_2023.06.09.otio
516+
/Users/yingjiew/Folio/edit-dept/More_OTIO/i110_BeliefSystem_2022.07.28_BT3.otio /Users/yingjiew/Folio/edit-dept/More_OTIO/i110_BeliefSystem_2023.06.09.otio
524517
'''

0 commit comments

Comments
 (0)