initial
This commit is contained in:
251
translate.py
Normal file
251
translate.py
Normal 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")
|
||||
Reference in New Issue
Block a user