plugin work, page navigation, reticks

This commit is contained in:
Michal Courson
2026-02-26 20:02:22 -05:00
parent 8c83819a17
commit 69c9d80a82
11 changed files with 432 additions and 56 deletions

View File

@ -2,12 +2,94 @@
{ {
"name": "Uncategorized", "name": "Uncategorized",
"id": 0, "id": 0,
"clips": [] "clips": [
{
"filename": "C:\\Users\\mickl\\Desktop\\cliptrim-ui\\ClipTrimApp\\audio-service\\recordings\\audio_capture_20260226_195932.wav",
"name": "Clip 20260226_195932",
"playbackType": "playOverlap",
"volume": 1
}
]
}, },
{ {
"name": "Test", "name": "Test",
"id": 1, "id": 1,
"clips": [] "clips": [
{
"filename": "C:\\Users\\mickl\\Desktop\\cliptrim-ui\\ClipTrimApp\\audio-service\\recordings\\audio_capture_20260226_183607.wav",
"name": "Clip 20260226_183607",
"playbackType": "playStop",
"volume": 1
},
{
"filename": "C:\\Users\\mickl\\Desktop\\cliptrim-ui\\ClipTrimApp\\audio-service\\recordings\\audio_capture_20260226_183812.wav",
"name": "Clip 20260226_183812",
"playbackType": "playStop",
"volume": 1
},
{
"filename": "C:\\Users\\mickl\\Desktop\\cliptrim-ui\\ClipTrimApp\\audio-service\\recordings\\audio_capture_20260226_183822.wav",
"name": "Clip 20260226_183822",
"playbackType": "playStop",
"volume": 1
},
{
"filename": "C:\\Users\\mickl\\Desktop\\cliptrim-ui\\ClipTrimApp\\audio-service\\recordings\\audio_capture_20260226_184028.wav",
"name": "Clip 20260226_184028",
"playbackType": "playStop",
"volume": 1
},
{
"filename": "C:\\Users\\mickl\\Desktop\\cliptrim-ui\\ClipTrimApp\\audio-service\\recordings\\audio_capture_20260226_184030.wav",
"name": "Clip 20260226_184030",
"playbackType": "playStop",
"volume": 1
},
{
"filename": "C:\\Users\\mickl\\Desktop\\cliptrim-ui\\ClipTrimApp\\audio-service\\recordings\\audio_capture_20260226_184032.wav",
"name": "Clip 20260226_184032",
"playbackType": "playStop",
"volume": 1
},
{
"filename": "C:\\Users\\mickl\\Desktop\\cliptrim-ui\\ClipTrimApp\\audio-service\\recordings\\audio_capture_20260226_184037.wav",
"name": "Clip 20260226_184037",
"playbackType": "playStop",
"volume": 1
},
{
"filename": "C:\\Users\\mickl\\Desktop\\cliptrim-ui\\ClipTrimApp\\audio-service\\recordings\\audio_capture_20260226_184040.wav",
"name": "Clip 20260226_184040",
"playbackType": "playStop",
"volume": 1
},
{
"filename": "C:\\Users\\mickl\\Desktop\\cliptrim-ui\\ClipTrimApp\\audio-service\\recordings\\audio_capture_20260226_184041.wav",
"name": "Clip 20260226_184041",
"playbackType": "playStop",
"volume": 1
},
{
"filename": "C:\\Users\\mickl\\Desktop\\cliptrim-ui\\ClipTrimApp\\audio-service\\recordings\\audio_capture_20260226_184042.wav",
"name": "Clip 20260226_184042",
"playbackType": "playStop",
"volume": 1
},
{
"filename": "C:\\Users\\mickl\\Desktop\\cliptrim-ui\\ClipTrimApp\\audio-service\\recordings\\audio_capture_20260226_184043.wav",
"name": "Clip 20260226_184043",
"playbackType": "playStop",
"volume": 1
},
{
"endTime": 14.772566995768694,
"filename": "C:\\Users\\mickl\\Desktop\\cliptrim-ui\\ClipTrimApp\\audio-service\\recordings\\audio_capture_20260226_194441.wav",
"name": "Test",
"playbackType": "playStop",
"startTime": 9.8548571932299,
"volume": 0.6
}
]
}, },
{ {
"name": "New", "name": "New",
@ -18,15 +100,15 @@
"filename": "C:\\Users\\mickl\\Desktop\\cliptrim-ui\\ClipTrimApp\\audio-service\\recordings\\audio_capture_20260220_193822.wav", "filename": "C:\\Users\\mickl\\Desktop\\cliptrim-ui\\ClipTrimApp\\audio-service\\recordings\\audio_capture_20260220_193822.wav",
"name": "Pee pee\npoo poo", "name": "Pee pee\npoo poo",
"playbackType": "playOverlap", "playbackType": "playOverlap",
"startTime": 27.76674010920584, "startTime": 27.64044943820222,
"volume": 0.25 "volume": 0.31
}, },
{ {
"endTime": 28.566433566433446, "endTime": 30,
"filename": "C:\\Users\\mickl\\Desktop\\cliptrim-ui\\ClipTrimApp\\audio-service\\recordings\\audio_capture_20260220_200442.wav", "filename": "C:\\Users\\mickl\\Desktop\\cliptrim-ui\\ClipTrimApp\\audio-service\\recordings\\audio_capture_20260220_200442.wav",
"name": "Clip 20260220_200442", "name": "Test",
"playbackType": "playOverlap", "playbackType": "playOverlap",
"startTime": 25.664335664335663, "startTime": 26.14685314685314,
"volume": 0.64 "volume": 0.64
} }
] ]

