Skip to content

Commit 25c3baf

Browse files
committed
Add "Commas" plugin for fixing subtitle commas with LM Studio
Introduced the "Commas" plugin, including its main logic, UI, and integration with LM Studio API to process subtitles and fix comma-related issues. Updated the solution and project files to include the new plugin. Additionally, upgraded the "libse" package to version 4.0.10 to support the plugin.
1 parent 374dfea commit 25c3baf

File tree

8 files changed

+556
-1
lines changed

8 files changed

+556
-1
lines changed

source/Commas/Commas.csproj

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>net48</TargetFramework>
5+
<ImplicitUsings>enable</ImplicitUsings>
6+
<Nullable>enable</Nullable>
7+
<UseWindowsForms>true</UseWindowsForms>
8+
</PropertyGroup>
9+
10+
<ItemGroup>
11+
<Compile Update="Main.cs">
12+
<SubType>Form</SubType>
13+
</Compile>
14+
</ItemGroup>
15+
16+
<ItemGroup>
17+
<PackageReference Include="libse" PrivateAssets="all"/>
18+
<PackageReference Include="Newtonsoft.Json" PrivateAssets="all"/>
19+
</ItemGroup>
20+
21+
</Project>

source/Commas/EntryPoint.cs

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
using System.Diagnostics;
2+
using Commas;
3+
using Nikse.SubtitleEdit.Core.Common;
4+
using Nikse.SubtitleEdit.Core.SubtitleFormats;
5+
6+
namespace Nikse.SubtitleEdit.PluginLogic;
7+
8+
/// <summary>
9+
/// The Commas class is a plugin designed for the Subtitle Edit application.
10+
/// Its purpose is to process and fix comma-related issues in subtitle files.
11+
/// </summary>
12+
public class Commas : IPlugin
13+
{
14+
public string Name => "Commas";
15+
public string Text => "Commas";
16+
public decimal Version => 1m;
17+
public string Description => "Fixes commas via LM Studio";
18+
public string ActionType => "tool";
19+
public string Shortcut => string.Empty;
20+
21+
public Commas()
22+
{
23+
// AppDomain.CurrentDomain.AssemblyResolve += (sender, args) =>
24+
// {
25+
// _ = AppDomain.CurrentDomain.GetAssemblies();
26+
// return null;
27+
// };
28+
}
29+
30+
public string DoAction(Form parentForm, string srtText, double frameRate, string uiLineBreak, string file, string videoFile, string rawText)
31+
{
32+
#if DEBUG
33+
if (!Debugger.IsAttached)
34+
{
35+
Debugger.Launch();
36+
}
37+
#endif
38+
39+
var subtitle = Subtitle.Parse(srtText.SplitToLines(), ".srt");
40+
using var mainForm = new Main(subtitle);
41+
42+
if (mainForm.ShowDialog() == DialogResult.OK)
43+
{
44+
}
45+
46+
return "";
47+
}
48+
}
49+
50+
public interface IPlugin
51+
{
52+
/// <summary>
53+
/// Name of the plug-in
54+
/// </summary>
55+
string Name { get; }
56+
57+
/// <summary>
58+
/// Text used in Subtitle Edit menu
59+
/// </summary>
60+
string Text { get; }
61+
62+
/// <summary>
63+
/// Version number of plugin
64+
/// </summary>
65+
decimal Version { get; }
66+
67+
/// <summary>
68+
/// Description of what plugin does
69+
/// </summary>
70+
string Description { get; }
71+
72+
/// <summary>
73+
/// Can be one of these: file, tool, sync, translate, spellcheck
74+
/// </summary>
75+
string ActionType { get; }
76+
77+
/// <summary>
78+
/// Shortcut used to active plugin - e.g. Control+Shift+F9
79+
/// </summary>
80+
string Shortcut { get; }
81+
82+
/// <summary>
83+
/// This action of callsed when Subtitle Edit calls plugin
84+
/// </summary>
85+
/// <param name="parentForm">Main form in Subtitle Edit</param>
86+
/// <param name="srtText">SubRip text</param>
87+
/// <param name="frameRate">Current frame rate</param>
88+
/// <param name="uiLineBreak"></param>
89+
/// <param name="file">Current subtitle file name</param>
90+
/// <param name="videoFile">Current video file name</param>
91+
/// <param name="rawText">Subtitle raw format</param>
92+
/// <returns>Dialog</returns>
93+
string DoAction(Form parentForm,
94+
string srtText,
95+
double frameRate,
96+
string uiLineBreak,
97+
string file,
98+
string videoFile,
99+
string rawText);
100+
}

