diff --git a/Las gymkhanikas de Uli.csproj b/Las gymkhanikas de Uli.csproj new file mode 100644 index 00000000..ba5a9d9c --- /dev/null +++ b/Las gymkhanikas de Uli.csproj @@ -0,0 +1,8 @@ + + + net8.0 + net9.0 + true + LasgymkhanikasdeUli + + \ No newline at end of file diff --git a/Las gymkhanikas de Uli.sln b/Las gymkhanikas de Uli.sln new file mode 100644 index 00000000..ae9cfc63 --- /dev/null +++ b/Las gymkhanikas de Uli.sln @@ -0,0 +1,19 @@ +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2012 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Las gymkhanikas de Uli", "Las gymkhanikas de Uli.csproj", "{9E3EA186-FBCB-4B4D-BF1C-89064EF61C6C}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + ExportDebug|Any CPU = ExportDebug|Any CPU + ExportRelease|Any CPU = ExportRelease|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {9E3EA186-FBCB-4B4D-BF1C-89064EF61C6C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9E3EA186-FBCB-4B4D-BF1C-89064EF61C6C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9E3EA186-FBCB-4B4D-BF1C-89064EF61C6C}.ExportDebug|Any CPU.ActiveCfg = ExportDebug|Any CPU + {9E3EA186-FBCB-4B4D-BF1C-89064EF61C6C}.ExportDebug|Any CPU.Build.0 = ExportDebug|Any CPU + {9E3EA186-FBCB-4B4D-BF1C-89064EF61C6C}.ExportRelease|Any CPU.ActiveCfg = ExportRelease|Any CPU + {9E3EA186-FBCB-4B4D-BF1C-89064EF61C6C}.ExportRelease|Any CPU.Build.0 = ExportRelease|Any CPU + EndGlobalSection +EndGlobal diff --git a/README.md b/README.md index 6acbfeed..53a79428 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,13 @@ Point-and-click adventure game developed using Escoria framework and Godot engin 2) Clone `escoria-demo-game` repo (develop branch) 3) Create `gymkhana/addons/escoria-core` symlink pointing to `escoria-demo-game/addons/escoria-core` +### Frog Subtitles + +Frog Subtitles is a C# plugin which requires some extra steps: + +1) Use Godot .Net version +2) Download .Net SDK v8+ (Ubuntu 24.04: `sudo apt install dotnet-sdk-8.0`) +3) Build C# solution in Godot: Project -> Tools -> C# -> Create C# solution ## Video export. - 1280 x 720 | 25fps diff --git a/addons/frog_subtitles/SubtitlesImportPlugin.cs b/addons/frog_subtitles/SubtitlesImportPlugin.cs new file mode 100644 index 00000000..3e960aa5 --- /dev/null +++ b/addons/frog_subtitles/SubtitlesImportPlugin.cs @@ -0,0 +1,176 @@ +#if TOOLS +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Text; +using System.Text.RegularExpressions; +using Godot; +using Godot.Collections; + +namespace Frog; + +public partial class SubtitlesImportPlugin : EditorImportPlugin +{ + private readonly static Regex timeRegex = new Regex(@"(?[0-9,:\.]+)\ -->\ (?[0-9,:\.]+)"); + private readonly static Regex colorRegex = new Regex(@""); + private readonly static Regex linePositionRegex = new Regex(@"\{\\a([0-9]+)\}"); + + public override string _GetImporterName() => "frog-subtitles.plugin"; + + public override string _GetVisibleName() => "Frog Subtitles"; + + public override string[] _GetRecognizedExtensions() => new string[] { "srt" }; + + public override string _GetSaveExtension() => "res"; + + public override string _GetResourceType() => "Resource"; + + public override int _GetPresetCount() => 1; + + public override float _GetPriority() => 1; + + public override int _GetImportOrder() => 0; + + public override string _GetPresetName(int presetIndex) => "Default"; + + public override Array _GetImportOptions(string path, int presetIndex) + { + return new Array(); + } + + public override Error _Import(string sourceFile, string savePath, Dictionary options, Array platformVariants, Array genFiles) + { + Error parseResult = Error.Ok; + ReaderState state = ReaderState.ReadId; + List entries = new(); + try + { + int currentId = 0; + TimeSpan startTime = TimeSpan.Zero; + TimeSpan endTime = TimeSpan.Zero; + StringBuilder content = new(); + + void RegisterEntry() + { + content.Replace("", "[b]").Replace("", "[/b]").Replace("{b}", "[b]").Replace("{/b}", "[/b]"); + content.Replace("", "[i]").Replace("", "[/i]").Replace("{i}", "[i]").Replace("{/i}", "[/i]"); + content.Replace("", "[u]").Replace("", "[/u]").Replace("{u}", "[u]").Replace("{/u}", "[/u]"); + content.Replace("", "[/color]"); + string contentString = content.ToString(); + contentString = colorRegex.Replace(contentString, match => $"[color={match.Groups[1].Value}]"); + contentString = linePositionRegex.Replace(contentString, match => + { + string result = string.Empty; + if (int.TryParse(match.Groups[1].Value, out int lineCount)) + { + for (int i = 1; i < lineCount; ++i) + { + result += '\n'; + } + } + else + { + Trace.TraceError($"Can't parse subtitles line position tag: {match.Value}"); + } + + return result; + }); + + entries.Add(new SubtitleEntry() + { + Id = currentId, + StartTime = startTime.TotalSeconds, + EndTime = endTime.TotalSeconds, + Content = contentString, + }); + + content.Clear(); + } + + using (StreamReader reader = File.OpenText(ProjectSettings.GlobalizePath(sourceFile))) + { + while (!reader.EndOfStream) + { + string line = reader.ReadLine(); + switch (state) + { + case ReaderState.ReadId: + if (!int.TryParse(line, out currentId)) + { + Trace.TraceError($"Can't parse subtitles id: {line}"); + parseResult = Error.Failed; + } + + state = ReaderState.ReadTime; + break; + + case ReaderState.ReadTime: + Match match = SubtitlesImportPlugin.timeRegex.Match(line); + if (!match.Success || + !TimeSpan.TryParse(match.Groups["start"].Value.Replace(',', '.'), out startTime) || + !TimeSpan.TryParse(match.Groups["end"].Value.Replace(',', '.'), out endTime)) + { + Trace.TraceError($"Can't parse subtitles time: {line}"); + parseResult = Error.Failed; + } + + state = ReaderState.ReadContent; + break; + + case ReaderState.ReadContent: + if (string.IsNullOrEmpty(line)) + { + // End of current entry. Store it and clear buffers. + RegisterEntry(); + state = ReaderState.ReadId; + break; + } + + if (content.Length > 0) + { + content.Append('\n'); + } + + content.Append(line); + break; + } + } + + // Register last entry (no need to end a line, end of file is enough) + if (content.Length > 0) + { + RegisterEntry(); + } + } + } + catch + { + return Error.Failed; + } + + if (parseResult != Error.Ok) + { + return parseResult; + } + + Debug.WriteLine($"{sourceFile} parsed. Found {entries.Count} subtitles entries."); + entries.Sort((left, right) => left.Id.CompareTo(right.Id)); + var subtitles = new Subtitles() + { + Entries = new Array(entries), + }; + + string filename = $"{savePath}.{this._GetSaveExtension()}"; + return ResourceSaver.Save(subtitles, filename); + } + + private enum ReaderState + { + ReadId, + ReadTime, + ReadContent, + } +} + +#endif diff --git a/addons/frog_subtitles/SubtitlesImportPlugin.cs.uid b/addons/frog_subtitles/SubtitlesImportPlugin.cs.uid new file mode 100644 index 00000000..053a41c4 --- /dev/null +++ b/addons/frog_subtitles/SubtitlesImportPlugin.cs.uid @@ -0,0 +1 @@ +uid://cp8l0est28gtv diff --git a/addons/frog_subtitles/SubtitlesPlugin.cs b/addons/frog_subtitles/SubtitlesPlugin.cs new file mode 100644 index 00000000..bcd1571b --- /dev/null +++ b/addons/frog_subtitles/SubtitlesPlugin.cs @@ -0,0 +1,34 @@ +#if TOOLS +using Godot; + +namespace Frog; + +[Tool] +public partial class SubtitlesPlugin : EditorPlugin +{ + private SubtitlesImportPlugin? importPlugin; + + public override void _EnterTree() + { + Script videoStreamSubtitlesScript = GD.Load