feat(subtitles): working video subtitles. TODO: enable/disable subtitles option

This commit is contained in:
2025-09-27 00:12:44 +02:00
parent d3eff4e006
commit aa74656925
10 changed files with 197 additions and 4 deletions

View File

@@ -0,0 +1,17 @@
class_name SubtitleEntry
# Entry order. First is 1.
var id: int
# Start and end time in seconds.
var start_time: float
# End time in seconds.
var end_time: float
# Subtitle text.
var content: String
func _init(_id: int, _start_time: float, _end_time: float, _content: String) -> void:
id = _id
start_time = _start_time
end_time = _end_time
content = _content

View File

@@ -0,0 +1 @@
uid://curj8kp4ivly0

View File

@@ -0,0 +1,123 @@
@tool
class_name SubtitlesLabel
extends RichTextLabel
var _player: VideoStreamPlayer
var _subtitles: Array[SubtitleEntry] = []
var _current_entry: SubtitleEntry = null
var _template: String = ""
func _ready() -> void:
_player = $".."
func _process(_delta: float) -> void:
if _player and _player.visible and _player.is_playing() and not _subtitles.is_empty():
_update_content(_player.stream_position)
func _update_content(current_time: float) -> void:
if _current_entry and current_time > _current_entry.end_time:
_current_entry = null
text = ""
if _current_entry == null:
for entry in _subtitles:
if current_time >= entry.start_time and current_time <= entry.end_time:
_current_entry = entry
break
if _current_entry:
if _template.is_empty():
text = _current_entry.content
else:
text = _template % [_current_entry.content]
func parse_subtitles_file(path: String) -> Error:
_subtitles = []
var file := FileAccess.open(path, FileAccess.READ)
if not file:
return FAILED
var state := 0 # 0: read id, 1: read time, 2: read content
var current_id := 0
var start_time := 0.0
var end_time := 0.0
var content := ""
# Compile regex patterns
var time_regex = RegEx.new()
time_regex.compile("(?<start>[0-9,:]+)\\s*-->\\s*(?<end>[0-9,:]+)")
while not file.eof_reached():
var line := file.get_line().strip_edges()
match state:
0: # Read ID
if line.is_empty():
continue
current_id = line.to_int()
state = 1
1: # Read Time
if line.is_empty():
continue
var result = time_regex.search(line)
if result:
start_time = _parse_time_string(result.get_string("start"))
end_time = _parse_time_string(result.get_string("end"))
state = 2
2: # Read Content
if line.is_empty():
if not content.is_empty():
content = _process_content(content)
var entry = SubtitleEntry.new(current_id, start_time, end_time, content)
_subtitles.append(entry)
content = ""
state = 0
continue
if not content.is_empty():
content += "\n"
content += line
if not content.is_empty():
content = _process_content(content)
var entry = SubtitleEntry.new(current_id, start_time, end_time, content)
_subtitles.append(entry)
return OK
func _parse_time_string(time_str: String) -> float:
var time_split = time_str.replace(",", ".").split(":")
var hours := time_split[0].to_float()
var minutes := time_split[1].to_float()
var seconds := time_split[2].to_float()
return hours * 3600 + minutes * 60 + seconds
func _process_content(content: String) -> String:
content = content.replace("<b>", "[b]").replace("</b>", "[/b]").replace("{b}", "[b]").replace("{/b}", "[/b]")
content = content.replace("<i>", "[i]").replace("</i>", "[/i]").replace("{i}", "[i]").replace("{/i}", "[/i]")
content = content.replace("<u>", "[u]").replace("</u>", "[/u]").replace("{u}", "[u]").replace("{/u}", "[/u]")
content = content.replace("</font>", "[/color]")
var color_regex = RegEx.new()
color_regex.compile("<font\\s+color=[\"'](.+)[\"']>")
content = color_regex.sub(content, "[color=\\1]")
var line_pos_regex = RegEx.new()
line_pos_regex.compile("\\{\\\\a([0-9]+)\\}")
var matches = line_pos_regex.search_all(content)
for m in matches:
var count = m.get_string(1).to_int()
if count > 0:
content = content.replace(m.get_string(), "\n".repeat(count - 1))
else:
content = content.replace(m.get_string(), "")
return content

View File

@@ -0,0 +1 @@
uid://dnjyb6nrg1s02

View File

@@ -3,8 +3,9 @@ extends Node
signal finished
func play(video_file: String):
$VideoStreamPlayer.set_stream(load(video_file))
func play(video_path: String):
$VideoStreamPlayer.set_stream(load(video_path))
$VideoStreamPlayer/SubtitlesLabel.parse_subtitles_file(_get_srt_path(video_path))
$VideoStreamPlayer.play()
func _on_VideoPlayer_finished():
@@ -17,11 +18,21 @@ func skip():
self.visible = false
emit_signal("finished")
func get_player():
return $VideoStreamPlayer
func is_playing() -> bool:
var play = $VideoStreamPlayer.is_playing()
return play
func _on_Skip_pressed():
skip()
# Get subtitles file path from the video file path.
func _get_srt_path(video_path: String):
var locale = TranslationServer.get_locale()
return video_path.left(-3) + locale + ".srt"

View File

@@ -1,10 +1,13 @@
[gd_scene load_steps=5 format=3 uid="uid://ctg3fukoficqk"]
[gd_scene load_steps=7 format=3 uid="uid://ctg3fukoficqk"]
[ext_resource type="Script" uid="uid://ckl3iy3v3v68s" path="res://addons/escoria-ui-return-monkey-island/video_player/video_player.gd" id="1"]
[ext_resource type="Theme" uid="uid://bf2eet52fueam" path="res://addons/escoria-ui-return-monkey-island/theme/ui.tres" id="1_384st"]
[ext_resource type="Script" uid="uid://dnjyb6nrg1s02" path="res://addons/escoria-ui-return-monkey-island/video_player/subtitles/subtitles_label.gd" id="SubtitlesLabel"]
[sub_resource type="VideoStreamTheora" id="1"]
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_reqeu"]
[sub_resource type="Shortcut" id="3"]
[node name="video_player" type="Control"]
@@ -27,6 +30,23 @@ offset_right = 1280.0
offset_bottom = 720.0
stream = SubResource("1")
[node name="SubtitlesLabel" type="RichTextLabel" parent="VideoStreamPlayer"]
layout_mode = 1
anchors_preset = 12
anchor_top = 1.0
anchor_right = 1.0
anchor_bottom = 1.0
offset_top = -89.0
offset_bottom = -2.0
grow_horizontal = 2
grow_vertical = 0
theme_override_font_sizes/normal_font_size = 24
theme_override_styles/normal = SubResource("StyleBoxEmpty_reqeu")
autowrap_mode = 0
horizontal_alignment = 1
script = ExtResource("SubtitlesLabel")
metadata/_custom_type_script = "uid://dnjyb6nrg1s02"
[node name="PanelContainer" type="PanelContainer" parent="."]
layout_mode = 1
anchors_preset = 3

View File

@@ -0,0 +1,8 @@
1
00:00:00,000 --> 00:00:03,000
Test
2
00:00:05,000 --> 00:00:36,000
2

View File

@@ -0,0 +1,13 @@
1
00:00:00,000 --> 00:00:03,000
Test ES
2
00:00:05,000 --> 00:00:07,000
2 ES
2
00:00:08,000 --> 00:00:36,000
Aquí va una línea que es bastante larga, a ver que pasa con ella
Y una seguna línea de regalo

View File

@@ -1 +0,0 @@
uid://k2q0o6vc54p7