View File

@ -10,8 +10,8 @@
"output_device": { "output_device": {
"channels": 2, "channels": 2,
"default_samplerate": 48000, "default_samplerate": 48000,
"index": 44, "index": 45,
"name": "VM to Headset (VB-Audio Voicemeeter VAIO)" "name": "VM to Discord (VB-Audio Voicemeeter VAIO)"
}, },
"http_port": 5010 "http_port": 5010
} }

View File

@ -27,7 +27,7 @@ def handle_connect():
emit('full_data', MetaDataManager().collections) emit('full_data', MetaDataManager().collections)
@socketio.on('record_clip') @socketio.on('record_clip')
def record_clip(data): def record_clip():
io = AudioIO() io = AudioIO()
io.save_last_n_seconds(); io.save_last_n_seconds();

View File

@ -41,6 +41,8 @@ class MetaDataManager:
if collection is None: if collection is None:
raise ValueError(f"Collection '{collection_name}' does not exist.") raise ValueError(f"Collection '{collection_name}' does not exist.")
collection["clips"].append(clip_metadata) collection["clips"].append(clip_metadata)
if not self.socket is None:
self.socket.emit('collection_updated', collection)
self.save_metadata() self.save_metadata()
def remove_clip_from_collection(self, collection_name, clip_metadata): def remove_clip_from_collection(self, collection_name, clip_metadata):
@ -56,11 +58,18 @@ class MetaDataManager:
clip for clip in collection["clips"] clip for clip in collection["clips"]
if clip.get("filename") != clip_metadata.get("filename") if clip.get("filename") != clip_metadata.get("filename")
] ]
if not self.socket is None:
self.socket.emit('collection_updated', collection)
self.save_metadata() self.save_metadata()
def move_clip_to_collection(self, source_collection, target_collection, clip_metadata): def move_clip_to_collection(self, source_collection, target_collection, clip_metadata):
self.remove_clip_from_collection(source_collection, clip_metadata) self.remove_clip_from_collection(source_collection, clip_metadata)
self.add_clip_to_collection(target_collection, clip_metadata) self.add_clip_to_collection(target_collection, clip_metadata)
if not self.socket is None:
self.socket.emit('collection_updated', source_collection)
self.socket.emit('collection_updated', target_collection)
def edit_clip_in_collection(self, collection_name, new_clip_metadata): def edit_clip_in_collection(self, collection_name, new_clip_metadata):
collection = next((c for c in self.collections if c.get("name") == collection_name), None) collection = next((c for c in self.collections if c.get("name") == collection_name), None)
@ -72,6 +81,8 @@ class MetaDataManager:
raise ValueError(f"Clip with filename '{new_clip_metadata.get('filename')}' not found in collection '{collection_name}'.") raise ValueError(f"Clip with filename '{new_clip_metadata.get('filename')}' not found in collection '{collection_name}'.")
collection["clips"][index] = new_clip_metadata collection["clips"][index] = new_clip_metadata
if not self.socket is None:
self.socket.emit('collection_updated', collection)
self.save_metadata() self.save_metadata()
def get_collections(self): def get_collections(self):

