import re import os import lxml.etree import ox from .render_kdenlive import melt_xml from .utils import format_duration def parse_lang(lang): if lang and "," in lang: lang = lang.split(",") if isinstance(lang, list): tlang = lang[1:] lang = lang[0] else: tlang = None if lang == "en": lang = None return lang, tlang def random_int(seq, length): n = n_ = length - 1 # print('len', n) 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 def random_choice(seq, items, pop=False): n = random_int(seq, len(items)) if pop: return items.pop(n) return items[n] def chance(seq, chance): return (seq() / 10) < chance def get_clip_by_seqid(clips, seqid): selected = None for i, clip in enumerate(clips): if clip["seqid"] == seqid: selected = i break if selected is not None: return clips.pop(i) return None def get_scene_duration(scene, fps=24, track=None): 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(): if track and '%s:%s' % (key, name) != track: continue if clips: for clip in clips: duration += round(clip["duration"] * fps) #print("scene duration based on %s:%s is %s %s" % (key, name, duration / fps, format_duration(duration / fps, fps))) return duration / fps def get_offset_duration(prefix): duration = 0 for root, folders, files in os.walk(prefix): for f in files: if f == "scene.json": 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: duration += int(clip["duration"] * 24) return duration / 24 def get_project_duration(file): out = melt_xml(file) chain = lxml.etree.fromstring(out).xpath("producer")[0] duration = int(chain.attrib["out"]) + 1 return duration def fix_overlaps(data): previous = None for sub in data: if previous is None: previous = sub else: if sub["in"] < previous["out"]: previous["out"] = sub["in"] - 0.001 previous = sub return data def shift_clips(data, offset): for clip in data: clip["in"] += offset clip["out"] += offset return data def ass_timestamp(seconds): hours = int(seconds // 3600) minutes = int((seconds % 3600) // 60) secs = seconds % 60 whole_seconds = int(secs) centiseconds = int(round((secs - whole_seconds) * 100)) # Handle centisecond rollover (e.g., 59.999 → 60.00) if centiseconds == 100: whole_seconds += 1 centiseconds = 0 if whole_seconds == 60: whole_seconds = 0 minutes += 1 if minutes == 60: minutes = 0 hours += 1 return f"{hours}:{minutes:02d}:{whole_seconds:02d}.{centiseconds:02d}" def ass_encode(subs, options): if "lang" in options: langs = options["lang"].split(",") else: langs = list(subs[0]["values"]) # print('ass_encode', langs, options) # print(subs) header = """[Script Info] 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 """ 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: font = "SimHei" if lang in ("zh", "jp") else "Menlo" if isinstance(options.get("font_size"), list) and lang in options["font_size"]: size = options["font_size"][lang] else: size = 46 if font == "SimHei" else 42 styles.append( 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" ) offset += size + spacing ass += "\n".join(reversed(styles)) + "\n" events = [ "Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text" ] for sub in subs: start = ass_timestamp(sub["in"]) stop = ass_timestamp(sub["out"]) for lang in reversed(langs): value = sub["values"][lang] value = value.replace('\n', '\\N') event = f"Dialogue: 0,{start},{stop},{lang},,0,0,0,,{value}" events.append(event) ass += "\n\n[Events]\n" + "\n".join(events) + "\n" return ass