Files
MixerTwitch/2026/0csv_to_xml.py
T
Raincloud 42d7afb087 bulk - entire MixerTwitch repo 2019-present
I should have done this a long time ago. Oh well, no time like the present!
2026-05-17 14:33:23 -06:00

365 lines
16 KiB
Python

import csv
import xml.etree.ElementTree as ET
import os
import uuid # Import the uuid module
from lxml import etree # Import lxml for XML handling
from xml.dom import minidom
import msvcrt
# Function to convert timecode (HH:MM:SS:FF) to total frames based on the fps
def timecode_to_frames(timecode, fps=60):
"""Convert a timecode (HH:MM:SS:FF) to total frames based on the fps."""
try:
h, m, s, f = map(int, timecode.split(':')) # Split into hours, minutes, seconds, and frames
return (h * 3600 + m * 60 + s) * fps + f # Convert to frames
except ValueError:
print(f"Invalid timecode: {timecode}")
return 0 # Return 0 or handle error as appropriate
# Define marker colors
# pproColor keys:
# default/blank/invalid = Green
# "4281740498" # Red
# "4289825711" # Purple
# "4280578025" # Orange
# "4281049552" # Yellow
# "4294967295" # White
# "4294741314" # Blue
# "4292277273" # Turquoise
marker_colors = {
"FUNI": "4294741314", # Blue
"SEG": "4280578025", # Orange
"gamign": "4289825711", # Purple
"env": "4289825711", # Purple
"AFK": "4281049552", # Yellow
"UNAFK": "4281049552", # Yellow
"BEGIN": "4281049552", # Yellow
"END": "4281049552" # Yellow
}
# Get a list of all CSV files in the current directory
csv_files = [f for f in os.listdir('.') if f.endswith('.csv')]
for csv_file in csv_files:
print(f"Processing '{csv_file}'...")
# Extract base name for sequence name
video_base = os.path.splitext(csv_file)[0]
sequence_name = f"{video_base}"
# Function to generate the XML structure
def generate_xml(markers, duration, sequence_name):
# Create the root <xmeml> element
xmeml = ET.Element("xmeml", version="4")
# Create the <sequence> element
sequence = ET.SubElement(xmeml, "sequence", id="sequence")
# Manually add the attributes with dots
sequence.set("TL.SQAudioVisibleBase", "0")
sequence.set("TL.SQVideoVisibleBase", "0")
sequence.set("TL.SQVisibleBaseTime", "0")
sequence.set("TL.SQAVDividerPosition", "0.5")
sequence.set("TL.SQHideShyTracks", "0")
sequence.set("TL.SQHeaderWidth", "292")
sequence.set("Monitor.ProgramZoomOut", "0")
sequence.set("Monitor.ProgramZoomIn", "0")
sequence.set("TL.SQTimePerPixel", "0.19999999999999998")
sequence.set("MZ.EditLine", "0")
sequence.set("MZ.Sequence.PreviewFrameSizeHeight", "1080")
sequence.set("MZ.Sequence.PreviewFrameSizeWidth", "1920")
sequence.set("MZ.Sequence.AudioTimeDisplayFormat", "200")
sequence.set("MZ.Sequence.PreviewRenderingClassID", "1061109567")
sequence.set("MZ.Sequence.PreviewRenderingPresetCodec", "1634755439")
sequence.set("MZ.Sequence.PreviewRenderingPresetPath", "EncoderPresets/SequencePreview/795454d9-d3c2-429d-9474-923ab13b7018/QuickTime.epr")
sequence.set("MZ.Sequence.PreviewUseMaxRenderQuality", "false")
sequence.set("MZ.Sequence.PreviewUseMaxBitDepth", "false")
sequence.set("MZ.Sequence.EditingModeGUID", "795454d9-d3c2-429d-9474-923ab13b7018")
sequence.set("MZ.Sequence.VideoTimeDisplayFormat", "101")
sequence.set("MZ.WorkOutPoint", "4612930560000")
sequence.set("MZ.WorkInPoint", "0")
sequence.set("explodedTracks", "true")
# Generate a unique UUID and set it in the sequence element
uuid_val = str(uuid.uuid4()).replace('-', '') # Generate a unique UUID without dashes
ET.SubElement(sequence, "uuid").text = uuid_val
# Set the duration right after the UUID
duration_tag = ET.SubElement(sequence, "duration")
duration_tag.text = str(duration)
# Add rate (timebase and ntsc)
rate = ET.SubElement(sequence, "rate")
timebase = ET.SubElement(rate, "timebase")
timebase.text = "60"
ntsc = ET.SubElement(rate, "ntsc")
ntsc.text = "FALSE"
# Set sequence name
name = ET.SubElement(sequence, "name")
name.text = sequence_name
# Create media section
media = ET.SubElement(sequence, "media")
video = ET.SubElement(media, "video")
format_tag = ET.SubElement(video, "format")
samplecharacteristics = ET.SubElement(format_tag, "samplecharacteristics")
# Now let's add a rate and other information under samplecharacteristics (to make it meaningful)
rate = ET.SubElement(samplecharacteristics, "rate")
timebase = ET.SubElement(rate, "timebase")
timebase.text = "60"
ntsc = ET.SubElement(rate, "ntsc")
ntsc.text = "FALSE"
# Add codec information
codec = ET.SubElement(samplecharacteristics, "codec")
name = ET.SubElement(codec, "name")
name.text = "Apple ProRes 422"
appspecificdata = ET.SubElement(codec, "appspecificdata")
appname = ET.SubElement(appspecificdata, "appname")
appname.text = "Final Cut Pro"
appmanufacturer = ET.SubElement(appspecificdata, "appmanufacturer")
appmanufacturer.text = "Apple Inc."
appversion = ET.SubElement(appspecificdata, "appversion")
appversion.text = "7.0"
data = ET.SubElement(appspecificdata, "data")
qtcodec = ET.SubElement(data, "qtcodec")
codecname1 = ET.SubElement(qtcodec, "codecname")
codecname1.text = "Apple ProRes 422"
codectypename = ET.SubElement(qtcodec, "codectypename")
codectypename.text = "Apple ProRes 422"
codecname2 = ET.SubElement(qtcodec, "codecname")
codecname2.text = "Apple ProRes 422"
codectypecode = ET.SubElement(qtcodec, "codectypecode")
codectypecode.text = "apcn"
codecvendorcode = ET.SubElement(qtcodec, "codecvendorcode")
codecvendorcode.text = "appl"
spatialquality = ET.SubElement(qtcodec, "spatialquality")
spatialquality.text = "1024"
temporalquality = ET.SubElement(qtcodec, "temporalquality")
temporalquality.text = "0"
keyframerate = ET.SubElement(qtcodec, "keyframerate")
keyframerate.text = "0"
datarate = ET.SubElement(qtcodec, "datarate")
datarate.text = "0"
# Add dimension info
width = ET.SubElement(samplecharacteristics, "width")
width.text = "1920"
height = ET.SubElement(samplecharacteristics, "height")
height.text = "1080"
anamorphic = ET.SubElement(samplecharacteristics, "anamorphic")
anamorphic.text = "FALSE"
pixelaspectratio = ET.SubElement(samplecharacteristics, "pixelaspectratio")
pixelaspectratio.text = "square"
fielddominance = ET.SubElement(samplecharacteristics, "fielddominance")
fielddominance.text = "none"
colordepth = ET.SubElement(samplecharacteristics, "colordepth")
colordepth.text = "24"
# Create the <track> element
track = ET.SubElement(video, "track")
track.set("TL.SQTrackShy", "0")
track.set("TL.SQTrackExpandedHeight", "25")
track.set("TL.SQTrackExpanded", "0")
track.set("MZ.TrackTargeted", "0")
#Add track modifiers
enabled = ET.SubElement(track, "enabled")
enabled.text = "TRUE"
locked = ET.SubElement(track, "locked")
locked.text = "FALSE"
# Add generator item (matte)
generatoritem = ET.SubElement(track, "generatoritem", id="clipitem-1")
item_name = ET.SubElement(generatoritem, "name")
item_name.text = f"Marker Color Matte ({markers[0]['time']})"
enabled = ET.SubElement(generatoritem, "enabled")
enabled.text = "TRUE"
generatoritem_duration = ET.SubElement(generatoritem, "duration")
generatoritem_duration.text = str(duration)
rate = ET.SubElement(generatoritem, "rate")
timebase = ET.SubElement(rate, "timebase")
timebase.text = "60"
ntsc = ET.SubElement(rate, "ntsc")
ntsc.text = "FALSE"
start = ET.SubElement(generatoritem, "start")
start.text = "0"
end = ET.SubElement(generatoritem, "end")
end.text = str(duration)
in_time = ET.SubElement(generatoritem, "in")
in_time.text = "0"
out_time = ET.SubElement(generatoritem, "out")
out_time.text = str(duration)
alphatype = ET.SubElement(generatoritem, "alphatype")
alphatype.text = "none"
effect = ET.SubElement(generatoritem, "effect")
effect_name = ET.SubElement(effect, "name")
effect_name.text = "Color"
effectid = ET.SubElement(effect, "effectid")
effectid.text = "Color"
effectcategory = ET.SubElement(effect, "effectcategory")
effectcategory.text = "Matte"
effecttype = ET.SubElement(effect, "effecttype")
effecttype.text = "generator"
mediatype = ET.SubElement(effect, "mediatype")
mediatype.text = "video"
parameter = ET.SubElement(effect, "parameter", authoringApp="PremierePro")
parameterid = ET.SubElement(parameter, "parameterid")
parameterid.text = "fillcolor"
param_name = ET.SubElement(parameter, "name")
param_name.text = "Color"
value = ET.SubElement(parameter, "value")
alpha = ET.SubElement(value, "alpha")
alpha.text = "0"
red = ET.SubElement(value, "red")
red.text = "0"
green = ET.SubElement(value, "green")
green.text = "0"
blue = ET.SubElement(value, "blue")
blue.text = "0"
filter_tag = ET.SubElement(generatoritem, "filter")
filter_effect = ET.SubElement(filter_tag, "effect")
filter_effect_name = ET.SubElement(filter_effect, "name")
filter_effect_name.text = "Opacity"
filter_effectid = ET.SubElement(filter_effect, "effectid")
filter_effectid.text = "opacity"
filter_effectcategory = ET.SubElement(filter_effect, "effectcategory")
filter_effectcategory.text = "motion"
filter_effecttype = ET.SubElement(filter_effect, "effecttype")
filter_effecttype.text = "motion"
filter_mediatype = ET.SubElement(filter_effect, "mediatype")
filter_mediatype.text = "video"
pproBypass = ET.SubElement(filter_effect, "pproBypass")
pproBypass.text = "false"
filter_parameter = ET.SubElement(filter_effect, "parameter", authoringApp="PremierePro")
filter_parameterid = ET.SubElement(filter_parameter, "parameterid")
filter_parameterid.text = "opacity"
filter_param_name = ET.SubElement(filter_parameter, "name")
filter_param_name.text = "opacity"
filter_valuemin = ET.SubElement(filter_parameter, "valuemin")
filter_valuemin.text = "0"
filter_valuemax = ET.SubElement(filter_parameter, "valuemax")
filter_valuemax.text = "100"
filter_value = ET.SubElement(filter_parameter, "value")
filter_value.text = "0"
# Create the <audio> element and its children
audio = ET.SubElement(media, "audio")
ET.SubElement(audio, "numOutputChannels").text = "2"
format_elem = ET.SubElement(audio, "format")
samplecharacteristics = ET.SubElement(format_elem, "samplecharacteristics")
ET.SubElement(samplecharacteristics, "depth").text = "16"
ET.SubElement(samplecharacteristics, "samplerate").text = "48000"
outputs = ET.SubElement(audio, "outputs")
for i in range(1, 3):
group = ET.SubElement(outputs, "groups")
ET.SubElement(group, "index").text = str(i)
ET.SubElement(group, "numchannels").text = "1"
ET.SubElement(group, "downmix").text = "0"
channel = ET.SubElement(group, "channel")
ET.SubElement(channel, "index").text = str(i)
for i in range(1, 9):
track = ET.SubElement(audio, "track")
track.set("TL.SQTrackAudioKeyframeStyle", "0")
track.set("TL.SQTrackShy", "0")
track.set("TL.SQTrackExpandedHeight", "25")
track.set("TL.SQTrackExpanded", "0")
track.set("MZ.TrackTargeted", "1")
track.set("PannerCurrentValue", "0.5")
track.set("PannerIsInverted", "true")
track.set("PannerStartKeyframe", "-91445760000000000,0.5,0,0,0,0,0,0")
track.set("PannerName", "Balance")
track.set("currentExplodedTrackIndex", "0")
track.set("totalExplodedTrackCount", "2")
track.set("premiereTrackType", "Stereo")
ET.SubElement(track, "enabled").text = "TRUE"
ET.SubElement(track, "locked").text = "FALSE"
ET.SubElement(track, "outputchannelindex").text = str((i-1) % 2 + 1)
# Create the timecode section under sequence
timecode = ET.SubElement(sequence, "timecode")
rate = ET.SubElement(timecode, "rate")
ET.SubElement(rate, "timebase").text = "60"
ET.SubElement(rate, "ntsc").text = "FALSE"
ET.SubElement(timecode, "string").text = "00:00:00:00"
ET.SubElement(timecode, "frame").text = "0"
ET.SubElement(timecode, "displayformat").text = "NDF"
# Create the labels section under sequence
labels = ET.SubElement(sequence, "labels")
ET.SubElement(labels, "label2").text = "Iris"
# Create the logginginfo section under sequence
logginginfo = ET.SubElement(sequence, "logginginfo")
ET.SubElement(logginginfo, "description")
ET.SubElement(logginginfo, "scene")
ET.SubElement(logginginfo, "shottake")
ET.SubElement(logginginfo, "lognote")
ET.SubElement(logginginfo, "good")
ET.SubElement(logginginfo, "originalvideofilename")
ET.SubElement(logginginfo, "originalaudiofilename")
# Add markers again but under sequence
for marker in markers:
marker_element = ET.SubElement(sequence, "marker")
ET.SubElement(marker_element, "comment")
ET.SubElement(marker_element, "name").text = marker['name']
# Set the in and out time as the frame count
in_frame = str(timecode_to_frames(marker['time']))
ET.SubElement(marker_element, "in").text = in_frame
ET.SubElement(marker_element, "out").text = "-1"
# Set color based on marker name
color_code = marker_colors.get(marker['name'])
if color_code:
ppro_color = ET.SubElement(marker_element, "pproColor")
ppro_color.text = color_code
print(f"Set color for marker '{marker['name']}' to '{color_code}'")
else:
print(f"Default color (Green) used for marker '{marker['name']}'")
# Create the labels section under sequence
labels = ET.SubElement(sequence, "labels")
ET.SubElement(labels, "label2").text = "Forest"
# Write to XML file
output_file = csv_file.replace('.csv', '.xml')
tree = ET.ElementTree(xmeml)
tree.write(output_file, encoding="UTF-8", xml_declaration=True)
print(f"XML file '{output_file}' has been created successfully.")
# Read the CSV file and process the data
markers = [] # List to store markers
with open(csv_file, newline='', encoding='utf-8') as csvfile:
csv_reader = csv.DictReader(csvfile, delimiter='\t')
marker_count = 0
for row in csv_reader:
marker_name = row['Marker Name']
in_point = row['In']
out_point = row['Out']
print(f"Adding marker: Name='{marker_name}', In='{in_point}', Out='{out_point}'")
# Add marker to list
markers.append({'name': marker_name, 'time': in_point})
marker_count += 1
print(f"Total markers added for '{csv_file}': {marker_count}")
# Calculate the duration using the final marker's timecode
final_marker = markers[-1] if markers else None
duration = 0
if final_marker:
duration = timecode_to_frames(final_marker['time'], fps=60)
generate_xml(markers, duration, sequence_name) # Pass sequence_name
print("------------------------------------------------------------")
print("All files have been processed.")
print("Press any key to exit...")
msvcrt.getch()