View File

@ -1,4 +1,6 @@
using BarRaider.SdTools; using BarRaider.SdTools;
using ClipTrimDotNet.Keys;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Newtonsoft.Json; using Newtonsoft.Json;
using SocketIOClient; using SocketIOClient;
using System; using System;
@ -104,23 +106,96 @@ namespace ClipTrimDotNet.Client
public List<CollectionMetaData> Collections { get; private set; } = new List<CollectionMetaData>(); public List<CollectionMetaData> Collections { get; private set; } = new List<CollectionMetaData>();
public int SelectedCollection { get; private set; } = -1; public int SelectedCollection { get; private set; } = -1;
public int PageIndex { get; private set; } = 0;
public Dictionary<int, int> CollectionIndexes { get; private set; } = new();
public int PageIndex
{
get
{
if (SelectedCollection == -1) return 0;
if (!CollectionIndexes.ContainsKey(SelectedCollection))
{
CollectionIndexes[SelectedCollection] = 0;
}
return CollectionIndexes[SelectedCollection];
}
set
{
if (SelectedCollection == -1) return;
CollectionIndexes[SelectedCollection] = value;
}
}
public bool PageMode { get; set; } = false;
public int PageCount
{
get
{
if (SelectedCollection == -1) return 0;
var collection = Collections[SelectedCollection];
return (collection.Clips.Count - 1) / 10 + 1;
}
}
public bool CanPageUp
{
get
{
if(PageMode) return false;
if (SelectedCollection == -1) return false;
return PageCount - PageIndex > 1;
}
}
public bool CanPageDown
{
get
{
if (PageMode) return false;
return PageIndex > 0;
}
}
public void PageDown()
{
if (CanPageDown)
{
PageIndex--;
}
}
public void PageUp()
{
if (CanPageUp)
{
PageIndex++;
}
}
public List<string> GetCollectionNames() public List<string> GetCollectionNames()
{ {
//await GetMetadata(); //await GetMetadata();
return Collections.Select(x => x.Name).ToList(); return Collections.Where(x => x.Name != "Uncategorized").Select(x => x.Name).ToList();
} }
public void SetSelectedCollectionByName(string name) public string GetPlayerStringByCoordinateIndex(int index)
{ {
SelectedCollection = Collections.FindIndex(x => x.Name == name); if (PageMode)
if (SelectedCollection != -1)
{ {
PageIndex = 0; int pageNumber = index + 1;
if(pageNumber <= PageCount)
{
return pageNumber.ToString();
}
return "";
}
else
{
var collection = GetClipByPagedIndex(index);
return collection?.Name ?? "";
} }
} }
public ClipMetadata? GetClipByPagedIndex(int index) public ClipMetadata? GetClipByPagedIndex(int index)
{ {
SelectedCollection = Collections.FindIndex(x => x.Name == GlobalSettings.Instance.ProfileName); SelectedCollection = Collections.FindIndex(x => x.Name == GlobalSettings.Instance.ProfileName);
@ -134,11 +209,29 @@ namespace ClipTrimDotNet.Client
return null; return null;
} }
public async void PlayClip(ClipMetadata? metadata) public async void PlayClip(int index)
{ {
if (metadata == null) return; if (PageMode)
Logger.Instance.LogMessage(TracingLevel.INFO, $"playing clip:"); {
await socket.EmitAsync("play_clip", new List<object>() { metadata }); if(index < 0 || index >= PageCount) return;
PageIndex = index;
PageMode = false;
Player.TickAll();
PageNavigator.TickAll();
}
else
{
var metadata = GetClipByPagedIndex(index);
if (metadata == null) return;
Logger.Instance.LogMessage(TracingLevel.INFO, $"playing clip:");
await socket.EmitAsync("play_clip", new List<object>() { metadata });
}
}
public async void SaveClip()
{
await socket.EmitAsync("record_clip", new List<object>() { });
} }
} }
} }

