diff --git a/render.py b/render.py index e62f513..7cea153 100644 --- a/render.py +++ b/render.py @@ -15,40 +15,7 @@ import lxml.etree from .pi import random from .render_kdenlive import KDEnliveProject, _CACHE, melt_xml, get_melt - - -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 +from .render_utils import * def write_if_new(path, data, mode=''): @@ -342,35 +309,6 @@ def compose(clips, target=150, base=1024, voice_over=None, options=None): print(scene['audio-center']['A1'][-1]) return scene, used -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_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(): - for clip in clips: - duration += int(clip['duration'] * 24) - return duration / 24 - -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 write_subtitles(data, folder, options): data = fix_overlaps(data) path = folder / "front.srt" @@ -439,12 +377,6 @@ def render(root, scene, prefix='', options=None): files.append(path) return files -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 get_fragments(clips, voice_over, prefix): import itemlist.models import item.models @@ -496,19 +428,6 @@ def get_fragments(clips, voice_over, prefix): fragments.sort(key=lambda f: ox.sort_string(f['name'])) return fragments -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 render_all(options): options = load_defaults(options) prefix = options['prefix'] @@ -861,23 +780,6 @@ def get_srt(sub, offset, lang, tlang): sdata["out"] += offset return sdata -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 scene_subtitles(scene, options): import item.models offset = 0 @@ -939,57 +841,6 @@ def update_subtitles(options): subs = scene_subtitles(scene, options) write_subtitles(subs, folder, options) -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 = ox.format_timecode(sub['in']).rstrip('0') - stop = ox.format_timecode(sub['out']).rstrip('0') - for lang in reversed(langs): - value = sub['values'][lang] - 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 - def update_m3u(render_prefix, exclude=[]): files = ox.sorted_strings(glob(render_prefix + "*/*/back.mp4")) for ex in exclude: @@ -1102,7 +953,7 @@ def generate_clips(options): options = load_defaults(options) prefix = options['prefix'] lang, tlang = parse_lang(options["lang"]) - if options['censored']: + if options.get('censored'): censored_list = itemlist.models.List.get(options["censored"]) censored = list(censored_list.get_items( censored_list.user @@ -1130,7 +981,7 @@ def generate_clips(options): os.symlink(source, target) if type_ == "original": original_target = target - if options['censored'] and e.public_id in censored: + if options.get('censored') and e.public_id in censored: clip[type_ + "_censored"] = target target = '/srv/t_for_time/censored.mp4' clip[type_] = target @@ -1199,7 +1050,7 @@ def generate_clips(options): with open(os.path.join(prefix, 'voice_over.json'), 'w') as fd: json.dump(voice_over, fd, indent=2, ensure_ascii=False) - if options['censored']: + if options.get('censored'): censored_mp4 = '/srv/t_for_time/censored.mp4' if not os.path.exists(censored_mp4): cmd = [ diff --git a/render_utils.py b/render_utils.py new file mode 100644 index 0000000..ecffdda --- /dev/null +++ b/render_utils.py @@ -0,0 +1,166 @@ +import re +import os + +import lxml.etree + +from .render_kdenlive import melt_xml + + +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): + 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(): + for clip in clips: + duration += int(clip["duration"] * 24) + return duration / 24 + + +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_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 = ox.format_timecode(sub["in"]).rstrip("0") + stop = ox.format_timecode(sub["out"]).rstrip("0") + for lang in reversed(langs): + value = sub["values"][lang] + 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