From e67cb145802983c56939469e780247e2ccf6c254 Mon Sep 17 00:00:00 2001 From: voidarc Date: Fri, 26 Jun 2026 18:48:22 +0100 Subject: [PATCH] initial --- .gitignore | 3 + translate.py | 251 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 254 insertions(+) create mode 100644 .gitignore create mode 100644 translate.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..063583b --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/.venv +/.session +/STREAM diff --git a/translate.py b/translate.py new file mode 100644 index 0000000..7535534 --- /dev/null +++ b/translate.py @@ -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")