View File

@ -0,0 +1,49 @@
using BarRaider.SdTools;
using ClipTrimDotNet.Client;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ClipTrimDotNet.Keys
{
[PluginActionId("com.michal-courson.cliptrim.clip-save")]
public class ClipSave : KeypadBase
{
public ClipSave(SDConnection connection, InitialPayload payload) : base(connection, payload)
{
GlobalSettingsManager.Instance.RequestGlobalSettings();
}
public void Instance_OnReceivedGlobalSettings(object sender, ReceivedGlobalSettingsPayload e)
{
Tools.AutoPopulateSettings(GlobalSettings.Instance, e.Settings);
}
public override void Dispose()
{
}
public override void KeyPressed(KeyPayload payload)
{
ClipTrimClient.Instance.SaveClip();
}
public override void OnTick()
{
}
public override void KeyReleased(KeyPayload payload)
{
}
public override void ReceivedSettings(ReceivedSettingsPayload payload)
{
}
public override void ReceivedGlobalSettings(ReceivedGlobalSettingsPayload payload)
{
Tools.AutoPopulateSettings(GlobalSettings.Instance, payload.Settings);
}
}
}

View File

@ -0,0 +1,107 @@
using BarRaider.SdTools;
using ClipTrimDotNet.Client;
using Microsoft.AspNetCore.Authentication;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ClipTrimDotNet.Keys
{
[PluginActionId("com.michal-courson.cliptrim.page-navigator")]
public class PageNavigator : KeypadBase
{
static List<PageNavigator> instances = new();
private KeyCoordinates coordinates;
public PageNavigator(SDConnection connection, InitialPayload payload) : base(connection, payload)
{
coordinates = payload.Coordinates;
GlobalSettingsManager.Instance.RequestGlobalSettings();
instances.Add(this);
OnTick();
}
public void Instance_OnReceivedGlobalSettings(object sender, ReceivedGlobalSettingsPayload e)
{
Tools.AutoPopulateSettings(GlobalSettings.Instance, e.Settings);
}
public int GetIndex()
{
int index = Math.Min(Math.Max(coordinates.Column - 1, 0), 2);
return index;
}
public override void Dispose()
{
}
public override void KeyPressed(KeyPayload payload)
{
switch (GetIndex())
{
case 0:
ClipTrimClient.Instance.PageDown();
break;
case 1:
ClipTrimClient.Instance.PageMode = !ClipTrimClient.Instance.PageMode;
break;
case 2:
ClipTrimClient.Instance.PageUp();
break;
}
Player.TickAll();
TickAll();
}
public static void TickAll()
{
foreach (var instance in instances)
{
instance.OnTick();
}
instances.RemoveAll(i => i == null);
}
private async void SetTitle()
{
switch (GetIndex())
{
case 0:
await Connection.SetTitleAsync(ClipTrimClient.Instance.CanPageDown ? "<" : "");
break;
case 1:
await Connection.SetTitleAsync((ClipTrimClient.Instance.PageIndex + 1).ToString());
break;
case 2:
await Connection.SetTitleAsync(ClipTrimClient.Instance.CanPageUp ? ">" : "");
break;
}
}
public override void OnTick()
{
SetTitle();
}
public override void KeyReleased(KeyPayload payload)
{
}
public override void ReceivedSettings(ReceivedSettingsPayload payload)
{
}
public override void ReceivedGlobalSettings(ReceivedGlobalSettingsPayload payload)
{
Tools.AutoPopulateSettings(GlobalSettings.Instance, payload.Settings);
//if (payload.Settings == null || payload.Settings.Count == 0)
//{
// var inst = GlobalSettings.Instance;
// //GlobalSettingsManager.Instance.SetGlobalSettings(JObject.FromObject(inst));
//}
//else
//{
// GlobalSettings.Instance = payload.Settings.ToObject<GlobalSettings>();
//}
}
}
}

