Compare commits

..

3 commits

Author SHA1 Message Date
j
4503e26d80 censored might not be set 2026-01-16 10:09:11 +00:00
j
22ebf5bb4e format render_utils 2026-01-06 15:17:19 +01:00
j
12613d7393 split render_utils 2026-01-06 15:16:47 +01:00
2 changed files with 170 additions and 153 deletions

157
render.py
View file

@ -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 = [

166
render_utils.py Normal file
View file

@ -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