Skip to content

Commit d3d577e

Browse files
authored
Add files via upload
0 parents  commit d3d577e

6 files changed

+377
-0
lines changed

README.md

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# VGal
2+
#### 介绍:
3+
本项目主要用于实现静态视频播放的体验优化。通过视频分段播放,获得近似视觉小说的体验。
4+
5+
#### 注意:
6+
请勿用于非法用途,
7+
请勿用于已正常移植的游戏,
8+
请勿未经汉化组允许而录屏其译的游戏,
9+
请勿未经录屏者允许而用其录屏。
10+
11+
#### 建议:
12+
通过magpie缩放,让录屏更高清。
13+
14+
#### 原理:
15+
PC(供给端):负责录屏,处理分割点。通过OCR识别视频各位置文本,进而获取视频分割点。
16+
17+
安卓(使用端):通过分割点,实现视频分段播放(点击屏幕,跳转到下一段视频,也就是下一段话),获得类似移植版的体验。
18+
19+
#### 安装:
20+
21+
> pip uninstall opencv-python opencv-python-headless
22+
> pip install opencv-contrib-python easyocr Pillow difflib json torch torchvision torchaudio
23+
24+
在C:\Users\你的用户名\.EasyOCR\model中,安装craft_mlt_25k,english_g2,zh_sim_g2的PTH文件
25+
26+
安卓源码:https://github.com/Linyoux/VGal/tree/main/Android/app/src/main/java/client/vgal

confirmRegion.py

+89
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import time
2+
import tkinter as tk
3+
from PIL import Image, ImageTk
4+
5+
import videoprocess
6+
7+
file_path = ""
8+
region = None
9+
root = None
10+
11+
intervalEntry = None
12+
similarityEntry = None
13+
14+
def button1_action():
15+
global intervalEntry,similarityEntry,root
16+
17+
interval = float(intervalEntry.get())
18+
similarityEntry = float(similarityEntry.get())
19+
20+
root.destroy()
21+
start_time = time.time() # 获取当前时间戳
22+
text_groups = videoprocess.process_video(file_path,region,interval,similarityEntry)
23+
24+
25+
if "/" in file_path:
26+
file_name = file_path[file_path.rindex("/") + 1:]
27+
else:
28+
file_name = file_path
29+
videoprocess.write_to_script(file_name,text_groups,"start.vgs")
30+
31+
end_time = time.time() # 获取当前时间戳
32+
elapsed_time = end_time - start_time # 计算耗时
33+
print("处理完成")
34+
print(f"耗时: {elapsed_time:.4f} 秒")
35+
36+
def reset_action():
37+
exit()
38+
39+
40+
def show_confirmWindow(tknode,crop_area,file):
41+
global file_path,region,root,similarityEntry,intervalEntry
42+
region = crop_area
43+
file_path = file
44+
root = tk.Toplevel(tknode)
45+
root.title("区域确认")
46+
47+
# 加载图片对象
48+
49+
image_path = "current_frame.jpg" # 替换为你的图片路径
50+
pil_image = Image.open(image_path)
51+
cropped_image = pil_image.crop(crop_area)
52+
tk_image = ImageTk.PhotoImage(cropped_image)
53+
#
54+
# # 创建标签显示图片
55+
image_label = tk.Label(root, image=tk_image)
56+
image_label.grid(row=0, column=0, columnspan=2, padx=20, pady=20)
57+
58+
label = tk.Label(root, text="处理间隔(单位:秒):")
59+
label.grid(row=1, column=0, pady=10 ) # 添加一些垂直间距
60+
61+
# 创建一个输入框
62+
default_value = tk.StringVar()
63+
default_value.set("0.5") # 设置默认值
64+
intervalEntry = tk.Entry(root,textvariable=default_value)
65+
intervalEntry.grid(row=1, column=1, pady=10)
66+
67+
68+
label2 = tk.Label(root, text="文本相似度:")
69+
label2.grid(row=2, column=0, pady=10 ) # 添加一些垂直间距
70+
71+
default_value = tk.StringVar()
72+
default_value.set("0.6") # 设置默认值
73+
# 创建一个输入框
74+
similarityEntry = tk.Entry(root,textvariable=default_value)
75+
similarityEntry.grid(row=2, column=1, pady=10 )
76+
77+
# 创建按钮
78+
button1 = tk.Button(root, text="确定",command=button1_action)
79+
button1.grid(row=3, column=0, pady=10 )
80+
81+
button2 = tk.Button(root, text="取消", command=reset_action)
82+
button2.grid(row=3, column=1, pady=10)
83+
84+
# 调整行和列的权重,使组件能够在水平和垂直方向上平均分布
85+
root.rowconfigure(0, weight=1)
86+
root.columnconfigure(0, weight=1)
87+
root.columnconfigure(1, weight=1)
88+
89+
root.mainloop()