View File

@ -12,36 +12,46 @@ using System.Linq;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace ClipTrimDotNet namespace ClipTrimDotNet.Keys
{ {
[PluginActionId("com.michal-courson.cliptrim.player")] [PluginActionId("com.michal-courson.cliptrim.player")]
public class Player : KeypadBase public class Player : KeypadBase
{ {
private ClipMetadata? metadata; private int coordIndex;
private string? current_text = null;
private KeyCoordinates coordinates; private KeyCoordinates coordinates;
static List<Player> instances = new();
public Player(SDConnection connection, InitialPayload payload) : base(connection, payload) public Player(SDConnection connection, InitialPayload payload) : base(connection, payload)
{ {
this.coordinates = payload.Coordinates; coordinates = payload.Coordinates;
GlobalSettingsManager.Instance.RequestGlobalSettings(); GlobalSettingsManager.Instance.RequestGlobalSettings();
CheckFile(); instances.Add(this);
coordIndex = Math.Max((coordinates.Row - 1) * 5 + coordinates.Column, 0);
OnTick();
} }
public void Instance_OnReceivedGlobalSettings(object sender, ReceivedGlobalSettingsPayload e) public static void TickAll()
{ {
Tools.AutoPopulateSettings(GlobalSettings.Instance, e.Settings); Logger.Instance.LogMessage(TracingLevel.INFO, "Ticking all Player instances" + instances.Count);
foreach (var instance in instances)
{
instance.OnTick();
}
instances.RemoveAll(i => i == null);
} }
public int GetIndex()
{
return Math.Max((coordinates.Row - 1) * 5 + coordinates.Column, 0);
}
private async void CheckFile() private async void CheckFile()
{ {
metadata = ClipTrimClient.Instance.GetClipByPagedIndex(GetIndex()); var next_text = ClipTrimClient.Instance.GetPlayerStringByCoordinateIndex(coordIndex);
await Connection.SetTitleAsync($"{metadata?.Name ?? ""}"); if(next_text != current_text)
{
current_text = next_text;
await Connection.SetTitleAsync($"{current_text ?? ""}");
}
current_text = next_text;
} }
public override void Dispose() public override void Dispose()
@ -50,7 +60,7 @@ namespace ClipTrimDotNet
public override void KeyPressed(KeyPayload payload) public override void KeyPressed(KeyPayload payload)
{ {
ClipTrimClient.Instance.PlayClip(metadata); ClipTrimClient.Instance.PlayClip(coordIndex);
} }
@ -60,15 +70,7 @@ namespace ClipTrimDotNet
public override void ReceivedGlobalSettings(ReceivedGlobalSettingsPayload payload) { public override void ReceivedGlobalSettings(ReceivedGlobalSettingsPayload payload) {
if (payload.Settings == null || payload.Settings.Count == 0) Tools.AutoPopulateSettings(GlobalSettings.Instance, payload.Settings);
{
var inst = GlobalSettings.Instance;
//GlobalSettingsManager.Instance.SetGlobalSettings(JObject.FromObject(inst));
}
else
{
GlobalSettings.Instance = payload.Settings.ToObject<GlobalSettings>();
}
} }
public override void KeyReleased(KeyPayload payload) public override void KeyReleased(KeyPayload payload)

View File

@ -12,12 +12,12 @@ using System.Linq;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace ClipTrimDotNet namespace ClipTrimDotNet.Keys
{ {
public class DataSourceItem public class DataSourceItem
{ {
public string label { get; set; } public string label { get; set; } = "";
public string value { get; set; } public string value { get; set; } = "";
} }
[PluginActionId("com.michal-courson.cliptrim.profile-switcher")] [PluginActionId("com.michal-courson.cliptrim.profile-switcher")]
public class ProfileSwitcher : KeypadBase public class ProfileSwitcher : KeypadBase
@ -44,13 +44,14 @@ namespace ClipTrimDotNet
{ {
if (payload.Settings == null || payload.Settings.Count == 0) if (payload.Settings == null || payload.Settings.Count == 0)
{ {
this.settings = PluginSettings.CreateDefaultSettings(); settings = PluginSettings.CreateDefaultSettings();
SaveSettings(); SaveSettings();
} }
else else
{ {
this.settings = payload.Settings.ToObject<PluginSettings>()!; settings = payload.Settings.ToObject<PluginSettings>()!;
} }
Logger.Instance.LogMessage(TracingLevel.INFO, $"ProfileSwitcher initialized with payload {JsonConvert.SerializeObject(payload)}");
GlobalSettingsManager.Instance.RequestGlobalSettings(); GlobalSettingsManager.Instance.RequestGlobalSettings();
Connection.OnSendToPlugin += Connection_OnSendToPlugin; Connection.OnSendToPlugin += Connection_OnSendToPlugin;
SetTitle(); SetTitle();
@ -112,14 +113,15 @@ namespace ClipTrimDotNet
public override void ReceivedGlobalSettings(ReceivedGlobalSettingsPayload payload) public override void ReceivedGlobalSettings(ReceivedGlobalSettingsPayload payload)
{ {
if (payload.Settings == null || payload.Settings.Count == 0) Tools.AutoPopulateSettings(GlobalSettings.Instance, payload.Settings);
{ //if (payload.Settings == null || payload.Settings.Count == 0)
var inst = GlobalSettings.Instance; //{
} // var inst = GlobalSettings.Instance;
else //}
{ //else
GlobalSettings.Instance = payload.Settings.ToObject<GlobalSettings>(); //{
} // GlobalSettings.Instance = payload.Settings.ToObject<GlobalSettings>();
//}
} }
#region Private Methods #region Private Methods

View File

@ -30,6 +30,36 @@
"Tooltip": "Selects which sub folder to use and opens effect profile", "Tooltip": "Selects which sub folder to use and opens effect profile",
"UUID": "com.michal-courson.cliptrim.profile-switcher", "UUID": "com.michal-courson.cliptrim.profile-switcher",
"PropertyInspectorPath": "PropertyInspector/profile_swticher.html" "PropertyInspectorPath": "PropertyInspector/profile_swticher.html"
},
{
"Icon": "Images/icon",
"Name": "Page Navigator",
"States": [
{
"Image": "Images/pluginAction",
"TitleAlignment": "middle",
"FontSize": 16
}
],
"SupportedInMultiActions": false,
"Tooltip": "Navigates pages",
"UUID": "com.michal-courson.cliptrim.page-navigator",
"PropertyInspectorPath": "PropertyInspector/file_player.html"
},
{
"Icon": "Images/icon",
"Name": "Save Clip",
"States": [
{
"Image": "Images/pluginAction",
"TitleAlignment": "middle",
"FontSize": 16
}
],
"SupportedInMultiActions": false,
"Tooltip": "Saves the current clip",
"UUID": "com.michal-courson.cliptrim.clip-save",
"PropertyInspectorPath": "PropertyInspector/file_player.html"
} }
], ],
"Author": "Michal Courson", "Author": "Michal Courson",