diff --git a/render.py b/render.py
index 16a8ac7..16c0219 100644
--- a/render.py
+++ b/render.py
@@ -11,64 +11,14 @@ import time
from pathlib import Path
import ox
-import lxml.etree
from .pi import random
-from .render_kdenlive import KDEnliveProject, _CACHE, melt_xml, get_melt
+from .render_kdenlive import KDEnliveProject, _CACHE, get_melt
+from .utils import resolve_roman, write_if_new, format_duration
+from .render_utils import *
default_prefix = "/srv/p_for_power"
-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 write_if_new(path, data, mode=''):
- read_mode = 'r' + mode
- write_mode = 'w' + mode
- if os.path.exists(path):
- with open(path, read_mode) as fd:
- old = fd.read()
- else:
- old = ""
- is_new = data != old
- if path.endswith(".kdenlive"):
- is_new = re.sub(r'\{.{36}\}', '', data) != re.sub(r'\{.{36}\}', '', old)
- if is_new:
- with open(path, write_mode) as fd:
- fd.write(data)
-
-def format_duration(duration, fps):
- return float('%0.5f' % (round(duration * fps) / fps))
def compose(clips, target=150, base=1024, voice_over=None, options=None):
if options is None:
@@ -80,13 +30,6 @@ def compose(clips, target=150, base=1024, voice_over=None, options=None):
'V1': [],
'V2': [],
},
- 'back': {
- 'V1': [],
- 'V2': [],
- },
- 'audio-back': {
- 'A1': [],
- },
'audio-center': {
'A1': [],
},
@@ -251,9 +194,7 @@ def compose(clips, target=150, base=1024, voice_over=None, options=None):
#if chance(seq, 0.5):
if chance(seq, 0.8):
transparency_front = transparency
- transparency_back = 0
else:
- transparency_back = random_choice(seq, [0.25, 0.5, 0.75, 1])
transparency_front = 0
transparency_original = seq() / 9
transparency_original = 1
@@ -265,13 +206,6 @@ def compose(clips, target=150, base=1024, voice_over=None, options=None):
'transparency': transparency_front
}
})
- scene['back']['V2'].append({
- 'duration': clip['duration'],
- 'src': clip['background'],
- "filter": {
- 'transparency': transparency_back
- }
- })
else:
scene['front']['V1'].append({
'duration': clip['duration'],
@@ -280,34 +214,7 @@ def compose(clips, target=150, base=1024, voice_over=None, options=None):
'transparency': 0,
}
})
- scene['back']['V2'].append({
- 'duration': clip['duration'],
- 'src': clip['original'],
- "filter": {
- 'transparency': 0,
- }
- })
- scene['back']['V1'].append({
- 'duration': clip['duration'],
- 'src': clip['original'],
- "filter": {
- 'transparency': transparency_original,
- }
- })
- # 50 % chance to blur original from 0 to 30
- if chance(seq, 0.5):
- blur = seq() * 3
- if blur:
- scene['back']['V1'][-1]['filter']['blur'] = blur
- volume_back = '-8.2'
- if options.get('stereo_downmix'):
- volume_back = '-7.2'
- scene['audio-back']['A1'].append({
- 'duration': clip['duration'],
- 'src': clip['original'],
- 'filter': {'volume': volume_back},
- })
# TBD: Foley
cf_volume = '-2.5'
scene['audio-front']['A2'].append({
@@ -343,35 +250,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"
@@ -440,12 +318,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
@@ -488,8 +360,6 @@ def get_fragments(clips, voice_over, prefix):
#if set(clip['tags']) & set(fragment['tags']) and not set(clip['tags']) & set(fragment['anti-tags']):
key = 'original'
original = clip['original']
- if 'original_censored' in clip:
- original = clip['original_censored']
if original in originals:
fragment['clips'].append(clip)
fragment["voice_over"] = voice_over.get(str(fragment["id"]), {})
@@ -497,19 +367,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']
@@ -589,7 +446,6 @@ def render_all(options):
if ext == '.wav':
cmd += ['vn=1']
else:
- #if not timeline.endswith("back.kdenlive"):
cmd += ['an=1']
cmd += ['vcodec=libx264', 'x264opts=keyint=1', 'crf=15']
subprocess.call(cmd)
@@ -633,7 +489,6 @@ def render_all(options):
"-map", "[a]", "-c:a", "aac", fragment_prefix / "audio-5.1.mp4"
])
audio_front = "audio-5.1.mp4"
- audio_back = "audio-back.wav"
copy = '-c'
if options["stereo_downmix"]:
cmds.append([
@@ -642,13 +497,11 @@ def render_all(options):
"-i", fragment_prefix / "audio-front.wav",
"-i", fragment_prefix / "audio-center.wav",
"-i", fragment_prefix / "audio-rear.wav",
- "-i", fragment_prefix / audio_back,
"-filter_complex",
"amix=inputs=4:duration=longest:dropout_transition=0",
'-ac', '2', fragment_prefix / "audio-stereo.wav"
])
audio_front = "audio-stereo.wav"
- audio_back = "audio-stereo.wav"
copy = '-c:v'
cmds.append([
@@ -660,22 +513,12 @@ def render_all(options):
"-movflags", "+faststart",
fragment_prefix / "front-mixed.mp4",
])
- cmds.append([
- "ffmpeg", "-y",
- "-nostats", "-loglevel", "error",
- "-i", fragment_prefix / "back.mp4",
- "-i", fragment_prefix / audio_back,
- "-c:v", "copy",
- "-movflags", "+faststart",
- fragment_prefix / "back-audio.mp4",
- ])
for cmd in cmds:
if options["debug"]:
print(" ".join([str(x) for x in cmd]))
subprocess.call(cmd)
for a, b in (
- ("back-audio.mp4", "back.mp4"),
("front-mixed.mp4", "front.mp4"),
):
duration_a = ox.avinfo(str(fragment_prefix / a))['duration']
@@ -684,16 +527,14 @@ def render_all(options):
print('!!', duration_a, fragment_prefix / a)
print('!!', duration_b, fragment_prefix / b)
sys.exit(-1)
- shutil.move(fragment_prefix / "back-audio.mp4", fragment_prefix / "back.mp4")
shutil.move(fragment_prefix / "front-mixed.mp4", fragment_prefix / "front.mp4")
if options["keep_audio"]:
shutil.move(fragment_prefix / "audio-center.wav", fragment_prefix / "vocals.wav")
shutil.move(fragment_prefix / "audio-front.wav", fragment_prefix / "foley.wav")
- shutil.move(fragment_prefix / "audio-back.wav", fragment_prefix / "original.wav")
for fn in (
"audio-5.1.mp4",
"audio-center.wav", "audio-rear.wav",
- "audio-front.wav", "audio-back.wav", "back-audio.mp4",
+ "audio-front.wav",
"fl.wav", "fr.wav", "fc.wav", "lfe.wav", "bl.wav", "br.wav",
"audio-stereo.wav",
):
@@ -706,8 +547,6 @@ def render_all(options):
base_prefix = Path(base_prefix)
for timeline in (
"front",
- "back",
- "audio-back",
"audio-center",
"audio-front",
"audio-rear",
@@ -762,40 +601,19 @@ def render_all(options):
"-movflags", "+faststart",
base_prefix / "front-mixed.mp4",
])
- cmds.append([
- "ffmpeg", "-y",
- "-nostats", "-loglevel", "error",
- "-i", base_prefix / "back.mp4",
- "-i", base_prefix / "audio-back.wav",
- "-c:v", "copy",
- "-movflags", "+faststart",
- base_prefix / "back-audio.mp4",
- ])
for cmd in cmds:
if options["debug"]:
print(" ".join([str(x) for x in cmd]))
subprocess.call(cmd)
- for a, b in (
- ("back-audio.mp4", "back.mp4"),
- ("front-mixed.mp4", "back.mp4"),
- ):
- duration_a = ox.avinfo(str(base_prefix / a))['duration']
- duration_b = ox.avinfo(str(base_prefix / b))['duration']
- if duration_a != duration_b:
- print('!!', duration_a, base_prefix / a)
- print('!!', duration_b, base_prefix / b)
- sys.exit(-1)
- shutil.move(base_prefix / "back-audio.mp4", base_prefix / "back.mp4")
shutil.move(base_prefix / "front-mixed.mp4", base_prefix / "front.mp4")
if options["keep_audio"]:
shutil.move(base_prefix / "audio-center.wav", base_prefix / "vocals.wav")
shutil.move(base_prefix / "audio-front.wav", base_prefix / "foley.wav")
- shutil.move(base_prefix / "audio-back.wav", base_prefix / "original.wav")
for fn in (
"audio-5.1.mp4",
"audio-center.wav", "audio-rear.wav",
- "audio-front.wav", "audio-back.wav", "back-audio.mp4",
+ "audio-front.wav",
"fl.wav", "fr.wav", "fc.wav", "lfe.wav", "bl.wav", "br.wav",
):
fn = base_prefix / fn
@@ -847,7 +665,6 @@ def add_translations_dict(sub, langs):
values[slang] = tvalue
return values
-
def get_srt(sub, offset, lang, tlang):
sdata = sub.json(keys=['in', 'out', 'value'])
sdata['value'] = sdata['value'].replace('
', '
').replace('
\n', '\n').replace('
', '\n').strip()
@@ -862,23 +679,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
@@ -940,77 +740,18 @@ 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"))
+ files = ox.sorted_strings(glob(render_prefix + "*/*/front.mp4"))
for ex in exclude:
files = [f for f in files if not f.startswith(ex + "/")]
- back_m3u = "\n".join(files)
- back_m3u = back_m3u.replace(render_prefix, "")
- front_m3u = back_m3u.replace("back.mp4", "front.mp4")
+ front_m3u = "\n".join(files)
+ front_m3u = front_m3u.replace(render_prefix, "")
- back_m3u_f = render_prefix + "back.m3u"
front_m3u_f = render_prefix + "front.m3u"
- with open(back_m3u_f + "_", "w") as fd:
- fd.write(back_m3u)
with open(front_m3u_f + "_", "w") as fd:
fd.write(front_m3u)
shutil.move(front_m3u_f + "_", front_m3u_f)
- cmd = ["scp", front_m3u_f, "front:" + front_m3u_f]
- subprocess.check_call(cmd)
- shutil.move(back_m3u_f + "_", back_m3u_f)
-
def render_infinity(options):
prefix = options['prefix']
@@ -1048,13 +789,7 @@ def render_infinity(options):
folder = render_prefix + folder
print("remove", folder)
shutil.rmtree(folder)
- cmd = ["ssh", "front", "rm", "-rf", folder]
- #print(cmd)
- subprocess.check_call(cmd)
render_all(state)
- path = "%s%s/" % (render_prefix, state["offset"])
- cmd = ['rsync', '-a', path, "front:" + path]
- subprocess.check_call(cmd)
update_m3u(render_prefix)
state["offset"] += 1
with open(state_f + "~", "w") as fd:
@@ -1063,17 +798,6 @@ def render_infinity(options):
def join_subtitles(base_prefix, options):
- '''
- subtitles = list(sorted(glob('%s/*/front.srt' % base_prefix)))
- data = []
- position = 0
- for srt in subtitles:
- scene = srt.replace('front.srt', 'scene.json')
- data += ox.srt.load(srt, offset=position)
- position += get_scene_duration(scene)
- with open(base_prefix / 'front.srt', 'wb') as fd:
- fd.write(ox.srt.encode(data))
- '''
scenes = list(sorted(glob('%s/*/scene.json' % base_prefix)))
data = []
position = 0
@@ -1085,17 +809,6 @@ def join_subtitles(base_prefix, options):
position += get_scene_duration(scene)
write_subtitles(data, base_prefix, options)
-def resolve_roman(s):
- extra = re.compile(r'^\d+(.*?)$').findall(s)
- if extra:
- extra = extra[0].lower()
- new = {
- 'i': '1', 'ii': '2', 'iii': '3', 'iv': '4', 'v': '5',
- 'vi': '6', 'vii': 7, 'viii': '8', 'ix': '9', 'x': '10'
- }.get(extra, extra)
- return s.replace(extra, new)
- return s
-
def generate_clips(options):
import item.models
import itemlist.models
@@ -1103,11 +816,6 @@ def generate_clips(options):
options = load_defaults(options)
prefix = options['prefix']
lang, tlang = parse_lang(options["lang"])
- if options['censored']:
- censored_list = itemlist.models.List.get(options["censored"])
- censored = list(censored_list.get_items(
- censored_list.user
- ).all().values_list('public_id', flat=True))
clips = []
for i in item.models.Item.objects.filter(sort__type='original'):
original_target = ""
@@ -1131,9 +839,6 @@ def generate_clips(options):
os.symlink(source, target)
if type_ == "original":
original_target = target
- if options['censored'] and e.public_id in censored:
- clip[type_ + "_censored"] = target
- target = '/srv/t_for_time/censored.mp4'
clip[type_] = target
durations.append(e.files.filter(selected=True)[0].duration)
clip["duration"] = min(durations)
@@ -1141,11 +846,8 @@ def generate_clips(options):
print('!!', durations, clip)
continue
cd = format_duration(clip["duration"], 24)
- #if cd != clip["duration"]:
- # print(clip["duration"], '->', cd, durations, clip)
clip["duration"] = cd
clip['tags'] = i.data.get('tags', [])
- clip['editingtags'] = i.data.get('editingtags', [])
name = os.path.basename(original_target)
seqid = re.sub(r"Hotel Aporia_(\d+)", "S\\1_", name)
seqid = re.sub(r"Night March_(\d+)", "S\\1_", seqid)
@@ -1161,12 +863,8 @@ def generate_clips(options):
except:
print(name, seqid, 'failed')
raise
- if "original" in clip and "foreground" in clip and "background" in clip:
- clips.append(clip)
- elif "original" in clip and "animation" in clip:
- clips.append(clip)
- else:
- print("ignoring incomplete video", i)
+
+ clips.append(clip)
with open(os.path.join(prefix, 'clips.json'), 'w') as fd:
json.dump(clips, fd, indent=2, ensure_ascii=False)
@@ -1199,18 +897,3 @@ 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']:
- censored_mp4 = '/srv/t_for_time/censored.mp4'
- if not os.path.exists(censored_mp4):
- cmd = [
- "ffmpeg",
- "-nostats", "-loglevel", "error",
- "-f", "lavfi",
- "-i", "color=color=white:size=1920x1080:rate=24",
- "-t", "3600",
- "-c:v", "libx264",
- "-pix_fmt", "yuv420p",
- censored_mp4
- ]
- subprocess.call(cmd)
diff --git a/render_utils.py b/render_utils.py
new file mode 100644
index 0000000..424d0c1
--- /dev/null
+++ b/render_utils.py
@@ -0,0 +1,159 @@
+import re
+import os
+
+import lxml.etree
+
+from .render_kdenlive melt_xml, get_melt
+
+
+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
+
diff --git a/utils.py b/utils.py
index b6efc6d..8824d2b 100644
--- a/utils.py
+++ b/utils.py
@@ -1,4 +1,16 @@
+import os
+import re
+def resolve_roman(s):
+ extra = re.compile(r'^\d+(.*?)$').findall(s)
+ if extra:
+ extra = extra[0].lower()
+ new = {
+ 'i': '1', 'ii': '2', 'iii': '3', 'iv': '4', 'v': '5',
+ 'vi': '6', 'vii': 7, 'viii': '8', 'ix': '9', 'x': '10'
+ }.get(extra, extra)
+ return s.replace(extra, new)
+ return s
def upgrade_originals():
import item.models
@@ -30,3 +42,21 @@ def remove_deselected_files():
if changed:
i.save()
+def write_if_new(path, data, mode=''):
+ read_mode = 'r' + mode
+ write_mode = 'w' + mode
+ if os.path.exists(path):
+ with open(path, read_mode) as fd:
+ old = fd.read()
+ else:
+ old = ""
+ is_new = data != old
+ if path.endswith(".kdenlive"):
+ is_new = re.sub(r'\{.{36}\}', '', data) != re.sub(r'\{.{36}\}', '', old)
+ if is_new:
+ with open(path, write_mode) as fd:
+ fd.write(data)
+
+def format_duration(duration, fps):
+ return float('%0.5f' % (round(duration * fps) / fps))
+