Created
December 6, 2023 18:21
-
-
Save jeromecornet/7477bbd9e8e8ada14b1eb8ef358b2ffc to your computer and use it in GitHub Desktop.
Davinci Resolve Studio import HiLight tags from GoPro footage as a clip marker. Credit for actual tag parsing goes to https://github.com/icegoogles/GoPro-Highlight-Parser
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python | |
""" | |
Script to import GoPro HiLight tags as clip markers. | |
Execute this in the bin where your footage is stored, it will extract the highlight tags | |
""" | |
import os | |
import sys | |
import struct | |
sys.path.append("/Library/Application Support/Blackmagic Design/DaVinci Resolve/Developer/Scripting/Modules") | |
import DaVinciResolveScript as dvr_script | |
from math import floor | |
resolve = dvr_script.scriptapp("Resolve") | |
project = resolve.GetProjectManager().GetCurrentProject() | |
if not project: | |
print("No project is loaded") | |
sys.exit() | |
resolve.OpenPage("media") | |
projectManager = resolve.GetProjectManager() | |
mediaPool = project.GetMediaPool() | |
currentBin = mediaPool.GetCurrentFolder() | |
# Gets clips | |
clips = currentBin.GetClipList() | |
if not clips or not clips[0]: | |
print("Error: MediaPool bin doesn't contain any clips!") | |
sys.exit() | |
def find_boxes(f, start_offset=0, end_offset=float("inf")): | |
s = struct.Struct("> I 4s") | |
e = struct.Struct("> Q") | |
boxes = {} | |
offset = start_offset | |
f.seek(offset, 0) | |
while offset < end_offset: | |
data = f.read(8) # read box header | |
if data == b"": break # EOF | |
length, text = s.unpack(data) | |
if text == b'uuid': | |
raise NotImplementedError("Cannot parse UUID box type") | |
if length == 1: | |
data = f.read(8) | |
length = e.unpack(data)[0] | |
boxes[text] = (offset, offset + length) | |
offset += length | |
f.seek(offset) # skip to next box | |
return boxes | |
def examine_mp4(filename): | |
with open(filename, "rb") as f: | |
boxes = find_boxes(f) | |
print(f"File {filename}: {boxes}") | |
# Sanity check that this really is a movie file. | |
def fileerror(): # function to call if file is not a movie file | |
print("") | |
print("ERROR, file is not a mp4-video-file!") | |
os.system("pause") | |
exit() | |
try: | |
if boxes[b'ftyp'][0] != 0: | |
fileerror() | |
except: | |
fileerror() | |
moov_boxes = find_boxes(f, boxes[b'moov'][0] + 8, boxes[b'moov'][1]) | |
udta_boxes = find_boxes(f, moov_boxes[b'udta'][0] + 8, moov_boxes[b'udta'][1]) | |
if b'GPMF' in udta_boxes.keys(): | |
### get GPMF Box | |
highlights = parse_highlights(f, udta_boxes[b'GPMF'][0] + 8, udta_boxes[b'GPMF'][1]) | |
else: | |
# parsing for versions before Hero6 | |
highlights = parse_highlights_old_version(f, udta_boxes[b'HMMT'][0] + 12, udta_boxes[b'HMMT'][1]) | |
print("") | |
print("Filename:", filename) | |
print("Found", len(highlights), "Highlight(s)!") | |
print('Here are all Highlights: ', highlights) | |
return highlights | |
def parse_highlights_old_version(f, start_offset=0, end_offset=float("inf")): | |
listOfHighlights = [] | |
offset = start_offset | |
f.seek(offset, 0) | |
while True: | |
data = f.read(4) | |
timestamp = int.from_bytes(data, "big") | |
if timestamp != 0: | |
listOfHighlights.append(timestamp) | |
else: | |
break | |
return listOfHighlights | |
def parse_highlights(f, start_offset=0, end_offset=float("inf")): | |
inHighlights = False | |
inHLMT = False | |
listOfHighlights = [] | |
offset = start_offset | |
f.seek(offset, 0) | |
while offset < end_offset: | |
data = f.read(4) | |
if data == b"": break | |
if data == b'High' and inHighlights == False: | |
data = f.read(4) | |
if data == b'ligh': | |
inHighlights = True | |
if data == b'HLMT' and inHighlights == True and inHLMT == False: | |
inHLMT = True | |
if data == b'MANL' and inHighlights == True and inHLMT == True: | |
currPos = f.tell() | |
f.seek(currPos - 20) | |
data = f.read(4) | |
timestamp = int.from_bytes(data, "big") | |
if timestamp != 0: | |
listOfHighlights.append(timestamp) | |
f.seek(currPos) | |
return listOfHighlights | |
for clip in clips: | |
fmt = clip.GetClipProperty("Format") | |
file_path = clip.GetClipProperty("File Path") | |
if fmt != 'QuickTime': | |
print(f"#{file_path} is not a gopro footage") | |
continue | |
# Get clip framerate | |
fps = clip.GetClipProperty("FPS") | |
if not fps: | |
print("Error: Failed to get clip 'Frames' property !") | |
sys.exit() | |
highlights = examine_mp4(file_path) | |
if highlights is not None and len(highlights) > 0 : | |
for i, highlight in enumerate(highlights): | |
frameId = floor(highlight * fps / 1000) | |
isSuccess = clip.AddMarker(frameId, "Yellow", "HiLight", "GoPro HiLight", 1) | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This was tested with Resolve Studio 18.6 on MacOS. You need to install a version of python between 3.6 and 3.11 (3.12 doesn't work with the davinci library).
/Library/Application Support/Blackmagic Design/DaVinci Resolve/Fusion/Scripts/Utility
External scripting using
toLocal