Created
January 14, 2021 01:42
-
-
Save wastee/b3184227e51b3efc8c45a4a6daa0e683 to your computer and use it in GitHub Desktop.
REAPER自动化字幕生成脚本
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import os | |
import re | |
import uuid | |
from reaper_python import * | |
from pathlib import Path | |
RPR_Undo_BeginBlock() | |
RPR_ClearConsole() | |
reaper_resource_path = RPR_GetResourcePath() | |
def p(msg): | |
RPR_ShowConsoleMsg(str(msg) + '\n') | |
def mb(msg): | |
RPR_MB(str(msg), '错误提示', 0) | |
# item = RPR_GetSelectedMediaItem(0, 0) | |
# chunk = RPR_GetItemStateChunk(item, '', 10**5, True) | |
# p(chunk[2]) | |
def is_int(str): | |
try: | |
int(str) | |
return True | |
except: | |
return False | |
def is_empty_item(item): | |
take = RPR_GetActiveTake(item) | |
# 从active take判断是否为empty item | |
if take != '(MediaItem_Take*)0x0000000000000000': | |
return False | |
else: | |
return True | |
def all_tracks_dict(): | |
# 获取工程轨道字典 | |
# guid作为key, track和depth作为值 | |
tracks_count = RPR_CountTracks(0) | |
if tracks_count != 0: | |
tracks_dict = {} | |
for i in range(tracks_count): | |
the_track = RPR_GetTrack(0, i) | |
the_track_guid = RPR_GetTrackGUID(the_track) | |
the_track_depth = RPR_GetMediaTrackInfo_Value(the_track, 'I_FOLDERDEPTH') | |
the_track_name = RPR_GetTrackName(the_track, 'Track', 100)[2] | |
tracks_dict[the_track_guid] = [the_track, the_track_depth, the_track_name] | |
return tracks_dict | |
else: | |
return None | |
def jsfx_subtitle_template(fxid='', text='', font='Arial', font_color='1.0000000000'): | |
t1 = f"""<TAKEFX | |
WNDRECT 406 203 809 425 | |
SHOW 0 | |
LASTSEL 0 | |
DOCKED 0 | |
BYPASS 0 0 0 | |
<VIDEO_EFFECT "Video processor" "" | |
<CODE | |
|// Text overlay | |
|#text="{text}"; // set to string to override | |
|font="{font}";""" | |
t2 = """ | |
|//@param1:size 'text height' 0.05 0.01 0.2 0.1 0.001 | |
|//@param2:ypos 'y position' 0.95 0 1 0.5 0.01 | |
|//@param3:xpos 'x position' 0 0 1 0.5 0.01 | |
|//@param4:border 'border' 0 0 1 0.5 0.01 | |
|//@param5:fgc 'text bright' 1.0 0 1 0.5 0.01 | |
|//@param6:fga 'text alpha' 1.0 0 1 0.5 0.01 | |
|//@param7:bgc 'bg bright' 0.75 0 1 0.5 0.01 | |
|//@param8:bga 'bg alpha' 0.5 0 1 0.5 0.01 | |
|//@param10:ignoreinput 'ignore input' 0 0 1 0.5 1 | |
| | |
|input = ignoreinput ? -2:0; | |
|project_wh_valid===0 ? input_info(input,project_w,project_h); | |
|gfx_a2=0; | |
|gfx_blit(input,1); | |
|gfx_setfont(size*project_h,font); | |
|strcmp(#text,"")==0 ? input_get_name(-1,#text); | |
|gfx_str_measure(#text,txtw,txth); | |
|yt = (project_h- txth*(1+border*2))*ypos; | |
|gfx_set(bgc,bgc,bgc,bga); | |
|gfx_fillrect(0, yt, project_w, txth*(1+border*2)); | |
|gfx_set(fgc,fgc,fgc,fga); | |
|gfx_str_draw(#text,xpos * (project_w-txtw),yt+txth*border); | |
> | |
CODEPARM 0.0800000000 0.9500000000 0.5000000000 0.0000000000 """+font_color+""" 1.0000000000 0.7500000000 0.5 0.0000000000 0.0000000000 0.0000000000 0.0000000000 0.0000000000 0.0000000000 0.0000000000 0.0000000000 0.0000000000 0.0000000000 0.0000000000 0.0000000000 0.0000000000 0.0000000000 0.0000000000 0.0000000000 | |
> | |
FLOATPOS 0 0 0 0 | |
FXID {"""+fxid+"""} | |
WAK 0 0 | |
>""" | |
return t1+t2 | |
# 字幕轨名字 | |
subtrack_name = 'subtitle_auto' | |
# 选择轨道只能唯一 | |
if RPR_CountSelectedTracks(0) < 1: | |
mb('必须选择一条轨道') | |
elif RPR_CountSelectedTracks(0) > 1: | |
mb('只能选择一条轨道') | |
else: | |
the_track = RPR_GetSelectedTrack(0, 0) | |
the_track_guid = RPR_GetTrackGUID(the_track) | |
the_track_depth = RPR_GetMediaTrackInfo_Value(the_track, 'I_FOLDERDEPTH') | |
the_track_index = RPR_CSurf_TrackToID(the_track, False) | |
# 保留原始轨道名称作变量使用 | |
ori_track_name = RPR_GetTrackName(the_track, 'Track', 100)[2] | |
# 全选该轨道所有item | |
RPR_Main_OnCommand(40421, 0) | |
# 检查选中item数量 | |
selected_item_count = RPR_CountSelectedMediaItems(0) | |
if selected_item_count == 0: | |
mb('轨道必须有至少一个empty item') | |
else: | |
other_item_count = 0 | |
ori_items = [] | |
for i in range(selected_item_count): | |
the_item = RPR_GetSelectedMediaItem(0, i) | |
ori_items.append(the_item) | |
# 判断该item是否为empty item | |
if is_empty_item(the_item) is False: | |
other_item_count += 1 | |
# 判断是否只存在empty item | |
if other_item_count != 0: | |
mb('只允许empty item') | |
elif other_item_count == 0: | |
# empty item条件满足 | |
# 删除名字和字幕轨一样的轨道 | |
for i in range(RPR_CountTracks(0)): | |
tr = RPR_GetTrack(0, i) | |
tr_name = RPR_GetTrackName(tr, 'Track', 100)[2] | |
if tr_name == subtrack_name: | |
RPR_DeleteTrack(tr) | |
# 选择上面保存的 the track | |
RPR_SetTrackSelected(the_track, True) | |
# 添加一条子轨道 | |
the_track_id = RPR_CSurf_TrackToID(the_track, False) | |
RPR_InsertTrackAtIndex(the_track_id,True) | |
next_track = RPR_GetTrack(0, the_track_id) | |
RPR_GetSetMediaTrackInfo_String(next_track, 'P_NAME', subtrack_name, True) | |
# 遍历原始item | |
# 做成一个 start, end, length 的list | |
item_info_list = [] | |
for item in ori_items: | |
start_time = RPR_GetMediaItemInfo_Value(item, 'D_POSITION') | |
length_time = RPR_GetMediaItemInfo_Value(item, 'D_LENGTH') | |
end_time = start_time + length_time | |
item_info_list.append([start_time, length_time, end_time]) | |
# 断言组数据量正确 | |
assert len(item_info_list) == selected_item_count | |
# 创建对应的midi item | |
for info in item_info_list: | |
RPR_CreateNewMIDIItemInProj(next_track, info[0], info[2], False) | |
# 选择next_track的所有item | |
RPR_Main_OnCommand(40297, 0) # 不选所有轨道 | |
RPR_SetTrackSelected(next_track, True) | |
RPR_Main_OnCommand(40421, 0) | |
# next_track 中的 midi item | |
# 即jxfx的item | |
selected_item_count = RPR_CountSelectedMediaItems(0) | |
jsfx_items = [] | |
jsfx_note = [] | |
for i in range(selected_item_count): | |
the_item = RPR_GetSelectedMediaItem(0, i) | |
jsfx_items.append(the_item) | |
# 读取note item的note | |
for item in ori_items: | |
get_item_chunk = RPR_GetItemStateChunk(item, '', 10**5, False) | |
the_chunk = the_chunk = get_item_chunk[2].strip() | |
notes = re.findall(r'\<NOTES(.*?)\>', the_chunk, re.DOTALL) | |
# 限定只能一句 | |
if notes == []: | |
notes = [''] | |
assert len(notes) == 1 | |
notes = notes[0].strip()[1:] | |
jsfx_note.append(notes) | |
# 替换写入chunk | |
for i, item in enumerate(jsfx_items): | |
get_item_chunk = RPR_GetItemStateChunk(item, '', 10**5, False) | |
the_chunk = get_item_chunk[2].strip() | |
get_chunk_status = get_item_chunk[0] | |
# 获取和替换chunk内容 | |
the_chunk = the_chunk[:-1] | |
# 检查原轨道命名 | |
# 格式化字体名称与大小 | |
if '|' in ori_track_name: | |
tr = ori_track_name.split('|') | |
if tr[1] == 'b': | |
font_color = '0' | |
elif tr[1] == 'w': | |
font_color = '1' | |
the_chunk += jsfx_subtitle_template( | |
fxid=str(uuid.uuid4()), | |
text=jsfx_note[i], | |
font=tr[0], | |
font_color=font_color, | |
) | |
else: | |
the_chunk += jsfx_subtitle_template( | |
fxid=str(uuid.uuid4()), | |
text=jsfx_note[i], | |
font='Arial' | |
) | |
the_chunk += '>' | |
# 写入chunk | |
set_chunk_status = RPR_SetItemStateChunk(item, the_chunk, False) | |
# 刷新UI | |
# RPR_UpdateArrange() | |
RPR_Undo_EndBlock('生成字幕轨道', -1) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from reaper_python import * | |
from tkinter import * | |
import tkinter.font as font | |
RPR_Undo_BeginBlock() | |
RPR_ClearConsole() | |
def p(msg): | |
RPR_ShowConsoleMsg(str(msg) + '\n') | |
def mb(msg): | |
RPR_MB(str(msg), '错误提示', 0) | |
def is_empty_item(item): | |
take = RPR_GetActiveTake(item) | |
# 从active take判断是否为empty item | |
if take != '(MediaItem_Take*)0x0000000000000000': | |
return False | |
else: | |
return True | |
class CustomText(Text): | |
def __init__(self, *args, **kwargs): | |
Text.__init__(self, *args, **kwargs) | |
# create a proxy for the underlying widget | |
self._orig = self._w + "_orig" | |
self.tk.call("rename", self._w, self._orig) | |
self.tk.createcommand(self._w, self._proxy) | |
def _proxy(self, *args): | |
# let the actual widget perform the requested action | |
cmd = (self._orig,) + args | |
result = self.tk.call(cmd) | |
# generate an event if something was added or deleted, | |
# or the cursor position changed | |
if (args[0] in ("insert", "replace", "delete") or | |
args[0:3] == ("mark", "set", "insert") or | |
args[0:2] == ("xview", "moveto") or | |
args[0:2] == ("xview", "scroll") or | |
args[0:2] == ("yview", "moveto") or | |
args[0:2] == ("yview", "scroll") | |
): | |
self.event_generate("<<Change>>", when="tail") | |
# return what the actual widget returned | |
return result | |
class TextLineNumbers(Canvas): | |
def __init__(self, *args, **kwargs): | |
Canvas.__init__(self, *args, **kwargs) | |
self.textwidget = None | |
def attach(self, text_widget): | |
self.textwidget = text_widget | |
def redraw(self, *args): | |
'''redraw line numbers''' | |
self.delete("all") | |
i = self.textwidget.index("@0,0") | |
while True : | |
dline= self.textwidget.dlineinfo(i) | |
if dline is None: break | |
y = dline[1] | |
linenum = str(i).split(".")[0] | |
self.create_text(2,y,anchor="nw", text=linenum) | |
i = self.textwidget.index("%s+1line" % i) | |
# 检查选中item数量 | |
text_content = '' | |
selected_item_count = RPR_CountSelectedMediaItems(0) | |
if selected_item_count == 0: | |
mb('必须选择至少一个item') | |
else: | |
selected_items = [RPR_GetSelectedMediaItem(0, i) for i in range(selected_item_count)] | |
# 检查item所属轨道是否唯一 | |
item_parent_tracks = [RPR_GetMediaItemTrack(x) for x in selected_items] | |
if len(set(item_parent_tracks)) != 1: | |
mb('只能选择同一轨道上的item') | |
else: | |
# 遍历这些item | |
other_item_count = 0 | |
for i in range(selected_item_count): | |
the_item = RPR_GetSelectedMediaItem(0, i) | |
# 判断该item是否为empty item | |
if is_empty_item(the_item) is False: | |
other_item_count += 1 | |
# 判断是否只存在empty item | |
if other_item_count != 0: | |
mb('只允许empty item') | |
elif other_item_count == 0: | |
# 只存在empty item为真,开启tkinter | |
root = Tk() | |
root.title('输入字幕文本,一行一句') | |
root.geometry('600x400') | |
f1 = Frame(root) | |
f2 = Frame(root) | |
f3 = Frame(root) | |
# 全局变量,赋值输入内容 | |
def getTextInput(): | |
global text_content | |
text_content = text.get('1.0', 'end-1c').split('\n') | |
root.destroy() | |
# 按钮 | |
confirm_button = Button(f1, text='确定', command=lambda : getTextInput(), state=DISABLED) | |
cancel_button = Button(f1, text='取消', command=root.destroy) | |
# 输入框 | |
scrollbar = Scrollbar(f2) | |
text = CustomText(f2, width=40, height=4, yscrollcommand=scrollbar.set) | |
text['font'] = font.Font(size=12) | |
scrollbar.config(command=text.yview, width='14') | |
scrollbar.pack(side=RIGHT, fill=Y) | |
linenumbers = TextLineNumbers(f2, width=20) | |
linenumbers.attach(text) | |
linenumbers.pack(side='left', fill='y') | |
# 提示label | |
label = Label(f3, anchor='w') | |
label['font'] = font.Font(size=10) | |
# pack | |
confirm_button.pack(side=LEFT) | |
cancel_button.pack(side=LEFT) | |
text.pack(side=TOP, fill='both', expand=True) | |
label.pack(side=BOTTOM, padx=(20, 0)) | |
f1.pack(side=TOP, anchor=NW) | |
f2.pack(side=TOP, fill='both', expand=True) | |
f3.pack(side=BOTTOM, anchor=NW) | |
def onModification(event, button=confirm_button, item_count=selected_item_count): | |
lines = int(event.widget.index('end-1c').split('.')[0]) | |
if lines == item_count: | |
button.config(state=ACTIVE) | |
else: | |
button.config(state=DISABLED) | |
label.configure(text=f'{lines} 行 / {item_count} items') | |
linenumbers.redraw() | |
# 给Text做全选方法 | |
def select_all(event): | |
event.widget.tag_add(SEL, "1.0", END) | |
event.widget.mark_set(INSERT, "1.0") | |
event.widget.see(INSERT) | |
return 'break' | |
# Text的监测 | |
text.bind('<<Change>>', onModification) | |
text.bind('<Configure>', onModification) | |
text.bind('<Control-KeyRelease-a>', select_all) | |
# 开启窗口 | |
root.mainloop() | |
# 如果内容不完全为空 | |
if selected_item_count == len(text_content): | |
# 遍历填入文字 | |
for i, text in enumerate(text_content): | |
the_item = RPR_GetSelectedMediaItem(0, i) | |
RPR_GetSetMediaItemInfo_String(the_item, 'P_NOTES', text, True) | |
else: | |
# 不做任何操作 | |
pass | |
# 刷新UI | |
RPR_UpdateArrange() | |
RPR_Undo_EndBlock('填写字幕生成empty item', -1) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment