2026-01-06 15:04:26 +01:00
|
|
|
import re
|
|
|
|
|
import os
|
|
|
|
|
|
|
|
|
|
import lxml.etree
|
2026-01-16 13:29:22 +00:00
|
|
|
import ox
|
2026-01-06 15:04:26 +01:00
|
|
|
|
2026-01-06 15:17:28 +01:00
|
|
|
from .render_kdenlive import melt_xml
|
2026-01-06 15:04:26 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def parse_lang(lang):
|
|
|
|
|
if lang and "," in lang:
|
2026-01-06 15:17:28 +01:00
|
|
|
lang = lang.split(",")
|
2026-01-06 15:04:26 +01:00
|
|
|
if isinstance(lang, list):
|
|
|
|
|
tlang = lang[1:]
|
|
|
|
|
lang = lang[0]
|
|
|
|
|
else:
|
|
|
|
|
tlang = None
|
|
|
|
|
if lang == "en":
|
|
|
|
|
lang = None
|
|
|
|
|
return lang, tlang
|
|
|
|
|
|
2026-01-06 15:17:28 +01:00
|
|
|
|
2026-01-06 15:04:26 +01:00
|
|
|
def random_int(seq, length):
|
|
|
|
|
n = n_ = length - 1
|
2026-01-06 15:17:28 +01:00
|
|
|
# print('len', n)
|
2026-01-06 15:04:26 +01:00
|
|
|
if n == 0:
|
|
|
|
|
return n
|
|
|
|
|
r = seq() / 9 * 10
|
|
|
|
|
base = 10
|
|
|
|
|
while n > 10:
|
|
|
|
|
n /= 10
|
|
|
|
|
r += seq() / 9 * 10
|
|
|
|
|
base += 10
|
|
|
|
|
r = int(round(n_ * r / base))
|
|
|
|
|
return r
|
|
|
|
|
|
2026-01-06 15:17:28 +01:00
|
|
|
|
2026-01-06 15:04:26 +01:00
|
|
|
def random_choice(seq, items, pop=False):
|
|
|
|
|
n = random_int(seq, len(items))
|
|
|
|
|
if pop:
|
|
|
|
|
return items.pop(n)
|
|
|
|
|
return items[n]
|
|
|
|
|
|
2026-01-06 15:17:28 +01:00
|
|
|
|
2026-01-06 15:04:26 +01:00
|
|
|
def chance(seq, chance):
|
|
|
|
|
return (seq() / 10) < chance
|
|
|
|
|
|
2026-01-06 15:17:28 +01:00
|
|
|
|
2026-01-06 15:04:26 +01:00
|
|
|
def get_clip_by_seqid(clips, seqid):
|
|
|
|
|
selected = None
|
|
|
|
|
for i, clip in enumerate(clips):
|
2026-01-06 15:17:28 +01:00
|
|
|
if clip["seqid"] == seqid:
|
2026-01-06 15:04:26 +01:00
|
|
|
selected = i
|
|
|
|
|
break
|
|
|
|
|
if selected is not None:
|
|
|
|
|
return clips.pop(i)
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def get_scene_duration(scene):
|
|
|
|
|
if isinstance(scene, str):
|
|
|
|
|
with open(scene) as fd:
|
|
|
|
|
scene = json.load(fd)
|
|
|
|
|
duration = 0
|
|
|
|
|
for key, value in scene.items():
|
|
|
|
|
for name, clips in value.items():
|
2026-01-06 20:08:31 +01:00
|
|
|
if clips:
|
|
|
|
|
for clip in clips:
|
|
|
|
|
duration += int(clip["duration"] * 24)
|
|
|
|
|
return duration / 24
|
2026-01-06 15:04:26 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def get_offset_duration(prefix):
|
|
|
|
|
duration = 0
|
|
|
|
|
for root, folders, files in os.walk(prefix):
|
|
|
|
|
for f in files:
|
2026-01-06 15:17:28 +01:00
|
|
|
if f == "scene.json":
|
2026-01-06 15:04:26 +01:00
|
|
|
duration += get_scene_duration(scene)
|
|
|
|
|
return duration
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def get_track_duration(scene, k, n):
|
|
|
|
|
duration = 0
|
|
|
|
|
for key, value in scene.items():
|
|
|
|
|
if key == k:
|
|
|
|
|
for name, clips in value.items():
|
|
|
|
|
if name == n:
|
|
|
|
|
for clip in clips:
|
2026-01-06 15:17:28 +01:00
|
|
|
duration += int(clip["duration"] * 24)
|
2026-01-06 15:04:26 +01:00
|
|
|
return duration / 24
|
|
|
|
|
|
2026-01-06 15:17:28 +01:00
|
|
|
|
2026-01-06 15:04:26 +01:00
|
|
|
def get_project_duration(file):
|
|
|
|
|
out = melt_xml(file)
|
2026-01-06 15:17:28 +01:00
|
|
|
chain = lxml.etree.fromstring(out).xpath("producer")[0]
|
|
|
|
|
duration = int(chain.attrib["out"]) + 1
|
2026-01-06 15:04:26 +01:00
|
|
|
return duration
|
|
|
|
|
|
2026-01-06 15:17:28 +01:00
|
|
|
|
2026-01-06 15:04:26 +01:00
|
|
|
def fix_overlaps(data):
|
|
|
|
|
previous = None
|
|
|
|
|
for sub in data:
|
|
|
|
|
if previous is None:
|
|
|
|
|
previous = sub
|
|
|
|
|
else:
|
2026-01-06 15:17:28 +01:00
|
|
|
if sub["in"] < previous["out"]:
|
|
|
|
|
previous["out"] = sub["in"] - 0.001
|
2026-01-06 15:04:26 +01:00
|
|
|
previous = sub
|
|
|
|
|
return data
|
|
|
|
|
|
2026-01-06 15:17:28 +01:00
|
|
|
|
2026-01-06 15:04:26 +01:00
|
|
|
def shift_clips(data, offset):
|
|
|
|
|
for clip in data:
|
2026-01-06 15:17:28 +01:00
|
|
|
clip["in"] += offset
|
|
|
|
|
clip["out"] += offset
|
2026-01-06 15:04:26 +01:00
|
|
|
return data
|
|
|
|
|
|
2026-01-06 15:17:28 +01:00
|
|
|
|
2026-01-06 15:04:26 +01:00
|
|
|
def ass_encode(subs, options):
|
|
|
|
|
if "lang" in options:
|
2026-01-06 15:17:28 +01:00
|
|
|
langs = options["lang"].split(",")
|
2026-01-06 15:04:26 +01:00
|
|
|
else:
|
|
|
|
|
langs = list(subs[0]["values"])
|
2026-01-06 15:17:28 +01:00
|
|
|
# print('ass_encode', langs, options)
|
|
|
|
|
# print(subs)
|
2026-01-06 15:04:26 +01:00
|
|
|
|
2026-01-06 15:17:28 +01:00
|
|
|
header = """[Script Info]
|
2026-01-06 15:04:26 +01:00
|
|
|
ScriptType: v4.00+
|
|
|
|
|
PlayResX: 1920
|
|
|
|
|
PlayResY: 1080
|
|
|
|
|
ScaledBorderAndShadow: yes
|
|
|
|
|
YCbCr Matrix: None
|
|
|
|
|
|
|
|
|
|
[V4+ Styles]
|
|
|
|
|
Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding
|
2026-01-06 15:17:28 +01:00
|
|
|
"""
|
2026-01-06 15:04:26 +01:00
|
|
|
ass = header
|
|
|
|
|
offset = options.get("sub_margin", 10)
|
|
|
|
|
spacing = options.get("sub_spacing", 20)
|
|
|
|
|
height = 42
|
|
|
|
|
styles = []
|
|
|
|
|
for lang in reversed(langs):
|
|
|
|
|
if isinstance(options.get("font"), list) and lang in options["font"]:
|
|
|
|
|
font = options["font"][lang]
|
|
|
|
|
else:
|
2026-01-06 15:17:28 +01:00
|
|
|
font = "SimHei" if lang in ("zh", "jp") else "Menlo"
|
2026-01-06 15:04:26 +01:00
|
|
|
if isinstance(options.get("font_size"), list) and lang in options["font_size"]:
|
|
|
|
|
size = options["font_size"][lang]
|
|
|
|
|
else:
|
2026-01-06 15:17:28 +01:00
|
|
|
size = 46 if font == "SimHei" else 42
|
2026-01-06 15:04:26 +01:00
|
|
|
|
|
|
|
|
styles.append(
|
2026-01-06 15:17:28 +01:00
|
|
|
f"Style: {lang},{font},{size},&Hffffff,&Hffffff,&H0,&H0,0,0,0,0,100,100,0,0,1,1,0,2,10,10,{offset},1"
|
2026-01-06 15:04:26 +01:00
|
|
|
)
|
|
|
|
|
offset += size + spacing
|
2026-01-06 15:17:28 +01:00
|
|
|
ass += "\n".join(reversed(styles)) + "\n"
|
2026-01-06 15:04:26 +01:00
|
|
|
events = [
|
2026-01-06 15:17:28 +01:00
|
|
|
"Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text"
|
2026-01-06 15:04:26 +01:00
|
|
|
]
|
|
|
|
|
for sub in subs:
|
2026-01-06 15:17:28 +01:00
|
|
|
start = ox.format_timecode(sub["in"]).rstrip("0")
|
|
|
|
|
stop = ox.format_timecode(sub["out"]).rstrip("0")
|
2026-01-06 15:04:26 +01:00
|
|
|
for lang in reversed(langs):
|
2026-01-06 15:17:28 +01:00
|
|
|
value = sub["values"][lang]
|
|
|
|
|
event = f"Dialogue: 0,{start},{stop},{lang},,0,0,0,,{value}"
|
2026-01-06 15:04:26 +01:00
|
|
|
events.append(event)
|
2026-01-06 15:17:28 +01:00
|
|
|
ass += "\n\n[Events]\n" + "\n".join(events) + "\n"
|
2026-01-06 15:04:26 +01:00
|
|
|
return ass
|