├── tests ├── __init__.py ├── test_srttime.py ├── static │ ├── I Move On.txt │ ├── I Move On_dictionary.txt │ ├── I Move On_bases.srt │ ├── I Move On_unsynchronized.srt │ ├── non_subsimport.lrc │ ├── I Move On.lrc │ └── I Move On_tops.srt ├── test_textparser.py ├── test_pysrt.py └── test_pylrc.py ├── operators ├── pylrc │ ├── __init__.py │ ├── tools │ │ ├── __init__.py │ │ ├── find_even_split.py │ │ └── timecode.py │ ├── parser.py │ ├── lyricline.py │ └── lyrics.py ├── pysrt │ ├── __init__.py │ ├── version.py │ ├── compat.py │ ├── srtexc.py │ ├── comparablemixin.py │ ├── srtitem.py │ ├── convert_enhanced.py │ ├── srttime.py │ └── commands.py ├── tools │ ├── __init__.py │ ├── get_font.py │ ├── color_to_hexcode.py │ ├── get_base_strip.py │ ├── update_progress.py │ ├── get_open_channel.py │ ├── get_text_strips.py │ ├── hexcode_to_color.py │ ├── remove_punctuation.py │ └── subtitles_to_sequencer.py ├── hyphenator │ ├── __init__.py │ ├── patterns │ │ ├── bn.txt │ │ ├── gu.txt │ │ ├── pa.txt │ │ ├── kn.txt │ │ ├── te.txt │ │ ├── hi.txt │ │ ├── or.txt │ │ ├── getter.txt │ │ ├── ta.txt │ │ ├── ml.txt │ │ ├── pt.txt │ │ ├── fi.txt │ │ ├── it.txt │ │ ├── la.txt │ │ ├── tr.txt │ │ ├── el-monoton.txt │ │ ├── ro.txt │ │ └── ca.txt │ ├── get_dictionary.py │ └── hyphenator.py ├── textparser │ ├── __init__.py │ ├── tools │ │ ├── __init__.py │ │ ├── seconds_to_srt_timecode.py │ │ └── find_even_split.py │ └── parser.py ├── __init__.py ├── refresh_highlight.py ├── refresh_font_data.py ├── save_syllables.py ├── export_srt.py ├── select.py ├── duration_adjust.py ├── export_lrc.py ├── import_subtitles.py ├── syllabify.py ├── split_words.py ├── shortcuts.py └── combine_words.py ├── .gitignore ├── tools ├── pattern_converter.py ├── automatic_text_alignment.py ├── word_collector.py └── (early version) syllabator.py ├── README.rst └── __init__.py /tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /operators/pylrc/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /operators/pysrt/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /operators/tools/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /operators/hyphenator/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /operators/pylrc/tools/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /operators/textparser/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /operators/textparser/tools/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /operators/pysrt/version.py: -------------------------------------------------------------------------------- 1 | VERSION = (1, 0, 1) 2 | VERSION_STRING = '.'.join(str(i) for i in VERSION) -------------------------------------------------------------------------------- /operators/tools/get_font.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | 3 | import os.path 4 | 5 | def get_font(font_path): 6 | 7 | if os.path.isfile(bpy.path.abspath(font_path)): 8 | return bpy.data.fonts.load(font_path, check_existing=True) 9 | else: 10 | return None 11 | -------------------------------------------------------------------------------- /operators/tools/color_to_hexcode.py: -------------------------------------------------------------------------------- 1 | def color_to_hexcode(color): 2 | """convert float color to hexcode color""" 3 | for i in range(len(color)): 4 | color[i] = int(color[i] * 256) 5 | if color[i] == 256: 6 | color[i] -= 1 7 | return '#%02x%02x%02x' % tuple(color) -------------------------------------------------------------------------------- /operators/tools/get_base_strip.py: -------------------------------------------------------------------------------- 1 | def get_base_strip(strip, base_strips): 2 | """Get the strip's base strip""" 3 | for i in range(len(base_strips)): 4 | if base_strips[i].frame_start <= strip.frame_start: 5 | if base_strips[i].frame_final_end >= strip.frame_final_end: 6 | return base_strips[i] -------------------------------------------------------------------------------- /tests/test_srttime.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import unittest 4 | 5 | file_path = os.path.abspath(os.path.join(os.path.dirname(__file__), '..')) 6 | sys.path.insert(0, os.path.abspath(file_path)) 7 | 8 | from operators.pysrt.srttime import SubRipTime 9 | 10 | x = SubRipTime() 11 | x.from_millis(500000) 12 | # 00:08:20,000 13 | print(x) -------------------------------------------------------------------------------- /operators/tools/update_progress.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | def update_progress(job_title, progress): 4 | length = 20 5 | block = int(round(length * progress)) 6 | msg = "\r{0}: [{1}] {2}%".format(job_title, 7 | "#" * block + "-" * (length-block), 8 | "%.2f" % (progress * 100)) 9 | 10 | if progress >= 1: 11 | msg += " DONE\r\n" 12 | sys.stdout.write(msg) 13 | sys.stdout.flush() -------------------------------------------------------------------------------- /operators/tools/get_open_channel.py: -------------------------------------------------------------------------------- 1 | def get_open_channel(scene): 2 | """Get a channel with nothing in it""" 3 | channels = [] 4 | try: 5 | for strip in scene.sequence_editor.sequences_all: 6 | channels.append(strip.channel) 7 | if len(channels) > 0: 8 | return max(channels) + 1 9 | else: 10 | return 1 11 | except AttributeError: 12 | return 1 -------------------------------------------------------------------------------- /operators/pysrt/compat.py: -------------------------------------------------------------------------------- 1 | 2 | import sys 3 | 4 | # Syntax sugar. 5 | _ver = sys.version_info 6 | 7 | #: Python 2.x? 8 | is_py2 = (_ver[0] == 2) 9 | 10 | #: Python 3.x? 11 | is_py3 = (_ver[0] == 3) 12 | 13 | from io import open as io_open 14 | 15 | if is_py2: 16 | basestring = basestring 17 | str = unicode 18 | open = io_open 19 | elif is_py3: 20 | basestring = (str, bytes) 21 | str = str 22 | open = open -------------------------------------------------------------------------------- /operators/hyphenator/patterns/bn.txt: -------------------------------------------------------------------------------- 1 | 1ক 2 | 1খ 3 | 1গ 4 | 1ঘ 5 | 1ঙ 6 | 1চ 7 | 1ছ 8 | 1জ 9 | 1ঝ 10 | 1ঞ 11 | 1ট 12 | 1ঠ 13 | 1ড 14 | 1ঢ 15 | 1ণ 16 | 1ত 17 | 1থ 18 | 1দ 19 | 1ধ 20 | 1ন 21 | 1প 22 | 1ফ 23 | 1ব 24 | 1ভ 25 | 1ম 26 | 1য 27 | 1র 28 | 1ল 29 | 1শ 30 | 1ষ 31 | 1স 32 | 1হ 33 | 2ং1 34 | 2ঃ1 35 | ং1 36 | ঃ1 37 | অ1 38 | আ1 39 | ই1 40 | ঈ1 41 | উ1 42 | ঊ1 43 | ঋ1 44 | এ1 45 | ঐ1 46 | ঔ1 47 | া1 48 | ি1 49 | ী1 50 | ু1 51 | ৃ1 52 | ে1 53 | ো1 54 | ৌ1 55 | ্2 56 | ৗ1 -------------------------------------------------------------------------------- /operators/hyphenator/patterns/gu.txt: -------------------------------------------------------------------------------- 1 | 1ક 2 | 1ખ 3 | 1ગ 4 | 1ઘ 5 | 1ઙ 6 | 1ચ 7 | 1છ 8 | 1જ 9 | 1ઝ 10 | 1ઞ 11 | 1ટ 12 | 1ઠ 13 | 1ડ 14 | 1ઢ 15 | 1ણ 16 | 1ત 17 | 1થ 18 | 1દ 19 | 1ધ 20 | 1ન 21 | 1પ 22 | 1ફ 23 | 1બ 24 | 1ભ 25 | 1મ 26 | 1ય 27 | 1ર 28 | 1લ 29 | 1ળ 30 | 1વ 31 | 1શ 32 | 1ષ 33 | 1સ 34 | 1હ 35 | ં1 36 | ઃ1 37 | અ1 38 | આ1 39 | ઇ1 40 | ઈ1 41 | ઉ1 42 | ઊ1 43 | ઋ1 44 | એ1 45 | ઐ1 46 | ઔ1 47 | ા1 48 | િ1 49 | ી1 50 | ુ1 51 | ૂ1 52 | ૃ1 53 | ે1 54 | ો1 55 | ૌ1 56 | ્2 -------------------------------------------------------------------------------- /operators/hyphenator/patterns/pa.txt: -------------------------------------------------------------------------------- 1 | %u 2 | (" 3 | 02 4 | 0A 5 | 11 6 | ca 7 | es 8 | pe 9 | un 10 | ਃ1 11 | ਅ1 12 | ਆ1 13 | ਇ1 14 | ਈ1 15 | ਉ1 16 | ਊ1 17 | ਏ1 18 | ਐ1 19 | ਔ1 20 | ਕ1 21 | ਖ1 22 | ਗ1 23 | ਘ1 24 | ਙ1 25 | ਚ1 26 | ਛ1 27 | ਜ1 28 | ਝ1 29 | ਞ1 30 | ਟ1 31 | ਠ1 32 | ਡ1 33 | ਢ1 34 | ਣ1 35 | ਤ1 36 | ਥ1 37 | ਦ1 38 | ਧ1 39 | ਨ1 40 | ਪ1 41 | ਫ1 42 | ਬ1 43 | ਭ1 44 | ਮ1 45 | ਯ1 46 | ਰ1 47 | ਲ1 48 | ਲ਼ 49 | ਵ1 50 | ਸ਼1 51 | ਸ1 52 | ਹ1 53 | ਾ1 54 | ਿ1 55 | ੀ1 56 | ੁ1 57 | ੂ1 58 | ੇ1 59 | ੋ1 60 | ੌ1 61 | ੍2 -------------------------------------------------------------------------------- /operators/hyphenator/patterns/kn.txt: -------------------------------------------------------------------------------- 1 | 1ಕ 2 | 1ಖ 3 | 1ಗ 4 | 1ಘ 5 | 1ಙ 6 | 1ಚ 7 | 1ಛ 8 | 1ಜ 9 | 1ಝ 10 | 1ಞ 11 | 1ಟ 12 | 1ಠ 13 | 1ಡ 14 | 1ಢ 15 | 1ಣ 16 | 1ತ 17 | 1ಥ 18 | 1ದ 19 | 1ಧ 20 | 1ನ 21 | 1ಪ 22 | 1ಫ 23 | 1ಬ 24 | 1ಭ 25 | 1ಮ 26 | 1ಯ 27 | 1ರ 28 | 1ಱ 29 | 1ಲ 30 | 1ಳ 31 | 1ವ 32 | 1ಶ 33 | 1ಷ 34 | 1ಸ 35 | 1ಹ 36 | 2ಂ1 37 | 2ಃ1 38 | ಂ1 39 | ಃ1 40 | ಅ1 41 | ಆ1 42 | ಇ1 43 | ಈ1 44 | ಉ1 45 | ಊ1 46 | ಋ1 47 | ಎ1 48 | ಏ1 49 | ಐ1 50 | ಒ1 51 | ಔ1 52 | ೀ1 53 | ು1 54 | ೂ1 55 | ೃ1 56 | ೆ1 57 | ೇ1 58 | ೊ1 59 | ೋ1 60 | ೌ1 61 | ್2 -------------------------------------------------------------------------------- /operators/hyphenator/patterns/te.txt: -------------------------------------------------------------------------------- 1 | 1క 2 | 1ఖ 3 | 1గ 4 | 1ఘ 5 | 1ఙ 6 | 1చ 7 | 1ఛ 8 | 1జ 9 | 1ఝ 10 | 1ఞ 11 | 1ట 12 | 1ఠ 13 | 1డ 14 | 1ఢ 15 | 1ణ 16 | 1త 17 | 1థ 18 | 1ద 19 | 1ధ 20 | 1న 21 | 1ప 22 | 1ఫ 23 | 1బ 24 | 1భ 25 | 1మ 26 | 1య 27 | 1ర 28 | 1ఱ 29 | 1ల 30 | 1ళ 31 | 1వ 32 | 1శ 33 | 1ష 34 | 1స 35 | 1హ 36 | ం1 37 | ః1 38 | అ1 39 | ఆ1 40 | ఇ1 41 | ఈ1 42 | ఉ1 43 | ఊ1 44 | ఋ1 45 | ఎ1 46 | ఏ1 47 | ఐ1 48 | ఒ1 49 | ఔ1 50 | ా1 51 | ి1 52 | ీ1 53 | ు1 54 | ూ1 55 | ృ1 56 | ె1 57 | ే1 58 | ొ1 59 | ో1 60 | ౌ1 61 | ్2 -------------------------------------------------------------------------------- /operators/hyphenator/patterns/hi.txt: -------------------------------------------------------------------------------- 1 | 1क 2 | 1ख 3 | 1ग 4 | 1घ 5 | 1ङ 6 | 1च 7 | 1छ 8 | 1ज 9 | 1झ 10 | 1ञ 11 | 1ट 12 | 1ठ 13 | 1ड 14 | 1ढ 15 | 1ण 16 | 1त 17 | 1थ 18 | 1द 19 | 1ध 20 | 1न 21 | 1प 22 | 1फ 23 | 1ब 24 | 1भ 25 | 1म 26 | 1य 27 | 1र 28 | 1ऱ 29 | 1ल 30 | 1ळ 31 | 1ऴ 32 | 1व 33 | 1श 34 | 1ष 35 | 1स 36 | 1ह 37 | ं1 38 | ः1 39 | अ1 40 | आ1 41 | इ1 42 | ई1 43 | उ1 44 | ऊ1 45 | ऋ1 46 | ऎ1 47 | ए1 48 | ऐ1 49 | ऒ1 50 | औ1 51 | ा1 52 | ि1 53 | ी1 54 | ु1 55 | ू1 56 | ृ1 57 | ॆ1 58 | े1 59 | ॊ1 60 | ो1 61 | ौ1 62 | ्2 -------------------------------------------------------------------------------- /operators/tools/get_text_strips.py: -------------------------------------------------------------------------------- 1 | def get_text_strips(scene, channel=None): 2 | """Get all the text strips in the edit channel""" 3 | 4 | if channel == None: 5 | channel = scene.subtitle_edit_channel 6 | 7 | all_strips = list(sorted(scene.sequence_editor.sequences, 8 | key=lambda x: x.frame_start)) 9 | 10 | text_strips = [] 11 | for strip in all_strips: 12 | if (strip.channel == channel and 13 | strip.type == 'TEXT'): 14 | text_strips.append(strip) 15 | 16 | return text_strips -------------------------------------------------------------------------------- /operators/tools/hexcode_to_color.py: -------------------------------------------------------------------------------- 1 | def hexcode_to_color(value): 2 | """ 3 | Return [float_red, float_green, float_blue] 4 | for the color given as #rrggbb. 5 | """ 6 | color = [] 7 | value = value.lstrip('#') 8 | 9 | for i in range(0, len(value), 2): 10 | color.append(int(value[i:i + 2], 16)) 11 | 12 | for i in range(len(color)): 13 | if color[i] == 255: 14 | color[i] = 256 15 | 16 | color[i] = color[i] / 256 17 | 18 | return color 19 | 20 | if __name__ == '__main__': 21 | 22 | print(hexcode_to_color('#FF0000')) -------------------------------------------------------------------------------- /operators/pysrt/srtexc.py: -------------------------------------------------------------------------------- 1 | """ 2 | Exception classes 3 | """ 4 | 5 | 6 | class Error(Exception): 7 | """ 8 | Pysrt's base exception 9 | """ 10 | pass 11 | 12 | 13 | class InvalidTimeString(Error): 14 | """ 15 | Raised when parser fail on bad formated time strings 16 | """ 17 | pass 18 | 19 | 20 | class InvalidItem(Error): 21 | """ 22 | Raised when parser fail to parse a sub title item 23 | """ 24 | pass 25 | 26 | 27 | class InvalidIndex(InvalidItem): 28 | """ 29 | Raised when parser fail to parse a sub title index 30 | """ 31 | pass -------------------------------------------------------------------------------- /operators/hyphenator/patterns/or.txt: -------------------------------------------------------------------------------- 1 | %u20 2 | ("2ନ 3 | 00D 4 | 00D2 5 | 0D2ଣ 6 | 1କ 7 | 1ଖ 8 | 1ଗ1 9 | 1ଘ 10 | 1ଙ 11 | 1ଚ 12 | 1ଛ 13 | 1ଜ 14 | 1ଝ 15 | 1ଞ 16 | 1ଟ 17 | 1ଠ 18 | 1ଡ 19 | 1ଢ 20 | 1ଣ 21 | 1ତ 22 | 1ଥ 23 | 1ଦ 24 | 1ଧ 25 | 1ନ 26 | 1ପ 27 | 1ଫ 28 | 1ବ 29 | 1ଭ 30 | 1ମ 31 | 1ଯ 32 | 1ର 33 | 1ଲ 34 | 1ଳ 35 | 1ଵ 36 | 1ଶ 37 | 1ଷ 38 | 1ସ 39 | 1ହ 40 | 200D 41 | 2ଲ୍% 42 | D2ଳ୍ 43 | cape 44 | u200 45 | unes 46 | ଂ1 47 | ଃ1 48 | ଅ1 49 | ଆ1 50 | ଇ1 51 | ଈ1 52 | ଉ1 53 | ଊ1 54 | ଋ1 55 | ଏ1 56 | ଐ1 57 | ଔ1 58 | ର୍%u 59 | ା1 60 | ି1 61 | ୀ1 62 | ୁ1 63 | ୂ1 64 | ୃ1 65 | େ1 66 | ୋ1 67 | ୌ1 68 | ୍%u2 69 | ୍%u2 70 | ୍2 71 | ୗ1 -------------------------------------------------------------------------------- /operators/tools/remove_punctuation.py: -------------------------------------------------------------------------------- 1 | def remove_punctuation(word): 2 | """Remove punctuation from the beginning and end of a word""" 3 | 4 | puncs = ['\\', '/', ':', '*', '?', '"', '<', '>', '|', '\n', '(', 5 | ')', '!', '@', '#', '$', '%', '^', '-', '+', '=', "'", 6 | ';', ',', '.', '{', '}','[', ']'] 7 | 8 | while len(word) > 0 and word[0] in puncs: 9 | word = word[1::] 10 | 11 | while len(word) > 0 and word[-1] in puncs: 12 | word = word[0:-1] 13 | 14 | return word 15 | 16 | if __name__ == '__main__': 17 | print(remove_punctuation('...')) -------------------------------------------------------------------------------- /operators/hyphenator/patterns/getter.txt: -------------------------------------------------------------------------------- 1 | %u20 2 | ("2ନ 3 | 00D 4 | 00D2 5 | 0D2ଣ 6 | 1କ 7 | 1ଖ 8 | 1ଗ1 9 | 1ଘ 10 | 1ଙ 11 | 1ଚ 12 | 1ଛ 13 | 1ଜ 14 | 1ଝ 15 | 1ଞ 16 | 1ଟ 17 | 1ଠ 18 | 1ଡ 19 | 1ଢ 20 | 1ଣ 21 | 1ତ 22 | 1ଥ 23 | 1ଦ 24 | 1ଧ 25 | 1ନ 26 | 1ପ 27 | 1ଫ 28 | 1ବ 29 | 1ଭ 30 | 1ମ 31 | 1ଯ 32 | 1ର 33 | 1ଲ 34 | 1ଳ 35 | 1ଵ 36 | 1ଶ 37 | 1ଷ 38 | 1ସ 39 | 1ହ 40 | 200D 41 | 2ଲ୍% 42 | D2ଳ୍ 43 | cape 44 | u200 45 | unes 46 | ଂ1 47 | ଃ1 48 | ଅ1 49 | ଆ1 50 | ଇ1 51 | ଈ1 52 | ଉ1 53 | ଊ1 54 | ଋ1 55 | ଏ1 56 | ଐ1 57 | ଔ1 58 | ର୍%u 59 | ା1 60 | ି1 61 | ୀ1 62 | ୁ1 63 | ୂ1 64 | ୃ1 65 | େ1 66 | ୋ1 67 | ୌ1 68 | ୍%u2 69 | ୍%u2 70 | ୍2 71 | ୗ1 -------------------------------------------------------------------------------- /operators/hyphenator/patterns/ta.txt: -------------------------------------------------------------------------------- 1 | 1அ1 2 | 1ஆ1 3 | 1இ1 4 | 1ஈ1 5 | 1உ1 6 | 1ஊ1 7 | 1எ1 8 | 1ஏ1 9 | 1ஐ1 10 | 1ஒ1 11 | 1ஓ1 12 | 1ஔ1 13 | 1க 14 | 1ங 15 | 1ச 16 | 1ஜ 17 | 1ஞ 18 | 1ட 19 | 1ண 20 | 1த 21 | 1ந 22 | 1ப 23 | 1ம 24 | 1ய 25 | 1ர 26 | 1ற 27 | 1ல 28 | 1ள 29 | 1ழ 30 | 1வ 31 | 1ஷ 32 | 1ஸ 33 | 1ஹ 34 | 2ஂ1 35 | 2ஃ1 36 | 2க்1 37 | 2ங்1 38 | 2ச்1 39 | 2ஞ்1 40 | 2ட்1 41 | 2ண்1 42 | 2த்1 43 | 2ந்1 44 | 2ன்1 45 | 2ப்1 46 | 2ம்1 47 | 2ய்1 48 | 2ர்1 49 | 2ற்1 50 | 2ல்1 51 | 2ள்1 52 | 2ழ்1 53 | 2வ்1 54 | 2ஷ்1 55 | 2ஸ்1 56 | 2ஹ்1 57 | 2்1 58 | 2ௗ1 59 | ா1 60 | ி1 61 | ீ1 62 | ு1 63 | ூ1 64 | ெ1 65 | ே1 66 | ை1 67 | ொ1 68 | ோ1 69 | ௌ1 -------------------------------------------------------------------------------- /operators/textparser/tools/seconds_to_srt_timecode.py: -------------------------------------------------------------------------------- 1 | def seconds_to_srt_timecode(sec_time): 2 | """ 3 | Converts time (in seconds) into a timecode with the format 4 | 23:59:59,999 5 | """ 6 | hours = int(sec_time / 3600) 7 | minutes = int((sec_time % 3600) / 60) 8 | seconds = int((((sec_time % 3600) / 60) - minutes) * 60) 9 | milliseconds = int((sec_time - int(sec_time)) * 1000) 10 | 11 | hours = "%02d" % hours 12 | minutes = "%02d" % minutes 13 | seconds = "%02d" % seconds 14 | milliseconds = "%03d" % milliseconds 15 | 16 | return ''.join([hours, ':', minutes, ':', seconds, ',', 17 | milliseconds]) -------------------------------------------------------------------------------- /operators/pylrc/tools/find_even_split.py: -------------------------------------------------------------------------------- 1 | def find_even_split(line): 2 | """ 3 | Given a string, splits it into two (almost) evenly spaced lines 4 | """ 5 | word_list = line.split(' ') 6 | differences = [] 7 | for i in range(len(word_list)): 8 | group1 = ' '.join(word_list[0:i + 1]) 9 | group2 = ' '.join(word_list[i + 1::]) 10 | differences.append(abs(len(group1) - len(group2))) 11 | index = differences.index(min(differences)) 12 | for i in range(len(word_list)): 13 | if i == index: 14 | group1 = ' '.join(word_list[0:i+1]) 15 | group2 = ' '.join(word_list[i+1::]) 16 | 17 | return ''.join([group1, '\n', group2]).rstrip() -------------------------------------------------------------------------------- /operators/hyphenator/get_dictionary.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | def get_dictionary(dic_path=None, lang='en-us'): 4 | """Collect the words from the default dictionary""" 5 | 6 | if dic_path == None: 7 | module_path = os.path.dirname(__file__) 8 | dic_path = os.path.join(module_path, 'dictionaries', lang + '.txt') 9 | 10 | dictionary = {} 11 | 12 | try: 13 | f = open(dic_path, 'r') 14 | lines = f.readlines() 15 | f.close() 16 | 17 | for line in lines: 18 | word = line.rstrip().replace(' ', '') 19 | dictionary[word] = line.rstrip() 20 | 21 | except FileNotFoundError: 22 | dictionary = {} 23 | 24 | return dictionary -------------------------------------------------------------------------------- /operators/hyphenator/patterns/ml.txt: -------------------------------------------------------------------------------- 1 | %u20 2 | ("2ന 3 | 00D2 4 | 00D2 5 | 0D2ണ 6 | 1അ1 7 | 1ആ1 8 | 1ഇ1 9 | 1ഈ1 10 | 1ഉ1 11 | 1ഊ1 12 | 1ഋ1 13 | 1ഌ1 14 | 1എ1 15 | 1ഏ1 16 | 1ഐ1 17 | 1ഒ1 18 | 1ഓ1 19 | 1ഔ1 20 | 1ക 21 | 1ഖ 22 | 1ഗ 23 | 1ഘ 24 | 1ങ 25 | 1ച 26 | 1ഛ 27 | 1ജ 28 | 1ഝ 29 | 1ഞ 30 | 1ട 31 | 1ഠ 32 | 1ഡ 33 | 1ഢ 34 | 1ണ 35 | 1ത 36 | 1ഥ 37 | 1ദ 38 | 1ധ 39 | 1ന 40 | 1പ 41 | 1ഫ 42 | 1ബ 43 | 1ഭ 44 | 1മ 45 | 1യ 46 | 1ര 47 | 1റ 48 | 1ല 49 | 1ള 50 | 1ഴ 51 | 1വ 52 | 1ശ 53 | 1ഷ 54 | 1സ 55 | 1ഹ 56 | 1ൠ1 57 | 1ൡ1 58 | 200D 59 | 200D 60 | 2ം1 61 | 2ഃ1 62 | 2ല്% 63 | 2്2 64 | 2ൺ 65 | 2ൻ 66 | 2ർ 67 | 2ൽ 68 | 2ൾ 69 | 2ൿ 70 | D2ള് 71 | cape 72 | u200 73 | unes 74 | ക്%u 75 | ക്2 76 | ണ്2 77 | ന്2 78 | ര്%u 79 | ര്2 80 | ല്2 81 | ള്2 82 | ാ1 83 | ി1 84 | ീ1 85 | ു1 86 | ൂ1 87 | ൃ1 88 | െ1 89 | േ1 90 | ൈ1 91 | ൊ1 92 | ോ1 93 | ൌ1 94 | ്%u2 95 | ്%u2 96 | ൗ1 -------------------------------------------------------------------------------- /tests/static/I Move On.txt: -------------------------------------------------------------------------------- 1 | Come take my journey 2 | Into night 3 | Come be my shadow 4 | Walk at my side 5 | And when you see 6 | All that I have seen 7 | Can you tell me 8 | Love from pride? 9 | I have been waiting 10 | all this time 11 | for one to wake me 12 | one to call mine 13 | So when you're near 14 | all that you hold dear 15 | do you fear what you will find? 16 | As the dawn 17 | Breaks through the night 18 | I move on 19 | Forever longing for the home 20 | I found in your eyes 21 | I will be listening 22 | for the drum 23 | to call me over 24 | far away from 25 | my tender youth 26 | and the very truth 27 | showing me what I've become 28 | As the dawn 29 | Breaks through the night 30 | I move on 31 | Forever longing for the home 32 | I found in your eyes 33 | Your voice 34 | saw me through the night 35 | -------------------------------------------------------------------------------- /tests/static/I Move On_dictionary.txt: -------------------------------------------------------------------------------- 1 | all 2 | and 3 | as 4 | at 5 | a way 6 | be 7 | be come 8 | been 9 | breaks 10 | call 11 | can 12 | come 13 | dawn 14 | dear 15 | do 16 | drum 17 | eyes 18 | far 19 | fear 20 | find 21 | for 22 | for ev er 23 | found 24 | from 25 | have 26 | hold 27 | home 28 | i 29 | i've 30 | in 31 | in to 32 | jour ney 33 | lis ten ing 34 | long ing 35 | love 36 | me 37 | mine 38 | move 39 | my 40 | near 41 | night 42 | on 43 | one 44 | ov er 45 | pride 46 | saw 47 | see 48 | seen 49 | shad ow 50 | show ing 51 | side 52 | so 53 | take 54 | tell 55 | ten der 56 | that 57 | the 58 | this 59 | through 60 | time 61 | to 62 | truth 63 | ver y 64 | voice 65 | wait ing 66 | wake 67 | walk 68 | what 69 | when 70 | will 71 | you 72 | you're 73 | your 74 | youth 75 | -------------------------------------------------------------------------------- /operators/textparser/parser.py: -------------------------------------------------------------------------------- 1 | from .tools.find_even_split import find_even_split 2 | from .tools.seconds_to_srt_timecode import seconds_to_srt_timecode 3 | 4 | def text_to_srt(text, fps, reflow_long_lines=False): 5 | """ 6 | Creates an SRT string out of plain text, with 1 second for each 7 | segment 8 | """ 9 | text = text.strip() 10 | lines = text.split('\n') 11 | 12 | output = [] 13 | sec_time = 0 14 | for i in range(len(lines)): 15 | seg = str(i + 1) + '\n' 16 | 17 | start = seconds_to_srt_timecode(i + 0.00000001) 18 | sec_time = i + 1.00000001 19 | end = seconds_to_srt_timecode(sec_time) 20 | seg += start + ' --> ' + end + '\n' 21 | 22 | if len(lines[i].rstrip()) > 31 and reflow_long_lines: 23 | lines[i] = find_even_split(lines[i].rstrip()) 24 | 25 | seg += lines[i] + '\n' 26 | output.append(seg) 27 | 28 | return '\n'.join(output).rstrip() 29 | -------------------------------------------------------------------------------- /operators/pysrt/comparablemixin.py: -------------------------------------------------------------------------------- 1 | class ComparableMixin(object): 2 | def _compare(self, other, method): 3 | try: 4 | return method(self._cmpkey(), other._cmpkey()) 5 | except (AttributeError, TypeError): 6 | # _cmpkey not implemented, or return different type, 7 | # so I can't compare with "other". 8 | return NotImplemented 9 | 10 | def __lt__(self, other): 11 | return self._compare(other, lambda s, o: s < o) 12 | 13 | def __le__(self, other): 14 | return self._compare(other, lambda s, o: s <= o) 15 | 16 | def __eq__(self, other): 17 | return self._compare(other, lambda s, o: s == o) 18 | 19 | def __ge__(self, other): 20 | return self._compare(other, lambda s, o: s >= o) 21 | 22 | def __gt__(self, other): 23 | return self._compare(other, lambda s, o: s > o) 24 | 25 | def __ne__(self, other): 26 | return self._compare(other, lambda s, o: s != o) -------------------------------------------------------------------------------- /operators/textparser/tools/find_even_split.py: -------------------------------------------------------------------------------- 1 | def make_lines(line, space): 2 | """ 3 | Make a list of lines that are less than or equal to space long 4 | """ 5 | word_list = line.split(' ') 6 | lines = [] 7 | growing_string = '' 8 | while len(word_list) > 0: 9 | if len((growing_string + ' ' + word_list[0]).strip()) <= space: 10 | growing_string += ' ' + word_list[0] 11 | growing_string = growing_string.strip() 12 | else: 13 | lines.append(growing_string.strip()) 14 | growing_string = word_list[0] 15 | word_list.pop(0) 16 | lines.append(growing_string) 17 | return lines 18 | 19 | def find_even_split(line): 20 | max_line_length = 31 21 | output = make_lines(line, max_line_length) 22 | max_lines = len(output) 23 | space = max_line_length - 1 24 | while len(make_lines(line, space)) == max_lines: 25 | space -= 1 26 | lines = make_lines(line, space + 1) 27 | return '\n'.join(make_lines(line, space + 1)) -------------------------------------------------------------------------------- /tests/test_textparser.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import unittest 4 | 5 | file_path = os.path.abspath(os.path.join(os.path.dirname(__file__), '..')) 6 | sys.path.insert(0, os.path.abspath(file_path)) 7 | 8 | from operators.textparser.parser import text_to_srt 9 | 10 | class TestTextParser(unittest.TestCase): 11 | def setUp(self): 12 | self.static_path = os.path.join(file_path, 'tests', 'static') 13 | 14 | lyrics_path = os.path.join(self.static_path, 'I Move On.txt') 15 | f = open(lyrics_path) 16 | self.lyrics = f.read() 17 | f.close() 18 | 19 | lyrics_output_path = os.path.join( 20 | self.static_path, 'I Move On_unsynchronized.srt') 21 | f = open(lyrics_output_path) 22 | self.output = f.read().rstrip() 23 | f.close() 24 | 25 | def test_parsing(self): 26 | self.maxDiff = None 27 | self.assertEqual(text_to_srt(self.lyrics), self.output) 28 | 29 | if __name__ == '__main__': 30 | unittest.main() -------------------------------------------------------------------------------- /operators/__init__.py: -------------------------------------------------------------------------------- 1 | from .combine_words import SEQUENCER_OT_combine_words 2 | from .duration_adjust import SEQUENCER_OT_duration_x_2 3 | from .duration_adjust import SEQUENCER_OT_duration_x_half 4 | from .export_lrc import SEQUENCER_OT_export_lrc 5 | from .export_srt import SEQUENCER_OT_export_srt 6 | from .import_subtitles import SEQUENCER_OT_import_subtitles 7 | from .refresh_font_data import SEQUENCER_OT_refresh_font_data 8 | from .refresh_highlight import SEQUENCER_OT_refresh_highlight 9 | from .save_syllables import SEQUENCER_OT_save_syllables 10 | from .select import SEQUENCER_OT_select_channel_right 11 | from .select import SEQUENCER_OT_select_channel_left 12 | from .shortcuts import SEQUENCER_OT_shift_frame_start 13 | from .shortcuts import SEQUENCER_OT_shift_frame_end 14 | from .shortcuts import SEQUENCER_OT_shift_frame_start_end 15 | from .shortcuts import SEQUENCER_OT_shift_frame_end_start 16 | from .shortcuts import SEQUENCER_OT_reset_children 17 | from .split_words import SEQUENCER_OT_split_words 18 | from .syllabify import SEQUENCER_OT_syllabify 19 | -------------------------------------------------------------------------------- /operators/refresh_highlight.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | 3 | from .tools.get_text_strips import get_text_strips 4 | 5 | class SEQUENCER_OT_refresh_highlight(bpy.types.Operator): 6 | bl_label = "" 7 | bl_idname = 'sequencerextra.refresh_highlight' 8 | bl_description = "Refresh the color of highlighted words" 9 | 10 | @classmethod 11 | def poll(self, context): 12 | scene = context.scene 13 | try: 14 | text_strips = get_text_strips(scene) 15 | 16 | if len(text_strips) > 0: 17 | return True 18 | else: 19 | return False 20 | except AttributeError: 21 | return False 22 | 23 | def execute(self, context): 24 | 25 | scene = context.scene 26 | 27 | text_strips = get_text_strips(scene) 28 | 29 | for strip in text_strips: 30 | strip.color[0] = scene.enhanced_subs_color[0] 31 | strip.color[1] = scene.enhanced_subs_color[1] 32 | strip.color[2] = scene.enhanced_subs_color[2] 33 | 34 | return {"FINISHED"} 35 | -------------------------------------------------------------------------------- /operators/refresh_font_data.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | 3 | from .tools.get_text_strips import get_text_strips 4 | from .tools.get_font import get_font 5 | 6 | 7 | class SEQUENCER_OT_refresh_font_data(bpy.types.Operator): 8 | bl_label = "Refresh Font Data" 9 | bl_idname = 'sequencerextra.refresh_font_data' 10 | bl_description = "Refresh the font size for all text strips on the edit channel" 11 | 12 | @classmethod 13 | def poll(self, context): 14 | scene = context.scene 15 | try: 16 | text_strips = get_text_strips(scene) 17 | 18 | if len(text_strips) > 0: 19 | return True 20 | else: 21 | return False 22 | except AttributeError: 23 | return False 24 | 25 | def execute(self, context): 26 | 27 | scene = context.scene 28 | 29 | text_strips = get_text_strips(scene) 30 | 31 | for strip in text_strips: 32 | strip.font_size = scene.subtitle_font_size 33 | strip.font = get_font(scene.subtitle_font) 34 | strip.location[1] = scene.subtitle_font_height 35 | 36 | return {"FINISHED"} 37 | -------------------------------------------------------------------------------- /tests/test_pysrt.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import unittest 4 | 5 | file_path = os.path.abspath(os.path.join(os.path.dirname(__file__), '..')) 6 | sys.path.insert(0, os.path.abspath(file_path)) 7 | 8 | from operators.pysrt.srtfile import SubRipFile 9 | from operators.pysrt.convert_enhanced import convert_enhanced 10 | 11 | class TestPySRT(unittest.TestCase): 12 | def setUp(self): 13 | self.static_path = os.path.join(file_path, 'tests', 'static') 14 | 15 | lyrics_path = os.path.join(self.static_path, 'I Move On.srt') 16 | f = open(lyrics_path) 17 | self.lyrics = SubRipFile.from_string(f.read()) 18 | f.close() 19 | 20 | lyrics_bases = os.path.join( 21 | self.static_path, 'I Move On_bases.srt') 22 | f = open(lyrics_bases, 'r') 23 | self.lyrics_bases = f.read() 24 | f.close() 25 | 26 | lyrics_tops = os.path.join( 27 | self.static_path, 'I Move On_tops.srt') 28 | f = open(lyrics_tops, 'r') 29 | self.lyrics_tops = f.read() 30 | f.close() 31 | 32 | def test_parsing(self): 33 | self.maxDiff = None 34 | bases, tops, color = convert_enhanced(self.lyrics) 35 | self.assertEqual(bases.to_string(), self.lyrics_bases) 36 | self.assertEqual(tops.to_string().rstrip(), self.lyrics_tops.rstrip()) 37 | self.assertEqual(color, "#ff8800") 38 | self.assertEqual(1, 1) 39 | 40 | if __name__ == '__main__': 41 | unittest.main() -------------------------------------------------------------------------------- /operators/save_syllables.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | from .hyphenator.get_dictionary import get_dictionary 3 | import os 4 | 5 | class SEQUENCER_OT_save_syllables(bpy.types.Operator): 6 | bl_label = 'Save' 7 | bl_idname = 'sequencerextra.save_syllables' 8 | bl_description = "Add the syllables to the default syllable dictionary" 9 | 10 | @classmethod 11 | def poll(self, context): 12 | scene = context.scene 13 | path = bpy.path.abspath(scene.syllable_dictionary_path) 14 | 15 | if os.path.isfile(path): 16 | return True 17 | else: 18 | return False 19 | 20 | def execute(self, context): 21 | scene = context.scene 22 | dictionary = get_dictionary( 23 | lang=scene.syllabification_language) 24 | 25 | path = bpy.path.abspath(scene.syllable_dictionary_path) 26 | f = open(path, 'r') 27 | words = f.readlines() 28 | f.close() 29 | 30 | for i in range(len(words)): 31 | words[i] = words[i].rstrip() 32 | word = words[i].replace(' ', '') 33 | dictionary[word] = words[i] 34 | 35 | values = sorted(list(dictionary.values())) 36 | 37 | module_path = os.path.dirname(__file__) 38 | dic_path = os.path.join( 39 | module_path, 'hyphenator', 'dictionaries', 40 | scene.syllabification_language + '.txt') 41 | 42 | f = open(dic_path, 'w') 43 | f.write('\n'.join(values)) 44 | f.close() 45 | 46 | return {"FINISHED"} 47 | -------------------------------------------------------------------------------- /operators/export_srt.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | import codecs 3 | from bpy_extras.io_utils import ExportHelper 4 | 5 | from .pysrt.srtitem import SubRipItem 6 | from .pysrt.srtfile import SubRipFile 7 | 8 | from .tools.get_text_strips import get_text_strips 9 | 10 | class SEQUENCER_OT_export_srt(bpy.types.Operator, ExportHelper): 11 | bl_label = 'Export SRT' 12 | bl_idname = 'sequencerextra.export_srt' 13 | bl_description = 'Export subtitles as SRT\n\nThis format is usually used for movies.' 14 | 15 | filename_ext = ".srt" 16 | 17 | @classmethod 18 | def poll(self, context): 19 | scene = context.scene 20 | try: 21 | text_strips = get_text_strips(scene) 22 | 23 | if len(text_strips) > 0: 24 | return True 25 | else: 26 | return False 27 | except AttributeError: 28 | return False 29 | 30 | def execute(self, context): 31 | scene = context.scene 32 | fps = scene.render.fps/scene.render.fps_base 33 | text_strips = get_text_strips(scene) 34 | 35 | sub_lines = [] 36 | for i in range(len(text_strips)): 37 | strip = text_strips[i] 38 | 39 | start = (strip.frame_start / fps) * 1000 40 | end = (strip.frame_final_end / fps) * 1000 41 | text = strip.text 42 | 43 | item = SubRipItem() 44 | item.text = text 45 | item.start.from_millis(start) 46 | item.end.from_millis(end) 47 | item.index = i + 1 48 | sub_lines.append(item) 49 | 50 | output = SubRipFile(sub_lines).to_string() 51 | 52 | outfile = codecs.open(self.filepath, 'w', 'utf-8') 53 | outfile.write(output) 54 | outfile.close() 55 | 56 | return {"FINISHED"} 57 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | linker.py 7 | 8 | # C extensions 9 | *.so 10 | 11 | # Distribution / packaging 12 | .Python 13 | env/ 14 | build/ 15 | develop-eggs/ 16 | dist/ 17 | downloads/ 18 | eggs/ 19 | .eggs/ 20 | lib/ 21 | lib64/ 22 | parts/ 23 | sdist/ 24 | var/ 25 | wheels/ 26 | *.egg-info/ 27 | .installed.cfg 28 | *.egg 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *.cover 49 | .hypothesis/ 50 | 51 | # Translations 52 | *.mo 53 | *.pot 54 | 55 | # Django stuff: 56 | *.log 57 | local_settings.py 58 | 59 | # Flask stuff: 60 | instance/ 61 | .webassets-cache 62 | 63 | # Scrapy stuff: 64 | .scrapy 65 | 66 | # Sphinx documentation 67 | docs/_build/ 68 | 69 | # PyBuilder 70 | target/ 71 | 72 | # Jupyter Notebook 73 | .ipynb_checkpoints 74 | 75 | # pyenv 76 | .python-version 77 | 78 | # celery beat schedule file 79 | celerybeat-schedule 80 | 81 | # SageMath parsed files 82 | *.sage.py 83 | 84 | # dotenv 85 | .env 86 | 87 | # virtualenv 88 | .venv 89 | venv/ 90 | ENV/ 91 | 92 | # Spyder project settings 93 | .spyderproject 94 | .spyproject 95 | 96 | # Rope project settings 97 | .ropeproject 98 | 99 | # mkdocs documentation 100 | /site 101 | 102 | # mypy 103 | .mypy_cache/ 104 | 105 | # vs code 106 | .vscode/ -------------------------------------------------------------------------------- /operators/tools/subtitles_to_sequencer.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | 3 | from .get_open_channel import get_open_channel 4 | from .get_font import get_font 5 | 6 | def subtitles_to_sequencer(context, subs): 7 | """Add subtitles to the video sequencer""" 8 | 9 | scene = context.scene 10 | fps = scene.render.fps / scene.render.fps_base 11 | open_channel = get_open_channel(scene) 12 | 13 | if not scene.sequence_editor: 14 | scene.sequence_editor_create() 15 | 16 | wm = context.window_manager 17 | wm.progress_begin(0, 100.0) 18 | 19 | added_strips = [] 20 | 21 | for i in range(len(subs)): 22 | start_time = subs[i].start.to_millis() / 1000 23 | strip_start = int(round(start_time * fps, 0)) 24 | 25 | end_time = subs[i].end.to_millis() / 1000 26 | strip_end = int(round(end_time * fps, 0)) 27 | sub_name = str(open_channel) + '_' + str(i + 1) 28 | try: 29 | if '[locked start]' in subs[i].name: 30 | sub_name = '[locked start]' + sub_name 31 | if '[locked end]' in subs[i].name: 32 | sub_name += '[locked end]' 33 | except AttributeError: 34 | pass 35 | 36 | text_strip = scene.sequence_editor.sequences.new_effect( 37 | name=sub_name, 38 | type='TEXT', 39 | channel=open_channel, 40 | frame_start=strip_start, 41 | frame_end=strip_end 42 | ) 43 | 44 | text_strip.font = get_font(scene.subtitle_font) 45 | text_strip.font_size = scene.subtitle_font_size 46 | text_strip.location[1] = scene.subtitle_font_height 47 | text_strip.text = subs[i].text 48 | text_strip.use_shadow = True 49 | text_strip.select = True 50 | text_strip.blend_type = 'ALPHA_OVER' 51 | 52 | added_strips.append(text_strip) 53 | 54 | return added_strips 55 | -------------------------------------------------------------------------------- /tests/test_pylrc.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import unittest 4 | 5 | file_path = os.path.abspath(os.path.join(os.path.dirname(__file__), '..')) 6 | sys.path.insert(0, os.path.abspath(file_path)) 7 | 8 | from operators.pylrc.parser import parse 9 | from operators.pysrt.srtfile import SubRipFile 10 | from operators.pysrt.convert_enhanced import convert_enhanced 11 | 12 | class TestPyLRC(unittest.TestCase): 13 | def setUp(self): 14 | self.static_path = os.path.join(file_path, 'tests', 'static') 15 | lyrics_path = os.path.join(self.static_path, 'I Move On.lrc') 16 | f = open(lyrics_path) 17 | self.lyrics = parse(f.read()) 18 | f.close() 19 | 20 | lyrics_path = os.path.join(self.static_path, 'I Move On_bases.srt') 21 | f = open(lyrics_path) 22 | self.base_comparison = f.read() 23 | f.close() 24 | 25 | lyrics_path = os.path.join(self.static_path, 'I Move On_tops.srt') 26 | f = open(lyrics_path) 27 | self.tops_comparison = f.read() 28 | f.close() 29 | 30 | lyrics_path = os.path.join(self.static_path, 'non_subsimport.lrc') 31 | f = open(lyrics_path) 32 | self.non_subsimport = parse(f.read()) 33 | f.close() 34 | 35 | def test_parsing(self): 36 | self.maxDiff = None 37 | srt_string = self.lyrics.to_SRT() 38 | subs = SubRipFile().from_string(srt_string) 39 | bases, tops, color = convert_enhanced(subs) 40 | self.assertEqual(1, 1) 41 | self.assertEqual(self.base_comparison, bases.to_string()) 42 | self.assertEqual(self.tops_comparison, tops.to_string()) 43 | 44 | srt_string = self.non_subsimport.to_SRT() 45 | subs = SubRipFile().from_string(srt_string) 46 | bases, tops, color = convert_enhanced(subs) 47 | 48 | 49 | if __name__ == '__main__': 50 | unittest.main() -------------------------------------------------------------------------------- /tools/pattern_converter.py: -------------------------------------------------------------------------------- 1 | # This script was used to get the hyphenator patterns from https://github.com/mnater/Hyphenopoly 2 | 3 | import os 4 | import ntpath 5 | 6 | def remove_punctuation(word): 7 | """Remove punctuation from the beginning and end of a word""" 8 | 9 | puncs = ['\\', '/', ':', '*', '?', '"', '<', '>', '|', '\n', '(', 10 | ')', '!', '@', '#', '$', '%', '^', '-', '+', '=', "'", 11 | ';', ',', '.', '{', '}','[', ']'] 12 | 13 | while word[0] in puncs: 14 | word = word[1::] 15 | 16 | while word[-1] in puncs: 17 | word = word[0:-1] 18 | 19 | return word 20 | 21 | def extract_patterns(path): 22 | patterns = [] 23 | 24 | f = open(path, 'r', encoding='utf-8') 25 | lines = f.readlines() 26 | f.close() 27 | 28 | important_lines = [] 29 | for i in range(len(lines)): 30 | try: 31 | step = int(remove_punctuation(lines[i].strip().split(':')[0])) 32 | important_lines.append(lines[i]) 33 | except: 34 | pass 35 | 36 | for i in range(len(important_lines)): 37 | step = int(remove_punctuation(important_lines[i].strip().split(':')[0])) 38 | text = remove_punctuation(important_lines[i].split(':')[-1].strip()) 39 | while len(text) > 0: 40 | patterns.append(text[0:step].replace('_', '.')) 41 | text = text[step::] 42 | 43 | return patterns 44 | 45 | if __name__ == '__main__': 46 | files = os.listdir(os.getcwd()) 47 | 48 | for file in files: 49 | if file.endswith('.js'): 50 | patterns = list(sorted(extract_patterns(file))) 51 | fname = os.path.splitext(ntpath.basename(file))[0] 52 | 53 | f = open(os.path.join('/home/doakey/Desktop/new_patterns', fname + '.txt'), 'w', encoding='utf-8') 54 | f.write('\n'.join(patterns)) 55 | f.close() -------------------------------------------------------------------------------- /operators/pylrc/parser.py: -------------------------------------------------------------------------------- 1 | from .lyricline import LyricLine 2 | from .lyrics import Lyrics 3 | from .tools.timecode import is_timecode, unpack_timecode 4 | 5 | def parse(lrc): 6 | 7 | lines = lrc.split('\n') 8 | lyrics = Lyrics() 9 | items = [] 10 | 11 | for i in range(len(lines)): 12 | if lines[i].startswith('[ar:'): 13 | lyrics.artist = lines[i].rstrip()[4:-1].lstrip() 14 | 15 | elif lines[i].startswith('[ti:'): 16 | lyrics.title = lines[i].rstrip()[4:-1].lstrip() 17 | 18 | elif lines[i].startswith('[al:'): 19 | lyrics.album = lines[i].rstrip()[4:-1].lstrip() 20 | 21 | elif lines[i].startswith('[by:'): 22 | lyrics.author = lines[i].rstrip()[4:-1].lstrip() 23 | 24 | elif lines[i].startswith('[length:'): 25 | lyrics.length = lines[i].rstrip()[8:-1].lstrip() 26 | 27 | elif lines[i].startswith('[offset:'): 28 | lyrics.offset = lines[i].rstrip()[8:-1].lstrip() 29 | 30 | elif lines[i].startswith('[re:'): 31 | lyrics.editor = lines[i].rstrip()[4:-1].lstrip() 32 | 33 | elif lines[i].startswith('[ve:'): 34 | lyrics.version = lines[i].rstrip()[4:-1].lstrip() 35 | 36 | elif len(lines[i].split(']')[0]) >= len('[0:0:0]'): 37 | if is_timecode(lines[i].split(']')[0] + ']'): 38 | while is_timecode(lines[i].split(']')[0] + ']'): 39 | timecode = lines[i].split(']')[0] + ']' 40 | text = ''.join(lines[i].split(']')[-1]).rstrip() 41 | lyric_line = LyricLine(timecode, text=text) 42 | items.append(lyric_line) 43 | 44 | lines[i] = lines[i][len(timecode)::] 45 | 46 | lyrics.extend(sorted(items)) 47 | 48 | if not lyrics.offset == "": 49 | offset_mins, offset_secs, offset_millis = unpack_timecode(lyrics.offset) 50 | for i in range(len(lyrics)): 51 | lyrics[i].shift(minutes=offset_mins, seconds=offset_secs, 52 | milliseconds=offset_millis) 53 | 54 | return lyrics -------------------------------------------------------------------------------- /operators/select.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | 3 | class SEQUENCER_OT_select_channel_right(bpy.types.Operator): 4 | bl_label = 'Select Channel Right' 5 | bl_idname = 'sequencerextra.select_channel_right' 6 | bl_description = 'Select all strips to the right of the CTI on the Subtitle Edit Channel' 7 | bl_options = {'REGISTER', 'UNDO'} 8 | 9 | @classmethod 10 | def poll(self, context): 11 | scn = context.scene 12 | if scn and scn.sequence_editor: 13 | return scn.sequence_editor.sequences 14 | else: 15 | return False 16 | 17 | def execute(self, context): 18 | scene = context.scene 19 | bpy.ops.sequencer.select_all(action="DESELECT") 20 | 21 | all_strips = list(sorted(scene.sequence_editor.sequences, 22 | key=lambda x: x.frame_start)) 23 | 24 | for strip in all_strips: 25 | if strip.channel == scene.subtitle_edit_channel: 26 | if strip.frame_final_end > scene.frame_current: 27 | strip.select = True 28 | 29 | return {'FINISHED'} 30 | 31 | class SEQUENCER_OT_select_channel_left(bpy.types.Operator): 32 | bl_label = 'Select Channel Left' 33 | bl_idname = 'sequencerextra.select_channel_left' 34 | bl_description = 'Select all strips to the right of the CTI on the Subtitle Edit Channel' 35 | bl_options = {'REGISTER', 'UNDO'} 36 | 37 | @classmethod 38 | def poll(self, context): 39 | scn = context.scene 40 | if scn and scn.sequence_editor: 41 | return scn.sequence_editor.sequences 42 | else: 43 | return False 44 | 45 | def execute(self, context): 46 | scene = context.scene 47 | bpy.ops.sequencer.select_all(action="DESELECT") 48 | 49 | all_strips = list(sorted(scene.sequence_editor.sequences, 50 | key=lambda x: x.frame_start)) 51 | 52 | for strip in all_strips: 53 | if strip.channel == scene.subtitle_edit_channel: 54 | if strip.frame_final_start < scene.frame_current: 55 | strip.select = True 56 | 57 | return {'FINISHED'} 58 | -------------------------------------------------------------------------------- /operators/pylrc/tools/timecode.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | import math 3 | 4 | def is_timecode(timecode): 5 | """Checks if a string is a proper lrc timecode""" 6 | 7 | timecode = timecode.replace('<', '[').replace('>', ']') 8 | try: 9 | x = datetime.strptime(timecode, '[%M:%S.%f]') 10 | return True 11 | 12 | except ValueError: 13 | return False 14 | 15 | 16 | def timecode_to_seconds(timecode): 17 | """convert timecode to seconds""" 18 | timecode = timecode.replace('<', '[').replace('>', ']') 19 | mins, secs, millis = unpack_timecode(timecode) 20 | seconds = (mins * 60) + secs + (millis / 1000) 21 | return seconds 22 | 23 | 24 | def unpack_timecode(timecode): 25 | """unpacks a timecode to minutes, seconds, and milliseconds""" 26 | timecode = timecode.replace('<', '[').replace('>', ']') 27 | x = datetime.strptime(timecode, '[%M:%S.%f]') 28 | 29 | minutes = x.minute 30 | seconds = x.second 31 | milliseconds = int(x.microsecond / 1000) 32 | return minutes, seconds, milliseconds 33 | 34 | 35 | def seconds_to_timecode(sec, sym='[]'): 36 | """Makes a timecode of the format [MM:SS.ff], or """ 37 | 38 | minutes = "%02d" % int(sec / 60) 39 | seconds = "%02d" % int(sec % 60) 40 | millis = ("%03d" % ((sec % 1) * 1000))[0:2] 41 | 42 | if sym == '[]': 43 | return ''.join(['[', minutes, ':', seconds, '.', millis, ']']) 44 | 45 | elif sym == '<>': 46 | return ''.join(['<', minutes, ':', seconds, '.', millis, '>']) 47 | 48 | def timecode_to_srt(timecode): 49 | """convert timecode of format [MM:SS.ff] to an srt format timecode""" 50 | secs = timecode_to_seconds(timecode) 51 | 52 | hours = "00" 53 | minutes = "%02d" % int(secs / 60) 54 | seconds = "%02d" % int(secs % 60) 55 | millis = "%03d" % (round(secs - int(secs), 2) * 1000) 56 | 57 | return ''.join([hours, ':', minutes, ':', seconds, ',', millis]) 58 | 59 | if __name__ == '__main__': 60 | print(is_timecode('[05:40.99]')) 61 | print(timecode_to_seconds('<01:00.99>')) 62 | print(unpack_timecode('<01:00.99>')) 63 | print(seconds_to_timecode(200.800, '<>')) 64 | print(timecode_to_srt('<03:59.29>')) -------------------------------------------------------------------------------- /operators/duration_adjust.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | from .tools.get_text_strips import get_text_strips 3 | 4 | class SEQUENCER_OT_duration_x_2(bpy.types.Operator): 5 | bl_label = 'Dur x 2' 6 | bl_idname = 'sequencerextra.duration_x_two' 7 | bl_description = 'Make all text strips in subtitle edit channel twice as long' 8 | 9 | @classmethod 10 | def poll(self, context): 11 | scene = context.scene 12 | try: 13 | text_strips = get_text_strips(scene) 14 | 15 | if len(text_strips) > 0: 16 | return True 17 | else: 18 | return False 19 | except AttributeError: 20 | return False 21 | 22 | def execute(self, context): 23 | scene = context.scene 24 | fps = scene.render.fps / scene.render.fps_base 25 | text_strips = get_text_strips(scene) 26 | 27 | text_strips = list(reversed(text_strips)) 28 | for strip in text_strips: 29 | strip.frame_final_end = int(strip.frame_final_end * 2) 30 | strip.frame_final_start = int(strip.frame_final_start * 2) 31 | 32 | return {"FINISHED"} 33 | 34 | class SEQUENCER_OT_duration_x_half(bpy.types.Operator): 35 | bl_label = 'Dur / 2' 36 | bl_idname = 'sequencerextra.duration_x_half' 37 | bl_description = 'Make all text strips in subtitle edit channel half as long.\nEach strip must be >=2 frames long for this to work.' 38 | 39 | @classmethod 40 | def poll(self, context): 41 | scene = context.scene 42 | try: 43 | text_strips = get_text_strips(scene) 44 | 45 | if len(text_strips) > 0: 46 | for strip in text_strips: 47 | if strip.frame_final_duration < 2: 48 | return False 49 | return True 50 | else: 51 | return False 52 | except AttributeError: 53 | return False 54 | 55 | def execute(self, context): 56 | scene = context.scene 57 | fps = scene.render.fps / scene.render.fps_base 58 | text_strips = get_text_strips(scene) 59 | 60 | for strip in text_strips: 61 | strip.frame_final_end = int(strip.frame_final_end / 2) 62 | strip.frame_final_start = int(strip.frame_final_start / 2) 63 | 64 | return {"FINISHED"} 65 | -------------------------------------------------------------------------------- /operators/export_lrc.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | import codecs 3 | from bpy_extras.io_utils import ExportHelper 4 | 5 | from .pylrc.lyrics import Lyrics 6 | from .pylrc.lyricline import LyricLine 7 | from .pylrc.tools.timecode import seconds_to_timecode 8 | 9 | from .tools.get_text_strips import get_text_strips 10 | 11 | class SEQUENCER_OT_export_lrc(bpy.types.Operator, ExportHelper): 12 | bl_label = 'Export LRC' 13 | bl_idname = 'sequencerextra.export_lrc' 14 | bl_description = 'Export subtitles as LRC\n\nThis format is usually used for music.' 15 | 16 | filename_ext = ".lrc" 17 | 18 | @classmethod 19 | def poll(self, context): 20 | scene = context.scene 21 | try: 22 | text_strips = get_text_strips(scene) 23 | 24 | if len(text_strips) > 0: 25 | return True 26 | else: 27 | return False 28 | except AttributeError: 29 | return False 30 | 31 | def execute(self, context): 32 | scene = context.scene 33 | fps = scene.render.fps/scene.render.fps_base 34 | text_strips = get_text_strips(scene) 35 | 36 | lyric_list = [] 37 | for i in range(len(text_strips)): 38 | strip = text_strips[i] 39 | if (strip.frame_final_start / fps) >= 3600: 40 | message = ".lrc files cannot store subtitles longer than 59:59.99" 41 | self.report(set({'ERROR'}), message) 42 | return {"FINISHED"} 43 | 44 | start = seconds_to_timecode(strip.frame_final_start / fps) 45 | 46 | text_lines = strip.text.split('\n') 47 | text = '' 48 | for line in text_lines: 49 | text += line.strip() + ' ' 50 | text = text.rstrip() 51 | 52 | lyric_list.append(LyricLine(start, text)) 53 | 54 | if i < len(text_strips) - 1: 55 | if text_strips[i + 1].frame_start > strip.frame_final_end: 56 | start = seconds_to_timecode(strip.frame_final_end / fps) 57 | lyric_list.append(LyricLine(start, "")) 58 | 59 | elif i == len(text_strips) - 1: 60 | start = seconds_to_timecode(strip.frame_final_end / fps) 61 | lyric_list.append(LyricLine(start, "")) 62 | 63 | output = Lyrics(lyric_list).to_LRC() 64 | outfile = codecs.open(self.filepath, 'w', 'utf-8') 65 | outfile.write(output) 66 | outfile.close() 67 | 68 | return {"FINISHED"} 69 | -------------------------------------------------------------------------------- /tests/static/I Move On_bases.srt: -------------------------------------------------------------------------------- 1 | 1 2 | 00:00:27,290 --> 00:00:30,180 3 | Come take my journey 4 | 5 | 2 6 | 00:00:30,180 --> 00:00:33,940 7 | Into night 8 | 9 | 3 10 | 00:00:33,940 --> 00:00:36,860 11 | Come be my shadow 12 | 13 | 4 14 | 00:00:36,860 --> 00:00:40,620 15 | Walk at my side 16 | 17 | 5 18 | 00:00:40,620 --> 00:00:43,550 19 | And when you see 20 | 21 | 6 22 | 00:00:43,550 --> 00:00:46,890 23 | All that I have seen 24 | 25 | 7 26 | 00:00:46,890 --> 00:00:48,580 27 | Can you tell me 28 | 29 | 8 30 | 00:00:48,580 --> 00:00:52,310 31 | Love from pride? 32 | 33 | 9 34 | 00:00:54,000 --> 00:00:56,940 35 | I have been waiting 36 | 37 | 10 38 | 00:00:56,940 --> 00:01:00,520 39 | all this time 40 | 41 | 11 42 | 00:01:00,520 --> 00:01:03,490 43 | for one to wake me 44 | 45 | 12 46 | 00:01:03,490 --> 00:01:07,200 47 | one to call mine 48 | 49 | 13 50 | 00:01:07,200 --> 00:01:10,120 51 | So when you're near 52 | 53 | 14 54 | 00:01:10,120 --> 00:01:13,370 55 | all that you hold dear 56 | 57 | 15 58 | 00:01:13,370 --> 00:01:19,690 59 | do you fear what you will find? 60 | 61 | 16 62 | 00:01:19,690 --> 00:01:22,280 63 | As the dawn 64 | 65 | 17 66 | 00:01:22,280 --> 00:01:25,630 67 | Breaks through the night 68 | 69 | 18 70 | 00:01:26,510 --> 00:01:29,230 71 | I move on 72 | 73 | 19 74 | 00:01:29,230 --> 00:01:35,540 75 | Forever longing for the home 76 | 77 | 20 78 | 00:01:35,540 --> 00:01:40,770 79 | I found in your eyes 80 | 81 | 21 82 | 00:01:47,660 --> 00:01:50,510 83 | I will be listening 84 | 85 | 22 86 | 00:01:50,510 --> 00:01:54,170 87 | for the drum 88 | 89 | 23 90 | 00:01:54,170 --> 00:01:57,300 91 | to call me over 92 | 93 | 24 94 | 00:01:57,300 --> 00:02:00,720 95 | far away from 96 | 97 | 25 98 | 00:02:00,720 --> 00:02:03,560 99 | my tender youth 100 | 101 | 26 102 | 00:02:03,560 --> 00:02:06,810 103 | and the very truth 104 | 105 | 27 106 | 00:02:06,810 --> 00:02:12,900 107 | showing me what I've become 108 | 109 | 28 110 | 00:02:12,900 --> 00:02:15,390 111 | As the dawn 112 | 113 | 29 114 | 00:02:15,390 --> 00:02:19,470 115 | Breaks through the night 116 | 117 | 30 118 | 00:02:19,470 --> 00:02:21,890 119 | I move on 120 | 121 | 31 122 | 00:02:21,890 --> 00:02:28,300 123 | Forever longing for the home 124 | 125 | 32 126 | 00:02:28,300 --> 00:02:32,100 127 | I found in your eyes 128 | 129 | 33 130 | 00:02:32,100 --> 00:02:34,530 131 | Your voice 132 | 133 | 34 134 | 00:02:34,530 --> 00:02:40,310 135 | saw me through the night 136 | -------------------------------------------------------------------------------- /tests/static/I Move On_unsynchronized.srt: -------------------------------------------------------------------------------- 1 | 1 2 | 00:00:00,000 --> 00:00:01,000 3 | Come take my journey 4 | 5 | 2 6 | 00:00:01,000 --> 00:00:02,000 7 | Into night 8 | 9 | 3 10 | 00:00:02,000 --> 00:00:03,000 11 | Come be my shadow 12 | 13 | 4 14 | 00:00:03,000 --> 00:00:04,000 15 | Walk at my side 16 | 17 | 5 18 | 00:00:04,000 --> 00:00:05,000 19 | And when you see 20 | 21 | 6 22 | 00:00:05,000 --> 00:00:06,000 23 | All that I have seen 24 | 25 | 7 26 | 00:00:06,000 --> 00:00:07,000 27 | Can you tell me 28 | 29 | 8 30 | 00:00:07,000 --> 00:00:08,000 31 | Love from pride? 32 | 33 | 9 34 | 00:00:08,000 --> 00:00:09,000 35 | I have been waiting 36 | 37 | 10 38 | 00:00:09,000 --> 00:00:10,000 39 | all this time 40 | 41 | 11 42 | 00:00:10,000 --> 00:00:11,000 43 | for one to wake me 44 | 45 | 12 46 | 00:00:11,000 --> 00:00:12,000 47 | one to call mine 48 | 49 | 13 50 | 00:00:12,000 --> 00:00:13,000 51 | So when you're near 52 | 53 | 14 54 | 00:00:13,000 --> 00:00:14,000 55 | all that you hold dear 56 | 57 | 15 58 | 00:00:14,000 --> 00:00:15,000 59 | do you fear what you will find? 60 | 61 | 16 62 | 00:00:15,000 --> 00:00:16,000 63 | As the dawn 64 | 65 | 17 66 | 00:00:16,000 --> 00:00:17,000 67 | Breaks through the night 68 | 69 | 18 70 | 00:00:17,000 --> 00:00:18,000 71 | I move on 72 | 73 | 19 74 | 00:00:18,000 --> 00:00:19,000 75 | Forever longing for the home 76 | 77 | 20 78 | 00:00:19,000 --> 00:00:20,000 79 | I found in your eyes 80 | 81 | 21 82 | 00:00:20,000 --> 00:00:21,000 83 | I will be listening 84 | 85 | 22 86 | 00:00:21,000 --> 00:00:22,000 87 | for the drum 88 | 89 | 23 90 | 00:00:22,000 --> 00:00:23,000 91 | to call me over 92 | 93 | 24 94 | 00:00:23,000 --> 00:00:24,000 95 | far away from 96 | 97 | 25 98 | 00:00:24,000 --> 00:00:25,000 99 | my tender youth 100 | 101 | 26 102 | 00:00:25,000 --> 00:00:26,000 103 | and the very truth 104 | 105 | 27 106 | 00:00:26,000 --> 00:00:27,000 107 | showing me what I've become 108 | 109 | 28 110 | 00:00:27,000 --> 00:00:28,000 111 | As the dawn 112 | 113 | 29 114 | 00:00:28,000 --> 00:00:29,000 115 | Breaks through the night 116 | 117 | 30 118 | 00:00:29,000 --> 00:00:30,000 119 | I move on 120 | 121 | 31 122 | 00:00:30,000 --> 00:00:31,000 123 | Forever longing for the home 124 | 125 | 32 126 | 00:00:31,000 --> 00:00:32,000 127 | I found in your eyes 128 | 129 | 33 130 | 00:00:32,000 --> 00:00:33,000 131 | Your voice 132 | 133 | 34 134 | 00:00:33,000 --> 00:00:34,000 135 | saw me through the night -------------------------------------------------------------------------------- /operators/hyphenator/hyphenator.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | class Hyphenator: 4 | def __init__(self): 5 | self.tree = {} 6 | self.patterns = "" 7 | self.text_exceptions = "" 8 | 9 | def setup_patterns(self): 10 | for pattern in self.patterns.split(): 11 | self._insert_pattern(pattern) 12 | 13 | self.exceptions = {} 14 | for ex in self.text_exceptions.split(): 15 | # Convert the hyphenated pattern into a point array for use later. 16 | self.exceptions[ex.replace('-', '')] = [0] + [ int(h == '-') for h in re.split(r"[a-z]", ex) ] 17 | 18 | def _insert_pattern(self, pattern): 19 | # Convert the a pattern like 'a1bc3d4' into a string of chars 'abcd' 20 | # and a list of points [ 0, 1, 0, 3, 4 ]. 21 | chars = re.sub('[0-9]', '', pattern) 22 | points = [ int(d or 0) for d in re.split("[.a-z]", pattern) ] 23 | 24 | # Insert the pattern into the tree. Each character finds a dict 25 | # another level down in the tree, and leaf nodes have the list of 26 | # points. 27 | t = self.tree 28 | for c in chars: 29 | if c not in t: 30 | t[c] = {} 31 | t = t[c] 32 | t[None] = points 33 | 34 | def hyphenate_word(self, word): 35 | """ Given a word, returns a list of pieces, broken at the possible 36 | hyphenation points. 37 | """ 38 | # If the word is an exception, get the stored points. 39 | if word.lower() in self.exceptions: 40 | points = self.exceptions[word.lower()] 41 | else: 42 | work = '.' + word.lower() + '.' 43 | points = [0] * (len(work)+1) 44 | for i in range(len(work)): 45 | t = self.tree 46 | for c in work[i:]: 47 | if c in t: 48 | t = t[c] 49 | if None in t: 50 | p = t[None] 51 | for j in range(len(p)): 52 | points[i+j] = max(points[i+j], p[j]) 53 | else: 54 | break 55 | # No hyphens in the first two chars or the last two. 56 | points[1] = points[2] = points[-2] = points[-3] = 0 57 | 58 | # Examine the points to build the pieces list. 59 | pieces = [''] 60 | for c, p in zip(word, points[2:]): 61 | pieces[-1] += c 62 | if p % 2: 63 | pieces.append('') 64 | return pieces 65 | 66 | if __name__ == '__main__': 67 | hyphenator = Hyphenator() 68 | print(hyphenator.hyphenate_word('multifiles')) -------------------------------------------------------------------------------- /operators/import_subtitles.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | from bpy_extras.io_utils import ImportHelper 3 | 4 | from .pylrc.parser import parse as lrc_parse 5 | 6 | from .pysrt.srtfile import SubRipFile 7 | from .pysrt.convert_enhanced import convert_enhanced 8 | 9 | from .textparser.parser import text_to_srt 10 | 11 | from .tools.subtitles_to_sequencer import subtitles_to_sequencer 12 | from .tools.hexcode_to_color import hexcode_to_color 13 | 14 | class SEQUENCER_OT_import_subtitles(bpy.types.Operator, ImportHelper): 15 | bl_label = 'Import' 16 | bl_idname = 'sequencerextra.import_subtitles' 17 | bl_description = 'Import subtitles (.txt, .lrc, or .srt) as text strips.' 18 | 19 | reflow_long_lines: bpy.props.BoolProperty( 20 | name="Reflow Long Lines", default=False) 21 | 22 | filter_glob: bpy.props.StringProperty( 23 | default="*.srt;*.lrc;*.txt", 24 | options={'HIDDEN'}, 25 | maxlen=255, 26 | ) 27 | 28 | def execute(self, context): 29 | scene = context.scene 30 | fps = scene.render.fps/scene.render.fps_base 31 | 32 | file = open(self.filepath, encoding='utf-8', errors='ignore') 33 | text = file.read() 34 | file.close() 35 | 36 | if self.filepath.endswith('.txt'): 37 | text = text_to_srt(text, fps, self.reflow_long_lines) 38 | 39 | elif self.filepath.endswith('.lrc'): 40 | lrc = lrc_parse(text) 41 | text = lrc.to_SRT() 42 | 43 | subs = SubRipFile().from_string(text) 44 | subs.remove_overlaps() 45 | 46 | scene.use_audio_scrub = True 47 | scene.sync_mode = 'AUDIO_SYNC' 48 | 49 | try: 50 | all_strips = list(sorted(scene.sequence_editor.sequences, 51 | key=lambda x: x.frame_start)) 52 | 53 | for strip in all_strips: 54 | strip.select = False 55 | 56 | except AttributeError: 57 | pass 58 | 59 | if subs.is_enhanced: 60 | 61 | bases, tops, color = convert_enhanced(subs) 62 | color = hexcode_to_color(color) 63 | 64 | scene.enhanced_subs_color[0] = color[0] 65 | scene.enhanced_subs_color[1] = color[1] 66 | scene.enhanced_subs_color[2] = color[2] 67 | 68 | subtitles_to_sequencer(context, bases) 69 | 70 | strips = subtitles_to_sequencer(context, tops) 71 | 72 | for strip in strips: 73 | strip.color[0] = scene.enhanced_subs_color[0] 74 | strip.color[1] = scene.enhanced_subs_color[1] 75 | strip.color[2] = scene.enhanced_subs_color[2] 76 | 77 | else: 78 | strips = subtitles_to_sequencer(context, subs) 79 | 80 | scene.subtitle_edit_channel = strips[0].channel 81 | 82 | # This causes blender 2.8 to crash, not sure why, will ignore for now 83 | # bpy.ops.sequencer.view_selected() 84 | 85 | return {"FINISHED"} 86 | -------------------------------------------------------------------------------- /tests/static/non_subsimport.lrc: -------------------------------------------------------------------------------- 1 | [00:27.29]<00:27.29>Come<00:27.64> take<00:28.07> my<00:28.45> jour<00:28.87>ney<00:30.18> 2 | [00:30.18]<00:30.18>In<00:31.37>to<00:31.81> night<00:33.21> 3 | [00:33.94]<00:33.94>Come<00:34.44> be<00:34.82> my<00:35.23> shad<00:35.65>ow<00:36.49> 4 | [00:36.86]<00:36.86>Walk<00:38.20> at<00:38.53> my<00:39.36> side<00:40.36> 5 | [00:40.62]And<00:41.01> when<00:41.40> you<00:41.81> see<00:43.29> 6 | [00:43.55]<00:43.55>All<00:44.10> that<00:44.31> I<00:44.68> have<00:45.11> seen<00:46.56> 7 | [00:46.89]<00:46.89>Can<00:47.32> you<00:47.66> tell<00:48.03> me<00:48.58> 8 | [00:48.58]<00:48.58>Love<00:49.40> from<00:50.28> pride?<00:51.96> 9 | [00:52.31] 10 | [00:54.00]<00:54.00>I<00:54.40> have<00:54.76> been<00:55.18> wait<00:55.64>ing<00:56.65> 11 | [00:56.94]<00:56.94>all<00:58.04> this<00:58.49> time<00:59.89> 12 | [01:00.52]<01:00.52>for<01:01.02> one<01:01.44> to<01:01.86> wake<01:02.36> me<01:03.49> 13 | [01:03.49]<01:03.49>one<01:04.75> to<01:05.20> call<01:06.00> mine<01:07.20> 14 | [01:07.20]<01:07.20>So<01:07.59> when<01:08.00> you're<01:08.46> near 15 | [01:10.12]<01:10.12>all<01:10.55> that<01:10.87> you<01:11.27> hold<01:11.73> dear<01:13.18> 16 | [01:13.37]<01:13.37>do<01:13.81> you<01:14.22> fear<01:14.68> what<01:15.09> you<01:16.37> will<01:16.81> find?<01:19.33> 17 | [01:19.69]<01:19.69>As<01:20.02> the<01:20.27> dawn<01:21.54> 18 | [01:22.28]<01:22.28>Breaks<01:22.65> through<01:23.09> the<01:23.54> night<01:25.18> 19 | [01:25.63] 20 | [01:26.51]<01:26.51>I<01:26.72> move<01:26.97> on<01:28.49> 21 | [01:29.23]<01:29.23>For<01:29.48>ev<01:29.92>er<01:30.40> long<01:31.66>ing<01:32.02> for<01:33.42> the<01:33.79> home<01:35.32> 22 | [01:35.54]<01:35.54>I<01:35.94> found<01:36.44> in<01:37.15> your<01:37.37> eyes<01:40.60> 23 | [01:40.77] 24 | [01:47.66]<01:47.66>I<01:48.05> will<01:48.45> be<01:48.83> lis<01:49.01>ten<01:49.15>ing<01:50.30> 25 | [01:50.51]<01:50.51>for<01:51.73> the<01:52.08> drum<01:53.29> 26 | [01:54.17]<01:54.17>to<01:54.56> call<01:54.96> me<01:55.37> ov<01:55.79>er<01:56.21> 27 | [01:57.30]<01:57.30>far<01:58.31> a<01:58.72>way<01:59.52> from<02:00.35> 28 | [02:00.72]<02:00.72>my<02:01.08> ten<02:01.46>der<02:01.84> youth<02:03.28> 29 | [02:03.56]<02:03.56>and<02:03.94> the<02:04.30> ver<02:04.73>y<02:05.11> truth<02:06.45> 30 | [02:06.81]show<02:07.21>ing<02:07.62> me<02:08.05> what<02:08.42> I've<02:09.70> be<02:10.11>come 31 | [02:12.90]<02:12.90>As<02:13.12> the<02:13.35> dawn<02:15.24> 32 | [02:15.39]<02:15.39>Breaks<02:15.79> through<02:16.18> the<02:16.57> night<02:18.40> 33 | [02:19.47]<02:19.47>I<02:19.64> move<02:19.87> on<02:21.56> 34 | [02:21.89]<02:21.89>For<02:22.33>ev<02:22.71>er<02:23.14> long<02:24.37>ing<02:24.79> for<02:26.01> the<02:26.38> home<02:27.74> 35 | [02:28.30]<02:28.30>I<02:28.38> found<02:28.84> in<02:29.48> your<02:29.67> eyes<02:31.85> 36 | [02:32.10]<02:32.10>Your<02:32.93> voice<02:34.06> 37 | [02:34.53]<02:34.53>saw<02:35.02> me<02:35.32> through<02:35.80> the<02:36.14> night<02:39.53> 38 | [02:40.31] 39 | -------------------------------------------------------------------------------- /operators/hyphenator/patterns/pt.txt: -------------------------------------------------------------------------------- 1 | 1 2 | 1b2l 3 | 1b2r 4 | 1ba 5 | 1be 6 | 1bi 7 | 1bo 8 | 1bu 9 | 1bá 10 | 1bâ 11 | 1bã 12 | 1bé 13 | 1bê 14 | 1bí 15 | 1bó 16 | 1bõ 17 | 1bú 18 | 1c2h 19 | 1c2l 20 | 1c2r 21 | 1ca 22 | 1ce 23 | 1ci 24 | 1co 25 | 1cu 26 | 1cá 27 | 1câ 28 | 1cã 29 | 1cé 30 | 1cê 31 | 1cí 32 | 1có 33 | 1cõ 34 | 1cú 35 | 1d2l 36 | 1d2r 37 | 1da 38 | 1de 39 | 1di 40 | 1do 41 | 1du 42 | 1dá 43 | 1dâ 44 | 1dã 45 | 1dé 46 | 1dê 47 | 1dí 48 | 1dó 49 | 1dõ 50 | 1dú 51 | 1f2l 52 | 1f2r 53 | 1fa 54 | 1fe 55 | 1fi 56 | 1fo 57 | 1fu 58 | 1fá 59 | 1fâ 60 | 1fã 61 | 1fé 62 | 1fê 63 | 1fí 64 | 1fó 65 | 1fõ 66 | 1fú 67 | 1g2l 68 | 1g2r 69 | 1ga 70 | 1ge 71 | 1gi 72 | 1go 73 | 1gu 74 | 1gu4a 75 | 1gu4e 76 | 1gu4i 77 | 1gu4o 78 | 1gá 79 | 1gâ 80 | 1gã 81 | 1gé 82 | 1gê 83 | 1gí 84 | 1gó 85 | 1gõ 86 | 1gú 87 | 1ja 88 | 1je 89 | 1ji 90 | 1jo 91 | 1ju 92 | 1já 93 | 1jâ 94 | 1jã 95 | 1jé 96 | 1jê 97 | 1jí 98 | 1jó 99 | 1jõ 100 | 1jú 101 | 1k2l 102 | 1k2r 103 | 1ka 104 | 1ke 105 | 1ki 106 | 1ko 107 | 1ku 108 | 1ká 109 | 1kâ 110 | 1kã 111 | 1ké 112 | 1kê 113 | 1kí 114 | 1kó 115 | 1kõ 116 | 1kú 117 | 1l2h 118 | 1la 119 | 1le 120 | 1li 121 | 1lo 122 | 1lu 123 | 1lá 124 | 1lâ 125 | 1lã 126 | 1lé 127 | 1lê 128 | 1lí 129 | 1ló 130 | 1lõ 131 | 1lú 132 | 1ma 133 | 1me 134 | 1mi 135 | 1mo 136 | 1mu 137 | 1má 138 | 1mâ 139 | 1mã 140 | 1mé 141 | 1mê 142 | 1mí 143 | 1mó 144 | 1mõ 145 | 1mú 146 | 1n2h 147 | 1na 148 | 1ne 149 | 1ni 150 | 1no 151 | 1nu 152 | 1ná 153 | 1nâ 154 | 1nã 155 | 1né 156 | 1nê 157 | 1ní 158 | 1nó 159 | 1nõ 160 | 1nú 161 | 1p2l 162 | 1p2r 163 | 1pa 164 | 1pe 165 | 1pi 166 | 1po 167 | 1pu 168 | 1pá 169 | 1pâ 170 | 1pã 171 | 1pé 172 | 1pê 173 | 1pí 174 | 1pó 175 | 1põ 176 | 1pú 177 | 1qu4a 178 | 1qu4e 179 | 1qu4i 180 | 1qu4o 181 | 1ra 182 | 1re 183 | 1ri 184 | 1ro 185 | 1ru 186 | 1rá 187 | 1râ 188 | 1rã 189 | 1ré 190 | 1rê 191 | 1rí 192 | 1ró 193 | 1rõ 194 | 1rú 195 | 1sa 196 | 1se 197 | 1si 198 | 1so 199 | 1su 200 | 1sá 201 | 1sâ 202 | 1sã 203 | 1sé 204 | 1sê 205 | 1sí 206 | 1só 207 | 1sõ 208 | 1sú 209 | 1t2l 210 | 1t2r 211 | 1ta 212 | 1te 213 | 1ti 214 | 1to 215 | 1tu 216 | 1tá 217 | 1tâ 218 | 1tã 219 | 1té 220 | 1tê 221 | 1tí 222 | 1tó 223 | 1tõ 224 | 1tú 225 | 1v2l 226 | 1v2r 227 | 1va 228 | 1ve 229 | 1vi 230 | 1vo 231 | 1vu 232 | 1vá 233 | 1vâ 234 | 1vã 235 | 1vé 236 | 1vê 237 | 1ví 238 | 1vó 239 | 1võ 240 | 1vú 241 | 1w2l 242 | 1w2r 243 | 1xa 244 | 1xe 245 | 1xi 246 | 1xo 247 | 1xu 248 | 1xá 249 | 1xâ 250 | 1xã 251 | 1xé 252 | 1xê 253 | 1xí 254 | 1xó 255 | 1xõ 256 | 1xú 257 | 1za 258 | 1ze 259 | 1zi 260 | 1zo 261 | 1zu 262 | 1zá 263 | 1zâ 264 | 1zã 265 | 1zé 266 | 1zê 267 | 1zí 268 | 1zó 269 | 1zõ 270 | 1zú 271 | 1ça 272 | 1çe 273 | 1çi 274 | 1ço 275 | 1çu 276 | 1çá 277 | 1çâ 278 | 1çã 279 | 1çé 280 | 1çê 281 | 1çí 282 | 1çó 283 | 1çõ 284 | 1çú 285 | a3a 286 | a3e 287 | a3o 288 | c3c 289 | e3a 290 | e3e 291 | e3o 292 | i3a 293 | i3e 294 | i3i 295 | i3o 296 | i3â 297 | i3ê 298 | i3ô 299 | o3a 300 | o3e 301 | o3o 302 | r3r 303 | s3s 304 | u3a 305 | u3e 306 | u3o 307 | u3u -------------------------------------------------------------------------------- /tests/static/I Move On.lrc: -------------------------------------------------------------------------------- 1 | [00:27.29]<00:27.29>Come<00:27.64> take<00:28.07> my<00:28.45> jour<00:28.87>ney<00:30.18> 2 | [00:30.18]<00:30.18>In<00:31.37>to<00:31.81> night<00:33.21> 3 | [00:33.94]<00:33.94>Come<00:34.44> be<00:34.82> my<00:35.23> shad<00:35.65>ow<00:36.49> 4 | [00:36.86]<00:36.86>Walk<00:38.20> at<00:38.53> my<00:39.36> side<00:40.36> 5 | [00:40.62]<00:40.62>And<00:41.01> when<00:41.40> you<00:41.81> see<00:43.29> 6 | [00:43.55]<00:43.55>All<00:44.10> that<00:44.31> I<00:44.68> have<00:45.11> seen<00:46.56> 7 | [00:46.89]<00:46.89>Can<00:47.32> you<00:47.66> tell<00:48.03> me<00:48.58> 8 | [00:48.58]<00:48.58>Love<00:49.40> from<00:50.28> pride?<00:51.96> 9 | [00:52.31] 10 | [00:54.00]<00:54.00>I<00:54.40> have<00:54.76> been<00:55.18> wait<00:55.64>ing<00:56.65> 11 | [00:56.94]<00:56.94>all<00:58.04> this<00:58.49> time<00:59.89> 12 | [01:00.52]<01:00.52>for<01:01.02> one<01:01.44> to<01:01.86> wake<01:02.36> me<01:03.49> 13 | [01:03.49]<01:03.49>one<01:04.75> to<01:05.20> call<01:06.00> mine<01:07.20> 14 | [01:07.20]<01:07.20>So<01:07.59> when<01:08.00> you're<01:08.46> near<01:09.76> 15 | [01:10.12]<01:10.12>all<01:10.55> that<01:10.87> you<01:11.27> hold<01:11.73> dear<01:13.18> 16 | [01:13.37]<01:13.37>do<01:13.81> you<01:14.22> fear<01:14.68> what<01:15.09> you<01:16.37> will<01:16.81> find?<01:19.33> 17 | [01:19.69]<01:19.69>As<01:20.02> the<01:20.27> dawn<01:21.54> 18 | [01:22.28]<01:22.28>Breaks<01:22.65> through<01:23.09> the<01:23.54> night<01:25.18> 19 | [01:25.63] 20 | [01:26.51]<01:26.51>I<01:26.72> move<01:26.97> on<01:28.49> 21 | [01:29.23]<01:29.23>For<01:29.48>ev<01:29.92>er<01:30.40> long<01:31.66>ing<01:32.02> for<01:33.42> the<01:33.79> home<01:35.32> 22 | [01:35.54]<01:35.54>I<01:35.94> found<01:36.44> in<01:37.15> your<01:37.37> eyes<01:40.60> 23 | [01:40.77] 24 | [01:47.66]<01:47.66>I<01:48.05> will<01:48.45> be<01:48.83> lis<01:49.01>ten<01:49.15>ing<01:50.30> 25 | [01:50.51]<01:50.51>for<01:51.73> the<01:52.08> drum<01:53.29> 26 | [01:54.17]<01:54.17>to<01:54.56> call<01:54.96> me<01:55.37> ov<01:55.79>er<01:56.21> 27 | [01:57.30]<01:57.30>far<01:58.31> a<01:58.72>way<01:59.52> from<02:00.35> 28 | [02:00.72]<02:00.72>my<02:01.08> ten<02:01.46>der<02:01.84> youth<02:03.28> 29 | [02:03.56]<02:03.56>and<02:03.94> the<02:04.30> ver<02:04.73>y<02:05.11> truth<02:06.45> 30 | [02:06.81]<02:06.81>show<02:07.21>ing<02:07.62> me<02:08.05> what<02:08.42> I've<02:09.70> be<02:10.11>come<02:12.60> 31 | [02:12.90]<02:12.90>As<02:13.12> the<02:13.35> dawn<02:15.24> 32 | [02:15.39]<02:15.39>Breaks<02:15.79> through<02:16.18> the<02:16.57> night<02:18.40> 33 | [02:19.47]<02:19.47>I<02:19.64> move<02:19.87> on<02:21.56> 34 | [02:21.89]<02:21.89>For<02:22.33>ev<02:22.71>er<02:23.14> long<02:24.37>ing<02:24.79> for<02:26.01> the<02:26.38> home<02:27.74> 35 | [02:28.30]<02:28.30>I<02:28.38> found<02:28.84> in<02:29.48> your<02:29.67> eyes<02:31.85> 36 | [02:32.10]<02:32.10>Your<02:32.93> voice<02:34.06> 37 | [02:34.53]<02:34.53>saw<02:35.02> me<02:35.32> through<02:35.80> the<02:36.14> night<02:39.53> 38 | [02:40.31] -------------------------------------------------------------------------------- /operators/pylrc/lyricline.py: -------------------------------------------------------------------------------- 1 | from .tools.timecode import unpack_timecode 2 | import re 3 | 4 | class LyricLine(): 5 | """An object that holds a lyric line and it's time""" 6 | 7 | def __init__(self, timecode, text=""): 8 | self.hours = 0 9 | self.minutes, self.seconds, self.milliseconds = unpack_timecode(timecode) 10 | self.time = sum([(self.hours * 3600), (self.minutes * 60), 11 | self.seconds, (self.milliseconds / 1000)]) 12 | self.text = text 13 | 14 | def shift(self, minutes=0, seconds=0, milliseconds=0): 15 | """Shift the timecode by the given amounts""" 16 | 17 | self.add_millis(milliseconds) 18 | self.add_seconds(seconds) 19 | self.add_minutes(minutes) 20 | 21 | def add_millis(self, milliseconds): 22 | summation = self.milliseconds + milliseconds 23 | if summation > 999 or summation < 0: 24 | self.milliseconds = (self.milliseconds + milliseconds) % 1000 25 | self.addSeconds(int((self.milliseconds + milliseconds) / 1000)) 26 | else: 27 | self.milliseconds = summation 28 | 29 | def add_seconds(self, seconds): 30 | summation = self.seconds + seconds 31 | if summation > 59 or summation < 0: 32 | self.seconds = (self.seconds + seconds) % 60 33 | self.add_minutes(int((self.seconds + seconds) / 60)) 34 | else: 35 | self.seconds = summation 36 | 37 | def add_minutes(self, minutes): 38 | summation = self.minutes + minutes 39 | if summation > 59 or summation < 0: 40 | self.minutes = (self.minutes + minutes) % 60 41 | self.add_hours(int((self.minutes + minutes) / 60)) 42 | else: 43 | self.minutes = self.minutes + minutes 44 | 45 | def add_hours(self, hours): 46 | summation = self._hours + hours 47 | if summation > 23: 48 | self.hours = 23 49 | elif summation < 0: 50 | self.hours = 0 51 | self.minutes = 0 52 | self.seconds = 0 53 | self.milliseconds = 0 54 | else: 55 | self._hours = summation 56 | 57 | def __lt__(self, other): 58 | """For sorting instances of this class""" 59 | return self.time < other.time 60 | 61 | @property 62 | def text_without_tags(self): 63 | re_tag = re.compile(r'<[^>]*?>') 64 | return re_tag.sub('', self.text) 65 | 66 | @property 67 | def srt_time(self): 68 | hours = "%02d" % self.hours 69 | mins = "%02d" % self.minutes 70 | secs = "%02d" % self.seconds 71 | millis = "%03d" % self.milliseconds 72 | timecode = ''.join([ 73 | hours, ':', mins, ':', secs, ',', millis]) 74 | return timecode 75 | 76 | @property 77 | def lrc_time(self): 78 | mins = "%02d" % self.minutes 79 | secs = "%02d" % self.seconds 80 | millis = ("%03d" % self.milliseconds)[0:2] 81 | 82 | timecode = ''.join([ 83 | '[', mins, ':', secs, '.', millis, ']']) 84 | return timecode -------------------------------------------------------------------------------- /operators/hyphenator/patterns/fi.txt: -------------------------------------------------------------------------------- 1 | .suu2r1a2 2 | .ydi2n1 3 | .ä2 4 | 1a2siaka2s1 5 | 1a2sian 6 | 1a2siat 7 | 1a2sioi 8 | 1b2lo 9 | 1b2ri 10 | 1b2ro 11 | 1b2ru 12 | 1ba 13 | 1be 14 | 1bi 15 | 1bo 16 | 1bu 17 | 1by 18 | 1d2ra 19 | 1da 20 | 1de 21 | 1di 22 | 1do 23 | 1du 24 | 1dy 25 | 1dä 26 | 1dö 27 | 1f2la 28 | 1f2ra 29 | 1f2re 30 | 1fa 31 | 1fe 32 | 1fi 33 | 1fo 34 | 1fu 35 | 1fy 36 | 1g2lo 37 | 1g2ra 38 | 1ga 39 | 1ge 40 | 1gi 41 | 1go 42 | 1gu 43 | 1gy 44 | 1gä 45 | 1gö 46 | 1ha 47 | 1he 48 | 1hi 49 | 1ho 50 | 1hu 51 | 1hy 52 | 1hä 53 | 1hö 54 | 1ja 55 | 1je 56 | 1ji 57 | 1jo 58 | 1ju 59 | 1jy 60 | 1jä 61 | 1jö 62 | 1k2ra 63 | 1k2re 64 | 1k2ri 65 | 1k2v 66 | 1k2va 67 | 1ka 68 | 1ke 69 | 1ki 70 | 1ko 71 | 1ku 72 | 1ky 73 | 1kä 74 | 1kö 75 | 1la 76 | 1le 77 | 1li 78 | 1lo 79 | 1lu 80 | 1ly 81 | 1lä 82 | 1lö 83 | 1ma 84 | 1me 85 | 1mi 86 | 1mo 87 | 1mu 88 | 1my 89 | 1mä 90 | 1mö 91 | 1na 92 | 1ne 93 | 1ni 94 | 1no 95 | 1nu 96 | 1ny 97 | 1nä 98 | 1nö 99 | 1p2ro 100 | 1pa 101 | 1pe 102 | 1pi 103 | 1po 104 | 1pu 105 | 1py 106 | 1pä 107 | 1pö 108 | 1q2vi 109 | 1ra 110 | 1re 111 | 1ri 112 | 1ro 113 | 1ru 114 | 1ry 115 | 1rä 116 | 1rö 117 | 1sa 118 | 1se 119 | 1si 120 | 1so 121 | 1sp2li 122 | 1st2r 123 | 1su 124 | 1sy 125 | 1sä 126 | 1sö 127 | 1ta 128 | 1te 129 | 1ti 130 | 1to 131 | 1tu 132 | 1ty 133 | 1tä 134 | 1tö 135 | 1va 136 | 1ve 137 | 1vi 138 | 1vo 139 | 1vu 140 | 1vy 141 | 1vä 142 | 1vö 143 | 2n1a2jan 144 | 2n1a2jo 145 | 2n1a2len 146 | 2n1aika 147 | 2n1anno 148 | 2n1anto 149 | 2n1e2dus 150 | 2n1o2mai 151 | 2n1o2pet 152 | 2n1o2pist 153 | 2n1o2sa 154 | 2n1oton 155 | 2n1otto 156 | 2n1y2lit 157 | 2s1a2jo 158 | 2s1a2len 159 | 2s1a2loi 160 | 2s1a2sia 161 | 2s1ajatu 162 | 2s1apu 163 | 2s1ase 164 | 2s1e2sity 165 | 2s1i2dea. 166 | 2s1i2dean 167 | 2s1o2pisk 168 | 2s1o2pist 169 | 2s1o2sa 170 | 2s1ohje 171 | 2s1y2hti 172 | 2s1y2rit 173 | a1ei 174 | a1oi 175 | a1uu 176 | a1ä 177 | a1ö 178 | aa1e2 179 | aa1i2 180 | aa1o2 181 | aa1u2 182 | ai1a 183 | ai1e 184 | ai1o 185 | ai1u 186 | ali1a2v 187 | alkei2s1 188 | alous1 189 | au1a 190 | au1e 191 | b2l 192 | b2r 193 | bib3li 194 | c2l 195 | ch2r 196 | d2r 197 | e1aa 198 | e1ai 199 | e1uu 200 | e1ää 201 | e1ö2 202 | ee1a2 203 | ee1i2 204 | ee1u2 205 | ee1y2 206 | eu1a 207 | f2l 208 | f2r 209 | g2l 210 | g2r 211 | i1aa 212 | i1au 213 | i1uu 214 | i1ää 215 | i1öö 216 | ie1a 217 | ie1o 218 | ie1y 219 | ii1a2 220 | ii1e2 221 | ii1o2 222 | io1a2 223 | io1e2 224 | iu1a 225 | iu1e 226 | iu1o 227 | k2l 228 | keus1 229 | l2as 230 | o1aa 231 | o1ui 232 | o1uu 233 | o1y 234 | o1ä 235 | o1ö 236 | oi1a 237 | oi1e 238 | oi1o 239 | oi1u 240 | ou1e 241 | ou1o 242 | p2l 243 | p2r 244 | perus1 245 | q2v 246 | r2as 247 | rtaus1 248 | sc2h 249 | ts2h 250 | u1aa 251 | u1ee 252 | u1y2 253 | u1ä2 254 | u1ö2 255 | u2s 256 | ue1a 257 | ui1e 258 | ulo2s1 259 | uo1a 260 | uo1u 261 | uu1a2 262 | uu1e2 263 | uu1i2 264 | uu1o2 265 | y1a2 266 | y1ei 267 | y1o2 268 | y1u2 269 | y1ää 270 | yli1o2p 271 | ä1u2 272 | ä2y 273 | ä2ä 274 | ä2ö 275 | ä3a2 276 | ä3o2 277 | ää1e 278 | ää1i 279 | ää3y 280 | ö1e2 281 | ö1u2 282 | ö2y 283 | ö2ä 284 | ö2ö 285 | ö3a2 286 | ö3o2 -------------------------------------------------------------------------------- /tools/automatic_text_alignment.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | 3 | import sys 4 | import os 5 | 6 | modules_path = os.path.dirname(__file__) 7 | 8 | if not modules_path in sys.path: 9 | sys.path.append(os.path.dirname(__file__)) 10 | 11 | import subsutils 12 | import pysrt 13 | 14 | from aeneas.executetask import ExecuteTask 15 | from aeneas.task import Task 16 | 17 | def write_word_level(text, txt_path): 18 | """ 19 | Writes each word of text on a separate line inside the .txt file 20 | """ 21 | words = [] 22 | text = text.split('\n') 23 | for line in text: 24 | split = line.split(' ') 25 | for word in split: 26 | words.append(word) 27 | 28 | txt_file = open(txt_path, 'w') 29 | txt_file.write('\n'.join(words)) 30 | txt_file.close() 31 | 32 | def make_subs(wav_path, txt_path, srt_path, start): 33 | """Gets the subtitles with the correct timing based on the wav file""" 34 | 35 | config_string = "task_language=eng|is_text_type=plain|os_task_file_format=srt" 36 | 37 | task = Task(config_string=config_string) 38 | task.audio_file_path_absolute = wav_path 39 | task.text_file_path_absolute = txt_path 40 | task.sync_map_file_path_absolute = srt_path 41 | 42 | ExecuteTask(task).execute() 43 | task.output_sync_map_file() 44 | 45 | subs = pysrt.open(srt_path) 46 | 47 | subs.shift(seconds=start) 48 | 49 | return subs 50 | 51 | 52 | class SplitWords(bpy.types.Operator): 53 | bl_label = 'Split Words' 54 | bl_idname = 'sequencerextra.split_words' 55 | bl_description = "Split each strip word by word. Needed for making enhanced subtitles" 56 | 57 | def execute(self, context): 58 | scene = context.scene 59 | edit_channel = scene.subtitle_edit_channel 60 | 61 | fps = scene.render.fps/scene.render.fps_base 62 | 63 | original_start = scene.frame_start 64 | original_end = scene.frame_end 65 | 66 | all_strips = list(sorted( 67 | scene.sequence_editor.sequences_all, 68 | key=lambda x: x.frame_start)) 69 | 70 | text_strips = [] 71 | for x in range(len(all_strips)): 72 | if (all_strips[x].type == "TEXT" and 73 | all_strips[x].channel == edit_channel): 74 | text_strips.append(all_strips[x]) 75 | 76 | wav_path = os.path.join(os.path.dirname(__file__), 'temp.wav') 77 | txt_path = os.path.join(os.path.dirname(__file__), 'temp.txt') 78 | srt_path = os.path.join(os.path.dirname(__file__), 'temp.srt') 79 | 80 | subs = pysrt.SubRipFile() 81 | 82 | for i in range(len(text_strips)): 83 | frame_start = text_strips[i].frame_start 84 | frame_end = text_strips[i].frame_final_end - 1 85 | start = (frame_start + 1) / fps 86 | text = text_strips[i].text 87 | 88 | scene.frame_start = frame_start 89 | scene.frame_end = frame_end 90 | 91 | bpy.ops.sound.mixdown(filepath=wav_path, container="WAV", codec="PCM") 92 | write_word_level(text, txt_path) 93 | subs.extend(make_subs(wav_path, txt_path, srt_path, start)) 94 | 95 | 96 | subsutils.addSubs(context, subs, use_color=True) 97 | 98 | return {"FINISHED"} -------------------------------------------------------------------------------- /operators/hyphenator/patterns/it.txt: -------------------------------------------------------------------------------- 1 | .a3p2n 2 | .anti1 3 | .anti3m2n 4 | .bio1 5 | .c2 6 | .ca4p3s 7 | .circu2m1 8 | .contro1 9 | .d2 10 | .di2s3cine 11 | .e2x1eu 12 | .fran2k3 13 | .free3 14 | .li3p2sa 15 | .narco1 16 | .opto1 17 | .orto3p2 18 | .p2s 19 | .para1 20 | .poli3p2 21 | .pre1 22 | .re1i2scr 23 | .sha2re3 24 | .su2b3lu 25 | .su2b3r 26 | .tran2s3c 27 | .tran2s3d 28 | .tran2s3l 29 | .tran2s3n 30 | .tran2s3p 31 | .tran2s3r 32 | .tran2s3t 33 | .wa2g3n 34 | .wel2t1 35 | .z2 36 | 1b 37 | 1c 38 | 1d 39 | 1f 40 | 1g 41 | 1h 42 | 1j 43 | 1k 44 | 1l 45 | 1m 46 | 1n 47 | 1p 48 | 1q 49 | 1r 50 | 1s2 51 | 1t 52 | 1v 53 | 1w 54 | 1x 55 | 1z 56 | 2at. 57 | 2b. 58 | 2bb 59 | 2bc 60 | 2bd 61 | 2bf 62 | 2bm 63 | 2bn 64 | 2bp 65 | 2bs 66 | 2bt 67 | 2bv 68 | 2b’ 69 | 2c. 70 | 2cb 71 | 2cc 72 | 2cd 73 | 2cf 74 | 2chb 75 | 2chh 76 | 2chn 77 | 2ck 78 | 2cm 79 | 2cn 80 | 2cq 81 | 2cs 82 | 2ct 83 | 2cz 84 | 2c’ 85 | 2d. 86 | 2db 87 | 2dd 88 | 2dg 89 | 2dl 90 | 2dm 91 | 2dn 92 | 2dp 93 | 2ds 94 | 2dt 95 | 2dv 96 | 2dw 97 | 2d’ 98 | 2f. 99 | 2fb 100 | 2ff 101 | 2fg 102 | 2fn 103 | 2fs 104 | 2ft 105 | 2f’ 106 | 2g. 107 | 2gb 108 | 2gd 109 | 2gf 110 | 2gg 111 | 2gh2t 112 | 2gm 113 | 2gp 114 | 2gs 115 | 2gt 116 | 2gv 117 | 2gw 118 | 2gz 119 | 2g’ 120 | 2h. 121 | 2hb 122 | 2hd 123 | 2hh 124 | 2hm 125 | 2hn 126 | 2hr 127 | 2hv 128 | 2h’ 129 | 2j. 130 | 2j’ 131 | 2k. 132 | 2kf 133 | 2kg 134 | 2kk 135 | 2km 136 | 2ks 137 | 2kt 138 | 2k’ 139 | 2l. 140 | 2l3f2 141 | 2lb 142 | 2lc 143 | 2ld 144 | 2lg 145 | 2lk 146 | 2ll 147 | 2lm 148 | 2ln 149 | 2lp 150 | 2lq 151 | 2lr 152 | 2ls 153 | 2lt 154 | 2lv 155 | 2lw 156 | 2lz 157 | 2l’. 158 | 2l’’ 159 | 2m. 160 | 2mb 161 | 2mc 162 | 2mf 163 | 2ml 164 | 2mm 165 | 2mn 166 | 2mp 167 | 2mq 168 | 2mr 169 | 2ms 170 | 2mt 171 | 2mv 172 | 2mw 173 | 2m’ 174 | 2n. 175 | 2nb 176 | 2nc 177 | 2nd 178 | 2nf 179 | 2ng 180 | 2nheit 181 | 2nk 182 | 2nl 183 | 2nm 184 | 2nn 185 | 2np 186 | 2nq 187 | 2nr 188 | 2ns 189 | 2nt 190 | 2nv 191 | 2nz 192 | 2n’ 193 | 2p. 194 | 2pd 195 | 2pn 196 | 2pp 197 | 2ps 198 | 2pt 199 | 2pz 200 | 2p’ 201 | 2q. 202 | 2qq 203 | 2q’ 204 | 2r. 205 | 2rb 206 | 2rc 207 | 2rd 208 | 2rf 209 | 2rg 210 | 2rk 211 | 2rl 212 | 2rm 213 | 2rn 214 | 2rp 215 | 2rq 216 | 2rr 217 | 2rs 218 | 2rt 219 | 2rv 220 | 2rw 221 | 2rx 222 | 2rz 223 | 2r’ 224 | 2s3p2n 225 | 2s3s 226 | 2sh. 227 | 2shm 228 | 2sh’ 229 | 2stb 230 | 2stc 231 | 2std 232 | 2stf 233 | 2stg 234 | 2stm 235 | 2stn 236 | 2stp 237 | 2sts 238 | 2stt 239 | 2stv 240 | 2sz 241 | 2t. 242 | 2tb 243 | 2tc 244 | 2td 245 | 2tf 246 | 2tg 247 | 2tm 248 | 2tn 249 | 2tp 250 | 2tt 251 | 2tv 252 | 2tw 253 | 2tzk 254 | 2t’. 255 | 2t’’ 256 | 2v. 257 | 2vc 258 | 2vv 259 | 2v’. 260 | 2v’’ 261 | 2w. 262 | 2w1y 263 | 2w’ 264 | 2x. 265 | 2xb 266 | 2xc 267 | 2xf 268 | 2xh 269 | 2xm 270 | 2xp 271 | 2xt 272 | 2xw 273 | 2x’ 274 | 2z. 275 | 2zb 276 | 2zd 277 | 2zl 278 | 2zn 279 | 2zp 280 | 2zs 281 | 2zt 282 | 2zv 283 | 2zz 284 | 2z’. 285 | 2z’’ 286 | 2’2 287 | 3p2ne 288 | 3p2sic 289 | 3t2sch 290 | 4s. 291 | 4s’. 292 | 4s’’ 293 | a1ia 294 | a1ie 295 | a1io 296 | a1iu 297 | a1uo 298 | a1ya 299 | b2l 300 | b2r 301 | c2h 302 | c2l 303 | c2r 304 | ch2r 305 | d2r 306 | e1iu 307 | e2w 308 | f2l 309 | f2r 310 | g2h 311 | g2l 312 | g2n 313 | g2r 314 | h2l 315 | hi3p2n 316 | k2h 317 | k2l 318 | k2r 319 | l2h 320 | n2g3n 321 | n2s3fer 322 | o1ia 323 | o1ie 324 | o1io 325 | o1iu 326 | p2h 327 | p2l 328 | p2r 329 | r2h 330 | r2t2s3 331 | s4s3m 332 | t2h 333 | t2l 334 | t2r 335 | t2s 336 | t2t3s 337 | t2z 338 | tz2s 339 | v2l 340 | v2r 341 | w2h 342 | wa2r 343 | y1i 344 | y1ou -------------------------------------------------------------------------------- /operators/hyphenator/patterns/la.txt: -------------------------------------------------------------------------------- 1 | .a2b3l 2 | .anti1 3 | .anti3m2n 4 | .circu2m1 5 | .co2n1iun 6 | .di2s3cine 7 | .e2x1 8 | .o2b3 9 | .para1i 10 | .para1u 11 | .su2b3lu 12 | .su2b3r 13 | 1b 14 | 1c 15 | 1d 16 | 1f 17 | 1g 18 | 1h 19 | 1j 20 | 1k 21 | 1l 22 | 1m 23 | 1n 24 | 1p 25 | 1qu2 26 | 1r 27 | 1s2 28 | 1t 29 | 1v 30 | 1x 31 | 1z 32 | 2b. 33 | 2bb 34 | 2bd 35 | 2bm 36 | 2bn 37 | 2bs 38 | 2bt 39 | 2c. 40 | 2cc 41 | 2cm 42 | 2cn 43 | 2cq 44 | 2cs 45 | 2ct 46 | 2cz 47 | 2d. 48 | 2dd 49 | 2dg 50 | 2dm 51 | 2ds 52 | 2dv 53 | 2f. 54 | 2ff 55 | 2fn 56 | 2ft 57 | 2g. 58 | 2gd 59 | 2gf 60 | 2gg 61 | 2gm 62 | 2gs 63 | 2gv 64 | 2h. 65 | 2hp 66 | 2ht 67 | 2kk 68 | 2l. 69 | 2lb 70 | 2lc 71 | 2ld 72 | 2lf 73 | 2lg 74 | 2lk 75 | 2ll 76 | 2lm 77 | 2ln 78 | 2lp 79 | 2lq 80 | 2lr 81 | 2ls 82 | 2lt 83 | 2lv 84 | 2m. 85 | 2mb 86 | 2ml 87 | 2mm 88 | 2mn 89 | 2mp 90 | 2mq 91 | 2mr 92 | 2mv 93 | 2n. 94 | 2nb 95 | 2nc 96 | 2nd 97 | 2nf 98 | 2ng 99 | 2nl 100 | 2nm 101 | 2nn 102 | 2np 103 | 2nq 104 | 2nr 105 | 2ns 106 | 2nt 107 | 2nv 108 | 2nx 109 | 2p. 110 | 2php 111 | 2pht 112 | 2pn 113 | 2pp 114 | 2ps 115 | 2pt 116 | 2pz 117 | 2r. 118 | 2rb 119 | 2rc 120 | 2rd 121 | 2rf 122 | 2rg 123 | 2rl 124 | 2rm 125 | 2rn 126 | 2rp 127 | 2rq 128 | 2rr 129 | 2rs 130 | 2rt 131 | 2rv 132 | 2rz 133 | 2s. 134 | 2s3dem. 135 | 2s3ph 136 | 2s3que. 137 | 2s3s 138 | 2st. 139 | 2st3l 140 | 2stb 141 | 2stc 142 | 2std 143 | 2stf 144 | 2stg 145 | 2stm 146 | 2stn 147 | 2stp 148 | 2stq 149 | 2sts 150 | 2stt 151 | 2stv 152 | 2t. 153 | 2tb 154 | 2tc 155 | 2td 156 | 2tf 157 | 2tg 158 | 2tm 159 | 2tn 160 | 2tp 161 | 2tq 162 | 2tt 163 | 2tv 164 | 2vv 165 | 2x. 166 | 2xt 167 | 2xx 168 | 2z. 169 | 3p2neu 170 | 3p2sic 171 | a1ia 172 | a1ie 173 | a1io 174 | a1iu 175 | a1ua 176 | a1ue 177 | a1ui 178 | a1uo 179 | a1uu 180 | a2l1ua 181 | a2l1ue 182 | a2l1ui 183 | a2l1uo 184 | a2l1uu 185 | a2m1ua 186 | a2m1ue 187 | a2m1ui 188 | a2m1uo 189 | a2m1uu 190 | a2n1ua 191 | a2n1ue 192 | a2n1ui 193 | a2n1uo 194 | a2n1uu 195 | a2r1ua 196 | a2r1ue 197 | a2r1ui 198 | a2r1uo 199 | a2r1uu 200 | ae1a 201 | ae1o 202 | ae1u 203 | b2l 204 | b2r 205 | c2h2 206 | c2l 207 | c2r 208 | d2r 209 | e1iu 210 | e1ua 211 | e1ue 212 | e1ui 213 | e1uo 214 | e1uu 215 | e2l1ua 216 | e2l1ue 217 | e2l1ui 218 | e2l1uo 219 | e2l1uu 220 | e2m1ua 221 | e2m1ue 222 | e2m1ui 223 | e2m1uo 224 | e2m1uu 225 | e2n1ua 226 | e2n1ue 227 | e2n1ui 228 | e2n1uo 229 | e2n1uu 230 | e2r1ua 231 | e2r1ue 232 | e2r1ui 233 | e2r1uo 234 | e2r1uu 235 | f2l 236 | f2r 237 | g2l 238 | g2n 239 | g2r 240 | i1ua 241 | i1ue 242 | i1ui 243 | i1uo 244 | i1uu 245 | i2l1ua 246 | i2l1ue 247 | i2l1ui 248 | i2l1uo 249 | i2l1uu 250 | i2m1ua 251 | i2m1ue 252 | i2m1ui 253 | i2m1uo 254 | i2m1uu 255 | i2n1ua 256 | i2n1ue 257 | i2n1ui 258 | i2n1uo 259 | i2n1uu 260 | i2r1ua 261 | i2r1ue 262 | i2r1ui 263 | i2r1uo 264 | i2r1uu 265 | io1i 266 | k2h2 267 | l3f2t 268 | n2s3f 269 | n2s3m 270 | o1ia 271 | o1ie 272 | o1io 273 | o1iu 274 | o1ua 275 | o1ue 276 | o1ui 277 | o1uo 278 | o1uu 279 | o2l1ua 280 | o2l1ue 281 | o2l1ui 282 | o2l1uo 283 | o2l1uu 284 | o2m1ua 285 | o2m1ue 286 | o2m1ui 287 | o2m1uo 288 | o2m1uu 289 | o2n1ua 290 | o2n1ue 291 | o2n1ui 292 | o2n1uo 293 | o2n1uu 294 | o2r1ua 295 | o2r1ue 296 | o2r1ui 297 | o2r1uo 298 | o2r1uu 299 | p2h 300 | p2l 301 | p2r 302 | r2h 303 | t2h 304 | t2l 305 | t2r 306 | u1ua 307 | u1ue 308 | u1ui 309 | u1uo 310 | u1uu 311 | u2l1ua 312 | u2l1ue 313 | u2l1ui 314 | u2l1uo 315 | u2l1uu 316 | u2m1ua 317 | u2m1ue 318 | u2m1ui 319 | u2m1uo 320 | u2m1uu 321 | u2n1ua 322 | u2n1ue 323 | u2n1ui 324 | u2n1uo 325 | u2n1uu 326 | u2r1ua 327 | u2r1ue 328 | u2r1ui 329 | u2r1uo 330 | u2r1uu 331 | uo3u 332 | v2l 333 | v2r 334 | æ1 335 | œ1 -------------------------------------------------------------------------------- /operators/pysrt/srtitem.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | SubRip's subtitle parser 4 | """ 5 | 6 | from .srtexc import InvalidItem, InvalidIndex 7 | from .srttime import SubRipTime 8 | from .comparablemixin import ComparableMixin 9 | from .compat import str, is_py2 10 | import re 11 | 12 | 13 | class SubRipItem(ComparableMixin): 14 | """ 15 | SubRipItem(index, start, end, text, position) 16 | 17 | index -> int: index of item in file. 0 by default. 18 | start, end -> SubRipTime or coercible. 19 | text -> unicode: text content for item. 20 | position -> unicode: raw srt/vtt "display coordinates" string 21 | """ 22 | ITEM_PATTERN = str('%s\n%s --> %s%s\n%s\n') 23 | TIMESTAMP_SEPARATOR = '-->' 24 | 25 | def __init__(self, index=0, start=None, end=None, text='', position=''): 26 | try: 27 | self.index = int(index) 28 | except (TypeError, ValueError): # try to cast as int, but it's not mandatory 29 | self.index = index 30 | 31 | self.start = SubRipTime.coerce(start or 0) 32 | self.end = SubRipTime.coerce(end or 0) 33 | self.position = str(position) 34 | self.text = str(text) 35 | 36 | @property 37 | def duration(self): 38 | return self.end - self.start 39 | 40 | @property 41 | def text_without_tags(self): 42 | RE_TAG = re.compile(r'<[^>]*?>') 43 | return RE_TAG.sub('', self.text) 44 | 45 | @property 46 | def characters_per_second(self): 47 | characters_count = len(self.text_without_tags.replace('\n', '')) 48 | try: 49 | return characters_count / (self.duration.ordinal / 1000.0) 50 | except ZeroDivisionError: 51 | return 0.0 52 | 53 | def __str__(self): 54 | position = ' %s' % self.position if self.position.strip() else '' 55 | return self.ITEM_PATTERN % (self.index, self.start, self.end, 56 | position, self.text) 57 | if is_py2: 58 | __unicode__ = __str__ 59 | 60 | def __str__(self): 61 | raise NotImplementedError('Use unicode() instead!') 62 | 63 | def _cmpkey(self): 64 | return (self.start, self.end) 65 | 66 | def shift(self, *args, **kwargs): 67 | """ 68 | shift(hours, minutes, seconds, milliseconds, ratio) 69 | 70 | Add given values to start and end attributes. 71 | All arguments are optional and have a default value of 0. 72 | """ 73 | self.start.shift(*args, **kwargs) 74 | self.end.shift(*args, **kwargs) 75 | 76 | @classmethod 77 | def from_string(cls, source): 78 | return cls.from_lines(source.splitlines(True)) 79 | 80 | @classmethod 81 | def from_lines(cls, lines): 82 | if len(lines) < 2: 83 | raise InvalidItem() 84 | lines = [l.rstrip() for l in lines] 85 | index = None 86 | if cls.TIMESTAMP_SEPARATOR not in lines[0]: 87 | index = lines.pop(0) 88 | start, end, position = cls.split_timestamps(lines[0]) 89 | body = '\n'.join(lines[1:]) 90 | return cls(index, start, end, body, position) 91 | 92 | @classmethod 93 | def split_timestamps(cls, line): 94 | timestamps = line.split(cls.TIMESTAMP_SEPARATOR) 95 | if len(timestamps) != 2: 96 | raise InvalidItem() 97 | start, end_and_position = timestamps 98 | end_and_position = end_and_position.lstrip().split(' ', 1) 99 | end = end_and_position[0] 100 | position = end_and_position[1] if len(end_and_position) > 1 else '' 101 | return (s.strip() for s in (start, end, position)) -------------------------------------------------------------------------------- /operators/syllabify.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | from bpy_extras.io_utils import ExportHelper 3 | 4 | import os 5 | 6 | from .hyphenator.hyphenator import Hyphenator 7 | from .hyphenator.get_dictionary import get_dictionary 8 | 9 | from .tools.get_text_strips import get_text_strips 10 | from .tools.remove_punctuation import remove_punctuation 11 | 12 | def collect_words(scene): 13 | """Collect, clean, and alphabetize the words in the subtitles""" 14 | 15 | words = [] 16 | 17 | text_strips = get_text_strips(scene) 18 | for strip in text_strips: 19 | strip_words = strip.text.lower().replace('--', ' ').replace('\n', ' ').split(' ') 20 | words.extend(strip_words) 21 | 22 | i = 0 23 | while i < len(words): 24 | if words[i].rstrip() == '': 25 | words.pop(i) 26 | else: 27 | i += 1 28 | words = set(words) 29 | words = list(sorted(words)) 30 | 31 | for i in range(len(words)): 32 | words[i] = remove_punctuation(words[i]) 33 | 34 | i = 0 35 | while i < len(words): 36 | if words[i] == '': 37 | words.pop(i) 38 | else: 39 | i += 1 40 | 41 | return words 42 | 43 | def get_patterns(lang): 44 | """Get language patterns for the given language""" 45 | module_path = os.path.dirname(__file__) 46 | pat_path = os.path.join( 47 | module_path, 'hyphenator', 'patterns', lang + '.txt') 48 | f = open(pat_path, 'r', encoding='utf-8') 49 | patterns = f.read() 50 | f.close() 51 | 52 | return patterns 53 | 54 | 55 | class SEQUENCER_OT_syllabify(bpy.types.Operator, ExportHelper): 56 | bl_label = 'Syllabify' 57 | bl_idname = 'sequencerextra.syllabify' 58 | bl_description = "Create a list of words, separated by syllables.\nNeeded for splitting words with accurate syllable differentiation" 59 | 60 | filename_ext = ".txt" 61 | 62 | @classmethod 63 | def poll(self, context): 64 | scene = context.scene 65 | try: 66 | text_strips = get_text_strips(scene) 67 | 68 | if len(text_strips) > 0: 69 | return True 70 | else: 71 | return False 72 | except AttributeError: 73 | return False 74 | 75 | def execute(self, context): 76 | scene = context.scene 77 | 78 | words = collect_words(scene) 79 | 80 | found_words = [] 81 | not_founds = [] 82 | if scene.use_dictionary_syllabification: 83 | dictionary = get_dictionary( 84 | lang=scene.syllabification_language) 85 | for i in range(len(words)): 86 | try: 87 | words[i] = dictionary[words[i]] 88 | found_words.append(words[i]) 89 | except KeyError: 90 | not_founds.append(words[i]) 91 | if len(not_founds) > 0: 92 | print('=================\nNot in Dictionary\n=================') 93 | not_founds = list(set(not_founds)) 94 | for i in range(len(not_founds)): 95 | print(not_founds[i].encode('ascii', errors='replace').decode('ascii')) 96 | 97 | if scene.use_algorithmic_syllabification: 98 | hyphenator = Hyphenator() 99 | patterns = get_patterns(scene.syllabification_language) 100 | hyphenator.patterns = patterns 101 | hyphenator.setup_patterns() 102 | for i in range(len(words)): 103 | if not words[i] in found_words: 104 | words[i] = hyphenator.hyphenate_word(words[i]) 105 | words[i] = ' '.join(words[i]) 106 | words = set(words) 107 | words = sorted(list(words)) 108 | f = open(self.filepath, 'w') 109 | f.write('\n'.join(words)) 110 | f.close() 111 | 112 | scene.syllable_dictionary_path = self.filepath 113 | 114 | return {"FINISHED"} 115 | -------------------------------------------------------------------------------- /operators/split_words.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | import os 3 | 4 | from .tools.get_text_strips import get_text_strips 5 | from .tools.subtitles_to_sequencer import subtitles_to_sequencer 6 | from .tools.remove_punctuation import remove_punctuation 7 | 8 | from .pysrt.srtitem import SubRipItem 9 | from .pysrt.srtfile import SubRipFile 10 | 11 | from .hyphenator.get_dictionary import get_dictionary 12 | 13 | 14 | def break_strip(scene, strip): 15 | """ 16 | Break a strip into it's words/syllables and 17 | return a list of the pieces 18 | """ 19 | 20 | dic_path = bpy.path.abspath(scene.syllable_dictionary_path) 21 | if os.path.isfile(dic_path): 22 | dictionary = get_dictionary(dic_path) 23 | else: 24 | dictionary = {} 25 | 26 | words = strip.text.replace('\n', ' ').split(' ') 27 | i = 0 28 | while i < len(words): 29 | if words[i].rstrip() == '': 30 | words.pop(i) 31 | else: 32 | i += 1 33 | 34 | finished_pieces = [] 35 | 36 | for i in range(len(words)): 37 | words[i] = words[i].strip() 38 | 39 | try: 40 | key = remove_punctuation(words[i].lower()) 41 | pieces = dictionary[key].split(' ') 42 | except KeyError: 43 | pieces = [words[i]] 44 | 45 | for x in range(len(pieces)): 46 | string = '' 47 | while len(pieces[x]) > 0: 48 | 49 | if words[i][0].lower() == pieces[x][0]: 50 | pieces[x] = pieces[x][1::] 51 | string += words[i][0] 52 | words[i] = words[i][1::] 53 | 54 | if x == len(pieces) - 1: 55 | string += words[i] 56 | pieces[x] = '' 57 | 58 | finished_pieces.append(string) 59 | 60 | return finished_pieces 61 | 62 | 63 | def form_items(scene, strip, pieces): 64 | """ 65 | Create SubRipItems for each piece 66 | """ 67 | 68 | fps = scene.render.fps / scene.render.fps_base 69 | start = strip.frame_final_start / fps 70 | end = strip.frame_final_end / fps 71 | 72 | text = strip.text 73 | lines = text.split('\n') 74 | empty_text = '' 75 | for i in range(len(lines)): 76 | for char in range(len(lines[i])): 77 | empty_text += ' ' 78 | empty_text += '\n' 79 | 80 | new_pieces = [] 81 | for i in range(len(pieces)): 82 | if i == 0: 83 | string = '' 84 | else: 85 | string = new_pieces[i - 1].rstrip() 86 | while len(pieces[i]) > 0: 87 | if text[0] == pieces[i][0]: 88 | pieces[i] = pieces[i][1::] 89 | string += text[0] 90 | text = text[1::] 91 | string = string + empty_text[len(string)::] 92 | new_pieces.append(string) 93 | 94 | new_pieces = list(reversed(new_pieces)) 95 | sub_list = [] 96 | step = 1 / fps 97 | 98 | for i in range(len(new_pieces)): 99 | if not i == len(new_pieces) - 1: 100 | start_time = end - (step * i) - step 101 | end_time = start_time + step 102 | else: 103 | end_time = end - (step * i) 104 | start_time = start 105 | 106 | sub_item = SubRipItem() 107 | sub_item.start.from_millis(start_time * 1000) 108 | sub_item.end.from_millis(end_time * 1000) 109 | sub_item.text = new_pieces[i] 110 | sub_item.name = '' 111 | 112 | if i == 0: 113 | sub_item.name = '[locked end]' 114 | if i == len(new_pieces) - 1: 115 | sub_item.name += '[locked start]' 116 | 117 | sub_list.append(sub_item) 118 | 119 | return sub_list 120 | 121 | 122 | class SEQUENCER_OT_split_words(bpy.types.Operator): 123 | bl_label = 'Split' 124 | bl_idname = 'sequencerextra.split_words' 125 | bl_description = 'Create new subtitles where each word is separated.\n\nIf a syllable dictionary is provided, words will be further split by their syllables' 126 | 127 | @classmethod 128 | def poll(self, context): 129 | scene = context.scene 130 | try: 131 | text_strips = get_text_strips(scene) 132 | 133 | if len(text_strips) > 0: 134 | return True 135 | else: 136 | return False 137 | except AttributeError: 138 | return False 139 | 140 | def execute(self, context): 141 | 142 | scene = context.scene 143 | text_strips = get_text_strips(scene) 144 | 145 | sub_list = [] 146 | for strip in text_strips: 147 | pieces = break_strip(scene, strip) 148 | sub_list.extend(form_items(scene, strip, pieces )) 149 | 150 | for i in range(len(sub_list)): 151 | sub_list[i].index = i + 1 152 | 153 | subs = SubRipFile(sub_list) 154 | 155 | new_strips = subtitles_to_sequencer(context, subs) 156 | 157 | for strip in new_strips: 158 | strip.color[0] = scene.enhanced_subs_color[0] 159 | strip.color[1] = scene.enhanced_subs_color[1] 160 | strip.color[2] = scene.enhanced_subs_color[2] 161 | 162 | scene.subtitle_edit_channel = new_strips[0].channel 163 | 164 | return {"FINISHED"} 165 | -------------------------------------------------------------------------------- /operators/pysrt/convert_enhanced.py: -------------------------------------------------------------------------------- 1 | from .srtfile import SubRipFile 2 | from .srtitem import SubRipItem 3 | import re 4 | 5 | def get_all_bases(temp_subs): 6 | """Get the bases (whole sentences) from the enhanced subs""" 7 | 8 | subs = [] 9 | for temp in temp_subs: 10 | sub = SubRipItem( 11 | index = temp.index, start=temp.start, end=temp.end, 12 | text=temp.text) 13 | subs.append(sub) 14 | 15 | sub_items = [] 16 | 17 | while len(subs) > 0: 18 | 19 | if subs[0].text == subs[0].text_without_tags: 20 | 21 | sub_items.append(subs[0]) 22 | subs.pop(0) 23 | if len(subs) > 0: 24 | go = True 25 | while go: 26 | lesser = subs[0].text.split('')[0] 27 | lesser = re.compile(r'<[^>]*?>').sub('', lesser) 28 | if (len(lesser.rstrip()) <= len(sub_items[-1].text) and 29 | lesser in sub_items[-1].text): 30 | sub_items[-1].end = subs[0].end 31 | subs.pop(0) 32 | else: 33 | go = False 34 | 35 | if len(sub_items) == 0: 36 | sub_items.append(subs[0]) 37 | sub_items[-1].text = sub_items[-1].text.split('')[0] 38 | sub_items[-1].text = sub_items[-1].text_without_tags 39 | subs.pop(0) 40 | 41 | if len(subs) > 0: 42 | 43 | subs[0].text = subs[0].text.split('')[0] 44 | subs[0].text = subs[0].text_without_tags 45 | 46 | if subs[0].text.startswith(sub_items[-1].text): 47 | sub_items[-1].end = subs[0].end 48 | sub_items[-1].text = subs[0].text 49 | subs.pop(0) 50 | 51 | else: 52 | sub_items.append(subs[0]) 53 | subs.pop(0) 54 | 55 | bases = SubRipFile(sub_items) 56 | bases.clean_indexes() 57 | return bases 58 | 59 | def get_base(sub, bases): 60 | """Get the base that corresponds with a sub""" 61 | 62 | start = sub.start.to_millis() 63 | end = sub.end.to_millis() 64 | 65 | for base in bases: 66 | b_start = base.start.to_millis() 67 | b_end = base.end.to_millis() 68 | 69 | if b_start <= start and b_end >= end: 70 | return base 71 | 72 | def get_children(base, tops): 73 | """Get all of the children of a base""" 74 | 75 | children = [] 76 | 77 | start = base.start.to_millis() 78 | end = base.end.to_millis() 79 | 80 | for i in range(len(tops)): 81 | t_start = tops[i].start.to_millis() 82 | t_end = tops[i].end.to_millis() 83 | 84 | if start <= t_start and end >= t_end: 85 | children.append(i) 86 | 87 | elif start < t_end: 88 | return children 89 | 90 | return children 91 | 92 | def make_locks(tops, bases): 93 | """Add [locked start] and [locked end] to appropriate tops""" 94 | 95 | for base in bases: 96 | children = get_children(base, tops) 97 | tops[children[0]].name = '[locked start]' 98 | tops[children[-1]].name = '[locked end]' 99 | 100 | return tops 101 | 102 | 103 | def get_all_tops(subs, bases): 104 | """Get the top subs (syllable/word splits)""" 105 | 106 | sub_items = [] 107 | 108 | i = 0 109 | while i < len(subs): 110 | if subs[i].text == subs[i].text_without_tags: 111 | subs.pop(i) 112 | 113 | elif i > 0: 114 | base = get_base(subs[i], bases) 115 | old_base = get_base(subs[i - 1], bases) 116 | 117 | if subs[i].text == subs[i - 1].text and base == old_base: 118 | subs.pop(i) 119 | 120 | else: 121 | i += 1 122 | else: 123 | i += 1 124 | 125 | for i in range(len(subs)): 126 | subs[i].text = subs[i].text.split('')[0] 127 | subs[i].text = subs[i].text_without_tags 128 | 129 | base = get_base(subs[i], bases) 130 | empty = empty_text(base.text) 131 | subs[i].text = subs[i].text + empty[len(subs[i].text)::] 132 | 133 | sub_items.append(subs[i]) 134 | 135 | tops = SubRipFile(sub_items) 136 | tops.clean_indexes() 137 | return tops 138 | 139 | def empty_text(text): 140 | """ 141 | Creates a string from text where each character (except newline) 142 | is a space 143 | """ 144 | lines = text.split('\n') 145 | empty_text = '' 146 | for x in range(len(lines)): 147 | for char in range(len(lines[x])): 148 | empty_text += ' ' 149 | empty_text += '\n' 150 | 151 | return empty_text[0:-1] 152 | 153 | def retrieve_color(subs): 154 | for sub in subs: 155 | if sub.text.startswith(' ' + end_timecode + '\n' 48 | if len(self[i].text) > 31: 49 | srt = srt + find_even_split(self[i].text) + '\n' 50 | else: 51 | srt = srt + self[i].text + '\n' 52 | output.append(srt) 53 | 54 | return '\n'.join(output).rstrip() 55 | 56 | def to_ESRT(self): 57 | """Convert ELRC to ESRT""" 58 | output = [] 59 | for i in range(len(self) - 1): 60 | sub_list = [] 61 | if not self[i].text == '': 62 | text = self[i].text 63 | base_text = self[i].text_without_tags 64 | 65 | if len(base_text) > 31: 66 | base_text = find_even_split(base_text) 67 | 68 | if not is_timecode(text[0:10]): 69 | time = self[i].lrc_time 70 | time = time.replace('[', '<').replace(']', '>') 71 | text = time + text 72 | 73 | if not is_timecode(text[-10::]): 74 | time = self[i + 1].lrc_time 75 | time = time.replace('[', '<').replace(']', '>') 76 | text = text + time 77 | 78 | if not text[0:10].replace('<', '[').replace('>', ']') == self[i].lrc_time: 79 | start = self[i].srt_time 80 | end = timecode_to_srt(text[0:10]) 81 | body = base_text 82 | 83 | segment = ''.join( 84 | ['0\n', start, ' --> ', end, '\n', body, '\n']) 85 | sub_list.append(segment) 86 | 87 | growing = "" 88 | while len(text) > 10: 89 | start = timecode_to_srt(text[0:10]) 90 | for c in range(1, len(text)): 91 | if text[c] == '<' and is_timecode(text[c:c + 10]): 92 | end = timecode_to_srt(text[c: c + 10]) 93 | body = text[10:c] 94 | text = text[c::] 95 | break 96 | 97 | if growing.endswith('\n'): 98 | growing += body.lstrip() 99 | 100 | else: 101 | growing += body 102 | 103 | try: 104 | if base_text[len(growing)] == '\n': 105 | growing = growing + '\n' 106 | except IndexError: 107 | pass 108 | 109 | body = ''.join( 110 | ['', growing, '', 111 | base_text[len(growing)::]]) 112 | 113 | segment = ''.join( 114 | ['0\n', start, ' --> ', end, '\n', body, '\n']) 115 | sub_list.append(segment) 116 | 117 | if not text[0:10].replace('<', '[').replace('>', ']') == self[i + 1].lrc_time: 118 | start = timecode_to_srt(text[0:10]) 119 | end = self[i + 1].srt_time 120 | body = '' + base_text + '' 121 | 122 | segment = ''.join( 123 | ['0\n', start, ' --> ', end, '\n', body, '\n']) 124 | sub_list.append(segment) 125 | 126 | output.extend(sub_list) 127 | 128 | for i in range(len(output)): 129 | output[i] = str(i + 1) + output[i][1::] 130 | 131 | return '\n'.join(output).rstrip() 132 | 133 | 134 | def to_LRC(self): 135 | output = [] 136 | if not self.artist == "": 137 | output.append('[ar:' + self.artist + ']') 138 | if not self.album == "": 139 | output.append('[al:' + self.album + ']') 140 | if not self.title == "": 141 | output.append('[ti:' + self.title + ']') 142 | if not self.author == "": 143 | output.append('[au:' + self.author + ']') 144 | if not self.length == "": 145 | output.append('[length:' + self.length + ']') 146 | if not self.offset == "": 147 | output.append('[offset:' + self.offset + ']') 148 | 149 | if len(output) > 0: 150 | output.append('') 151 | 152 | lrc = "" 153 | for i in range(len(self)): 154 | lrc = self[i].lrc_time + self[i].text 155 | output.append(lrc) 156 | return '\n'.join(output).rstrip() -------------------------------------------------------------------------------- /operators/hyphenator/patterns/ro.txt: -------------------------------------------------------------------------------- 1 | .a2z 2 | .a3ic 3 | .a4n3is 4 | .cre1 5 | .de2aj 6 | .de2z1 7 | .g4 8 | .i2a 9 | .i2e 10 | .i3v 11 | .i3ț 12 | .i4u3 13 | .n2 14 | .ni2 15 | .p4 16 | .pre3ș 17 | .s4 18 | .u4i 19 | .u5ni 20 | .z2 21 | .î4m 22 | .ș4 23 | 1b 24 | 1c 25 | 1d 26 | 1efa 27 | 1evit 28 | 1f4 29 | 1g2 30 | 1j 31 | 1k 32 | 1l 33 | 1m 34 | 1p2 35 | 1s 36 | 1t2 37 | 1v 38 | 1x 39 | 1z 40 | 1și 41 | 1ț 42 | 2acă 43 | 2alt 44 | 2an. 45 | 2anu 46 | 2atr 47 | 2au 48 | 2ața 49 | 2ață 50 | 2b. 51 | 2bc 52 | 2bd 53 | 2bs 54 | 2bt 55 | 2bț 56 | 2c5n 57 | 2cc 58 | 2chi. 59 | 2ci. 60 | 2cm 61 | 2cs 62 | 2ct 63 | 2cv 64 | 2cț 65 | 2d1n 66 | 2dc 67 | 2dj 68 | 2dm 69 | 2e2s 70 | 2ec 71 | 2el 72 | 2en 73 | 2era 74 | 2erc 75 | 2eră 76 | 2ez 77 | 2eș 78 | 2eț 79 | 2f. 80 | 2fi. 81 | 2ft 82 | 2g. 83 | 2g3m 84 | 2g3n 85 | 2g3v 86 | 2ghi. 87 | 2gi. 88 | 2h. 89 | 2h1n 90 | 2hi. 91 | 2i1 92 | 2j. 93 | 2jd 94 | 2ji. 95 | 2jl 96 | 2l3ș 97 | 2l5n 98 | 2lb 99 | 2lc 100 | 2ld 101 | 2lf 102 | 2lg 103 | 2lm 104 | 2lp 105 | 2ls 106 | 2lt 107 | 2lv 108 | 2lț 109 | 2m. 110 | 2m1n 111 | 2m3s2 112 | 2mb 113 | 2mf 114 | 2mp 115 | 2mt 116 | 2mv 117 | 2mț 118 | 2n3s2 119 | 2nc 120 | 2nd 121 | 2ng 122 | 2nt 123 | 2nz 124 | 2nț 125 | 2oca 126 | 2od 127 | 2on 128 | 2p. 129 | 2p3c 130 | 2p3s 131 | 2p3t 132 | 2p3ș 133 | 2p3ț 134 | 2pi. 135 | 2r1h 136 | 2r1n 137 | 2r1r 138 | 2r3ș 139 | 2rb 140 | 2rc 141 | 2rd 142 | 2rf 143 | 2rg 144 | 2ri3un 145 | 2rk 146 | 2rl 147 | 2rm 148 | 2rp 149 | 2rs2 150 | 2rt 151 | 2rv 152 | 2rz 153 | 2rț 154 | 2sc 155 | 2sp 156 | 2st 157 | 2t3c 158 | 2t3d 159 | 2t3f 160 | 2t3m 161 | 2t3t 162 | 2tî. 163 | 2u1 164 | 2v. 165 | 2v1n 166 | 2vi. 167 | 2x. 168 | 2z. 169 | 2z1n 170 | 2z2g 171 | 2z2v 172 | 2zb 173 | 2zi. 174 | 2ă1 175 | 2ș 176 | 2ț. 177 | 2ți. 178 | 3b4lim 179 | 3bii 180 | 3cul 181 | 3du 182 | 3fa 183 | 3fo 184 | 3făș 185 | 3gu3 186 | 3i2ac 187 | 3inv 188 | 3lu 189 | 3ma 190 | 3me 191 | 3mi 192 | 3mo 193 | 3mu 194 | 3mî 195 | 3mă 196 | 3na 197 | 3ne 198 | 3ni 199 | 3no 200 | 3nî 201 | 3nă 202 | 3pa 203 | 3s2co 204 | 3s4l 205 | 3se 206 | 3si 207 | 3so 208 | 3sî 209 | 3tii. 210 | 3til 211 | 3tin 212 | 3tol 213 | 3tor 214 | 3tru. 215 | 3trul 216 | 3truo 217 | 3utor 218 | 3vr 219 | 3xa 220 | 3xe 221 | 3xi 222 | 3xo 223 | 3xu 224 | 3xă 225 | 3z2ol 226 | 3zii 227 | 3zil 228 | 3zon 229 | 3șa 230 | 3șe 231 | 3șin 232 | 3șo 233 | 3șu 234 | 3șî 235 | 3șă2 236 | 3ța 237 | 3ția 238 | 3ție 239 | 3ții 240 | 3țil 241 | 3țiu 242 | 3ță 243 | 4c. 244 | 4d. 245 | 4i. 246 | 4ila 247 | 4isp 248 | 4l. 249 | 4li. 250 | 4mi. 251 | 4n. 252 | 4n1ad 253 | 4n1eg 254 | 4n1ex 255 | 4nef 256 | 4nevi 257 | 4ni. 258 | 4r. 259 | 4ri. 260 | 4s. 261 | 4s2f 262 | 4sc. 263 | 4sm 264 | 4t. 265 | 4t3s2 266 | 4t3un 267 | 4t3z 268 | 4ti. 269 | 4ugu 270 | 4ș. 271 | 4ș3tr 272 | 4ș5n 273 | 4și. 274 | 4ști. 275 | 5nu 276 | 5sa 277 | 5sfî 278 | 5su 279 | 5să 280 | 5șii 281 | 5șil 282 | 6u. 283 | a1 284 | a2m 285 | a2n 286 | a3e 287 | a3i2a 288 | a3i2e 289 | a3il 290 | a3iu 291 | a3u2ț 292 | a3ua 293 | a3ud 294 | a3ug 295 | a3ul 296 | a3un 297 | a3ur 298 | a3us 299 | a3ute 300 | a3uz 301 | a5n2e 302 | a5t4u 303 | achi5 304 | afo3 305 | ai3s2 306 | alie6 307 | an2z 308 | an4s 309 | ani2e 310 | ani3ș4 311 | ao2g 312 | ati4a 313 | b2l 314 | b4lu 315 | ba2ț 316 | bi2a. 317 | bi2at 318 | bi2e 319 | bo1 320 | bo3ric 321 | bu3 322 | bănu5 323 | bți4ne. 324 | c4l 325 | ca3ut 326 | ce2a 327 | ce2ț 328 | ci2o 329 | ci3ale 330 | ci3sp 331 | cis2 332 | ciza2 333 | co2ț 334 | copia2tă 335 | cu2ț 336 | cu3im 337 | că2c 338 | cătu5 339 | da2ț 340 | da4m 341 | de4sc 342 | dez3in 343 | di2an 344 | dia2tă 345 | do4il 346 | e1ac 347 | e1aj 348 | e1al 349 | e1at 350 | e1av 351 | e1aș 352 | e1h 353 | e1o1 354 | e1r 355 | e1î 356 | e2m 357 | e2x 358 | e2z1o 359 | e3e 360 | e3i2a 361 | e3i2e 362 | e3i2o 363 | e3i3s2 364 | e3i4u 365 | e3ii 366 | e3il 367 | e3im 368 | e3in 369 | e3it 370 | e3on 371 | e3u 372 | e3și 373 | e5ne 374 | ea2ț 375 | ebu5i 376 | eci2a 377 | ecla2re 378 | edi4ulu 379 | ee2a 380 | emon5 381 | es3co 382 | es5ti 383 | etan4ț 384 | eu5ș 385 | ezi3a 386 | eză5 387 | f5tu 388 | fi3e 389 | g4l 390 | go5n 391 | gă3ț 392 | hi2a 393 | hi3c 394 | hi4u 395 | i2ai 396 | i2aș 397 | i2ed 398 | i2n 399 | i2s 400 | i2î 401 | i3că 402 | i3ia 403 | i3ie 404 | i3ii 405 | i3il 406 | i3in 407 | i3ir 408 | i3it 409 | i3le 410 | i3lo 411 | i3od 412 | i3oni 413 | i3ua 414 | i3ul 415 | i3um 416 | i3und 417 | i3unu 418 | i3us 419 | i3ut 420 | i3ți2o 421 | i4n1ed 422 | i5ti 423 | ia2ț 424 | ia3g4 425 | iitu2ră 426 | imateri6 427 | in2gă 428 | inți4i 429 | io2ț 430 | ipă5 431 | is3f 432 | iz3v 433 | iș3t 434 | iți2a 435 | j4u 436 | ji2ț 437 | ju3t 438 | larați2 439 | le2a 440 | li3a 441 | li3e 442 | li3o 443 | lă2ti 444 | lătu5 445 | mblîn3 446 | me2z 447 | mi2ț 448 | mon4 449 | mu2ț 450 | n1n 451 | n1r 452 | n2cis 453 | n2ciz 454 | n3j 455 | n3ș2 456 | n4sî 457 | n4și 458 | n5t4u 459 | n5ti 460 | na3in 461 | ne1ab 462 | ne1an 463 | ne1ap 464 | ne3s2 465 | ng3ăt 466 | ni3ez 467 | no4ș 468 | ns3f 469 | ns3po 470 | nu3a 471 | nu3s2 472 | nu3ă 473 | nu5m 474 | o1ag 475 | o1o 476 | o1ra 477 | o1re 478 | o1ri 479 | o1ro 480 | o1ru 481 | o1rî 482 | o1ră 483 | o2al 484 | o2bi. 485 | o2ric 486 | o3e 487 | o3i2 488 | o3u 489 | o3și 490 | o5ti 491 | ocu5i 492 | odi2a 493 | oi3s2p 494 | oiecti2 495 | om4n 496 | omedi2e. 497 | opi3e 498 | opla2 499 | oplagi2 500 | or2c 501 | or2te. 502 | os5ti 503 | ot3od 504 | otați4 505 | p4l 506 | p4ți. 507 | pe2ț 508 | pecți2 509 | pi2e 510 | pi2z 511 | pi2ț 512 | pi3e. 513 | pi3ez 514 | pi3o 515 | po2ț 516 | po4ș 517 | pu3b4 518 | pu4ș 519 | puri2e 520 | păr3ț 521 | r2e 522 | r3sp 523 | r3st 524 | re2bi 525 | re3s2cr 526 | re4și 527 | recizi2 528 | ri3a 529 | ri3ez 530 | ri3eț 531 | ri3v 532 | ri4ali 533 | ri5mi 534 | rna2ț 535 | rografi6 536 | rtua2le 537 | ru3il 538 | ru3sp 539 | s1n 540 | se2a 541 | se3sp 542 | se4e. 543 | se4ș 544 | ses2 545 | si3p 546 | so3ric 547 | sto3 548 | su2ț 549 | să4m 550 | să4ș 551 | t4l 552 | ta3ut 553 | te2a 554 | te3s2p 555 | te5ni 556 | teri6ală 557 | ti2ț 558 | ti3a 559 | ti3e 560 | to2to 561 | tu3a 562 | tu3im 563 | tu4ș 564 | u2a. 565 | u2ad 566 | u2b1o 567 | u2b3l 568 | u2bia 569 | u2l 570 | u2s 571 | u2to 572 | u3au 573 | u3e 574 | u3i2a 575 | u3i2e 576 | u3i2ț 577 | u3in 578 | u3ir 579 | u3is 580 | u3it 581 | u3iz 582 | u3la 583 | u3le 584 | u3lii 585 | u3lo 586 | u3lî 587 | u3lă 588 | u3ui 589 | u3um 590 | u3ș 591 | u4st 592 | u4șt 593 | ub3s2 594 | umi5r 595 | ur2z 596 | us2pr 597 | uă3 598 | uăs2 599 | ve2z 600 | ve2ț 601 | ve5ni 602 | vi2ț 603 | vorbito2 604 | xe2z 605 | z3vă 606 | z4m 607 | za2ț 608 | zi2an 609 | zi2ar 610 | zu2ț 611 | î2 612 | î3d 613 | î3e 614 | î3lo 615 | î3ri 616 | î3rî 617 | î3t 618 | î3z 619 | î3ț 620 | î4ti 621 | î4ți 622 | î5ții 623 | în5ș 624 | îna3 625 | îr5ș 626 | îș3t 627 | ă2m2 628 | ă2ti. 629 | ă2zi 630 | ă3i 631 | ă3u 632 | ă3v 633 | ă3ș 634 | ă4ș3t 635 | ăi2e 636 | ănu3 637 | ărgi5 638 | ăti4e 639 | ș2p 640 | ș2ti 641 | șa2ț 642 | șnu5 643 | țe2ț 644 | ți2ț 645 | ți3a. 646 | țu3 647 | țu5i -------------------------------------------------------------------------------- /operators/pysrt/srttime.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | SubRip's time format parser: HH:MM:SS,mmm 4 | """ 5 | import re 6 | from datetime import time 7 | 8 | from .srtexc import InvalidTimeString 9 | from .comparablemixin import ComparableMixin 10 | from .compat import str, basestring 11 | 12 | 13 | class TimeItemDescriptor(object): 14 | # pylint: disable-msg=R0903 15 | def __init__(self, ratio, super_ratio=0): 16 | self.ratio = int(ratio) 17 | self.super_ratio = int(super_ratio) 18 | 19 | def _get_ordinal(self, instance): 20 | if self.super_ratio: 21 | return instance.ordinal % self.super_ratio 22 | return instance.ordinal 23 | 24 | def __get__(self, instance, klass): 25 | if instance is None: 26 | raise AttributeError 27 | return self._get_ordinal(instance) // self.ratio 28 | 29 | def __set__(self, instance, value): 30 | part = self._get_ordinal(instance) - instance.ordinal % self.ratio 31 | instance.ordinal += value * self.ratio - part 32 | 33 | 34 | class SubRipTime(ComparableMixin): 35 | TIME_PATTERN = '%02d:%02d:%02d,%03d' 36 | TIME_REPR = 'SubRipTime(%d, %d, %d, %d)' 37 | RE_TIME_SEP = re.compile(r'\:|\.|\,') 38 | RE_INTEGER = re.compile(r'^(\d+)') 39 | SECONDS_RATIO = 1000 40 | MINUTES_RATIO = SECONDS_RATIO * 60 41 | HOURS_RATIO = MINUTES_RATIO * 60 42 | 43 | hours = TimeItemDescriptor(HOURS_RATIO) 44 | minutes = TimeItemDescriptor(MINUTES_RATIO, HOURS_RATIO) 45 | seconds = TimeItemDescriptor(SECONDS_RATIO, MINUTES_RATIO) 46 | milliseconds = TimeItemDescriptor(1, SECONDS_RATIO) 47 | 48 | def __init__(self, hours=0, minutes=0, seconds=0, milliseconds=0): 49 | """ 50 | SubRipTime(hours, minutes, seconds, milliseconds) 51 | 52 | All arguments are optional and have a default value of 0. 53 | """ 54 | super(SubRipTime, self).__init__() 55 | self.ordinal = hours * self.HOURS_RATIO \ 56 | + minutes * self.MINUTES_RATIO \ 57 | + seconds * self.SECONDS_RATIO \ 58 | + milliseconds 59 | 60 | def __repr__(self): 61 | return self.TIME_REPR % tuple(self) 62 | 63 | def __str__(self): 64 | if self.ordinal < 0: 65 | # Represent negative times as zero 66 | return str(SubRipTime.from_ordinal(0)) 67 | return self.TIME_PATTERN % tuple(self) 68 | 69 | def _compare(self, other, method): 70 | return super(SubRipTime, self)._compare(self.coerce(other), method) 71 | 72 | def _cmpkey(self): 73 | return self.ordinal 74 | 75 | def __add__(self, other): 76 | return self.from_ordinal(self.ordinal + self.coerce(other).ordinal) 77 | 78 | def __iadd__(self, other): 79 | self.ordinal += self.coerce(other).ordinal 80 | return self 81 | 82 | def __sub__(self, other): 83 | return self.from_ordinal(self.ordinal - self.coerce(other).ordinal) 84 | 85 | def __isub__(self, other): 86 | self.ordinal -= self.coerce(other).ordinal 87 | return self 88 | 89 | def __mul__(self, ratio): 90 | return self.from_ordinal(int(round(self.ordinal * ratio))) 91 | 92 | def __imul__(self, ratio): 93 | self.ordinal = int(round(self.ordinal * ratio)) 94 | return self 95 | 96 | @classmethod 97 | def coerce(cls, other): 98 | """ 99 | Coerce many types to SubRipTime instance. 100 | Supported types: 101 | - str/unicode 102 | - int/long 103 | - datetime.time 104 | - any iterable 105 | - dict 106 | """ 107 | if isinstance(other, SubRipTime): 108 | return other 109 | if isinstance(other, basestring): 110 | return cls.from_string(other) 111 | if isinstance(other, int): 112 | return cls.from_ordinal(other) 113 | if isinstance(other, time): 114 | return cls.from_time(other) 115 | try: 116 | return cls(**other) 117 | except TypeError: 118 | return cls(*other) 119 | 120 | def __iter__(self): 121 | yield self.hours 122 | yield self.minutes 123 | yield self.seconds 124 | yield self.milliseconds 125 | 126 | def shift(self, *args, **kwargs): 127 | """ 128 | shift(hours, minutes, seconds, milliseconds) 129 | 130 | All arguments are optional and have a default value of 0. 131 | """ 132 | if 'ratio' in kwargs: 133 | self *= kwargs.pop('ratio') 134 | self += self.__class__(*args, **kwargs) 135 | 136 | @classmethod 137 | def from_ordinal(cls, ordinal): 138 | """ 139 | int -> SubRipTime corresponding to a total count of milliseconds 140 | """ 141 | return cls(milliseconds=int(ordinal)) 142 | 143 | @classmethod 144 | def from_string(cls, source): 145 | """ 146 | str/unicode(HH:MM:SS,mmm) -> SubRipTime corresponding to serial 147 | raise InvalidTimeString 148 | """ 149 | items = cls.RE_TIME_SEP.split(source) 150 | if len(items) != 4: 151 | raise InvalidTimeString 152 | return cls(*(cls.parse_int(i) for i in items)) 153 | 154 | @classmethod 155 | def parse_int(cls, digits): 156 | try: 157 | return int(digits) 158 | except ValueError: 159 | match = cls.RE_INTEGER.match(digits) 160 | if match: 161 | return int(match.group()) 162 | return 0 163 | 164 | @classmethod 165 | def from_time(cls, source): 166 | """ 167 | datetime.time -> SubRipTime corresponding to time object 168 | """ 169 | return cls(hours=source.hour, minutes=source.minute, 170 | seconds=source.second, milliseconds=source.microsecond // 1000) 171 | 172 | def to_time(self): 173 | """ 174 | Convert SubRipTime instance into a pure datetime.time object 175 | """ 176 | return time(self.hours, self.minutes, self.seconds, 177 | self.milliseconds * 1000) 178 | 179 | def to_millis(self): 180 | """ 181 | Returns SubRipTime corresponding to a total count of milliseconds 182 | """ 183 | total_millis = sum([ 184 | self.milliseconds, 185 | self.seconds * 1000, 186 | self.minutes * 60 * 1000, 187 | self.hours * 60 * 60 * 1000 188 | ]) 189 | return total_millis 190 | 191 | def from_millis(self, total_millis): 192 | """ 193 | Sets the hours, minutes, seconds, & milliseconds from total 194 | milliseconds 195 | """ 196 | millis_per_hour = 60 * 60 * 1000 197 | millis_per_min = 60 * 1000 198 | millis_per_sec = 1000 199 | 200 | hours = int(total_millis / millis_per_hour) 201 | remainder = total_millis % millis_per_hour 202 | 203 | minutes = int(remainder / millis_per_min) 204 | remainder = remainder % millis_per_min 205 | 206 | seconds = int(remainder / millis_per_sec) 207 | milliseconds = remainder % millis_per_sec 208 | 209 | self.hours = hours 210 | self.minutes = minutes 211 | self.seconds = seconds 212 | 213 | self.milliseconds = milliseconds -------------------------------------------------------------------------------- /operators/shortcuts.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | from .tools.get_base_strip import get_base_strip 3 | from .tools.get_text_strips import get_text_strips 4 | 5 | class SEQUENCER_OT_shift_frame_start(bpy.types.Operator): 6 | bl_idname = "sequencerextra.shift_frame_start" 7 | bl_label = "Shift Frame Start of Next Text Strip" 8 | bl_description = "Shifts frame start of text strip to the current frame" 9 | 10 | def execute(self, context): 11 | scene = context.scene 12 | 13 | try: 14 | text_strips = get_text_strips(scene) 15 | 16 | if len(text_strips) == 0: 17 | return {"FINISHED"} 18 | except AttributeError: 19 | return {"FINISHED"} 20 | 21 | current_frame = scene.frame_current 22 | base_channel = text_strips[0].channel - 1 23 | base_strips = get_text_strips(scene, channel=base_channel) 24 | 25 | for i in range(len(text_strips)): 26 | strip = text_strips[i] 27 | start = strip.frame_final_start 28 | end = strip.frame_final_end 29 | 30 | if current_frame >= start and current_frame < end: 31 | strip.frame_final_start = current_frame 32 | return {"FINISHED"} 33 | 34 | elif current_frame < start: 35 | if not strip.name.startswith('[locked start]'): 36 | strip.frame_final_start = current_frame 37 | return {"FINISHED"} 38 | 39 | elif len(base_strips) == 0: 40 | strip.frame_final_start = current_frame 41 | return {"FINISHED"} 42 | 43 | else: 44 | base = get_base_strip(strip, base_strips) 45 | try: 46 | if base.frame_final_start <= current_frame: 47 | strip.frame_final_start = current_frame 48 | return {"FINISHED"} 49 | else: 50 | strip.frame_final_start = base.frame_final_start 51 | return {"FINISHED"} 52 | except AttributeError: 53 | strip.frame_final_start = current_frame 54 | return {"FINISHED"} 55 | 56 | return {"FINISHED"} 57 | 58 | 59 | class SEQUENCER_OT_shift_frame_end(bpy.types.Operator): 60 | bl_idname = "sequencerextra.shift_frame_end" 61 | bl_label = "Shift Frame End of Next Text Strip" 62 | bl_description = "Shifts the frame end of text strip to the current frame" 63 | 64 | def execute(self, context): 65 | scene = context.scene 66 | 67 | try: 68 | text_strips = list(reversed(get_text_strips(scene))) 69 | 70 | if len(text_strips) == 0: 71 | return {"FINISHED"} 72 | except AttributeError: 73 | return {"FINISHED"} 74 | 75 | current_frame = scene.frame_current 76 | base_channel = text_strips[0].channel - 1 77 | base_strips = get_text_strips(scene, channel=base_channel) 78 | 79 | for i in range(len(text_strips)): 80 | strip = text_strips[i] 81 | start = strip.frame_final_start 82 | end = strip.frame_final_end 83 | 84 | if current_frame > start and current_frame <= end: 85 | strip.frame_final_end = current_frame 86 | return {"FINISHED"} 87 | 88 | elif current_frame > end: 89 | if not strip.name.endswith('[locked end]'): 90 | strip.frame_final_end = current_frame 91 | return {"FINISHED"} 92 | 93 | elif len(base_strips) == 0: 94 | strip.frame_final_end = current_frame 95 | return {"FINISHED"} 96 | 97 | else: 98 | base = get_base_strip(strip, base_strips) 99 | try: 100 | if base.frame_final_end >= current_frame: 101 | strip.frame_final_end = current_frame 102 | return {"FINISHED"} 103 | else: 104 | strip.frame_final_end = base.frame_final_end 105 | return {"FINISHED"} 106 | except AttributeError: 107 | strip.frame_final_end = current_frame 108 | return {"FINISHED"} 109 | 110 | return {"FINISHED"} 111 | 112 | 113 | class SEQUENCER_OT_shift_frame_start_end(bpy.types.Operator): 114 | bl_idname = "sequencerextra.shift_frame_start_end" 115 | bl_label = "Shift Frame End then Frame start of next" 116 | bl_description = "Like pressing D then F" 117 | 118 | def execute(self, context): 119 | bpy.ops.sequencerextra.shift_frame_start() 120 | bpy.ops.sequencerextra.shift_frame_end() 121 | 122 | return {"FINISHED"} 123 | 124 | 125 | class SEQUENCER_OT_shift_frame_end_start(bpy.types.Operator): 126 | bl_idname = "sequencerextra.shift_frame_end_start" 127 | bl_label = "Shift Frame End then Frame start of next" 128 | bl_description = "Like pressing F then D" 129 | 130 | def execute(self, context): 131 | bpy.ops.sequencerextra.shift_frame_end() 132 | bpy.ops.sequencerextra.shift_frame_start() 133 | 134 | return {"FINISHED"} 135 | 136 | class SEQUENCER_OT_reset_children(bpy.types.Operator): 137 | bl_idname = "sequencerextra.reset_children" 138 | bl_label = "Send the children to the final frames of the parent" 139 | bl_description = "Press Z while the current time indicator is on a parent and the upper strips will be sent to the end of the parent" 140 | 141 | def execute(self, context): 142 | scene = context.scene 143 | 144 | try: 145 | text_strips = get_text_strips(scene) 146 | low_channel = scene.subtitle_edit_channel - 1 147 | parents = get_text_strips(scene, low_channel) 148 | current_frame = scene.frame_current 149 | 150 | if len(text_strips) > 0 and len(parents) > 0: 151 | go = False 152 | for base in parents: 153 | b_start = base.frame_final_start 154 | b_end = base.frame_final_end 155 | if current_frame >= b_start and current_frame <= b_end: 156 | go = True 157 | break 158 | if not go: 159 | return {"FINISHED"} 160 | 161 | else: 162 | return {"FINISHED"} 163 | except AttributeError: 164 | return {"FINISHED"} 165 | 166 | for parent in parents: 167 | p_start = parent.frame_final_start 168 | p_end = parent.frame_final_end 169 | 170 | if current_frame >= p_start and current_frame <= p_end: 171 | current_parent = parent 172 | break 173 | 174 | p_start = current_parent.frame_final_start 175 | p_end = current_parent.frame_final_end 176 | 177 | maybe_children = get_text_strips(scene) 178 | 179 | children = [] 180 | for maybe_child in maybe_children: 181 | m_start = maybe_child.frame_final_start 182 | m_end = maybe_child.frame_final_end 183 | 184 | if p_start <= m_start and p_end >= m_end: 185 | children.append(maybe_child) 186 | 187 | children = list(reversed(children)) 188 | for i in range(len(children)): 189 | if children[i].frame_final_end > current_frame: 190 | children[i].frame_final_end = p_end - i 191 | children[i].frame_final_start = p_end - (i + 1) 192 | 193 | return {"FINISHED"} 194 | -------------------------------------------------------------------------------- /operators/combine_words.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | from .tools.get_text_strips import get_text_strips 3 | from .tools.get_base_strip import get_base_strip 4 | from .tools.subtitles_to_sequencer import subtitles_to_sequencer 5 | from .tools.color_to_hexcode import color_to_hexcode 6 | 7 | from .pylrc.tools.timecode import seconds_to_timecode 8 | 9 | from .pysrt.srtitem import SubRipItem 10 | from .pysrt.srtfile import SubRipFile 11 | 12 | def check_bases(top_strips, bases): 13 | """ 14 | Check to make sure that each top strip is within the start and end 15 | points of a base strip. 16 | """ 17 | 18 | for strip in top_strips: 19 | base = get_base_strip(strip, bases) 20 | if base == None: 21 | return strip 22 | return True 23 | 24 | def combine_esrt(fps, text_strips, bottom_text_strips, hexcolor): 25 | """ 26 | Combine the text_strips with the bottom_strips into a SubRipFile 27 | """ 28 | sub_list = [] 29 | old_base_strip = '' 30 | for i in range(len(text_strips)): 31 | strip = text_strips[i] 32 | base_strip = get_base_strip(strip, bottom_text_strips) 33 | 34 | if not base_strip == old_base_strip: 35 | if len(sub_list) > 0: 36 | old_end = old_base_strip.frame_final_end 37 | old_strip = text_strips[i - 1] 38 | if not old_strip.frame_final_end == old_end: 39 | start = old_strip.frame_final_end / fps 40 | end = old_end / fps 41 | text = '' 42 | text += old_base_strip.text.rstrip() 43 | text += '' 44 | 45 | sub_item = SubRipItem() 46 | sub_item.start.from_millis((start * 1000)) 47 | sub_item.end.from_millis((end * 1000)) 48 | sub_item.text = text 49 | sub_list.append(sub_item) 50 | 51 | start = base_strip.frame_final_start 52 | if not strip.frame_final_start == start: 53 | start = start / fps 54 | end = strip.frame_final_start / fps 55 | text = base_strip.text.rstrip() 56 | 57 | sub_item = SubRipItem() 58 | sub_item.start.from_millis((start * 1000)) 59 | sub_item.end.from_millis((end * 1000)) 60 | sub_item.text = text 61 | sub_list.append(sub_item) 62 | 63 | old_base_strip = base_strip 64 | 65 | start = strip.frame_final_start / fps 66 | end = strip.frame_final_end / fps 67 | text = '' 68 | text += strip.text.rstrip() 69 | text += '' 70 | text += base_strip.text[len(strip.text.rstrip())::] 71 | 72 | sub_item = SubRipItem() 73 | sub_item.start.from_millis((start * 1000)) 74 | sub_item.end.from_millis((end * 1000)) 75 | sub_item.text = text 76 | sub_list.append(sub_item) 77 | 78 | old_end = old_base_strip.frame_final_end 79 | old_strip = text_strips[-1] 80 | if not old_strip.frame_final_end == old_end: 81 | start = old_strip.frame_final_end / fps 82 | end = old_end / fps 83 | text = '' 84 | text += old_base_strip.text.rstrip() 85 | text += '' 86 | 87 | sub_item = SubRipItem() 88 | sub_item.start.from_millis((start * 1000)) 89 | sub_item.end.from_millis((end * 1000)) 90 | sub_item.text = text 91 | sub_list.append(sub_item) 92 | 93 | subs = SubRipFile(sub_list) 94 | 95 | return subs 96 | 97 | def combine_elrc(fps, text_strips, bottom_text_strips): 98 | """ 99 | Combine the text_strips with the bottom_strips into a SubRipFile 100 | """ 101 | sub_list = [] 102 | old_base_strip = '' 103 | for i in range(len(text_strips)): 104 | strip = text_strips[i] 105 | base_strip = get_base_strip(strip, bottom_text_strips) 106 | 107 | if not base_strip == old_base_strip: 108 | old_base_strip = base_strip 109 | start = base_strip.frame_final_start / fps 110 | end = base_strip.frame_final_end / fps 111 | 112 | sub_item = SubRipItem() 113 | sub_item.start.from_millis((start * 1000)) 114 | sub_item.end.from_millis((end * 1000)) 115 | 116 | start = strip.frame_final_start / fps 117 | time_code = seconds_to_timecode(start, '<>') 118 | text = time_code + strip.text.rstrip() 119 | sub_item.text = text 120 | 121 | if len(sub_list) > 0: 122 | end = text_strips[i - 1].frame_final_end / fps 123 | time_code = seconds_to_timecode(end, '<>') 124 | sub_list[-1].text += time_code 125 | 126 | sub_list.append(sub_item) 127 | 128 | else: 129 | start = strip.frame_final_start / fps 130 | time_code = seconds_to_timecode(start, '<>') 131 | text = strip.text[len(text_strips[i - 1].text.rstrip())::] 132 | text = time_code + text.rstrip() 133 | sub_list[-1].text += text 134 | 135 | if i == len(text_strips) - 1: 136 | end = text_strips[i].frame_final_end / fps 137 | time_code = seconds_to_timecode(end, '<>') 138 | sub_list[-1].text += time_code 139 | 140 | subs = SubRipFile(sub_list) 141 | 142 | return subs 143 | 144 | 145 | class SEQUENCER_OT_combine_words(bpy.types.Operator): 146 | bl_label = 'Combine' 147 | bl_idname = 'sequencerextra.combine_words' 148 | bl_description = 'Combine subtitles from edit channel with the subtitles in the channel below.' 149 | 150 | @classmethod 151 | def poll(self, context): 152 | scene = context.scene 153 | try: 154 | text_strips = get_text_strips(scene) 155 | low_channel = scene.subtitle_edit_channel - 1 156 | bottom_text_strips = get_text_strips(scene, low_channel) 157 | 158 | if len(text_strips) > 0 and len(bottom_text_strips) > 0: 159 | return True 160 | else: 161 | return False 162 | except AttributeError: 163 | return False 164 | 165 | def execute(self, context): 166 | scene = context.scene 167 | fps = scene.render.fps / scene.render.fps_base 168 | text_strips = get_text_strips(scene) 169 | low_channel = scene.subtitle_edit_channel - 1 170 | bottom_text_strips = get_text_strips(scene, low_channel) 171 | 172 | c = scene.enhanced_subs_color 173 | color = [c[0], c[1], c[2]] 174 | hexcolor = color_to_hexcode(color) 175 | 176 | base_check = check_bases(text_strips, bottom_text_strips) 177 | if not base_check == True: 178 | frame = str(base_check.frame_final_start) 179 | channel = str(base_check.channel) 180 | message = ' '.join([ 181 | 'The strip at frame', frame, ', channel ', channel, 182 | 'has no base strip.', '\n', 'Correct this and try again.']) 183 | self.report(set({'ERROR'}), message) 184 | return {"FINISHED"} 185 | 186 | if scene.subtitle_combine_mode == 'esrt': 187 | subs = combine_esrt(fps, text_strips, bottom_text_strips, hexcolor) 188 | 189 | 190 | elif scene.subtitle_combine_mode == 'elrc': 191 | subs = combine_elrc(fps, text_strips, bottom_text_strips) 192 | 193 | bpy.ops.sequencer.select_all(action="DESELECT") 194 | for strip in text_strips: 195 | strip.select = True 196 | bpy.ops.sequencer.delete() 197 | 198 | for strip in bottom_text_strips: 199 | strip.select = True 200 | bpy.ops.sequencer.delete() 201 | 202 | text_strips = subtitles_to_sequencer(context, subs) 203 | scene.subtitle_edit_channel = text_strips[0].channel 204 | 205 | return {"FINISHED"} 206 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | .. image:: https://img.shields.io/badge/Donate-PayPal-green.svg 2 | :target: https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=QA2T7WG47UTCL 3 | 4 | .. image:: http://i.imgur.com/KxRENJr.png 5 | 6 | .. contents:: 7 | 8 | About 9 | ===== 10 | 11 | Subs import is an addon for Blender_ that allows users to create and 12 | edit subtitles for movies or music. The keyboard shortcuts and automatic 13 | syllable separation tools make it a very fast tool. 14 | 15 | .. image:: http://i.imgur.com/CLDXWRd.gif 16 | 17 | .. _Blender: https://www.blender.org/ 18 | 19 | Installation 20 | ============ 21 | 22 | 1. Download the repository. 23 | 2. Open Blender. 24 | 3. Go to File > User Preferences > Addons 25 | 4. Click "Install From File" and navigate to the downloaded zip file and 26 | install. 27 | 5. Check the box next to "Subsimport" 28 | 29 | Use the correct release for your Blender version. Add-ons for Blender 2.80 and above will not work for Blender 2.79 30 | 31 | Usage 32 | ===== 33 | 34 | A `video tutorial`_ is available. 35 | 36 | The user interface is located to the right side of the video sequencer. 37 | 38 | .. image:: http://i.imgur.com/nNchW3Z.png 39 | 40 | .. _video tutorial: https://www.youtube.com/watch?v=R-jis3S6dxU 41 | 42 | Subtitle Edit Channel 43 | --------------------- 44 | 45 | .. image:: http://i.imgur.com/fgXDH1C.png 46 | 47 | The sequencer channel where The addon will have effect. Keyboard 48 | shortcuts, duration changing, exporting, syllabifying, splitting, and 49 | combining subtitles all depends on this value. 50 | 51 | Importing, splitting, and combining subtitles will automatically adjust 52 | the subtitle edit channel. 53 | 54 | Subtitle Font Size 55 | ------------------ 56 | 57 | .. image:: http://i.imgur.com/PNFAW5x.png 58 | 59 | The font size that will be applied to imported strips. You may change 60 | this value and refresh using the button to the right. (Changes only 61 | applied to the Subtitle Edit Channel) 62 | 63 | .. image:: http://i.imgur.com/sC6xx0n.gif 64 | 65 | Importing 66 | --------- 67 | 68 | .. image:: http://i.imgur.com/x93jx4s.png 69 | 70 | .. image:: http://i.imgur.com/8MM08X5.gif 71 | 72 | 3 filetypes may be imported with this addon: .txt, .lrc, and .srt files. 73 | 74 | .txt files do not contain any timing info. The text is imported so that 75 | each line of the text file becomes a strip in the sequencer. It is 76 | recommended that each line of text be no longer than **62** characters 77 | long. 78 | 79 | .lrc files are used with programs like MiniLyrics_ for displaying 80 | subtitles with music. 81 | 82 | .. _MiniLyrics: http://www.crintsoft.com/ 83 | 84 | .srt files are the standard subtitle filetype for movies. They work well 85 | with the VLC_ media player. 86 | 87 | .. _VLC: https://www.videolan.org/vlc/index.html 88 | 89 | Subsimport also supports "Enhanced" .srt and .lrc files. These are 90 | special subtitles that highlight parts of the subtitles at a time. 91 | 92 | On import, AV sync, scrubbing, and frame drop will be enabled. 93 | 94 | It is recommended that if you're making lyrics for songs that you 95 | increase the scene FPS to 100 or even 1000 frames per second before 96 | importing. The reason is that .srt files support time data down to the 97 | millisecond, but strips must conform to the scene's FPS value. If a low 98 | FPS is used, then the minimum timing difference will be limited by the 99 | scene FPS. 100 | 101 | Furthermore, if you attempt to import strips and one or more strips has 102 | a duration that is less than 1 / the scene fps, you will create an 103 | error. 104 | 105 | Subimport does not allow any subtitles to overlap times and will 106 | automatically remove overlaps on import. 107 | 108 | .. _Bligify's: https://github.com/doakey3/Bligify 109 | 110 | Dur x 2 and Dur / 2 111 | ------------------- 112 | 113 | .. image:: http://i.imgur.com/BmEDQiH.png 114 | 115 | .. image:: http://i.imgur.com/ZywhLfB.gif 116 | 117 | Doubles or halfs the duration of the strips in the 118 | "Subtitle Edit Channel". 119 | 120 | These buttons allow you to edit subtitles with a song playing at 50% 121 | speed, then convert the subtitles to normal speed. 122 | 123 | When making subtitles for music, I like to use Audacity_ to slow the 124 | music down by 50% and export it as a .wav file. I then use this in 125 | Blender for matching the lyrics to the song. 126 | 127 | .. _Audacity: http://www.audacityteam.org/ 128 | 129 | Exporting 130 | --------- 131 | 132 | .. image:: http://i.imgur.com/MzNk9S6.png 133 | 134 | Export the subtitles from the "Subtitle Edit Channel" as either .lrc 135 | or .srt file. 136 | 137 | Syllabify 138 | --------- 139 | 140 | .. image:: http://i.imgur.com/sjKYWt8.png 141 | 142 | After subtitles have been imported, you can separate words by syllables. 143 | Before splitting the syllables, you should create a syllabification 144 | dictionary for your subtitles that defines how each word should be 145 | broken up. 146 | 147 | Subsimport has a dictionary of words and an algorithm for splitting 148 | words. Both are enabled by default. The algorithm's accuracy depends 149 | on which language is set. 150 | 151 | After clicking the "Syllabify" button, you'll create a .txt file 152 | containing all of the words of the song. Subsimport will try to split 153 | them up into separate syllables. You should read through the .txt file 154 | and make any corrections as necessary before you split your words. 155 | 156 | After syllabifying words, you may save your dictionary to the default 157 | dictionary that Subsimport uses. This way, any words you may have needed 158 | to edit will be correctly syllabified the next time Subsimport 159 | encounters them. 160 | 161 | Split 162 | ----- 163 | 164 | .. image:: http://i.imgur.com/XKJfMb3.png 165 | 166 | .. image:: http://i.imgur.com/9gAon9U.gif 167 | 168 | After defining how words should be separated, you can split them apart 169 | and create individually colored text strips that will highlight 170 | sequentially as your audio plays. You can set the timing of each 171 | syllable in the song. 172 | 173 | Text strip color can be changed with the highlight property and the 174 | refresh button to the right. 175 | 176 | Combine 177 | ------- 178 | 179 | .. image:: http://i.imgur.com/4LJ3fQe.png 180 | 181 | .. image:: http://i.imgur.com/5lUFAt8.gif 182 | 183 | After synchronizing the syllables to the music, you can recombine 184 | the strips into enhanced strips prior to exporting the subtitles. 185 | 186 | The method used for combining the strips (ESRT or ELRC) depends on 187 | what kind of subtitles you would like to export. 188 | 189 | Keyboard Shortcuts 190 | ------------------ 191 | 192 | Make sure the "Subtitle Edit Channel" property is set to the channel 193 | where your subtitle strips have been imported. 194 | 195 | Note that splitted strips are set to not respond to these 4 shortcuts 196 | if it means going outside the bounds of their base strips. 197 | 198 | :D: 199 | Set the start of a text strip. 200 | 201 | :F: 202 | Set the end of a text strip. 203 | 204 | :S: 205 | (like pressing F, then D rapidly) 206 | 207 | :W: 208 | (like pressing D, then F rapidly) 209 | 210 | .. image:: http://i.imgur.com/D38fvvU.gif 211 | 212 | :Z: 213 | Send top strips to the end of the base strip. Useful for resetting 214 | the position of syllabified lyrics. 215 | 216 | You must be within the start and end points of a base strip and the 217 | "Subtitle Edit Channel" must be set to the top strips channel for 218 | this to work. 219 | 220 | .. image:: http://i.imgur.com/XoxELtD.gif 221 | 222 | :Ctrl + Shift + Right: 223 | Select all strips in the Subtitle Edit Channel to the right of the 224 | current time indicator. 225 | 226 | :Ctrl + Shift + Left: 227 | Select all strips in the Subtitle Edit Channel to the left of the 228 | current time indicator 229 | 230 | Contributing 231 | ============ 232 | 233 | Pull requests, feature requests, donations, and example song .srt files 234 | are welcome! Also, adding syllabified words to the default dictionary is 235 | encouraged. 236 | -------------------------------------------------------------------------------- /tools/word_collector.py: -------------------------------------------------------------------------------- 1 | """ 2 | I used this module to collect words from the cmudict-0.7 3 | """ 4 | 5 | #!/usr/bin/env python 6 | # Copyright (c) 2012-2013 Kyle Gorman 7 | # 8 | # Permission is hereby granted, free of charge, to any person obtaining a 9 | # copy of this software and associated documentation files (the 10 | # "Software"), to deal in the Software without restriction, including 11 | # without limitation the rights to use, copy, modify, merge, publish, 12 | # distribute, sublicense, and/or sell copies of the Software, and to 13 | # permit persons to whom the Software is furnished to do so, subject to 14 | # the following conditions: 15 | # 16 | # The above copyright notice and this permission notice shall be included 17 | # in all copies or substantial portions of the Software. 18 | # 19 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 20 | # OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 21 | # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 22 | # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 23 | # CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 24 | # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 25 | # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 26 | # 27 | # syllabify.py: prosodic parsing of ARPABET entries 28 | 29 | from itertools import chain 30 | 31 | ## constants 32 | SLAX = {'IH1', 'IH2', 'EH1', 'EH2', 'AE1', 'AE2', 'AH1', 'AH2', 33 | 'UH1', 'UH2',} 34 | 35 | VOWELS = {'IY1', 'IY2', 'IY0', 'EY1', 'EY2', 'EY0', 'AA1', 'AA2', 'AA0', 36 | 'ER1', 'ER2', 'ER0', 'AW1', 'AW2', 'AW0', 'AO1', 'AO2', 'AO0', 37 | 'AY1', 'AY2', 'AY0', 'OW1', 'OW2', 'OW0', 'OY1', 'OY2', 'OY0', 38 | 'IH0', 'EH0', 'AE0', 'AH0', 'UH0', 'UW1', 'UW2', 'UW0', 'UW', 39 | 'IY', 'EY', 'AA', 'ER', 'AW', 'AO', 'AY', 'OW', 'OY', 40 | 'UH', 'IH', 'EH', 'AE', 'AH', 'UH',} | SLAX 41 | 42 | ## licit medial onsets 43 | 44 | O2 = {('P', 'R'), ('T', 'R'), ('K', 'R'), ('B', 'R'), ('D', 'R'), 45 | ('G', 'R'), ('F', 'R'), ('TH', 'R'), 46 | ('P', 'L'), ('K', 'L'), ('B', 'L'), ('G', 'L'), 47 | ('F', 'L'), ('S', 'L'), 48 | ('K', 'W'), ('G', 'W'), ('S', 'W'), 49 | ('S', 'P'), ('S', 'T'), ('S', 'K'), 50 | ('HH', 'Y'), # "clerihew" 51 | ('R', 'W'),} 52 | O3 = {('S', 'T', 'R'), ('S', 'K', 'L'), ('T', 'R', 'W')} # "octroi" 53 | 54 | # This does not represent anything like a complete list of onsets, but 55 | # merely those that need to be maximized in medial position. 56 | 57 | def syllabify(pron, alaska_rule=True): 58 | """ 59 | Syllabifies a CMU dictionary (ARPABET) word string 60 | 61 | # Alaska rule: 62 | >>> pprint(syllabify('AH0 L AE1 S K AH0'.split())) # Alaska 63 | '-AH0-.L-AE1-S.K-AH0-' 64 | >>> pprint(syllabify('AH0 L AE1 S K AH0'.split(), 0)) # Alaska 65 | '-AH0-.L-AE1-.S K-AH0-' 66 | 67 | # huge medial onsets: 68 | >>> pprint(syllabify('M IH1 N S T R AH0 L'.split())) # minstrel 69 | 'M-IH1-N.S T R-AH0-L' 70 | >>> pprint(syllabify('AA1 K T R W AA0 R'.split())) # octroi 71 | '-AA1-K.T R W-AA0-R' 72 | 73 | # destressing 74 | >>> pprint(destress(syllabify('M IH1 L AH0 T EH2 R IY0'.split()))) 75 | 'M-IH-.L-AH-.T-EH-.R-IY-' 76 | 77 | # normal treatment of 'j': 78 | >>> pprint(syllabify('M EH1 N Y UW0'.split())) # menu 79 | 'M-EH1-N.Y-UW0-' 80 | >>> pprint(syllabify('S P AE1 N Y AH0 L'.split())) # spaniel 81 | 'S P-AE1-N.Y-AH0-L' 82 | >>> pprint(syllabify('K AE1 N Y AH0 N'.split())) # canyon 83 | 'K-AE1-N.Y-AH0-N' 84 | >>> pprint(syllabify('M IH0 N Y UW2 EH1 T'.split())) # minuet 85 | 'M-IH0-N.Y-UW2-.-EH1-T' 86 | >>> pprint(syllabify('JH UW1 N Y ER0'.split())) # junior 87 | 'JH-UW1-N.Y-ER0-' 88 | >>> pprint(syllabify('K L EH R IH HH Y UW'.split())) # clerihew 89 | 'K L-EH-.R-IH-.HH Y-UW-' 90 | 91 | # nuclear treatment of 'j' 92 | >>> pprint(syllabify('R EH1 S K Y UW0'.split())) # rescue 93 | 'R-EH1-S.K-Y UW0-' 94 | >>> pprint(syllabify('T R IH1 B Y UW0 T'.split())) # tribute 95 | 'T R-IH1-B.Y-UW0-T' 96 | >>> pprint(syllabify('N EH1 B Y AH0 L AH0'.split())) # nebula 97 | 'N-EH1-B.Y-AH0-.L-AH0-' 98 | >>> pprint(syllabify('S P AE1 CH UH0 L AH0'.split())) # spatula 99 | 'S P-AE1-.CH-UH0-.L-AH0-' 100 | >>> pprint(syllabify('AH0 K Y UW1 M AH0 N'.split())) # acumen 101 | '-AH0-K.Y-UW1-.M-AH0-N' 102 | >>> pprint(syllabify('S AH1 K Y AH0 L IH0 N T'.split())) # succulent 103 | 'S-AH1-K.Y-AH0-.L-IH0-N T' 104 | >>> pprint(syllabify('F AO1 R M Y AH0 L AH0'.split())) # formula 105 | 'F-AO1 R-M.Y-AH0-.L-AH0-' 106 | >>> pprint(syllabify('V AE1 L Y UW0'.split())) # value 107 | 'V-AE1-L.Y-UW0-' 108 | 109 | # everything else 110 | >>> pprint(syllabify('N AO0 S T AE1 L JH IH0 K'.split())) # nostalgic 111 | 'N-AO0-.S T-AE1-L.JH-IH0-K' 112 | >>> pprint(syllabify('CH ER1 CH M AH0 N'.split())) # churchmen 113 | 'CH-ER1-CH.M-AH0-N' 114 | >>> pprint(syllabify('K AA1 M P AH0 N S EY2 T'.split())) # compensate 115 | 'K-AA1-M.P-AH0-N.S-EY2-T' 116 | >>> pprint(syllabify('IH0 N S EH1 N S'.split())) # inCENSE 117 | '-IH0-N.S-EH1-N S' 118 | >>> pprint(syllabify('IH1 N S EH2 N S'.split())) # INcense 119 | '-IH1-N.S-EH2-N S' 120 | >>> pprint(syllabify('AH0 S EH1 N D'.split())) # ascend 121 | '-AH0-.S-EH1-N D' 122 | >>> pprint(syllabify('R OW1 T EY2 T'.split())) # rotate 123 | 'R-OW1-.T-EY2-T' 124 | >>> pprint(syllabify('AA1 R T AH0 S T'.split())) # artist 125 | '-AA1 R-.T-AH0-S T' 126 | >>> pprint(syllabify('AE1 K T ER0'.split())) # actor 127 | '-AE1-K.T-ER0-' 128 | >>> pprint(syllabify('P L AE1 S T ER0'.split())) # plaster 129 | 'P L-AE1-S.T-ER0-' 130 | >>> pprint(syllabify('B AH1 T ER0'.split())) # butter 131 | 'B-AH1-.T-ER0-' 132 | >>> pprint(syllabify('K AE1 M AH0 L'.split())) # camel 133 | 'K-AE1-.M-AH0-L' 134 | >>> pprint(syllabify('AH1 P ER0'.split())) # upper 135 | '-AH1-.P-ER0-' 136 | >>> pprint(syllabify('B AH0 L UW1 N'.split())) # balloon 137 | 'B-AH0-.L-UW1-N' 138 | >>> pprint(syllabify('P R OW0 K L EY1 M'.split())) # proclaim 139 | 'P R-OW0-.K L-EY1-M' 140 | >>> pprint(syllabify('IH0 N S EY1 N'.split())) # insane 141 | '-IH0-N.S-EY1-N' 142 | >>> pprint(syllabify('IH0 K S K L UW1 D'.split())) # exclude 143 | '-IH0-K.S K L-UW1-D' 144 | """ 145 | ## main pass 146 | mypron = list(pron) 147 | nuclei = [] 148 | onsets = [] 149 | i = -1 150 | for (j, seg) in enumerate(mypron): 151 | if seg in VOWELS: 152 | nuclei.append([seg]) 153 | onsets.append(mypron[i + 1:j]) # actually interludes, r.n. 154 | i = j 155 | codas = [mypron[i + 1:]] 156 | ## resolve disputes and compute coda 157 | for i in range(1, len(onsets)): 158 | coda = [] 159 | # boundary cases 160 | if len(onsets[i]) > 1 and onsets[i][0] == 'R': 161 | nuclei[i - 1].append(onsets[i].pop(0)) 162 | if len(onsets[i]) > 2 and onsets[i][-1] == 'Y': 163 | nuclei[i].insert(0, onsets[i].pop()) 164 | if len(onsets[i]) > 1 and alaska_rule and nuclei[i-1][-1] in SLAX \ 165 | and onsets[i][0] == 'S': 166 | coda.append(onsets[i].pop(0)) 167 | # onset maximization 168 | depth = 1 169 | if len(onsets[i]) > 1: 170 | if tuple(onsets[i][-2:]) in O2: 171 | depth = 3 if tuple(onsets[i][-3:]) in O3 else 2 172 | for j in range(len(onsets[i]) - depth): 173 | coda.append(onsets[i].pop(0)) 174 | # store coda 175 | codas.insert(i - 1, coda) 176 | 177 | syllab = [] 178 | for i in range(len(onsets)): 179 | group = [onsets[i], nuclei[i], codas[i]] 180 | syllab.append(group) 181 | #return output 182 | 183 | output = ('.'.join(' '.join(' '.join(p) for p in syl) for syl in syllab).strip()) 184 | output = output.split('.') 185 | return output 186 | 187 | 188 | if __name__ == '__main__': 189 | dictionary = open('dictionary2.txt', 'r') 190 | lines = dictionary.readlines() 191 | #lines = ['AABERG AA1 B ER0 G'] 192 | dictionary.close() 193 | 194 | new_lines = [] 195 | 196 | for i in range(len(lines)): 197 | lines[i] = lines[i].rstrip() 198 | word = lines[i].split(' ')[0].lower() 199 | pronunciation = lines[i].split(' ')[-1] 200 | parts = syllabify(pronunciation.split(' ')) 201 | count = len(parts) 202 | new_lines.append(word + ' ' + str(count) + '\n') 203 | 204 | dictionary = open('word_counts.txt', 'w') 205 | dictionary.write(''.join(new_lines)) 206 | dictionary.close() -------------------------------------------------------------------------------- /operators/hyphenator/patterns/ca.txt: -------------------------------------------------------------------------------- 1 | .antihi2 2 | .be2n 3 | .be2s 4 | .bi2s 5 | .ca2p 6 | .ce2l 7 | .ch2 8 | .cla2r 9 | .co2ll 10 | .co2n 11 | .co2r 12 | .de2s 13 | .di2s 14 | .en3a 15 | .hi2a 16 | .hi2e 17 | .hi2o 18 | .hi2u 19 | .hi2à 20 | .hi2è 21 | .hi2é 22 | .hi2ò 23 | .hi2ó 24 | .hi2ú 25 | .hipe2r 26 | .hiperm2n 27 | .hu2a 28 | .hu2e 29 | .hu2i 30 | .hu2o 31 | .hu2à 32 | .hu2è 33 | .hu2é 34 | .hu2í 35 | .hu2ò 36 | .hu2ó 37 | .i2è 38 | .i2ò 39 | .in3ac 40 | .in3ad 41 | .in3ap 42 | .in3es 43 | .in3o 44 | .inte2r 45 | .ma2l 46 | .mal1t2hus 47 | .pa2n 48 | .pe2r 49 | .pe3ri 50 | .pos2t 51 | .psa2l 52 | .re2d 53 | .rebe2s 54 | .su2b 55 | .sub3o 56 | .subde2s 57 | .supe2r 58 | .th2 59 | .tran2s 60 | .u2è 61 | .u2ò 62 | 1b2la 63 | 1b2le 64 | 1b2li 65 | 1b2lo 66 | 1b2lu 67 | 1b2là 68 | 1b2lè 69 | 1b2lé 70 | 1b2lí 71 | 1b2lò 72 | 1b2ló 73 | 1b2lú 74 | 1b2ra 75 | 1b2re 76 | 1b2ri 77 | 1b2ro 78 | 1b2ru 79 | 1b2rà 80 | 1b2rè 81 | 1b2ré 82 | 1b2rí 83 | 1b2rò 84 | 1b2ró 85 | 1b2rú 86 | 1ba 87 | 1be 88 | 1bi 89 | 1bo 90 | 1bu 91 | 1bà 92 | 1bè 93 | 1bé 94 | 1bí 95 | 1bò 96 | 1bó 97 | 1bú 98 | 1c2la 99 | 1c2le 100 | 1c2li 101 | 1c2lo 102 | 1c2lu 103 | 1c2là 104 | 1c2lè 105 | 1c2lé 106 | 1c2lí 107 | 1c2lò 108 | 1c2ló 109 | 1c2lú 110 | 1c2ra 111 | 1c2re 112 | 1c2ri 113 | 1c2ro 114 | 1c2ru 115 | 1c2rà 116 | 1c2rè 117 | 1c2ré 118 | 1c2rí 119 | 1c2rò 120 | 1c2ró 121 | 1c2rú 122 | 1ca 123 | 1ce 124 | 1ci 125 | 1co 126 | 1cu 127 | 1cà 128 | 1cè 129 | 1cé 130 | 1cí 131 | 1cò 132 | 1có 133 | 1cú 134 | 1d2ra 135 | 1d2re 136 | 1d2ri 137 | 1d2ro 138 | 1d2ru 139 | 1d2rà 140 | 1d2rè 141 | 1d2ré 142 | 1d2rí 143 | 1d2rò 144 | 1d2ró 145 | 1d2rú 146 | 1da 147 | 1de 148 | 1di 149 | 1do 150 | 1dà 151 | 1dè 152 | 1dé 153 | 1dí 154 | 1dò 155 | 1dó 156 | 1dú 157 | 1f2la 158 | 1f2le 159 | 1f2li 160 | 1f2lo 161 | 1f2lu 162 | 1f2là 163 | 1f2lè 164 | 1f2lé 165 | 1f2lí 166 | 1f2lò 167 | 1f2ló 168 | 1f2lú 169 | 1f2ra 170 | 1f2re 171 | 1f2ri 172 | 1f2ro 173 | 1f2ru 174 | 1f2rà 175 | 1f2rè 176 | 1f2ré 177 | 1f2rí 178 | 1f2rò 179 | 1f2ró 180 | 1f2rú 181 | 1fa 182 | 1fe 183 | 1fi 184 | 1fo 185 | 1fu 186 | 1fà 187 | 1fè 188 | 1fé 189 | 1fí 190 | 1fò 191 | 1fó 192 | 1fú 193 | 1g2la 194 | 1g2le 195 | 1g2li 196 | 1g2lo 197 | 1g2lu 198 | 1g2là 199 | 1g2lè 200 | 1g2lé 201 | 1g2lí 202 | 1g2lò 203 | 1g2ló 204 | 1g2lú 205 | 1g2ra 206 | 1g2re 207 | 1g2ri 208 | 1g2ro 209 | 1g2ru 210 | 1g2rà 211 | 1g2rè 212 | 1g2ré 213 | 1g2rí 214 | 1g2rò 215 | 1g2ró 216 | 1g2rú 217 | 1ga 218 | 1ge 219 | 1gi 220 | 1go 221 | 1gu 222 | 1gà 223 | 1gè 224 | 1gé 225 | 1gí 226 | 1gò 227 | 1gó 228 | 1gú 229 | 1gü 230 | 1ha 231 | 1he 232 | 1hi 233 | 1ho 234 | 1hu 235 | 1hà 236 | 1hè 237 | 1hé 238 | 1hí 239 | 1hò 240 | 1hó 241 | 1hú 242 | 1ja 243 | 1je 244 | 1ji 245 | 1jo 246 | 1ju 247 | 1jà 248 | 1jè 249 | 1jé 250 | 1jí 251 | 1jò 252 | 1jó 253 | 1jú 254 | 1l2le 255 | 1l2li 256 | 1l2lu 257 | 1l2là 258 | 1l2lè 259 | 1l2lé 260 | 1l2lí 261 | 1l2lò 262 | 1l2ló 263 | 1l2lú 264 | 1la 265 | 1le 266 | 1li 267 | 1lo 268 | 1lu 269 | 1là 270 | 1lè 271 | 1lé 272 | 1lí 273 | 1lò 274 | 1ló 275 | 1lú 276 | 1ma 277 | 1me 278 | 1mi 279 | 1mo 280 | 1mu 281 | 1mà 282 | 1mè 283 | 1mé 284 | 1mí 285 | 1mò 286 | 1mó 287 | 1mú 288 | 1n2ya 289 | 1n2ye 290 | 1n2yi 291 | 1n2yo 292 | 1n2yu 293 | 1n2yà 294 | 1n2yè 295 | 1n2yé 296 | 1n2yí 297 | 1n2yò 298 | 1n2yó 299 | 1n2yú 300 | 1na 301 | 1ne 302 | 1no 303 | 1nu 304 | 1nà 305 | 1nè 306 | 1né 307 | 1ní 308 | 1nò 309 | 1nó 310 | 1nú 311 | 1p2la 312 | 1p2le 313 | 1p2li 314 | 1p2lo 315 | 1p2lu 316 | 1p2là 317 | 1p2lè 318 | 1p2lé 319 | 1p2lí 320 | 1p2lò 321 | 1p2ló 322 | 1p2lú 323 | 1p2ra 324 | 1p2re 325 | 1p2ri 326 | 1p2ro 327 | 1p2ru 328 | 1p2rà 329 | 1p2rè 330 | 1p2ré 331 | 1p2rí 332 | 1p2rò 333 | 1p2ró 334 | 1p2rú 335 | 1pa 336 | 1pu 337 | 1pà 338 | 1pè 339 | 1pé 340 | 1pí 341 | 1pò 342 | 1pó 343 | 1pú 344 | 1qu 345 | 1qü 346 | 1ra 347 | 1re 348 | 1ri 349 | 1ro 350 | 1ru 351 | 1rà 352 | 1rè 353 | 1ré 354 | 1rí 355 | 1rò 356 | 1ró 357 | 1rú 358 | 1sa 359 | 1se 360 | 1si 361 | 1so 362 | 1su 363 | 1sà 364 | 1sè 365 | 1sé 366 | 1sí 367 | 1sò 368 | 1só 369 | 1sú 370 | 1t2ra 371 | 1t2re 372 | 1t2ri 373 | 1t2ro 374 | 1t2ru 375 | 1t2rà 376 | 1t2rè 377 | 1t2ré 378 | 1t2rí 379 | 1t2rò 380 | 1t2ró 381 | 1t2rú 382 | 1ta 383 | 1te 384 | 1ti 385 | 1to 386 | 1tu 387 | 1tà 388 | 1tè 389 | 1té 390 | 1tí 391 | 1tò 392 | 1tó 393 | 1tú 394 | 1va 395 | 1ve 396 | 1vi 397 | 1vo 398 | 1vu 399 | 1và 400 | 1vè 401 | 1vé 402 | 1ví 403 | 1vò 404 | 1vó 405 | 1vú 406 | 1xa 407 | 1xe 408 | 1xi 409 | 1xo 410 | 1xu 411 | 1xà 412 | 1xè 413 | 1xé 414 | 1xí 415 | 1xò 416 | 1xó 417 | 1xú 418 | 1za 419 | 1ze 420 | 1zi 421 | 1zo 422 | 1zu 423 | 1zà 424 | 1zè 425 | 1zé 426 | 1zí 427 | 1zò 428 | 1zó 429 | 1zú 430 | 1ça 431 | 1ço 432 | 1çu 433 | 1çà 434 | 1çò 435 | 1çó 436 | 1çú 437 | 3du 438 | 3exp 439 | 3l2la 440 | 3l2lo 441 | 3nef 442 | 3nei 443 | 3ni 444 | 3pe 445 | 3pi 446 | 3po 447 | 3pr 448 | 3ser 449 | a1a 450 | a1e 451 | a1i2a 452 | a1i2e 453 | a1i2o 454 | a1i2u 455 | a1i2à 456 | a1i2è 457 | a1i2é 458 | a1i2í 459 | a1i2ò 460 | a1i2ó 461 | a1i2ú 462 | a1isme. 463 | a1ista. 464 | a1o 465 | a1u2a 466 | a1u2e 467 | a1u2i 468 | a1u2o 469 | a1u2u 470 | a1u2à 471 | a1u2è 472 | a1u2é 473 | a1u2í 474 | a1u2ò 475 | a1u2ó 476 | a1u2ú 477 | a1um. 478 | a1à 479 | a1è 480 | a1é 481 | a1í 482 | a1ï 483 | a1ò 484 | a1ó 485 | a1ú 486 | a1ü 487 | a3ne 488 | a3ri 489 | bi3se 490 | des3ag 491 | des3ar 492 | des3av 493 | des3enc 494 | e1a 495 | e1e 496 | e1i2a 497 | e1i2e 498 | e1i2o 499 | e1i2u 500 | e1i2à 501 | e1i2è 502 | e1i2é 503 | e1i2í 504 | e1i2ò 505 | e1i2ó 506 | e1i2ú 507 | e1isme. 508 | e1ista. 509 | e1o 510 | e1u2a 511 | e1u2e 512 | e1u2i 513 | e1u2o 514 | e1u2u 515 | e1u2à 516 | e1u2è 517 | e1u2é 518 | e1u2í 519 | e1u2ò 520 | e1u2ó 521 | e1u2ú 522 | e1um. 523 | e1à 524 | e1è 525 | e1é 526 | e1í 527 | e1ï 528 | e1ò 529 | e1ó 530 | e1ú 531 | e1ü 532 | e3ism 533 | e3le 534 | e3rio 535 | e3ris 536 | ein1s2tein 537 | es3aco 538 | es3af 539 | es3ap 540 | es3arr 541 | es3as 542 | es3int 543 | g2no 544 | g2nò 545 | gu2a 546 | gu2e 547 | gu2i 548 | gu2o 549 | gu2à 550 | gu2è 551 | gu2é 552 | gu2í 553 | gu2ò 554 | gu2ó 555 | gü2e 556 | gü2i 557 | gü2è 558 | gü2é 559 | gü2í 560 | i1a 561 | i1e 562 | i1i2a 563 | i1i2e 564 | i1i2o 565 | i1i2u 566 | i1i2à 567 | i1i2è 568 | i1i2é 569 | i1i2í 570 | i1i2ò 571 | i1i2ó 572 | i1i2ú 573 | i1isme. 574 | i1ista. 575 | i1o 576 | i1u2a 577 | i1u2e 578 | i1u2i 579 | i1u2o 580 | i1u2u 581 | i1u2à 582 | i1u2è 583 | i1u2é 584 | i1u2í 585 | i1u2ò 586 | i1u2ó 587 | i1u2ú 588 | i1um. 589 | i1à 590 | i1è 591 | i1é 592 | i1í 593 | i1ï 594 | i1ò 595 | i1ó 596 | i1ú 597 | i1ü 598 | ig3n 599 | in3ex 600 | n3si 601 | ni2etz1sc2he 602 | o1a 603 | o1e 604 | o1i2a 605 | o1i2e 606 | o1i2o 607 | o1i2u 608 | o1i2à 609 | o1i2è 610 | o1i2é 611 | o1i2í 612 | o1i2ò 613 | o1i2ó 614 | o1i2ú 615 | o1isme. 616 | o1ista. 617 | o1o 618 | o1u2a 619 | o1u2e 620 | o1u2i 621 | o1u2o 622 | o1u2u 623 | o1u2à 624 | o1u2è 625 | o1u2é 626 | o1u2í 627 | o1u2ò 628 | o1u2ó 629 | o1u2ú 630 | o1um. 631 | o1à 632 | o1è 633 | o1é 634 | o1í 635 | o1ï 636 | o1ò 637 | o1ó 638 | o1ú 639 | o1ü 640 | o3gnò 641 | o3ro 642 | p2neu 643 | p2se 644 | p2si 645 | p2sí 646 | qu2a 647 | qu2e 648 | qu2i 649 | qu2o 650 | qu2à 651 | qu2è 652 | qu2é 653 | qu2í 654 | qu2ò 655 | qu2ó 656 | qui3e 657 | qü2e 658 | qü2i 659 | qü2è 660 | qü2é 661 | qü2í 662 | ru1t2herford 663 | s3emp 664 | s3esp 665 | sub3a 666 | u1a 667 | u1e 668 | u1i2a 669 | u1i2e 670 | u1i2o 671 | u1i2u 672 | u1i2à 673 | u1i2è 674 | u1i2é 675 | u1i2í 676 | u1i2ò 677 | u1i2ó 678 | u1i2ú 679 | u1isme. 680 | u1ista. 681 | u1o 682 | u1u2a 683 | u1u2e 684 | u1u2i 685 | u1u2o 686 | u1u2u 687 | u1u2à 688 | u1u2è 689 | u1u2é 690 | u1u2í 691 | u1u2ò 692 | u1u2ó 693 | u1u2ú 694 | u1um. 695 | u1à 696 | u1è 697 | u1é 698 | u1í 699 | u1ï 700 | u1ò 701 | u1ó 702 | u1ú 703 | u1ü 704 | ui3et 705 | à1a 706 | à1e 707 | à1i2a 708 | à1i2e 709 | à1i2o 710 | à1i2u 711 | à1o 712 | à1u2a 713 | à1u2e 714 | à1u2i 715 | à1u2o 716 | à1u2u 717 | à1ï 718 | à1ü 719 | è1a 720 | è1e 721 | è1i2a 722 | è1i2e 723 | è1i2o 724 | è1i2u 725 | è1o 726 | è1u2a 727 | è1u2e 728 | è1u2i 729 | è1u2o 730 | è1u2u 731 | è1ï 732 | è1ü 733 | é1a 734 | é1e 735 | é1i2a 736 | é1i2e 737 | é1i2o 738 | é1i2u 739 | é1o 740 | é1u2a 741 | é1u2e 742 | é1u2i 743 | é1u2o 744 | é1u2u 745 | é1ï 746 | é1ü 747 | í1a 748 | í1e 749 | í1i2a 750 | í1i2e 751 | í1i2o 752 | í1i2u 753 | í1o 754 | í1u2a 755 | í1u2e 756 | í1u2i 757 | í1u2o 758 | í1u2u 759 | í1ï 760 | í1ü 761 | ï1a 762 | ï1e 763 | ï1i 764 | ï1i2a 765 | ï1i2e 766 | ï1i2o 767 | ï1i2u 768 | ï1i2à 769 | ï1i2è 770 | ï1i2é 771 | ï1i2í 772 | ï1i2ò 773 | ï1i2ó 774 | ï1i2ú 775 | ï1o 776 | ï1u2a 777 | ï1u2e 778 | ï1u2i 779 | ï1u2o 780 | ï1u2u 781 | ï1u2à 782 | ï1u2è 783 | ï1u2é 784 | ï1u2í 785 | ï1u2ò 786 | ï1u2ó 787 | ï1u2ú 788 | ï1à 789 | ï1è 790 | ï1é 791 | ï1í 792 | ï1ò 793 | ï1ó 794 | ï1ú 795 | ò1a 796 | ò1e 797 | ò1i2a 798 | ò1i2e 799 | ò1i2o 800 | ò1i2u 801 | ò1o 802 | ò1u2a 803 | ò1u2e 804 | ò1u2i 805 | ò1u2o 806 | ò1u2u 807 | ò1ï 808 | ò1ü 809 | ó1a 810 | ó1e 811 | ó1i2a 812 | ó1i2e 813 | ó1i2o 814 | ó1i2u 815 | ó1o 816 | ó1u2a 817 | ó1u2e 818 | ó1u2i 819 | ó1u2o 820 | ó1u2u 821 | ó1ï 822 | ó1ü 823 | ú1a 824 | ú1e 825 | ú1i2a 826 | ú1i2e 827 | ú1i2o 828 | ú1i2u 829 | ú1o 830 | ú1u2a 831 | ú1u2e 832 | ú1u2i 833 | ú1u2o 834 | ú1u2u 835 | ú1ï 836 | ú1ü 837 | ü1a 838 | ü1e 839 | ü1i2a 840 | ü1i2e 841 | ü1i2o 842 | ü1i2u 843 | ü1i2à 844 | ü1i2è 845 | ü1i2é 846 | ü1i2í 847 | ü1i2ò 848 | ü1i2ó 849 | ü1i2ú 850 | ü1o 851 | ü1u2a 852 | ü1u2e 853 | ü1u2i 854 | ü1u2o 855 | ü1u2u 856 | ü1u2à 857 | ü1u2è 858 | ü1u2é 859 | ü1u2í 860 | ü1u2ò 861 | ü1u2ó 862 | ü1u2ú 863 | ü1à 864 | ü1è 865 | ü1é 866 | ü1í 867 | ü1ò 868 | ü1ó 869 | ü1ú -------------------------------------------------------------------------------- /operators/pysrt/commands.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # pylint: disable-all 4 | from __future__ import print_function 5 | 6 | import os 7 | import re 8 | import sys 9 | import codecs 10 | import shutil 11 | import argparse 12 | from textwrap import dedent 13 | 14 | from chardet import detect 15 | from .srtfile import SubRipFile 16 | from .srttime SubRipTime 17 | from .version.VERSION_STRING 18 | 19 | 20 | def underline(string): 21 | return "\033[4m%s\033[0m" % string 22 | 23 | 24 | class TimeAwareArgumentParser(argparse.ArgumentParser): 25 | 26 | RE_TIME_REPRESENTATION = re.compile(r'^\-?(\d+[hms]{0,2}){1,4}$') 27 | 28 | def parse_args(self, args=None, namespace=None): 29 | time_index = -1 30 | for index, arg in enumerate(args): 31 | match = self.RE_TIME_REPRESENTATION.match(arg) 32 | if match: 33 | time_index = index 34 | break 35 | 36 | if time_index >= 0: 37 | args.insert(time_index, '--') 38 | 39 | return super(TimeAwareArgumentParser, self).parse_args(args, namespace) 40 | 41 | 42 | class SubRipShifter(object): 43 | 44 | BACKUP_EXTENSION = '.bak' 45 | RE_TIME_STRING = re.compile(r'(\d+)([hms]{0,2})') 46 | UNIT_RATIOS = { 47 | 'ms': 1, 48 | '': SubRipTime.SECONDS_RATIO, 49 | 's': SubRipTime.SECONDS_RATIO, 50 | 'm': SubRipTime.MINUTES_RATIO, 51 | 'h': SubRipTime.HOURS_RATIO, 52 | } 53 | DESCRIPTION = dedent("""\ 54 | Srt subtitle editor 55 | 56 | It can either shift, split or change the frame rate. 57 | """) 58 | TIMESTAMP_HELP = "A timestamp in the form: [-][Hh][Mm]S[s][MSms]" 59 | SHIFT_EPILOG = dedent("""\ 60 | 61 | Examples: 62 | 1 minute and 12 seconds foreward (in place): 63 | $ srt -i shift 1m12s movie.srt 64 | 65 | half a second foreward: 66 | $ srt shift 500ms movie.srt > othername.srt 67 | 68 | 1 second and half backward: 69 | $ srt -i shift -1s500ms movie.srt 70 | 71 | 3 seconds backward: 72 | $ srt -i shift -3 movie.srt 73 | """) 74 | RATE_EPILOG = dedent("""\ 75 | 76 | Examples: 77 | Convert 23.9fps subtitles to 25fps: 78 | $ srt -i rate 23.9 25 movie.srt 79 | """) 80 | LIMITS_HELP = "Each parts duration in the form: [Hh][Mm]S[s][MSms]" 81 | SPLIT_EPILOG = dedent("""\ 82 | 83 | Examples: 84 | For a movie in 2 parts with the first part 48 minutes and 18 seconds long: 85 | $ srt split 48m18s movie.srt 86 | => creates movie.1.srt and movie.2.srt 87 | 88 | For a movie in 3 parts of 20 minutes each: 89 | $ srt split 20m 20m movie.srt 90 | => creates movie.1.srt, movie.2.srt and movie.3.srt 91 | """) 92 | FRAME_RATE_HELP = "A frame rate in fps (commonly 23.9 or 25)" 93 | ENCODING_HELP = dedent("""\ 94 | Change file encoding. Useful for players accepting only latin1 subtitles. 95 | List of supported encodings: http://docs.python.org/library/codecs.html#standard-encodings 96 | """) 97 | BREAK_EPILOG = dedent("""\ 98 | Break lines longer than defined length 99 | """) 100 | LENGTH_HELP = "Maximum number of characters per line" 101 | 102 | def __init__(self): 103 | self.output_file_path = None 104 | 105 | def build_parser(self): 106 | parser = TimeAwareArgumentParser(description=self.DESCRIPTION, formatter_class=argparse.RawTextHelpFormatter) 107 | parser.add_argument('-i', '--in-place', action='store_true', dest='in_place', 108 | help="Edit file in-place, saving a backup as file.bak (do not works for the split command)") 109 | parser.add_argument('-e', '--output-encoding', metavar=underline('encoding'), action='store', dest='output_encoding', 110 | type=self.parse_encoding, help=self.ENCODING_HELP) 111 | parser.add_argument('-v', '--version', action='version', version='%%(prog)s %s' % VERSION_STRING) 112 | subparsers = parser.add_subparsers(title='commands') 113 | 114 | shift_parser = subparsers.add_parser('shift', help="Shift subtitles by specified time offset", epilog=self.SHIFT_EPILOG, formatter_class=argparse.RawTextHelpFormatter) 115 | shift_parser.add_argument('time_offset', action='store', metavar=underline('offset'), 116 | type=self.parse_time, help=self.TIMESTAMP_HELP) 117 | shift_parser.set_defaults(action=self.shift) 118 | 119 | rate_parser = subparsers.add_parser('rate', help="Convert subtitles from a frame rate to another", epilog=self.RATE_EPILOG, formatter_class=argparse.RawTextHelpFormatter) 120 | rate_parser.add_argument('initial', action='store', type=float, help=self.FRAME_RATE_HELP) 121 | rate_parser.add_argument('final', action='store', type=float, help=self.FRAME_RATE_HELP) 122 | rate_parser.set_defaults(action=self.rate) 123 | 124 | split_parser = subparsers.add_parser('split', help="Split a file in multiple parts", epilog=self.SPLIT_EPILOG, formatter_class=argparse.RawTextHelpFormatter) 125 | split_parser.add_argument('limits', action='store', nargs='+', type=self.parse_time, help=self.LIMITS_HELP) 126 | split_parser.set_defaults(action=self.split) 127 | 128 | break_parser = subparsers.add_parser('break', help="Break long lines", epilog=self.BREAK_EPILOG, formatter_class=argparse.RawTextHelpFormatter) 129 | break_parser.add_argument('length', action='store', type=int, help=self.LENGTH_HELP) 130 | break_parser.set_defaults(action=self.break_lines) 131 | 132 | parser.add_argument('file', action='store') 133 | 134 | return parser 135 | 136 | def run(self, args): 137 | self.arguments = self.build_parser().parse_args(args) 138 | 139 | if os.path.isfile(self.arguments.file): 140 | if self.arguments.in_place: 141 | self.create_backup() 142 | self.arguments.action() 143 | 144 | else: 145 | print('No such file', self.arguments.file) 146 | 147 | def parse_time(self, time_string): 148 | negative = time_string.startswith('-') 149 | if negative: 150 | time_string = time_string[1:] 151 | ordinal = sum(int(value) * self.UNIT_RATIOS[unit] for value, unit 152 | in self.RE_TIME_STRING.findall(time_string)) 153 | return -ordinal if negative else ordinal 154 | 155 | def parse_encoding(self, encoding_name): 156 | try: 157 | codecs.lookup(encoding_name) 158 | except LookupError as error: 159 | raise argparse.ArgumentTypeError(error.message) 160 | return encoding_name 161 | 162 | def shift(self): 163 | self.input_file.shift(milliseconds=self.arguments.time_offset) 164 | self.input_file.write_into(self.output_file) 165 | 166 | def rate(self): 167 | ratio = self.arguments.final / self.arguments.initial 168 | self.input_file.shift(ratio=ratio) 169 | self.input_file.write_into(self.output_file) 170 | 171 | def split(self): 172 | limits = [0] + self.arguments.limits + [self.input_file[-1].end.ordinal + 1] 173 | base_name, extension = os.path.splitext(self.arguments.file) 174 | for index, (start, end) in enumerate(zip(limits[:-1], limits[1:])): 175 | file_name = '%s.%s%s' % (base_name, index + 1, extension) 176 | part_file = self.input_file.slice(ends_after=start, starts_before=end) 177 | part_file.shift(milliseconds=-start) 178 | part_file.clean_indexes() 179 | part_file.save(path=file_name, encoding=self.output_encoding) 180 | 181 | def create_backup(self): 182 | backup_file = self.arguments.file + self.BACKUP_EXTENSION 183 | if not os.path.exists(backup_file): 184 | shutil.copy2(self.arguments.file, backup_file) 185 | self.output_file_path = self.arguments.file 186 | self.arguments.file = backup_file 187 | 188 | def break_lines(self): 189 | split_re = re.compile(r'(.{,%i})(?:\s+|$)' % self.arguments.length) 190 | for item in self.input_file: 191 | item.text = '\n'.join(split_re.split(item.text)[1::2]) 192 | self.input_file.write_into(self.output_file) 193 | 194 | @property 195 | def output_encoding(self): 196 | return self.arguments.output_encoding or self.input_file.encoding 197 | 198 | @property 199 | def input_file(self): 200 | if not hasattr(self, '_source_file'): 201 | with open(self.arguments.file, 'rb') as f: 202 | content = f.read() 203 | encoding = detect(content).get('encoding') 204 | encoding = self.normalize_encoding(encoding) 205 | 206 | self._source_file = SubRipFile.open(self.arguments.file, 207 | encoding=encoding, error_handling=SubRipFile.ERROR_LOG) 208 | return self._source_file 209 | 210 | @property 211 | def output_file(self): 212 | if not hasattr(self, '_output_file'): 213 | if self.output_file_path: 214 | self._output_file = codecs.open(self.output_file_path, 'w+', encoding=self.output_encoding) 215 | else: 216 | self._output_file = sys.stdout 217 | return self._output_file 218 | 219 | def normalize_encoding(self, encoding): 220 | return encoding.lower().replace('-', '_') 221 | 222 | 223 | def main(): 224 | SubRipShifter().run(sys.argv[1:]) 225 | 226 | if __name__ == '__main__': 227 | main() -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | from .operators import * 3 | 4 | bl_info = { 5 | "name": "Subsimport", 6 | "description": "Import subtitles into blender", 7 | "author": "doakey3", 8 | "version": (1, 3, 2), 9 | "blender": (3, 2, 0), 10 | "wiki_url": "https://github.com/doakey3/subsimport", 11 | "tracker_url": "https://github.com/doakey3/subsimport/issues", 12 | "category": "Sequencer" 13 | } 14 | 15 | 16 | class SEQUENCER_PT_subsimport(bpy.types.Panel): 17 | bl_space_type = "SEQUENCE_EDITOR" 18 | bl_region_type = "UI" 19 | bl_label = "Subsimport" 20 | bl_category = "Tools" 21 | bl_options = {"DEFAULT_CLOSED"} 22 | 23 | @classmethod 24 | def poll(cls, context): 25 | return context.space_data.view_type == 'SEQUENCER' 26 | 27 | def draw(self, context): 28 | scene = context.scene 29 | layout = self.layout 30 | 31 | row = layout.row() 32 | row.prop(scene, 'subtitle_edit_channel', 33 | text="Subtitle Edit Channel") 34 | 35 | box = layout.box() 36 | row = box.row(align=False) 37 | row.prop(scene, 'subtitle_font', 38 | text='Font') 39 | row = box.row() 40 | row.prop(scene, 'subtitle_font_size', 41 | text='Font Size') 42 | row = box.row() 43 | row.prop(scene, 'subtitle_font_height', 44 | text='Font Height') 45 | row = box.row() 46 | row.operator('sequencerextra.refresh_font_data', 47 | icon="FILE_REFRESH") 48 | box = layout.box() 49 | row = box.row() 50 | row.operator('sequencerextra.import_subtitles', icon='ANIM') 51 | row = box.row() 52 | row.operator('sequencerextra.duration_x_two', icon='PREVIEW_RANGE') 53 | row.operator('sequencerextra.duration_x_half', icon='RECOVER_LAST') 54 | row = box.row() 55 | row.operator('sequencerextra.export_srt', icon='RENDER_ANIMATION') 56 | row.operator('sequencerextra.export_lrc', icon='FILE_SOUND') 57 | box = layout.box() 58 | row = box.row() 59 | row.prop(scene, 'use_dictionary_syllabification', text="Dictionary Syllabification") 60 | row = box.row() 61 | row.prop(scene, 'use_algorithmic_syllabification', text="Algorithm") 62 | row.prop(scene, 'syllabification_language', text='') 63 | row = box.row() 64 | row.operator('sequencerextra.syllabify', icon="ALIGN_FLUSH") 65 | row.operator('sequencerextra.save_syllables', icon="DISK_DRIVE") 66 | row = box.row() 67 | row.prop(scene, 'syllable_dictionary_path', icon='TEXT', text="Syll Dict") 68 | 69 | row = box.row() 70 | row.prop(scene, 'enhanced_subs_color', 71 | text='Highlight') 72 | row.operator('sequencerextra.refresh_highlight', 73 | icon='FILE_REFRESH') 74 | row = box.row() 75 | row.operator('sequencerextra.split_words', icon="MOD_EXPLODE") 76 | row = box.row() 77 | row.operator('sequencerextra.combine_words', icon="MOD_BUILD") 78 | row.prop(scene, 'subtitle_combine_mode', text='') 79 | 80 | 81 | def init_prop(): 82 | bpy.types.Scene.subtitle_edit_channel = bpy.props.IntProperty( 83 | description="The channel where keyboard shortcuts will act on text strips", 84 | default=1, 85 | min=0) 86 | 87 | bpy.types.Scene.subtitle_font = bpy.props.StringProperty( 88 | description="The font of the added text strips after import", 89 | subtype="FILE_PATH") 90 | 91 | bpy.types.Scene.subtitle_font_size = bpy.props.IntProperty( 92 | description="The font size of the added text strips after import", 93 | default=70, 94 | min=1) 95 | 96 | bpy.types.Scene.subtitle_font_height = bpy.props.FloatProperty( 97 | description="The height of the added text strips after import", 98 | default=0.0, 99 | min=0.0, 100 | max=1.0) 101 | 102 | bpy.types.Scene.syllable_dictionary_path = bpy.props.StringProperty( 103 | name="Syllable Dictionary Path", 104 | description="Path to the text file containing words separated by syllables.\nNeeded for accurate splitting of subtitles by syllable.", 105 | subtype="FILE_PATH", 106 | ) 107 | 108 | bpy.types.Scene.enhanced_subs_color = bpy.props.FloatVectorProperty( 109 | subtype='COLOR_GAMMA', 110 | description="Highlight color of the subtitles in the edit channel", 111 | size=3, 112 | default=(1.0, 0.5, 0.0), 113 | min=0.0, max=1.0,) 114 | 115 | bpy.types.Scene.use_dictionary_syllabification = bpy.props.BoolProperty( 116 | description="Use (Less-Error-Prone) algorithm to syllabify words.", 117 | default=True 118 | ) 119 | 120 | bpy.types.Scene.use_algorithmic_syllabification = bpy.props.BoolProperty( 121 | description="Use (imperfect) algorithm to syllabify words.\nIf dictionary method is enabled, the algorithm is used for words not found in the dictionary.", 122 | default=True 123 | ) 124 | 125 | language_options = [ 126 | # probably don't need this 127 | #('grc', 'Ancient Greek', ''), 128 | ('hy', 'Armenian', ''), 129 | ('be', 'Belarusian', ''), 130 | ('bn', 'Bengali', ''), 131 | ('ca', 'Catalan', ''), 132 | ('cs', 'Czech', ''), 133 | ('da', 'Danish', ''), 134 | ('nl', 'Dutch', ''), 135 | # Better off using en-us until I have a better dictionary 136 | #('en-gb', 'English-Great Britain', ''), 137 | ('en-us', 'English-U.S.', ''), 138 | ('eo', 'Esperanto', ''), 139 | ('et', 'Estonian', ''), 140 | ('fi', 'Finnish', ''), 141 | ('fr', 'French', ''), 142 | ('de', 'German', ''), 143 | ('gu', 'Gujarati', ''), 144 | ('hi', 'Hindi', ''), 145 | ('hu', 'Hungarian', ''), 146 | ('ga', 'Irish', ''), 147 | ('it', 'Italian', ''), 148 | ('lv', 'Latvian', ''), 149 | ('ml', 'Malayalam', ''), 150 | ('el-monoton', 'Monotonic Greek', ''), 151 | ('nb-no', 'Norwegian', ''), 152 | ('or', 'Oriya', ''), 153 | ('pl', 'Polish', ''), 154 | # Probably don't need this 155 | #('el-polyton', 'Polytonic Greek', ''), 156 | ('pt', 'Portuguese', ''), 157 | ('pa', 'Punjabi', ''), 158 | ('ro', 'Romanian', ''), 159 | ('ru', 'Russian', ''), 160 | ('sr-cyrl', 'Serbian Cyrillic', ''), 161 | ('sr-latn', 'Serbian Latin', ''), 162 | ('sk', 'Slovak', ''), 163 | ('sl', 'Slovene', ''), 164 | ('es', 'Spanish', ''), 165 | ('sv', 'Swedish', ''), 166 | ('ta', 'Tamil', ''), 167 | ('te', 'Telugu', ''), 168 | ('tr', 'Turkish', ''), 169 | ('uk', 'Ukrainian', ''), 170 | ] 171 | 172 | bpy.types.Scene.syllabification_language = bpy.props.EnumProperty( 173 | name="Syllabification Language", 174 | items=language_options, 175 | description="Set the language to use when syllabifying", 176 | default="en-us" 177 | ) 178 | 179 | combine_modes = [ 180 | ('esrt', 'ESRT', 'Combine subtitles as enhanced SRT strips'), 181 | ('elrc', 'ELRC', 'Combine subtitles as enhanced LRC strips') 182 | ] 183 | 184 | bpy.types.Scene.subtitle_combine_mode = bpy.props.EnumProperty( 185 | name="Subtitle Combine Mode", 186 | items=combine_modes, 187 | description="How to combine the subtitles", 188 | default="esrt" 189 | ) 190 | 191 | classes = [ 192 | SEQUENCER_PT_subsimport, 193 | SEQUENCER_OT_combine_words, 194 | SEQUENCER_OT_duration_x_2, 195 | SEQUENCER_OT_duration_x_half, 196 | SEQUENCER_OT_export_lrc, 197 | SEQUENCER_OT_export_srt, 198 | SEQUENCER_OT_import_subtitles, 199 | SEQUENCER_OT_refresh_font_data, 200 | SEQUENCER_OT_refresh_highlight, 201 | SEQUENCER_OT_save_syllables, 202 | SEQUENCER_OT_select_channel_right, 203 | SEQUENCER_OT_select_channel_left, 204 | SEQUENCER_OT_shift_frame_start, 205 | SEQUENCER_OT_shift_frame_end, 206 | SEQUENCER_OT_shift_frame_start_end, 207 | SEQUENCER_OT_shift_frame_end_start, 208 | SEQUENCER_OT_reset_children, 209 | SEQUENCER_OT_split_words, 210 | SEQUENCER_OT_syllabify, 211 | ] 212 | addon_keymaps = [] 213 | 214 | def register(): 215 | init_prop() 216 | from bpy.utils import register_class 217 | for cls in classes: 218 | register_class(cls) 219 | 220 | wm = bpy.context.window_manager 221 | km = wm.keyconfigs.addon.keymaps.new(name="Sequencer", space_type="SEQUENCE_EDITOR", region_type="WINDOW") 222 | 223 | kmi = km.keymap_items.new("sequencerextra.shift_frame_start", "D", 'PRESS') 224 | kmi = km.keymap_items.new("sequencerextra.shift_frame_end", "F", 'PRESS') 225 | kmi = km.keymap_items.new("sequencerextra.shift_frame_start_end", "W", "PRESS") 226 | kmi = km.keymap_items.new("sequencerextra.shift_frame_end_start", "S", 'PRESS') 227 | 228 | kmi = km.keymap_items.new("sequencerextra.reset_children", "Z", 'PRESS') 229 | 230 | kmi = km.keymap_items.new("sequencerextra.select_channel_right", "RIGHT_ARROW", "PRESS", alt=False, ctrl=True, shift=True) 231 | kmi = km.keymap_items.new("sequencerextra.select_channel_left", "LEFT_ARROW", "PRESS", alt=False, ctrl=True, shift=True) 232 | 233 | addon_keymaps.append(km) 234 | 235 | def unregister(): 236 | from bpy.utils import unregister_class 237 | for cls in reversed(classes): 238 | unregister_class(cls) 239 | 240 | wm = bpy.context.window_manager 241 | for km in addon_keymaps: 242 | wm.keyconfigs.addon.keymaps.remove(km) 243 | addon_keymaps.clear() 244 | 245 | 246 | if __name__ == "__main__": 247 | register() 248 | -------------------------------------------------------------------------------- /tools/(early version) syllabator.py: -------------------------------------------------------------------------------- 1 | class Syllabator(): 2 | def __init__(self): 3 | self.consonants = 'bcdfghjklmnpqrstvwxz' 4 | 5 | self.consonant_pairs = [ 6 | 'bl', 'br', 'ch', 'ck', 'cl', 'cr', 'ct', 'dg', 'dr', 'fl', 7 | 'fr', 'ft', 'gh', 'gl', 'gr', 'hr', 'ht', 'kh', 'ld', 'lf', 8 | 'lv', 'nc', 'nd', 'ng', 'nk', 'ns', 'nt', 'ph', 'pl', 'pr', 9 | 'rd', 'rd', 'rg', 'rh', 'sc', 'sh', 'sk', 'sl', 'sm', 'sn', 10 | 'sp', 'st', 'sw', 'th', 'tr', 'ts', 'tw', 'tz', 'wh', 'wr', 11 | 'hr', 'tc'] 12 | 13 | self.vowels = 'aeiouy' 14 | 15 | self.word_parts = [""] 16 | 17 | def split_consonants(self): 18 | """ 19 | make a split where there are 2 consonants next to 20 | eachother if the 2 consonants are not at the end of the word 21 | and they aren't in the consonant pairs 22 | example: 23 | pizzazz --> piz, zazz 24 | """ 25 | 26 | i = 0 27 | while i < len(self.word_parts): 28 | for x in range(1, len(self.word_parts[i])): 29 | a = self.word_parts[i][x - 1] 30 | b = self.word_parts[i][x] 31 | if (a in self.consonants and 32 | b in self.consonants and not 33 | a + b in self.consonant_pairs and not 34 | x == len(self.word_parts[i]) - 1): 35 | part1 = self.word_parts[i][0:x] 36 | part2 = self.word_parts[i][x::] 37 | self.word_parts.pop(i) 38 | self.word_parts.insert(i, part2) 39 | self.word_parts.insert(i, part1) 40 | break 41 | i += 1 42 | 43 | def handle_ckle(self): 44 | """ 45 | make a split between ck and le 46 | """ 47 | 48 | i = 0 49 | while i < len(self.word_parts): 50 | if self.word_parts[i].endswith('ckle'): 51 | part1 = self.word_parts[i][0:-2] 52 | part2 = 'le' 53 | self.word_parts.pop(i) 54 | self.word_parts.insert(i, part2) 55 | self.word_parts.insert(i, part1) 56 | i += 1 57 | 58 | def handle_le(self): 59 | """ 60 | make a split between word part and 'le' if it ends with le and the 61 | 3rd to last character is not a vowel. 62 | """ 63 | i = 0 64 | while i < len(self.word_parts): 65 | if self.word_parts[i].endswith('le') and len(self.word_parts[i]) > 3: 66 | if len(self.word_parts[i]) > 3 and not self.word_parts[i][-3] in self.vowels: 67 | part1 = self.word_parts[i][0:-3] 68 | part2 = self.word_parts[i][-3::] 69 | self.word_parts.pop(i) 70 | self.word_parts.insert(i, part2) 71 | self.word_parts.insert(i, part1) 72 | i += 1 73 | 74 | 75 | def split_surrounded_consonant(self, position='BEFORE'): 76 | """ 77 | make a split when there is a consonant that is surrounded by vowels 78 | """ 79 | 80 | i = 0 81 | while i < len(self.word_parts): 82 | for x in range(1, len(self.word_parts[i]) - 1): 83 | a = self.word_parts[i][x - 1] 84 | b = self.word_parts[i][x] 85 | c = self.word_parts[i][x + 1] 86 | if a in self.vowels and c in self.vowels and b in self.consonants: 87 | 88 | # mATE 89 | if x == len(self.word_parts[i]) - 2 and self.word_parts[i].endswith('e'): 90 | pass 91 | 92 | # cURE, cURED 93 | elif a == 'u' and b == 'r' and c == 'e' and self.word_parts[i].endswith('e') or self.word_parts[i].endswith('ed'): 94 | pass 95 | 96 | # brACElet 97 | elif a == 'a' and b == 'c' and c == 'e': 98 | pass 99 | 100 | else: 101 | if position == 'BEFORE': 102 | part1 = self.word_parts[i][0:x+1] 103 | part2 = self.word_parts[i][x+1::] 104 | 105 | else: 106 | part1 = self.word_parts[i][0:x+1] 107 | part2 = self.word_parts[i][x+1::] 108 | 109 | self.word_parts.pop(i) 110 | self.word_parts.insert(i, part2) 111 | self.word_parts.insert(i, part1) 112 | break 113 | 114 | i += 1 115 | 116 | def split_surrounded_consonant_triples(self): 117 | """ 118 | make a split where there are 2 consonant pairs back to back 119 | as in amaziNGLy 120 | """ 121 | 122 | i = 0 123 | while i < len(self.word_parts): 124 | for x in range(1, len(self.word_parts[i]) - 3): 125 | a = self.word_parts[i][x - 1] 126 | b = self.word_parts[i][x] 127 | c = self.word_parts[i][x + 1] 128 | d = self.word_parts[i][x + 2] 129 | e = self.word_parts[i][x + 3] 130 | if a in self.vowels and b + c in self.consonant_pairs and c + d in self.consonant_pairs and e in self.vowels: 131 | part1 = self.word_parts[i][0:x] 132 | part2 = self.word_parts[i][x::] 133 | self.word_parts.pop(i) 134 | self.word_parts.insert(i, part2) 135 | self.word_parts.insert(i, part1) 136 | break 137 | i += 1 138 | 139 | def split_surrounded_consonant_pairs(self, position='BEFORE'): 140 | """ 141 | make a split when there is a consonant pair surrounded by vowels 142 | """ 143 | 144 | i = 0 145 | while i < len(self.word_parts): 146 | for x in range(1, len(self.word_parts[i]) - 2): 147 | a = self.word_parts[i][x - 1] 148 | b = self.word_parts[i][x] 149 | c = self.word_parts[i][x + 1] 150 | d = self.word_parts[i][x + 2] 151 | if a in self.vowels and b + c in self.consonant_pairs and d in self.vowels: 152 | if x == len(self.word_parts[i]) - 3 and self.word_parts[i].endswith('e'): 153 | pass 154 | 155 | else: 156 | if position == 'BEFORE': 157 | part1 = self.word_parts[i][0:x+2] 158 | part2 = self.word_parts[i][x+2::] 159 | elif position == 'AFTER': 160 | part1 = self.word_parts[i][0:x+2] 161 | part2 = self.word_parts[i][x+2::] 162 | elif position == 'MIDDLE': 163 | part1 = self.word_parts[i][0:x+1] 164 | part2 = self.word_parts[i][x+1::] 165 | 166 | self.word_parts.pop(i) 167 | self.word_parts.insert(i, part2) 168 | self.word_parts.insert(i, part1) 169 | break 170 | i += 1 171 | 172 | 173 | def syllabify(self, word, pos1="BEFORE", pos2="BEFORE"): 174 | """Splits a word up into syllables""" 175 | self.word_parts = [word] 176 | self.split_consonants() 177 | self.handle_ckle() 178 | self.handle_le() 179 | 180 | self.split_surrounded_consonant_pairs(position=pos1) 181 | self.split_surrounded_consonant(position=pos2) 182 | self.split_surrounded_consonant_triples() 183 | return self.word_parts 184 | 185 | if __name__ == '__main__': 186 | 187 | count_dictionary = {} 188 | f = open('syllable_counts.txt', 'r') 189 | lines = f.readlines() 190 | f.close() 191 | for i in range(len(lines)): 192 | if not lines[i].rstrip() == '': 193 | word = lines[i].split(' ')[0] 194 | count = int(lines[i].split(' ')[1].rstrip()) 195 | count_dictionary[word] = count 196 | 197 | syllable_dictionary = {} 198 | f = open('syllable_splits.txt', 'r') 199 | lines = f.readlines() 200 | f.close() 201 | for i in range(len(lines)): 202 | word = lines[i].rstrip() 203 | syllable_dictionary[word] = lines[i].rstrip() 204 | 205 | old_words = list(syllable_dictionary.keys()) 206 | words = list(count_dictionary.keys()) 207 | 208 | syllabator = Syllabator() 209 | hyphenator = Hyphenator() 210 | 211 | confirmed_words = [] 212 | syl_splits = [] 213 | for i in range(len(words)): 214 | syl_splits.append(syllabator.syllabify(words[i])) 215 | syl_splits.append(syllabator.syllabify(words[i], pos1='AFTER', pos2='BEFORE')) 216 | syl_splits.append(syllabator.syllabify(words[i], pos1='MIDDLE', pos2='BEFORE')) 217 | syl_splits.append(syllabator.syllabify(words[i], pos1='BEFORE', pos2='AFTER')) 218 | syl_splits.append(syllabator.syllabify(words[i], pos1='AFTER', pos2='AFTER')) 219 | syl_splits.append(syllabator.syllabify(words[i], pos1='MIDDLE', pos2='AFTER')) 220 | 221 | hyp_split = hyphenator.hyphenate_word(words[i]) 222 | 223 | for x in range(len(syl_splits)): 224 | if len(syl_splits[x]) == count_dictionary[words[i]] and syl_splits[x] == hyp_split: 225 | w = ' '.join(syl_splits[x]) 226 | if not w in old_words: 227 | confirmed_words.append(w) 228 | break 229 | 230 | lines = [] 231 | for i in range(len(confirmed_words)): 232 | lines.append(confirmed_words[i] + '\n') 233 | f = open('new_splits.txt', 'w') 234 | f.write(''.join(lines)) 235 | f.close() -------------------------------------------------------------------------------- /tests/static/I Move On_tops.srt: -------------------------------------------------------------------------------- 1 | 1 2 | 00:00:27,290 --> 00:00:27,640 3 | Come 4 | 5 | 2 6 | 00:00:27,640 --> 00:00:28,070 7 | Come take 8 | 9 | 3 10 | 00:00:28,070 --> 00:00:28,450 11 | Come take my 12 | 13 | 4 14 | 00:00:28,450 --> 00:00:28,870 15 | Come take my jour 16 | 17 | 5 18 | 00:00:28,870 --> 00:00:30,180 19 | Come take my journey 20 | 21 | 6 22 | 00:00:30,180 --> 00:00:31,370 23 | In 24 | 25 | 7 26 | 00:00:31,370 --> 00:00:31,810 27 | Into 28 | 29 | 8 30 | 00:00:31,810 --> 00:00:33,210 31 | Into night 32 | 33 | 9 34 | 00:00:33,940 --> 00:00:34,440 35 | Come 36 | 37 | 10 38 | 00:00:34,440 --> 00:00:34,820 39 | Come be 40 | 41 | 11 42 | 00:00:34,820 --> 00:00:35,230 43 | Come be my 44 | 45 | 12 46 | 00:00:35,230 --> 00:00:35,650 47 | Come be my shad 48 | 49 | 13 50 | 00:00:35,650 --> 00:00:36,490 51 | Come be my shadow 52 | 53 | 14 54 | 00:00:36,860 --> 00:00:38,200 55 | Walk 56 | 57 | 15 58 | 00:00:38,200 --> 00:00:38,530 59 | Walk at 60 | 61 | 16 62 | 00:00:38,530 --> 00:00:39,360 63 | Walk at my 64 | 65 | 17 66 | 00:00:39,360 --> 00:00:40,360 67 | Walk at my side 68 | 69 | 18 70 | 00:00:40,620 --> 00:00:41,010 71 | And 72 | 73 | 19 74 | 00:00:41,010 --> 00:00:41,400 75 | And when 76 | 77 | 20 78 | 00:00:41,400 --> 00:00:41,810 79 | And when you 80 | 81 | 21 82 | 00:00:41,810 --> 00:00:43,290 83 | And when you see 84 | 85 | 22 86 | 00:00:43,550 --> 00:00:44,100 87 | All 88 | 89 | 23 90 | 00:00:44,100 --> 00:00:44,310 91 | All that 92 | 93 | 24 94 | 00:00:44,310 --> 00:00:44,680 95 | All that I 96 | 97 | 25 98 | 00:00:44,680 --> 00:00:45,110 99 | All that I have 100 | 101 | 26 102 | 00:00:45,110 --> 00:00:46,560 103 | All that I have seen 104 | 105 | 27 106 | 00:00:46,890 --> 00:00:47,320 107 | Can 108 | 109 | 28 110 | 00:00:47,320 --> 00:00:47,660 111 | Can you 112 | 113 | 29 114 | 00:00:47,660 --> 00:00:48,030 115 | Can you tell 116 | 117 | 30 118 | 00:00:48,030 --> 00:00:48,580 119 | Can you tell me 120 | 121 | 31 122 | 00:00:48,580 --> 00:00:49,400 123 | Love 124 | 125 | 32 126 | 00:00:49,400 --> 00:00:50,280 127 | Love from 128 | 129 | 33 130 | 00:00:50,280 --> 00:00:51,960 131 | Love from pride? 132 | 133 | 34 134 | 00:00:54,000 --> 00:00:54,400 135 | I 136 | 137 | 35 138 | 00:00:54,400 --> 00:00:54,760 139 | I have 140 | 141 | 36 142 | 00:00:54,760 --> 00:00:55,180 143 | I have been 144 | 145 | 37 146 | 00:00:55,180 --> 00:00:55,640 147 | I have been wait 148 | 149 | 38 150 | 00:00:55,640 --> 00:00:56,650 151 | I have been waiting 152 | 153 | 39 154 | 00:00:56,940 --> 00:00:58,040 155 | all 156 | 157 | 40 158 | 00:00:58,040 --> 00:00:58,490 159 | all this 160 | 161 | 41 162 | 00:00:58,490 --> 00:00:59,890 163 | all this time 164 | 165 | 42 166 | 00:01:00,520 --> 00:01:01,020 167 | for 168 | 169 | 43 170 | 00:01:01,020 --> 00:01:01,440 171 | for one 172 | 173 | 44 174 | 00:01:01,440 --> 00:01:01,860 175 | for one to 176 | 177 | 45 178 | 00:01:01,860 --> 00:01:02,360 179 | for one to wake 180 | 181 | 46 182 | 00:01:02,360 --> 00:01:03,490 183 | for one to wake me 184 | 185 | 47 186 | 00:01:03,490 --> 00:01:04,750 187 | one 188 | 189 | 48 190 | 00:01:04,750 --> 00:01:05,200 191 | one to 192 | 193 | 49 194 | 00:01:05,200 --> 00:01:06,000 195 | one to call 196 | 197 | 50 198 | 00:01:06,000 --> 00:01:07,200 199 | one to call mine 200 | 201 | 51 202 | 00:01:07,200 --> 00:01:07,590 203 | So 204 | 205 | 52 206 | 00:01:07,590 --> 00:01:08,000 207 | So when 208 | 209 | 53 210 | 00:01:08,000 --> 00:01:08,460 211 | So when you're 212 | 213 | 54 214 | 00:01:08,460 --> 00:01:09,760 215 | So when you're near 216 | 217 | 55 218 | 00:01:10,120 --> 00:01:10,550 219 | all 220 | 221 | 56 222 | 00:01:10,550 --> 00:01:10,870 223 | all that 224 | 225 | 57 226 | 00:01:10,870 --> 00:01:11,270 227 | all that you 228 | 229 | 58 230 | 00:01:11,270 --> 00:01:11,730 231 | all that you hold 232 | 233 | 59 234 | 00:01:11,730 --> 00:01:13,180 235 | all that you hold dear 236 | 237 | 60 238 | 00:01:13,370 --> 00:01:13,810 239 | do 240 | 241 | 61 242 | 00:01:13,810 --> 00:01:14,220 243 | do you 244 | 245 | 62 246 | 00:01:14,220 --> 00:01:14,680 247 | do you fear 248 | 249 | 63 250 | 00:01:14,680 --> 00:01:15,090 251 | do you fear what 252 | 253 | 64 254 | 00:01:15,090 --> 00:01:16,370 255 | do you fear what you 256 | 257 | 65 258 | 00:01:16,370 --> 00:01:16,810 259 | do you fear what you will 260 | 261 | 66 262 | 00:01:16,810 --> 00:01:19,330 263 | do you fear what you will find? 264 | 265 | 67 266 | 00:01:19,690 --> 00:01:20,020 267 | As 268 | 269 | 68 270 | 00:01:20,020 --> 00:01:20,270 271 | As the 272 | 273 | 69 274 | 00:01:20,270 --> 00:01:21,540 275 | As the dawn 276 | 277 | 70 278 | 00:01:22,280 --> 00:01:22,650 279 | Breaks 280 | 281 | 71 282 | 00:01:22,650 --> 00:01:23,090 283 | Breaks through 284 | 285 | 72 286 | 00:01:23,090 --> 00:01:23,540 287 | Breaks through the 288 | 289 | 73 290 | 00:01:23,540 --> 00:01:25,180 291 | Breaks through the night 292 | 293 | 74 294 | 00:01:26,510 --> 00:01:26,720 295 | I 296 | 297 | 75 298 | 00:01:26,720 --> 00:01:26,970 299 | I move 300 | 301 | 76 302 | 00:01:26,970 --> 00:01:28,490 303 | I move on 304 | 305 | 77 306 | 00:01:29,230 --> 00:01:29,480 307 | For 308 | 309 | 78 310 | 00:01:29,480 --> 00:01:29,920 311 | Forev 312 | 313 | 79 314 | 00:01:29,920 --> 00:01:30,400 315 | Forever 316 | 317 | 80 318 | 00:01:30,400 --> 00:01:31,660 319 | Forever long 320 | 321 | 81 322 | 00:01:31,660 --> 00:01:32,020 323 | Forever longing 324 | 325 | 82 326 | 00:01:32,020 --> 00:01:33,420 327 | Forever longing for 328 | 329 | 83 330 | 00:01:33,420 --> 00:01:33,790 331 | Forever longing for the 332 | 333 | 84 334 | 00:01:33,790 --> 00:01:35,320 335 | Forever longing for the home 336 | 337 | 85 338 | 00:01:35,540 --> 00:01:35,940 339 | I 340 | 341 | 86 342 | 00:01:35,940 --> 00:01:36,440 343 | I found 344 | 345 | 87 346 | 00:01:36,440 --> 00:01:37,150 347 | I found in 348 | 349 | 88 350 | 00:01:37,150 --> 00:01:37,370 351 | I found in your 352 | 353 | 89 354 | 00:01:37,370 --> 00:01:40,600 355 | I found in your eyes 356 | 357 | 90 358 | 00:01:47,660 --> 00:01:48,050 359 | I 360 | 361 | 91 362 | 00:01:48,050 --> 00:01:48,450 363 | I will 364 | 365 | 92 366 | 00:01:48,450 --> 00:01:48,830 367 | I will be 368 | 369 | 93 370 | 00:01:48,830 --> 00:01:49,010 371 | I will be lis 372 | 373 | 94 374 | 00:01:49,010 --> 00:01:49,150 375 | I will be listen 376 | 377 | 95 378 | 00:01:49,150 --> 00:01:50,300 379 | I will be listening 380 | 381 | 96 382 | 00:01:50,510 --> 00:01:51,730 383 | for 384 | 385 | 97 386 | 00:01:51,730 --> 00:01:52,080 387 | for the 388 | 389 | 98 390 | 00:01:52,080 --> 00:01:53,290 391 | for the drum 392 | 393 | 99 394 | 00:01:54,170 --> 00:01:54,560 395 | to 396 | 397 | 100 398 | 00:01:54,560 --> 00:01:54,960 399 | to call 400 | 401 | 101 402 | 00:01:54,960 --> 00:01:55,370 403 | to call me 404 | 405 | 102 406 | 00:01:55,370 --> 00:01:55,790 407 | to call me ov 408 | 409 | 103 410 | 00:01:55,790 --> 00:01:56,210 411 | to call me over 412 | 413 | 104 414 | 00:01:57,300 --> 00:01:58,310 415 | far 416 | 417 | 105 418 | 00:01:58,310 --> 00:01:58,720 419 | far a 420 | 421 | 106 422 | 00:01:58,720 --> 00:01:59,520 423 | far away 424 | 425 | 107 426 | 00:01:59,520 --> 00:02:00,350 427 | far away from 428 | 429 | 108 430 | 00:02:00,720 --> 00:02:01,080 431 | my 432 | 433 | 109 434 | 00:02:01,080 --> 00:02:01,460 435 | my ten 436 | 437 | 110 438 | 00:02:01,460 --> 00:02:01,840 439 | my tender 440 | 441 | 111 442 | 00:02:01,840 --> 00:02:03,280 443 | my tender youth 444 | 445 | 112 446 | 00:02:03,560 --> 00:02:03,940 447 | and 448 | 449 | 113 450 | 00:02:03,940 --> 00:02:04,300 451 | and the 452 | 453 | 114 454 | 00:02:04,300 --> 00:02:04,730 455 | and the ver 456 | 457 | 115 458 | 00:02:04,730 --> 00:02:05,110 459 | and the very 460 | 461 | 116 462 | 00:02:05,110 --> 00:02:06,450 463 | and the very truth 464 | 465 | 117 466 | 00:02:06,810 --> 00:02:07,210 467 | show 468 | 469 | 118 470 | 00:02:07,210 --> 00:02:07,620 471 | showing 472 | 473 | 119 474 | 00:02:07,620 --> 00:02:08,050 475 | showing me 476 | 477 | 120 478 | 00:02:08,050 --> 00:02:08,420 479 | showing me what 480 | 481 | 121 482 | 00:02:08,420 --> 00:02:09,700 483 | showing me what I've 484 | 485 | 122 486 | 00:02:09,700 --> 00:02:10,110 487 | showing me what I've be 488 | 489 | 123 490 | 00:02:10,110 --> 00:02:12,600 491 | showing me what I've become 492 | 493 | 124 494 | 00:02:12,900 --> 00:02:13,120 495 | As 496 | 497 | 125 498 | 00:02:13,120 --> 00:02:13,350 499 | As the 500 | 501 | 126 502 | 00:02:13,350 --> 00:02:15,240 503 | As the dawn 504 | 505 | 127 506 | 00:02:15,390 --> 00:02:15,790 507 | Breaks 508 | 509 | 128 510 | 00:02:15,790 --> 00:02:16,180 511 | Breaks through 512 | 513 | 129 514 | 00:02:16,180 --> 00:02:16,570 515 | Breaks through the 516 | 517 | 130 518 | 00:02:16,570 --> 00:02:18,400 519 | Breaks through the night 520 | 521 | 131 522 | 00:02:19,470 --> 00:02:19,640 523 | I 524 | 525 | 132 526 | 00:02:19,640 --> 00:02:19,870 527 | I move 528 | 529 | 133 530 | 00:02:19,870 --> 00:02:21,560 531 | I move on 532 | 533 | 134 534 | 00:02:21,890 --> 00:02:22,330 535 | For 536 | 537 | 135 538 | 00:02:22,330 --> 00:02:22,710 539 | Forev 540 | 541 | 136 542 | 00:02:22,710 --> 00:02:23,140 543 | Forever 544 | 545 | 137 546 | 00:02:23,140 --> 00:02:24,370 547 | Forever long 548 | 549 | 138 550 | 00:02:24,370 --> 00:02:24,790 551 | Forever longing 552 | 553 | 139 554 | 00:02:24,790 --> 00:02:26,010 555 | Forever longing for 556 | 557 | 140 558 | 00:02:26,010 --> 00:02:26,380 559 | Forever longing for the 560 | 561 | 141 562 | 00:02:26,380 --> 00:02:27,740 563 | Forever longing for the home 564 | 565 | 142 566 | 00:02:28,300 --> 00:02:28,380 567 | I 568 | 569 | 143 570 | 00:02:28,380 --> 00:02:28,840 571 | I found 572 | 573 | 144 574 | 00:02:28,840 --> 00:02:29,480 575 | I found in 576 | 577 | 145 578 | 00:02:29,480 --> 00:02:29,670 579 | I found in your 580 | 581 | 146 582 | 00:02:29,670 --> 00:02:31,850 583 | I found in your eyes 584 | 585 | 147 586 | 00:02:32,100 --> 00:02:32,930 587 | Your 588 | 589 | 148 590 | 00:02:32,930 --> 00:02:34,060 591 | Your voice 592 | 593 | 149 594 | 00:02:34,530 --> 00:02:35,020 595 | saw 596 | 597 | 150 598 | 00:02:35,020 --> 00:02:35,320 599 | saw me 600 | 601 | 151 602 | 00:02:35,320 --> 00:02:35,800 603 | saw me through 604 | 605 | 152 606 | 00:02:35,800 --> 00:02:36,140 607 | saw me through the 608 | 609 | 153 610 | 00:02:36,140 --> 00:02:39,530 611 | saw me through the night 612 | --------------------------------------------------------------------------------