python依赖安装.bat

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
pip uninstall opencv-python opencv-python-headless
2+
pip install opencv-contrib-python easyocr Pillow difflib json torch torchvision torchaudio

videoprocess.py

+147
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
# encoding:utf-8
2+
import re
3+
4+
import cv2
5+
import easyocr
6+
from difflib import SequenceMatcher
7+
import json
8+
9+
class TextGroup:
10+
def __init__(self):
11+
self.texts = [] # 文本列表
12+
self.timestamps = [] # 时间戳列表
13+
14+
15+
def calculate_similarity(str1, str2):
16+
"""计算两个字符串的相似度"""
17+
return SequenceMatcher(None, str1, str2).ratio()
18+
19+
def filter(text):
20+
japanese_pattern = r'[\u3040-\u309F\u30A0-\u30FF\u4E00-\u9FA5]'
21+
chinese_pattern = r'[\u4e00-\u9fa5]'
22+
23+
newtext = re.sub(japanese_pattern, "", text)
24+
newtext = re.sub(chinese_pattern, "", newtext)
25+
26+
return text != newtext
27+
28+
def format_timestamp(seconds):
29+
"""将秒数转换为 HH:MM:SS.milliseconds 格式"""
30+
hours = int(seconds // 3600)
31+
minutes = int((seconds % 3600) // 60)
32+
secs = int(seconds % 60)
33+
millis = int((seconds - int(seconds)) * 1000) # 获取毫秒部分
34+
return f"{hours:02d}:{minutes:02d}:{secs:02d}.{millis:03d}"
35+
36+
37+
def process_video(video_path, region, interval=0.5, similarity_threshold=0.6):
38+
"""处理视频并提取文本"""
39+
reader = easyocr.Reader(['ch_sim', 'en'],download_enabled=False)
40+
41+
cap = cv2.VideoCapture(video_path)
42+
fps = cap.get(cv2.CAP_PROP_FPS)
43+
total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
44+
45+
text_groups = []
46+
last_text = ""
47+
48+
interval_frames = int(fps * interval)
49+
50+
for frame_num in range(0, total_frames, interval_frames):
51+
current_timestamp = frame_num / fps
52+
cap.set(cv2.CAP_PROP_POS_FRAMES, frame_num)
53+
print(f"正在处理第{frame_num}/{total_frames}帧")
54+
55+
56+
57+
ret, frame = cap.read()
58+
if not ret:
59+
break
60+
61+
roi = frame[region[1]:region[3],
62+
region[0]:region[2]]
63+
64+
results = reader.readtext(roi)
65+
current_text = " ".join([result[1] for result in results])
66+
67+
if not current_text or not filter(current_text):
68+
continue
69+
70+
if not text_groups:
71+
new_group = TextGroup()
72+
new_group.texts.append(current_text)
73+
new_group.timestamps.append(current_timestamp)
74+
text_groups.append(new_group)
75+
last_text = current_text
76+
continue
77+
78+
similarity = calculate_similarity(last_text, current_text)
79+
80+
if similarity >= similarity_threshold:
81+
text_groups[-1].texts.append(current_text)
82+
text_groups[-1].timestamps.append(current_timestamp)
83+
else:
84+
new_group = TextGroup()
85+
new_group.texts.append(current_text)
86+
new_group.timestamps.append(current_timestamp)
87+
text_groups.append(new_group)
88+
89+
last_text = current_text
90+
#
91+
# if frame_num > 50:
92+
# break
93+
94+
cap.release()
95+
96+
return text_groups
97+
98+
def write_to_script(videoName,text_groups, output_file):
99+
with open(output_file,"w",encoding="utf-8") as f:
100+
f.write("play " + videoName)
101+
f.write("\n")
102+
f.write("proc")
103+
f.write("\n")
104+
f.write("\n")
105+
106+
for i, group in enumerate(text_groups, 1):
107+
time = round(group.timestamps[-1],2)
108+
text = group.texts[-1]
109+
110+
f.write("text " + text)
111+
f.write("\n")
112+
f.write("time " + str(time))
113+
f.write("\n")
114+
f.write("proc\n\n")
115+
116+
117+
118+
def write_to_file(text_groups, output_file):
119+
dats = []
120+
"""将文本分组写入文件"""
121+
with open(output_file, 'w', encoding='utf-8') as f:
122+
for i, group in enumerate(text_groups, 1):
123+
f.write(f"Group {i}:\n")
124+
for j, (text, timestamp) in enumerate(zip(group.texts, group.timestamps)):
125+
f.write(f" Timestamp: {format_timestamp(timestamp)} - Text: {text}\n")
126+
f.write("\n")
127+
128+
dats.append({
129+
"time": round(group.timestamps[-1],2) -0.05,
130+
"text": group.texts[-1]
131+
})
132+
with open("script.json","w",encoding="utf-16") as f:
133+
json.dump(dats,f,indent=4,ensure_ascii=False)
134+
135+
def main():
136+
video_path = 'b.mp4' # 替换为你的视频路径
137+
output_file = 'output.txt' # 输出文件名
138+
region = [00, 0, 2560, 496] # 示例区域,根据实际需求调整
139+
140+
text_groups = process_video(video_path, region,0.5)
141+
write_to_file(text_groups, output_file)
142+
143+
print(f"结果已写入 {output_file}")
144+
145+
146+
if __name__ == "__main__":
147+
main()

videoselect.py

+112
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
import cv2
2+
import tkinter as tk
3+
from tkinter import filedialog
4+
import confirmRegion
5+
import sys
6+
rect_start = None
7+
rect_end = None
8+
drawing = False
9+
paused = False
10+
current_frame = None
11+
root = None
12+
running = False
13+
file_path = None
14+
15+
def select_video():
16+
global root,file_path
17+
root = tk.Tk()
18+
root.withdraw() # 隐藏主窗口
19+
file_path = filedialog.askopenfilename(filetypes=[("Video Files", "*.mp4 *.avi *.mov *.mkv")])
20+
if file_path:
21+
preview_video(file_path)
22+
root.destroy()
23+
24+
25+
def draw_rectangle(event, x, y, flags, param):
26+
global rect_start, rect_end, drawing, current_frame, running, file_path
27+
28+
if event == cv2.EVENT_LBUTTONDOWN:
29+
drawing = True
30+
rect_start = (x, y)
31+
rect_end = rect_start
32+
33+
elif event == cv2.EVENT_MOUSEMOVE:
34+
if drawing:
35+
# 使用当前帧的副本来绘制矩形
36+
temp_frame = current_frame.copy()
37+
rect_end = (x, y)
38+
cv2.rectangle(temp_frame, rect_start, rect_end, (255, 0, 0), 2)
39+
cv2.imshow("Video Preview", temp_frame)
40+
41+
elif event == cv2.EVENT_LBUTTONUP:
42+
drawing = False
43+
rect_end = (x, y)
44+
45+
# 排序坐标
46+
x1, y1 = rect_start
47+
x2, y2 = rect_end
48+
left = min(x1, x2)
49+
right = max(x1, x2)
50+
top = min(y1, y2)
51+
bottom = max(y1, y2)
52+
53+
# 更新为排序后的区域
54+
rect_start = (left, top)
55+
rect_end = (right, bottom)
56+
57+
cv2.rectangle(current_frame, rect_start, rect_end, (255, 0, 0), 2)
58+
cv2.imshow("Video Preview", current_frame)
59+
60+
# 选择完区域后直接退出程序
61+
if rect_start and rect_end:
62+
print("选择的区域坐标:", rect_start, rect_end)
63+
cv2.imwrite('current_frame.jpg', current_frame)
64+
cv2.destroyAllWindows() # 关闭窗口
65+
running = False
66+
confirmRegion.show_confirmWindow(root, (left, top, right, bottom), file_path)
67+
exit() # 退出程序
68+
69+
70+
def preview_video(file_path):
71+
global current_frame, paused, running
72+
cap = cv2.VideoCapture(file_path)
73+
74+
if not cap.isOpened():
75+
print("Error opening video file")
76+
return
77+
78+
# 创建一个全屏窗口
79+
cv2.namedWindow("Video Preview", cv2.WND_PROP_FULLSCREEN)
80+
cv2.setWindowProperty("Video Preview", cv2.WND_PROP_FULLSCREEN, cv2.WINDOW_FULLSCREEN)
81+
cv2.setMouseCallback("Video Preview", draw_rectangle)
82+
running = True
83+
84+
while running:
85+
if not paused:
86+
ret, current_frame = cap.read()
87+
if not ret:
88+
cap.set(cv2.CAP_PROP_POS_FRAMES, 0) # 重置视频到开头
89+
continue
90+
91+
# 显示视频帧
92+
cv2.imshow("Video Preview", current_frame)
93+
94+
# 处理键盘输入
95+
key = cv2.waitKey(25) & 0xFF
96+
if key == 27: # ESC键
97+
break
98+
elif key == 32: # 空格键
99+
paused = not paused
100+
print("暂停状态:", "已暂停" if paused else "已恢复")
101+
102+
# 检查窗口是否关闭
103+
if cv2.getWindowProperty("Video Preview", cv2.WND_PROP_VISIBLE) < 1:
104+
break
105+
106+
cap.release()
107+
cv2.destroyAllWindows()
108+
109+
110+
111+
if __name__ == "__main__":
112+
select_video()

开始处理.bat

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
python videoselect.py

0 commit comments

Comments
 (0)