Skip to content

Instantly share code, notes, and snippets.

@wastee
Created January 14, 2021 01:42
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save wastee/b3184227e51b3efc8c45a4a6daa0e683 to your computer and use it in GitHub Desktop.
Save wastee/b3184227e51b3efc8c45a4a6daa0e683 to your computer and use it in GitHub Desktop.
REAPER自动化字幕生成脚本
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)
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