feat(subtitles): working video subtitles. TODO: enable/disable subtitles option
This commit is contained in:
@@ -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
|
||||
@@ -0,0 +1 @@
|
||||
uid://curj8kp4ivly0
|
||||
@@ -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
|
||||
@@ -0,0 +1 @@
|
||||
uid://dnjyb6nrg1s02
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
8
gymkhana/videos/turno_cocina/intro.en.srt
Normal file
8
gymkhana/videos/turno_cocina/intro.en.srt
Normal 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
|
||||
|
||||
13
gymkhana/videos/turno_cocina/intro.es.srt
Normal file
13
gymkhana/videos/turno_cocina/intro.es.srt
Normal 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
|
||||
|
||||
Binary file not shown.
@@ -1 +0,0 @@
|
||||
uid://k2q0o6vc54p7
|
||||
Reference in New Issue
Block a user