This commit is contained in:
2026-06-26 18:48:22 +01:00
commit e67cb14580
2 changed files with 254 additions and 0 deletions

3
.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
/.venv
/.session
/STREAM

251
translate.py Normal file
View File

@@ -0,0 +1,251 @@
import os
import shutil
import exiftool
import csv
import subprocess
def getFiles(): # Get list of files in STREAM dir
filesArray = []
for file in os.listdir("./STREAM/"):
filesArray.append("./STREAM/" + file)
print("found file " + file)
return filesArray
def getMetadata(filesArray):
with exiftool.ExifToolHelper() as et:
filteredArray = {}
metadata = et.get_metadata(filesArray, "-u")
for entry in metadata:
fileName = entry.get("SourceFile")
userBit = entry.get("H264:MDPM_0x0014")
timecode = entry.get("H264:TimeCode")
filteredArray[fileName] = [userBit, timecode]
print(filteredArray)
return filteredArray
def orderUserBit(filteredArray):
for key in filteredArray:
cleanValue = [ord(c) for c in filteredArray[key][0]]
filteredArray[key][0] = cleanValue
return filteredArray
def applyMetadataDict(fixedDict):
for key in fixedDict:
metaDict = {}
userBitArray = fixedDict[key][0]
metaDict["Act"] = userBitArray[3]
metaDict["Scene"] = userBitArray[2]
metaDict["Reel"] = userBitArray[1]
metaDict["Shot"] = userBitArray[0]
fixedDict[key][0] = metaDict
return fixedDict
def addTakeNumbers(clipDict):
# 1. Convert dict to a list of items to allow sorting
itemList = []
for path, data in clipDict.items():
itemList.append({"path": path, "metadata": data[0], "timecode": data[1]})
# 2. Sort by timecode string (HH:MM:SS:FF) to ensure chronological order
itemList.sort(key=lambda x: x["timecode"])
# 3. Track occurrences of Reel and Shot combinations
shotCounts = {}
newDict = {}
for item in itemList:
meta = item["metadata"]
# Create a unique key for the specific Reel and Shot
shotKey = f"{meta['Reel']}_{meta['Shot']}"
# 4. Increment the take number based on previous occurrences
takeNumber = shotCounts.get(shotKey, 0) + 1
shotCounts[shotKey] = takeNumber
# 5. Construct the updated metadata dictionary
# This keeps 'Take' right after 'Shot'
updatedMeta = {
"Act": meta["Act"],
"Scene": meta["Scene"],
"Reel": meta["Reel"],
"Shot": meta["Shot"],
"Take": takeNumber,
}
# Map it back to the original file path
newDict[item["path"]] = [updatedMeta, item["timecode"]]
return newDict
def addGoodKey(clipDict):
# 1. Group clips by (Reel, Scene, Shot) to find the max take for each specific setup
# Key: (Reel, Scene, Shot), Value: highestTakeFound
maxTakeTracker = {}
for path, data in clipDict.items():
meta = data[0]
# Adding Shot to the grouping key is the fix here
groupKey = (meta["Reel"], meta["Scene"], meta["Shot"])
currentTake = meta["Take"]
if groupKey not in maxTakeTracker or currentTake > maxTakeTracker[groupKey]:
maxTakeTracker[groupKey] = currentTake
# 2. Apply the 'Good' key based on the tracker
newDict = {}
for path, data in clipDict.items():
meta = data[0]
timecode = data[1]
groupKey = (meta["Reel"], meta["Scene"], meta["Shot"])
# Mark True if it's the highest take for that specific Shot
isGood = meta["Take"] == maxTakeTracker[groupKey]
# Update the metadata dictionary
updatedMeta = {
"Act": meta["Act"],
"Scene": meta["Scene"],
"Reel": meta["Reel"],
"Shot": meta["Shot"],
"Take": meta["Take"],
"Good": isGood,
}
newDict[path] = [updatedMeta, timecode]
return newDict
def makeFileNameDict(infoDict):
fileNameDict = {}
for key in infoDict:
meta = infoDict[key][0]
act = meta["Act"]
scene = meta["Scene"]
reel = meta["Reel"]
shot = meta["Shot"]
take = meta["Take"]
good = meta["Good"]
if good == True:
isGood = "_GOOD"
else:
isGood = ""
fileName = (
f"R{reel}S{shot}.{take}-ACT{act}_SCENE{scene}_SHOT{shot}_TAKE{take}{isGood}"
)
fileNameDict[key] = [meta, fileName]
print(f"Named file {key} {fileName}")
return fileNameDict
def setFilePath(fileNameDict):
for file in fileNameDict:
fileName = "./output/" + fileNameDict[file][1] + ".mov"
fileNameDict[file][1] = fileName
return fileNameDict
def copyFiles(filePathDict):
for file in filePathDict:
command = [
"ffmpeg",
"-i",
file,
"-c:v",
"copy",
"-c:a",
"pcm_s16le",
"-ar",
"48000",
"-y",
filePathDict[file][1],
]
try:
subprocess.run(command, check=True, capture_output=True, text=True)
print(f"copied {file} to {filePathDict[file][1]}")
except subprocess.CalledProcessError as e:
print(f"FFmpeg failed for {inputMts}: {e.stderr}")
def generateResolveCsv(clipDict, outputCsvPath):
"""
Generates a CSV file for DaVinci Resolve metadata import.
Matches metadata to the new .mov files based on the file name.
"""
# Column names that DaVinci Resolve recognizes natively
# 'File Name' is our primary key for matching
fieldnames = [
"File Name",
"Reel Number",
"Scene",
"Shot",
"Take",
"Good Take",
"Rating",
"Bin",
]
try:
with open(outputCsvPath, mode="w", newline="", encoding="utf-8") as csvfile:
writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
writer.writeheader()
for sourcePath, data in clipDict.items():
meta = data[0]
# data[1] is our output path (e.g., './output/R1S1.1-ACT1...mov')
outputPath = data[1]
# We use the basename so Resolve can match it to clips in the Media Pool
fileName = os.path.basename(outputPath)
writer.writerow(
{
"File Name": fileName,
"Reel Number": f"{meta['Reel']}",
"Scene": str(meta["Scene"]),
"Shot": str(meta["Shot"]),
"Take": str(meta["Take"]),
"Good Take": "1" if meta["Good"] else "0",
"Rating": "5" if meta["Good"] else "0",
"Bin": f"Clips/Reel {meta['Reel']}",
}
)
print(f"Successfully generated metadata CSV: {outputCsvPath}")
except Exception as e:
print(f"Error generating CSV: {e}")
files = getFiles()
userBitDict = getMetadata(files)
fixedUBDict = orderUserBit(userBitDict)
metadataDict = applyMetadataDict(fixedUBDict)
takesDict = addTakeNumbers(metadataDict)
final = addGoodKey(takesDict)
print(final)
fileNames = makeFileNameDict(final)
filePaths = setFilePath(fileNames)
copyFiles(filePaths)
print(filePaths)
generateResolveCsv(filePaths, "./out.csv")