initial
This commit is contained in:
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
/.venv
|
||||||
|
/.session
|
||||||
|
/STREAM
|
||||||
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