source/Commas/LmStudioClient.cs

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
using System.Net.Http;
2+
using System.Text;
3+
using Newtonsoft.Json;
4+
using Nikse.SubtitleEdit.Core.Common;
5+
6+
namespace Nikse.SubtitleEdit.PluginLogic;
7+
8+
public class LmStudioClient : IDisposable
9+
{
10+
private readonly string _prompt;
11+
private readonly HttpClient _httpClient;
12+
13+
private LmStudioClient(string url, string prompt)
14+
{
15+
_prompt = prompt;
16+
_httpClient = new HttpClient()
17+
{
18+
BaseAddress = new Uri(url)
19+
};
20+
}
21+
22+
public async Task<string> SendAsync(string text)
23+
{
24+
/*
25+
* curl http://0.0.0.0:1234/v1/chat/completions \
26+
-H "Content-Type: application/json" \
27+
-d '{
28+
"model": "deepseek-r1-distill-qwen-7b",
29+
"messages": [
30+
{ "role": "system", "content": "Always answer in rhymes." },
31+
{ "role": "user", "content": "Introduce yourself." }
32+
],
33+
"temperature": 0.7,
34+
"max_tokens": -1,
35+
"stream": true
36+
}'
37+
38+
*/
39+
40+
var chatCompletionRequest = new ChatCompletionRequest(false, new[]
41+
{
42+
new Message("user", $"{_prompt}:\n\"{text}\""),
43+
new Message("system", "You are very good at fixing commas.")
44+
}, 0.7);
45+
46+
var json = JsonConvert.SerializeObject(chatCompletionRequest);
47+
using var chatRequest = new HttpRequestMessage(HttpMethod.Post, "v1/chat/completions")
48+
{
49+
Content = new StringContent(json, Encoding.UTF8, "application/json")
50+
};
51+
52+
using var response = await _httpClient.SendAsync(chatRequest, HttpCompletionOption.ResponseContentRead).ConfigureAwait(false);
53+
var chatResponse = JsonConvert.DeserializeObject<ChatResponse>(await response.Content.ReadAsStringAsync().ConfigureAwait(false));
54+
55+
return chatResponse.Choices[0].Message.Content;
56+
}
57+
58+
public void Dispose() => _httpClient.Dispose();
59+
60+
public static LmStudioClient Create(string endpoint, string prompt)
61+
{
62+
return new LmStudioClient(endpoint, prompt);
63+
}
64+
65+
public class ChatCompletionRequest(bool stream, Message[] messages, double temperature)
66+
{
67+
[JsonProperty("stream")]
68+
public bool Stream { get; } = stream;
69+
70+
[JsonProperty("messages")]
71+
public Message[] Messages { get; } = messages;
72+
73+
[JsonProperty("temperature")]
74+
public double Temperature { get; } = temperature;
75+
}
76+
77+
public class Message(string role, string content)
78+
{
79+
[JsonProperty("role")]
80+
public string Role { get; } = role;
81+
82+
[JsonProperty("content")]
83+
public string Content { get; } = content;
84+
}
85+
86+
public class ChatResponse
87+
{
88+
[JsonProperty("model")]
89+
public string Model { get; set; }
90+
91+
[JsonProperty("choices")]
92+
public Choices[] Choices { get; set; }
93+
}
94+
95+
public class Choices
96+
{
97+
[JsonProperty("index")]
98+
public int Index { get; set; }
99+
100+
[JsonProperty("message")]
101+
public Message Message { get; set; }
102+
}
103+
}

source/Commas/Main.Designer.cs

Lines changed: 154 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)