4 Commits

62 changed files with 867 additions and 816 deletions

View File

@ -2,12 +2,100 @@
{ {
"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
},
{
"filename": "C:\\Users\\mickl\\Desktop\\cliptrim-ui\\ClipTrimApp\\audio-service\\recordings\\audio_capture_20260228_165611.wav",
"name": "Clip 20260228_165611",
"playbackType": "playStop",
"volume": 1.0
},
{
"filename": "C:\\Users\\mickl\\Desktop\\cliptrim-ui\\ClipTrimApp\\audio-service\\recordings\\audio_capture_20260228_165646.wav",
"name": "Clip 20260228_165646",
"playbackType": "playStop",
"volume": 1.0
}
]
}, },
{ {
"name": "Test", "name": "Test",
"id": 1, "id": 1,
"clips": [] "clips": [
{
"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_183607.wav",
"name": "Clip 20260226_183607",
"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_20260228_092721.wav",
"name": "Clip 20260228_092721",
"playbackType": "playStop",
"volume": 1,
"startTime": 6.438382145377559,
"endTime": 14.277258292166426
}
]
}, },
{ {
"name": "New", "name": "New",
@ -18,16 +106,28 @@
"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": 27.516843118383072, "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.120307988450435, "startTime": 26.14685314685314,
"volume": 0.64 "volume": 0.64
},
{
"filename": "C:\\Users\\mickl\\Desktop\\cliptrim-ui\\ClipTrimApp\\audio-service\\recordings\\audio_capture_20260228_085116.wav",
"name": "pp",
"playbackType": "playStop",
"volume": 1
},
{
"filename": "C:\\Users\\mickl\\Desktop\\cliptrim-ui\\ClipTrimApp\\audio-service\\recordings\\audio_capture_20260228_120955.wav",
"name": "nose",
"playbackType": "playStop",
"volume": 1
} }
] ]
} }

View File

@ -1,7 +1,6 @@
sounddevice==0.5.1 Flask==3.1.3
numpy==1.22.3 flask_cors==6.0.2
python-osc==1.9.3 flask_socketio==5.6.1
scipy==1.10.1 numpy==2.4.2
comtypes==1.4.8 scipy==1.17.1
pycaw==20240210 sounddevice==0.5.5
Flask==3.1.2

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

@ -121,7 +121,7 @@ class AudioIO:
} }
meta.add_clip_to_collection("Uncategorized", clip_metadata ) meta.add_clip_to_collection("Uncategorized", clip_metadata )
self.socket.emit('new_clip', clip_metadata)
return clip_metadata return clip_metadata

View File

@ -19,7 +19,7 @@ import threading
app = Flask(__name__) app = Flask(__name__)
CORS(app) CORS(app)
socketio = SocketIO(app, cors_allowed_origins="*") socketio = SocketIO(app, cors_allowed_origins="*", logger=True, engineio_logger=True, async_mode='eventlet')
@socketio.on('connect') @socketio.on('connect')
def handle_connect(): def handle_connect():
@ -27,10 +27,18 @@ 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();
@socketio.on('play_clip')
def play_clip(data):
io = AudioIO()
print(f"Received play_clip event with data: {data}")
if data:
io.play_clip(data)
def main(): def main():
# Create argument parser # Create argument parser
parser = argparse.ArgumentParser(description='Audio Recording Service') parser = argparse.ArgumentParser(description='Audio Recording Service')
@ -65,8 +73,9 @@ def main():
app.register_blueprint(device_bp) app.register_blueprint(device_bp)
app.register_blueprint(metadata_bp) app.register_blueprint(metadata_bp)
app.register_blueprint(settings_bp) app.register_blueprint(settings_bp)
print(f"Starting Flask server on port {settings.get_settings('http_port')}")
# app.run(host='127.0.0.1', port=settings.get_settings('http_port'), debug=False, use_reloader=True) # app.run(host='127.0.0.1', port=settings.get_settings('http_port'), debug=False, use_reloader=True)
socketio.run(app, host='127.0.0.1', port=settings.get_settings('http_port'), debug=False, use_reloader=True) socketio.run(app, host='127.0.0.1', port=settings.get_settings('http_port'), debug=False, use_reloader=True, allow_unsafe_werkzeug=True)

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,9 +1,5 @@
import sounddevice as sd import sounddevice as sd
import numpy as np import numpy as np
import comtypes
import comtypes.client
from comtypes import CLSCTX_ALL
from pycaw.pycaw import AudioUtilities, IAudioEndpointVolume
import json import json
class WindowsAudioManager: class WindowsAudioManager:

View File

@ -18,6 +18,8 @@ import { resolveHtmlPath } from './util';
import registerFileIpcHandlers from '../ipc/audio/main'; import registerFileIpcHandlers from '../ipc/audio/main';
import PythonSubprocessManager from './service'; import PythonSubprocessManager from './service';
const pythonManager = new PythonSubprocessManager('src/main.py');
class AppUpdater { class AppUpdater {
constructor() { constructor() {
log.transports.file.level = 'info'; log.transports.file.level = 'info';
@ -112,9 +114,6 @@ const createWindow = async () => {
registerFileIpcHandlers(); registerFileIpcHandlers();
const pythonManager = new PythonSubprocessManager('src/main.py');
pythonManager.start();
// Remove this if your app does not use auto updates // Remove this if your app does not use auto updates
// eslint-disable-next-line // eslint-disable-next-line
new AppUpdater(); new AppUpdater();
@ -127,6 +126,7 @@ const createWindow = async () => {
app.on('window-all-closed', () => { app.on('window-all-closed', () => {
// Respect the OSX convention of having the application in memory even // Respect the OSX convention of having the application in memory even
// after all windows have been closed // after all windows have been closed
pythonManager.stop();
if (process.platform !== 'darwin') { if (process.platform !== 'darwin') {
app.quit(); app.quit();
} }
@ -135,6 +135,7 @@ app.on('window-all-closed', () => {
app app
.whenReady() .whenReady()
.then(() => { .then(() => {
// pythonManager.start();
createWindow(); createWindow();
app.on('activate', () => { app.on('activate', () => {
// On macOS it's common to re-create a window in the app when the // On macOS it's common to re-create a window in the app when the

View File

@ -40,10 +40,10 @@ export default class PythonSubprocessManager {
}, },
); );
this.process.stdout.on('data', (data: Buffer) => { this.process.stdout.on('data', (data: Buffer) => {
console.log(`Python stdout: ${data.toString()}`); // console.log(`Python stdout: ${data.toString()}`);
}); });
this.process.stderr.on('data', (data: Buffer) => { this.process.stderr.on('data', (data: Buffer) => {
// console.error(`Python stderr: ${data.toString()}`); // console.error(`Python stderr: ${data.toString()}`);
const lines = data.toString().split('\n'); const lines = data.toString().split('\n');
// eslint-disable-next-line no-restricted-syntax // eslint-disable-next-line no-restricted-syntax
for (const line of lines) { for (const line of lines) {

View File

@ -67,32 +67,11 @@ const metadataSlice = createSlice({
targetState.clips.push(clip); targetState.clips.push(clip);
} }
}, },
addNewClips(state, action) { addNewClip(state, action) {
const { collections } = action.payload; const { clip } = action.payload;
Object.keys(collections).forEach((collection) => { state.collections.forEach((collection) => {
const collectionState = state.collections.find( if (collection.name === 'Uncategorized') {
(col) => col.name === collection, collection.clips.push(clip);
);
if (!collectionState) {
state.collections.push({
name: collection,
id: Date.now(),
clips: [],
});
}
const existingFilenames = new Set(
state.collections
.find((col) => col.name === collection)
?.clips.map((clip) => clip.filename) || [],
);
const newClips = collections[collection].filter(
(clip: ClipMetadata) => !existingFilenames.has(clip.filename),
);
// const collectionState = state.collections.find(
// (col) => col.name === collection,
// );
if (collectionState) {
collectionState.clips.push(...newClips);
} }
}); });
}, },
@ -113,6 +92,6 @@ export type RootState = ReturnType<AppStore['getState']>;
// Inferred type: {posts: PostsState, comments: CommentsState, users: UsersState} // Inferred type: {posts: PostsState, comments: CommentsState, users: UsersState}
export type AppDispatch = AppStore['dispatch']; export type AppDispatch = AppStore['dispatch'];
export const { setCollections, addNewClips, addCollection } = export const { setCollections, addNewClip, addCollection } =
metadataSlice.actions; metadataSlice.actions;
export default metadataSlice.reducer; export default metadataSlice.reducer;

View File

@ -7,6 +7,7 @@ import { ThemeProvider, createTheme } from '@mui/material/styles';
import DialogTitle from '@mui/material/DialogTitle'; import DialogTitle from '@mui/material/DialogTitle';
import DialogContent from '@mui/material/DialogContent'; import DialogContent from '@mui/material/DialogContent';
import DialogActions from '@mui/material/DialogActions'; import DialogActions from '@mui/material/DialogActions';
import io from 'socket.io-client';
// import 'tailwindcss/tailwind.css'; // import 'tailwindcss/tailwind.css';
import './App.css'; import './App.css';
import ClipList from './components/ClipList'; import ClipList from './components/ClipList';
@ -14,7 +15,7 @@ import { useAppDispatch, useAppSelector } from './hooks';
import { store } from '../redux/main'; import { store } from '../redux/main';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import SettingsPage from './Settings'; import SettingsPage from './Settings';
import apiFetch from './api'; import { apiFetch, getBaseUrl } from './api';
function MainPage() { function MainPage() {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
@ -27,20 +28,46 @@ function MainPage() {
const [newCollectionOpen, setNewCollectionOpen] = useState(false); const [newCollectionOpen, setNewCollectionOpen] = useState(false);
const [newCollectionName, setNewCollectionName] = useState<string>(''); const [newCollectionName, setNewCollectionName] = useState<string>('');
const navigate = useNavigate(); const navigate = useNavigate();
const [socket, setSocket] = useState<any>(null);
useEffect(() => {}, []);
useEffect(() => { useEffect(() => {
const fetchMetadata = async () => { const initializeSocket = async () => {
try { const baseUrl = await getBaseUrl();
const response = await apiFetch('meta'); const newSocket = io(baseUrl);
const data = await response.json(); setSocket(newSocket);
dispatch({ type: 'metadata/setAllData', payload: data }); newSocket.on('connect', () => {
} catch (error) { console.log('Connected to WebSocket server');
console.error('Error fetching metadata:', error); });
} newSocket.on('full_data', (data: any) => {
console.log('Received full_data from server:', data);
dispatch({
type: 'metadata/setAllData',
payload: { collections: data },
});
});
newSocket.on('new_clip', (data: any) => {
console.log('Received new_clips from server:', data);
dispatch({
type: 'metadata/addNewClip',
payload: { clip: data },
});
});
}; };
fetchMetadata(); initializeSocket();
const intervalId = setInterval(fetchMetadata, 5000); // const fetchMetadata = async () => {
return () => clearInterval(intervalId); // try {
// const response = await apiFetch('meta');
// const data = await response.json();
// dispatch({ type: 'metadata/setAllData', payload: data });
// } catch (error) {
// console.error('Error fetching metadata:', error);
// }
// };
// fetchMetadata();
// const intervalId = setInterval(fetchMetadata, 5000);
// return () => clearInterval(intervalId);
}, [dispatch]); }, [dispatch]);
useEffect(() => { useEffect(() => {

View File

@ -5,7 +5,7 @@ import './App.css';
import TextField from '@mui/material/TextField'; import TextField from '@mui/material/TextField';
import Select from '@mui/material/Select'; import Select from '@mui/material/Select';
import MenuItem from '@mui/material/MenuItem'; import MenuItem from '@mui/material/MenuItem';
import apiFetch from './api'; import { apiFetch } from './api';
type AudioDevice = { type AudioDevice = {
index: number; index: number;

View File

@ -1,4 +1,4 @@
const getBaseUrl = async () => { export const getBaseUrl = async () => {
const port = await window.audio.getPort(); const port = await window.audio.getPort();
if (port.error || !port.port) { if (port.error || !port.port) {
return `http://localhost:5010`; return `http://localhost:5010`;
@ -7,7 +7,7 @@ const getBaseUrl = async () => {
return `http://localhost:${port.port}`; return `http://localhost:${port.port}`;
}; };
export default async function apiFetch(endpoint: string, options = {}) { export async function apiFetch(endpoint: string, options = {}) {
const url = `${await getBaseUrl()}/${endpoint}`; const url = `${await getBaseUrl()}/${endpoint}`;
return fetch(url, options); return fetch(url, options);
} }

View File

@ -15,7 +15,7 @@ import { restrictToVerticalAxis } from '@dnd-kit/modifiers';
import AudioTrimmer from './AudioTrimer'; import AudioTrimmer from './AudioTrimer';
import { ClipMetadata } from '../../redux/types'; import { ClipMetadata } from '../../redux/types';
import { useAppDispatch, useAppSelector } from '../hooks'; import { useAppDispatch, useAppSelector } from '../hooks';
import apiFetch from '../api'; import { apiFetch } from '../api';
export interface ClipListProps { export interface ClipListProps {
collection: string; collection: string;

View File

@ -1,13 +1,16 @@
using System; using BarRaider.SdTools;
using ClipTrimDotNet.Keys;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Newtonsoft.Json;
using SocketIOClient;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Net.Http; using System.Net.Http;
using System.Runtime.CompilerServices;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using Newtonsoft.Json; using static System.Runtime.InteropServices.JavaScript.JSType;
using SocketIOClient;
using BarRaider.SdTools;
using System.Runtime.CompilerServices;
namespace ClipTrimDotNet.Client namespace ClipTrimDotNet.Client
{ {
@ -27,20 +30,26 @@ namespace ClipTrimDotNet.Client
} }
//private HttpClient httpClient; //private HttpClient httpClient;
private SocketIO socket; private SocketIO? socket;
public int PortNumber { get; set; } = 5010; public string HostName
public ClipTrimClient()
{ {
//httpClient = new HttpClient() get
//{ {
// BaseAddress = new Uri("http://localhost:5010/"), //return $"http://localhost:5010/";
// Timeout = TimeSpan.FromSeconds(10) return $"http://localhost:{GlobalSettings.Instance.PortNumber}/";
//}; }
Logger.Instance.LogMessage(TracingLevel.INFO, $"Starting ClipTrimClient on port {PortNumber}"); }
socket = new SocketIO(new Uri($"http://localhost:5010/"));
private string? currentHostname = null;
void CreateSocket()
{
Logger.Instance.LogMessage(TracingLevel.INFO, $"Starting ClipTrimClient on port {HostName}");
socket = new SocketIO(new Uri(HostName));
currentHostname = HostName;
socket.Options.AutoUpgrade = false; socket.Options.AutoUpgrade = false;
//socket.Options.Path = "/socket.io";
socket.Options.ConnectionTimeout = TimeSpan.FromSeconds(10); socket.Options.ConnectionTimeout = TimeSpan.FromSeconds(10);
socket.Options.Reconnection = true; socket.Options.Reconnection = true;
socket.On("full_data", ctx => socket.On("full_data", ctx =>
@ -50,6 +59,8 @@ namespace ClipTrimDotNet.Client
var response = ctx.GetValue<List<CollectionMetaData>>(0); var response = ctx.GetValue<List<CollectionMetaData>>(0);
Logger.Instance.LogMessage(TracingLevel.INFO, $"full_data event {JsonConvert.SerializeObject(response)}"); Logger.Instance.LogMessage(TracingLevel.INFO, $"full_data event {JsonConvert.SerializeObject(response)}");
Collections = response!; Collections = response!;
Player.TickAll();
PageNavigator.TickAll();
//Logger.Instance.LogMessage(TracingLevel.INFO, $"Collections {JsonConvert.SerializeObject(Collections)}"); //Logger.Instance.LogMessage(TracingLevel.INFO, $"Collections {JsonConvert.SerializeObject(Collections)}");
} }
catch (Exception ex) catch (Exception ex)
@ -68,6 +79,8 @@ namespace ClipTrimDotNet.Client
if (index != -1) if (index != -1)
{ {
Collections[index] = response; Collections[index] = response;
Player.TickAll();
PageNavigator.TickAll();
} }
} }
catch catch
@ -78,62 +91,146 @@ namespace ClipTrimDotNet.Client
return Task.CompletedTask; return Task.CompletedTask;
}); });
Task.Run(async () => await socket.ConnectAsync()); socket.OnConnected += (sender, e) =>
//Task.Run(ShortPoll);
}
//public async Task ShortPoll()
//{
// while (true)
// {
// await GetMetadata();
// await Task.Delay(TimeSpan.FromSeconds(5)); await Task.Delay(TimeSpan.FromSeconds(5));
// }
//}
public List<CollectionMetaData> Collections { get; private set; } = new List<CollectionMetaData>();
public int SelectedCollection { get; private set; } = -1;
public int PageIndex { get; private set; } = 0;
//private async Task GetMetadata()
//{
// try
// {
// var response = await httpClient.GetAsync("meta");
// if (response.IsSuccessStatusCode)
// {
// var json = await response.Content.ReadAsStringAsync();
// dynamic collections = JsonConvert.DeserializeObject(json);
// collections = collections.collections;
// Collections = JsonConvert.DeserializeObject<List<CollectionMetaData>>(collections.ToString());
// }
// }
// catch (Exception ex)
// {
// //Logger.Instance.LogMessage(TracingLevel.INFO, $"Error pinging ClipTrim API: {ex.Message}");
// return;
// }
//}
public List<string> GetCollectionNames()
{
//await GetMetadata();
return Collections.Select(x => x.Name).ToList();
}
public void SetSelectedCollectionByName(string name)
{
SelectedCollection = Collections.FindIndex(x => x.Name == name);
if (SelectedCollection != -1)
{ {
PageIndex = 0; Logger.Instance.LogMessage(TracingLevel.INFO, $"Socket connected: {e}");
};
socket.OnDisconnected += (sender, e) =>
{
Logger.Instance.LogMessage(TracingLevel.INFO, $"Socket disconnected: {e}");
Task.Run(async () => await Connect());
};
Task.Run(async () => await Connect());
}
public ClipTrimClient()
{
//httpClient = new HttpClient()
//{
// BaseAddress = new Uri("http://localhost:5010/"),
// Timeout = TimeSpan.FromSeconds(10)
//};
CreateSocket();
}
public async Task Connect()
{
if (socket is null) return;
while (!socket.Connected)
{
try
{
await socket.ConnectAsync();
}
catch
{
}
} }
} }
public List<CollectionMetaData> Collections { get; private set; } = new List<CollectionMetaData>();
public int SelectedCollection { get; private set; } = -1;
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()
{
//await GetMetadata();
return Collections.Where(x => x.Name != "Uncategorized").Select(x => x.Name).ToList();
}
public string GetCurrentCollectionName()
{
if (SelectedCollection == -1) return "";
return Collections[SelectedCollection].Name;
}
public string GetPlayerStringByCoordinateIndex(int index)
{
if (PageMode)
{
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);
if (SelectedCollection == -1) return null; if (SelectedCollection == -1) return null;
int clipIndex = PageIndex * 10 + index; int clipIndex = PageIndex * 10 + index;
var collection = Collections[SelectedCollection]; var collection = Collections[SelectedCollection];
@ -144,15 +241,47 @@ 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)
{
if(index < 0 || index >= PageCount) return;
PageIndex = index;
PageMode = false;
Player.TickAll();
PageNavigator.TickAll();
}
else
{
if (socket is null) return;
var metadata = GetClipByPagedIndex(index);
if (metadata == null) return;
//Logger.Instance.LogMessage(TracingLevel.INFO, $"playing clip:");
await socket.EmitAsync("play_clip", new List<object>() { metadata });
}
//var response = await httpClient.PostAsync("playback/start", new StringContent(JsonConvert.SerializeObject(metadata), Encoding.UTF8, "application/json")); }
//if (!response.IsSuccessStatusCode)
//{ public async void SaveClip()
// //Logger.Instance.LogMessage(TracingLevel.INFO, $"Error playing clip: {response.ReasonPhrase}"); {
//} if (socket is null) return;
await socket.EmitAsync("record_clip", new List<object>() { });
}
public async void CheckPort()
{
if (socket is null) return;
//Logger.Instance.LogMessage(TracingLevel.INFO, $"Checking port {socket}");
if (currentHostname != HostName)
{
//Logger.Instance.LogMessage(TracingLevel.INFO, $"port {socket}");
if (socket.Connected)
{
await socket.DisconnectAsync();
}
socket.Dispose();
CreateSocket();
}
} }
} }
} }

View File

@ -20,6 +20,20 @@
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<OutputPath>bin\Release\ClipTrimDotNet.sdPlugin\</OutputPath> <OutputPath>bin\Release\ClipTrimDotNet.sdPlugin\</OutputPath>
</PropertyGroup> </PropertyGroup>
<ItemGroup>
<None Remove="Images\app_icon.png" />
<None Remove="Images\back.png" />
<None Remove="Images\category_icon.png" />
<None Remove="Images\collection.png" />
<None Remove="Images\collection_icon.png" />
<None Remove="Images\page_nav.png" />
<None Remove="Images\page_nav_icon.png" />
<None Remove="Images\player.png" />
<None Remove="Images\player_icon.png" />
<None Remove="Images\record.png" />
<None Remove="Images\record_icon.png" />
<None Remove="manifest.json" />
</ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="CommandLineParser" Version="2.9.1" /> <PackageReference Include="CommandLineParser" Version="2.9.1" />
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" Version="8.0.0" /> <PackageReference Include="Microsoft.Bcl.AsyncInterfaces" Version="8.0.0" />
@ -54,34 +68,43 @@
<None Update="DialLayout.json"> <None Update="DialLayout.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None> </None>
<None Update="manifest.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Content Include="!!README!!.txt" /> <Content Include="!!README!!.txt" />
<Content Include="Images\categoryIcon%402x.png"> <Content Include="Images\app_icon.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content> </Content>
<Content Include="Images\categoryIcon.png"> <Content Include="Images\back.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content> </Content>
<Content Include="Images\icon%402x.png"> <Content Include="Images\category_icon.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content> </Content>
<Content Include="Images\icon.png"> <Content Include="Images\collection.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content> </Content>
<Content Include="Images\pluginAction%402x.png"> <Content Include="Images\collection_icon.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content> </Content>
<Content Include="Images\pluginAction.png"> <Content Include="Images\page_nav.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content> </Content>
<Content Include="Images\pluginIcon%402x.png"> <Content Include="Images\page_nav_icon.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content> </Content>
<Content Include="Images\pluginIcon.png"> <Content Include="Images\player_icon.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Images\record.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Images\player.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Images\record_icon.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="manifest.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content> </Content>
<Content Include="package.json" /> <Content Include="package.json" />

View File

@ -10,28 +10,6 @@ using Newtonsoft.Json.Linq;
namespace ClipTrimDotNet namespace ClipTrimDotNet
{ {
public class FileEntry
{
public FileEntry()
{
Volume = 1.0;
Playtype = "Play/Overlap";
}
[JsonProperty(PropertyName = "Volume")]
public double Volume { get; set; }
[JsonProperty(PropertyName = "Playtype")]
public string Playtype { get; set; }
}
public class CollectionEntry
{
public CollectionEntry()
{
Files = new Dictionary<string, FileEntry>();
}
[JsonProperty(PropertyName = "Files")]
public Dictionary<string, FileEntry> Files { get; set; }
}
public class GlobalSettings public class GlobalSettings
{ {
public static GlobalSettings? _inst; public static GlobalSettings? _inst;
@ -50,59 +28,22 @@ namespace ClipTrimDotNet
public static GlobalSettings CreateDefaultSettings() public static GlobalSettings CreateDefaultSettings()
{ {
GlobalSettings instance = new GlobalSettings(); GlobalSettings instance = new GlobalSettings();
instance.BasePath = null;
instance.ProfileName = null; instance.ProfileName = null;
instance.Collections = new Dictionary<string, CollectionEntry>(); instance.PortNumber = 5010;
return instance; return instance;
} }
[FilenameProperty]
[JsonProperty(PropertyName = "basePath")]
public string? BasePath { get; set; }
[JsonProperty(PropertyName = "profileName")] [JsonProperty(PropertyName = "profileName")]
public string? ProfileName { get; set; } public string? ProfileName { get; set; }
[JsonProperty(PropertyName = "outputDevice")] [JsonProperty(PropertyName = "portNumber")]
public string? OutputDevice { get; set; } public int? PortNumber { get; set; }
[JsonProperty(PropertyName = "collections")]
public Dictionary<string, CollectionEntry> Collections { get; set; }
public void SetCurrentProfile(string profile) public void SetCurrentProfile(string profile)
{ {
ProfileName = profile; ProfileName = profile;
if(!Collections.ContainsKey(profile))
{
Collections.Add(profile, new CollectionEntry());
}
}
public FileEntry GetFileOptionsInCurrentProfile(string filename)
{
if(!Collections.TryGetValue(ProfileName, out CollectionEntry collection))
{
return new FileEntry();
}
if(!collection.Files.TryGetValue(filename, out FileEntry file))
{
return new FileEntry();
}
//Logger.Instance.LogMessage(TracingLevel.INFO, "fetched file settings " + filename + JsonConvert.SerializeObject(file));
return file;
}
public void SetFileOptionsCurrentProfile(string filename, FileEntry file)
{
//Logger.Instance.LogMessage(TracingLevel.INFO, "SetFileOptionsCurrentProfile ");
if (!Collections.TryGetValue(ProfileName, out CollectionEntry collection))
{
return;
}
//Logger.Instance.LogMessage(TracingLevel.INFO, "SetFileOptionsCurrentProfile 2");
//collection.Files[filename] = file;
Collections[ProfileName].Files[filename] = file;
} }
} }
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 464 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 484 B

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

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.GetCurrentCollectionName() + "\n" + (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

@ -0,0 +1,87 @@
using BarRaider.SdTools;
using BarRaider.SdTools.Wrappers;
using ClipTrimDotNet.Client;
using System.Text.Json.Serialization;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Dynamic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ClipTrimDotNet.Keys
{
[PluginActionId("com.michal-courson.cliptrim.player")]
public class Player : KeypadBase
{
private int coordIndex;
private string? current_text = null;
private KeyCoordinates coordinates;
static List<Player> instances = new();
public Player(SDConnection connection, InitialPayload payload) : base(connection, payload)
{
coordinates = payload.Coordinates;
GlobalSettingsManager.Instance.RequestGlobalSettings();
instances.Add(this);
coordIndex = Math.Max((coordinates.Row - 1) * 5 + coordinates.Column, 0);
OnTick();
}
public static void TickAll()
{
Logger.Instance.LogMessage(TracingLevel.INFO, "Ticking all Player instances" + instances.Count);
foreach (var instance in instances)
{
instance.OnTick();
}
instances.RemoveAll(i => i == null);
}
private async void CheckFile()
{
var next_text = ClipTrimClient.Instance.GetPlayerStringByCoordinateIndex(coordIndex);
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 KeyPressed(KeyPayload payload)
{
ClipTrimClient.Instance.PlayClip(coordIndex);
}
public override void OnTick() {
CheckFile();
}
public override void ReceivedGlobalSettings(ReceivedGlobalSettingsPayload payload) {
Tools.AutoPopulateSettings(GlobalSettings.Instance, payload.Settings);
}
public override void KeyReleased(KeyPayload payload)
{
}
public override void ReceivedSettings(ReceivedSettingsPayload payload)
{
}
}
}

View File

@ -0,0 +1,140 @@
using BarRaider.SdTools;
using BarRaider.SdTools.Wrappers;
using ClipTrimDotNet.Client;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Dynamic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ClipTrimDotNet.Keys
{
public class DataSourceItem
{
public string label { get; set; } = "";
public string value { get; set; } = "";
}
[PluginActionId("com.michal-courson.cliptrim.profile-switcher")]
public class ProfileSwitcher : KeypadBase
{
private class PluginSettings
{
public static PluginSettings CreateDefaultSettings()
{
PluginSettings instance = new PluginSettings();
instance.ProfileName = null;
return instance;
}
[JsonProperty(PropertyName = "profileName")]
public string? ProfileName { get; set; }
}
#region Private Members
private PluginSettings settings;
#endregion
public ProfileSwitcher(SDConnection connection, InitialPayload payload) : base(connection, payload)
{
if (payload.Settings == null || payload.Settings.Count == 0)
{
settings = PluginSettings.CreateDefaultSettings();
SaveSettings();
}
else
{
settings = payload.Settings.ToObject<PluginSettings>()!;
}
Logger.Instance.LogMessage(TracingLevel.INFO, $"ProfileSwitcher initialized with payload {JsonConvert.SerializeObject(payload)}");
GlobalSettingsManager.Instance.RequestGlobalSettings();
Connection.OnSendToPlugin += Connection_OnSendToPlugin;
SetTitle();
}
private async void SetTitle()
{
await Connection.SetTitleAsync(settings.ProfileName);
}
private async void Connection_OnSendToPlugin(object? sender, SDEventReceivedEventArgs<BarRaider.SdTools.Events.SendToPlugin> e)
{
//Logger.Instance.LogMessage(TracingLevel.INFO, "get profiles");
if (e.Event.Payload["event"] is null) return;
if (e.Event.Payload["event"]!.ToString() == "getProfiles")
{
var files = ClipTrimClient.Instance.GetCollectionNames();
var items = files.Select(x => new DataSourceItem { label = x, value = x});
var obj = new JObject
{
["event"] = "getProfiles",
["items"] = JArray.FromObject(items)
};
await Connection.SendToPropertyInspectorAsync(obj);
}
}
public override void Dispose()
{
}
public override async void KeyPressed(KeyPayload payload)
{
GlobalSettings.Instance.SetCurrentProfile(settings.ProfileName??"");
ClipTrimClient.Instance.PageMode = false;
PageNavigator.TickAll();
await Connection.SetGlobalSettingsAsync(JObject.FromObject(GlobalSettings.Instance));
await Connection.SwitchProfileAsync("ClipTrim");
}
public override void KeyReleased(KeyPayload payload)
{
}
public override void OnTick()
{
SetTitle();
}
public override void ReceivedSettings(ReceivedSettingsPayload payload)
{
//Logger.Instance.LogMessage(TracingLevel.INFO, $"ProfileSwitcher received settings {JsonConvert.SerializeObject(payload.Settings)}");
Tools.AutoPopulateSettings(settings, payload.Settings);
SaveSettings();
}
public override void ReceivedGlobalSettings(ReceivedGlobalSettingsPayload payload)
{
Tools.AutoPopulateSettings(GlobalSettings.Instance, payload.Settings);
ClipTrimClient.Instance.CheckPort();
//if (payload.Settings == null || payload.Settings.Count == 0)
//{
// var inst = GlobalSettings.Instance;
//}
//else
//{
// GlobalSettings.Instance = payload.Settings.ToObject<GlobalSettings>();
//}
}
#region Private Methods
private Task SaveSettings()
{
return Connection.SetSettingsAsync(JObject.FromObject(settings));
}
#endregion
}
}

View File

@ -1,194 +0,0 @@
using BarRaider.SdTools;
using BarRaider.SdTools.Wrappers;
using ClipTrimDotNet.Client;
using System.Text.Json.Serialization;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Dynamic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ClipTrimDotNet
{
[PluginActionId("com.michal-courson.cliptrim.player")]
public class Player : KeypadBase
{
private ClipMetadata? metadata;
private KeyCoordinates coordinates;
private class PluginSettings
{
public static PluginSettings CreateDefaultSettings()
{
PluginSettings instance = new PluginSettings();
instance.Path = null;
instance.PlayType = "Play/Overlap";
instance.Index = 0;
instance.Volume = 1;
return instance;
}
[FilenameProperty]
[JsonPropertyName("path")]
public string? Path { get; set; }
[JsonPropertyName("index")]
public int? Index { get; set; }
[JsonPropertyName("playtype")]
public string PlayType { get; set; }
[JsonPropertyName("volume")]
public double Volume { get; set; }
}
#region Private Members
private PluginSettings settings;
#endregion
public Player(SDConnection connection, InitialPayload payload) : base(connection, payload)
{
if (payload.Settings == null || payload.Settings.Count == 0)
{
this.settings = PluginSettings.CreateDefaultSettings();
SaveSettings();
}
else
{
this.settings = payload.Settings.ToObject<PluginSettings>();
}
this.coordinates = payload.Coordinates;
GlobalSettingsManager.Instance.RequestGlobalSettings();
CheckFile();
}
private void Instance_OnReceivedGlobalSettings(object sender, ReceivedGlobalSettingsPayload e)
{
Tools.AutoPopulateSettings(GlobalSettings.Instance, e.Settings);
}
public int GetIndex()
{
return Math.Max((coordinates.Row - 1) * 5 + coordinates.Column, 0);
}
private async void CheckFile()
{
//if (settings == null || GlobalSettings.Instance.ProfileName ==null) return;
metadata = ClipTrimClient.Instance.GetClipByPagedIndex(GetIndex());
await Connection.SetTitleAsync($"{metadata?.Name ?? ""}");
//Logger.Instance.LogMessage(TracingLevel.INFO, $"Set title to {metadata?.Name ?? ""}");
return;
//var files = Directory.GetFiles(Path.Combine(Path.GetDirectoryName(GlobalSettings.Instance.BasePath), GlobalSettings.Instance.ProfileName), "*.wav", SearchOption.TopDirectoryOnly)
// .OrderBy(file => File.GetCreationTime(file))
// .ToArray();
//int? i = this.settings.Index;
//string new_path = "";
//if (i != null && i >= 0 && i < files.Length)
//{
// new_path = files[i ?? 0];
//}
//await Connection.SetTitleAsync(Path.GetFileNameWithoutExtension(new_path));
//if (new_path != settings.Path)
//{
// settings.Path = new_path;
// if(new_path != "")
// {
// FileEntry opts = GlobalSettings.Instance.GetFileOptionsInCurrentProfile(new_path);
// settings.Volume = opts.Volume;
// settings.PlayType = opts.Playtype;
// }
// await SaveSettings();
//}
}
private async void Connection_OnApplicationDidLaunch(object sender, BarRaider.SdTools.Wrappers.SDEventReceivedEventArgs<BarRaider.SdTools.Events.ApplicationDidLaunch> e)
{
}
private void Connection_OnTitleParametersDidChange(object sender, SDEventReceivedEventArgs<BarRaider.SdTools.Events.TitleParametersDidChange> e)
{
//titleParameters = e.Event?.Payload?.TitleParameters;
//userTitle = e.Event?.Payload?.Title;
}
public override void Dispose()
{
Connection.OnTitleParametersDidChange -= Connection_OnTitleParametersDidChange;
Connection.OnApplicationDidLaunch -= Connection_OnApplicationDidLaunch;
//Logger.Instance.LogMessage(TracingLevel.INFO, $"Destructor called");
}
public override void KeyPressed(KeyPayload payload)
{
//Logger.Instance.LogMessage(TracingLevel.INFO, "Key Pressedd");
Tools.AutoPopulateSettings(settings, payload.Settings);
// Logger.Instance.LogMessage(TracingLevel.INFO, JsonConvert.SerializeObject(settings));
ClipTrimClient.Instance.PlayClip(metadata);
//try
//{
// WavPlayer.Instance.Play(settings.Path, GlobalSettings.Instance.OutputDevice, settings.Volume, settings.PlayType == "Play/Overlap" ? WavPlayer.PlayMode.PlayOverlap : WavPlayer.PlayMode.PlayStop);
//}
//catch
//{
//}
}
public override void KeyReleased(KeyPayload payload) {
}
public override void OnTick() {
CheckFile();
}
public override async void ReceivedSettings(ReceivedSettingsPayload payload)
{
//Logger.Instance.LogMessage(TracingLevel.INFO, "Player rec settings");
Tools.AutoPopulateSettings(settings, payload.Settings);
GlobalSettings.Instance.SetFileOptionsCurrentProfile(settings.Path, new FileEntry() { Playtype = settings.PlayType, Volume = settings.Volume });
await Connection.SetGlobalSettingsAsync(JObject.FromObject(GlobalSettings.Instance));
//SaveSettings();
//CheckFile();
}
public override void ReceivedGlobalSettings(ReceivedGlobalSettingsPayload payload) {
//Logger.Instance.LogMessage(TracingLevel.INFO, "ReceivedGlobalSettings");
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>();
}
//CheckFile();
}
#region Private Methods
private Task SaveSettings()
{
return Connection.SetSettingsAsync(JObject.FromObject(settings));
}
#endregion
}
}

View File

@ -1,165 +0,0 @@
using BarRaider.SdTools;
using BarRaider.SdTools.Wrappers;
using ClipTrimDotNet.Client;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Dynamic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ClipTrimDotNet
{
public class DataSourceItem
{
public string label { get; set; }
public string value { get; set; }
}
[PluginActionId("com.michal-courson.cliptrim.profile-switcher")]
public class ProfileSwitcher : KeypadBase
{
private class PluginSettings
{
public static PluginSettings CreateDefaultSettings()
{
PluginSettings instance = new PluginSettings();
instance.ProfileName = null;
return instance;
}
[JsonProperty(PropertyName = "profileName")]
public string? ProfileName { get; set; }
}
#region Private Members
private PluginSettings settings;
#endregion
public ProfileSwitcher(SDConnection connection, InitialPayload payload) : base(connection, payload)
{
if (payload.Settings == null || payload.Settings.Count == 0)
{
this.settings = PluginSettings.CreateDefaultSettings();
SaveSettings();
}
else
{
this.settings = payload.Settings.ToObject<PluginSettings>();
}
GlobalSettingsManager.Instance.RequestGlobalSettings();
Connection.OnSendToPlugin += Connection_OnSendToPlugin;
SetTitle();
}
private async void SetTitle()
{
await Connection.SetTitleAsync(settings.ProfileName + " B");
}
private async void Connection_OnSendToPlugin(object sender, SDEventReceivedEventArgs<BarRaider.SdTools.Events.SendToPlugin> e)
{
//Logger.Instance.LogMessage(TracingLevel.INFO, "get profiles");
if (e.Event.Payload["event"].ToString() == "getProfiles")
{
//string basePath = "C:\\Users\\mickl\\Music\\clips";
//var files = Directory.GetDirectories(basePath, "*", SearchOption.TopDirectoryOnly).Select(x => Path.GetFileNameWithoutExtension(x)).Where(x => x != "original");
var files = ClipTrimClient.Instance.GetCollectionNames();
var items = files.Select(x => new DataSourceItem { label = x, value = x});
var obj = new JObject();
obj["event"] = "getProfiles";
obj["items"] = JArray.FromObject(items);
//Logger.Instance.LogMessage(TracingLevel.INFO, "get profiles return " + JsonConvert.SerializeObject(obj));
await Connection.SendToPropertyInspectorAsync(obj);
}
//if (e.Event.Payload["event"].ToString() == "getOutputDevices")
//{
// List<WaveOutCapabilities> devices = new List<WaveOutCapabilities>();
// for (int n = -1; n < WaveOut.DeviceCount; n++)
// {
// var caps = WaveOut.GetCapabilities(n);
// devices.Add(caps);
// }
// var items = devices.Select(x => new DataSourceItem { label = x.ProductName, value = x.ProductName });
// var obj = new JObject();
// obj["event"] = "getOutputDevices";
// obj["items"] = JArray.FromObject(items);
// //Logger.Instance.LogMessage(TracingLevel.INFO, "get devices return " + JsonConvert.SerializeObject(obj));
// await Connection.SendToPropertyInspectorAsync(obj);
//}
}
private void Connection_OnTitleParametersDidChange(object sender, SDEventReceivedEventArgs<BarRaider.SdTools.Events.TitleParametersDidChange> e)
{
}
public override void Dispose()
{
Connection.OnTitleParametersDidChange -= Connection_OnTitleParametersDidChange;
//Logger.Instance.LogMessage(TracingLevel.INFO, $"Destructor called");
}
public override async void KeyPressed(KeyPayload payload)
{
//Logger.Instance.LogMessage(TracingLevel.INFO, "KeyPressed");
//Logger.Instance.LogMessage(TracingLevel.INFO, JsonConvert.SerializeObject(settings));
//Logger.Instance.LogMessage(TracingLevel.INFO, JsonConvert.SerializeObject(GlobalSettings.Instance));
ClipTrimClient.Instance.SetSelectedCollectionByName(settings.ProfileName);
GlobalSettings.Instance.SetCurrentProfile(settings.ProfileName);
//Logger.Instance.LogMessage(TracingLevel.INFO, JsonConvert.SerializeObject(GlobalSettings.Instance));
await Connection.SetGlobalSettingsAsync(JObject.FromObject(GlobalSettings.Instance));
await Connection.SwitchProfileAsync("ClipTrim");
}
public override void KeyReleased(KeyPayload payload)
{
}
public override void OnTick()
{
SetTitle();
}
public override void ReceivedSettings(ReceivedSettingsPayload payload)
{
Tools.AutoPopulateSettings(settings, payload.Settings);
SaveSettings();
//tTitle();
//CheckFile();
}
public override void ReceivedGlobalSettings(ReceivedGlobalSettingsPayload payload)
{
//Logger.Instance.LogMessage(TracingLevel.INFO, "ReceivedGlobalSettings");
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>();
}
//CheckFile();
}
#region Private Methods
private Task SaveSettings()
{
return Connection.SetSettingsAsync(JObject.FromObject(settings));
}
#endregion
}
}

View File

@ -13,7 +13,7 @@ namespace ClipTrimDotNet
{ {
// Uncomment this line of code to allow for debugging // Uncomment this line of code to allow for debugging
//while (!System.Diagnostics.Debugger.IsAttached) { System.Threading.Thread.Sleep(100); } //while (!System.Diagnostics.Debugger.IsAttached) { System.Threading.Thread.Sleep(100); }
Client.ClipTrimClient.Instance.PortNumber = 5010; //Client.ClipTrimClient.Instance.PortNumber = 5010;
SDWrapper.Run(args); SDWrapper.Run(args);
} }
} }

View File

@ -11,7 +11,7 @@
<!-- <!--
Learn more about property inspector components at https://sdpi-components.dev/docs/components Learn more about property inspector components at https://sdpi-components.dev/docs/components
--> -->
<sdpi-item label="Index"> <!--<sdpi-item label="Index">
<sdpi-select setting="index" placeholder="file index"> <sdpi-select setting="index" placeholder="file index">
<option value="0">0</option> <option value="0">0</option>
<option value="1">1</option> <option value="1">1</option>
@ -39,7 +39,7 @@
</sdpi-item> </sdpi-item>
<sdpi-item label="Volume"> <sdpi-item label="Volume">
<sdpi-range setting="volume" min=".1" max="1" , step="0.05"></sdpi-range> <sdpi-range setting="volume" min=".1" max="1" , step="0.05"></sdpi-range>
</sdpi-item> </sdpi-item>-->
</body> </body>
</html> </html>

View File

@ -16,7 +16,14 @@
show-refresh="true" show-refresh="true"
placeholder="Select a ClipTrim page"></sdpi-select> placeholder="Select a ClipTrim page"></sdpi-select>
</sdpi-item> </sdpi-item>
<sdpi-item label="Base Path"> <sdpi-item>
<sdpi-textfield
global="true"
setting="portNumber"
placeholder="Port Number"
pattern="[0-9]+"></sdpi-textfield>
</sdpi-item>
<!--<sdpi-item label="Base Path">
<sdpi-file setting="basePath" <sdpi-file setting="basePath"
global="true" global="true"
webkitdirectory webkitdirectory
@ -29,6 +36,6 @@
datasource="getOutputDevices" datasource="getOutputDevices"
show-refresh="true" show-refresh="true"
placeholder="Select an Ouput Device"></sdpi-select> placeholder="Select an Ouput Device"></sdpi-select>
</sdpi-item> </sdpi-item>-->
</body> </body>
</html> </html>

View File

@ -1,225 +0,0 @@
//using System;
//using System.Collections.Concurrent;
//using System.Collections.Generic;
//using System.Linq;
//using System.ServiceModel.Security;
//using System.Threading.Tasks;
//using BarRaider.SdTools;
//using NAudio.Wave;
//using NAudio.Wave.SampleProviders;
//using Newtonsoft.Json;
//class CachedSound
//{
// public byte[] AudioData { get; private set; }
// public WaveFormat WaveFormat { get; private set; }
// public CachedSound(string audioFileName)
// {
// using (var audioFileReader = new AudioFileReader(audioFileName))
// {
// // TODO: could add resampling in here if required
// WaveFormat = audioFileReader.WaveFormat;
// var wholeFile = new List<byte>((int)(audioFileReader.Length));
// var readBuffer = new byte[audioFileReader.WaveFormat.SampleRate * audioFileReader.WaveFormat.Channels*4];
// int samplesRead;
// while ((samplesRead = audioFileReader.Read(readBuffer, 0, readBuffer.Length)) > 0)
// {
// wholeFile.AddRange(readBuffer.Take(samplesRead));
// }
// AudioData = wholeFile.ToArray();
// }
// //Logger.Instance.LogMessage(TracingLevel.INFO, $"AudioData Length {AudioData.Length}");
// }
//}
//class CachedSoundSampleProvider : IWaveProvider
//{
// private readonly CachedSound cachedSound;
// private long position;
// ~CachedSoundSampleProvider() {
// //Logger.Instance.LogMessage(TracingLevel.INFO, $"Cache destructor");
// }
// public CachedSoundSampleProvider(CachedSound cachedSound)
// {
// this.cachedSound = cachedSound;
// position = 0;
// }
// public int Read(byte[] buffer, int offset, int count)
// {
// //Logger.Instance.LogMessage(TracingLevel.INFO, $"Read1 byte");
// var availableSamples = cachedSound.AudioData.Length - position;
// var samplesToCopy = Math.Min(availableSamples, count);
// try
// {
// //Logger.Instance.LogMessage(TracingLevel.INFO, $"{cachedSound.AudioData.GetType()} {buffer.GetType()}");
// Array.Copy(cachedSound.AudioData, position, buffer, offset, samplesToCopy);
// }
// catch (Exception ex)
// {
// //Logger.Instance.LogMessage(TracingLevel.INFO, $"{ex.ToString()}");
// }
// //Logger.Instance.LogMessage(TracingLevel.INFO, $"Read3");
// position += samplesToCopy;
// //Logger.Instance.LogMessage(TracingLevel.INFO, $"Sending {samplesToCopy} samples");
// return (int)samplesToCopy;
// }
// public WaveFormat WaveFormat => cachedSound.WaveFormat;
//}
//public class WavPlayer
//{
// private static WavPlayer? instance;
// public static WavPlayer Instance
// {
// get
// {
// instance ??= new WavPlayer();
// return instance;
// }
// }
// public enum PlayMode
// {
// PlayOverlap,
// PlayStop
// }
// private readonly ConcurrentDictionary<string, List<Tuple<WaveOutEvent, IWaveProvider>>> _activePlayers;
// public WavPlayer()
// {
// _activePlayers = new ConcurrentDictionary<string, List<Tuple<WaveOutEvent, IWaveProvider>>>();
// }
// public void Play(string filePath, string id, double volume, PlayMode mode)
// {
// if (string.IsNullOrWhiteSpace(filePath))
// throw new ArgumentException("File path cannot be null or empty.", nameof(filePath));
// if (mode == PlayMode.PlayOverlap)
// {
// PlayWithOverlap(filePath, id, volume);
// }
// else if (mode == PlayMode.PlayStop)
// {
// PlayWithStop(filePath, id, volume);
// }
// else
// {
// throw new ArgumentOutOfRangeException(nameof(mode), "Invalid play mode specified.");
// }
// }
// private void PlayWithOverlap(string filePath, string id, double volume)
// {
// try
// {
// //Logger.Instance.LogMessage(TracingLevel.INFO, "Play overlap");
// var player = CreatePlayer(filePath, id);
// if (!_activePlayers.ContainsKey(filePath))
// {
// _activePlayers[filePath] = new List<Tuple<WaveOutEvent, IWaveProvider>>();
// }
// _activePlayers[filePath].Add(player);
// player.Item1.Volume = (float)volume;
// player.Item1.Play();
// }
// catch(Exception ex)
// {
// //Logger.Instance.LogMessage(TracingLevel.INFO, ex.ToString());
// }
// //var playersToDispose = _activePlayers[filePath].Where(x => x.Item1.PlaybackState == PlaybackState.Stopped).ToList();
// //foreach (var p in playersToDispose)
// //{
// // p.Item1.Stop();
// // p.Item1.Dispose();
// //}
// //_activePlayers[filePath].RemoveAll(x => x.Item1.PlaybackState == PlaybackState.Stopped);
// }
// private void PlayWithStop(string filePath, string id, double volume)
// {
// if (_activePlayers.TryGetValue(filePath, out var players))
// {
// // Stop and dispose all current players for this file
// if (players.Any(x => x.Item1.PlaybackState == PlaybackState.Playing))
// {
// var playersToDispose = players.ToList();
// foreach (var player in playersToDispose)
// {
// player.Item1.Stop();
// player.Item1.Dispose();
// }
// }
// else
// {
// PlayWithOverlap(filePath, id, volume);
// }
// }
// else
// {
// // Start a new player
// PlayWithOverlap(filePath, id, volume);
// }
// _activePlayers[filePath].RemoveAll(x => x.Item1.PlaybackState == PlaybackState.Stopped);
// }
// private Tuple<WaveOutEvent, IWaveProvider> CreatePlayer(string filePath, string name)
// {
// var reader = new CachedSoundSampleProvider(new CachedSound(filePath));
// //var reader = new AudioFileReader(filePath);
// int number = -1;
// for (int i = 0; i < WaveOut.DeviceCount; ++i)
// {
// if (WaveOut.GetCapabilities(i).ProductName == name)
// {
// number = i;
// }
// }
// var player = new WaveOutEvent() { DeviceNumber = number };
// player.Init(reader);
// return new Tuple<WaveOutEvent, IWaveProvider>(player, reader);
// }
// private void CleanupPlayer(string filePath)
// {
// if (_activePlayers.TryGetValue(filePath, out var players))
// {
// var playersToDispose = players.ToList();
// foreach (var p in playersToDispose)
// {
// p.Item1.Stop();
// p.Item1.Dispose();
// }
// }
// _activePlayers[filePath].RemoveAll(x => x.Item1.PlaybackState == PlaybackState.Stopped);
// }
// public void StopAll()
// {
// foreach (var players in _activePlayers.Values)
// {
// var playersToDispose = players.ToList();
// foreach (var player in playersToDispose)
// {
// player.Item1.Stop();
// player.Item1.Dispose();
// }
// players.Clear();
// }
// _activePlayers.Clear();
// }
//}

View File

@ -2,11 +2,11 @@
"Actions": [ "Actions": [
{ {
"Icon": "Images/icon", "Icon": "Images/player_icon",
"Name": "Player", "Name": "Player",
"States": [ "States": [
{ {
"Image": "Images/pluginAction", "Image": "Images/player",
"TitleAlignment": "middle", "TitleAlignment": "middle",
"FontSize": 11 "FontSize": 11
} }
@ -17,29 +17,59 @@
"PropertyInspectorPath": "PropertyInspector/file_player.html" "PropertyInspectorPath": "PropertyInspector/file_player.html"
}, },
{ {
"Icon": "Images/icon", "Icon": "Images/collection_icon",
"Name": "Profile Switcher", "Name": "Collection Selector",
"States": [ "States": [
{ {
"Image": "Images/pluginAction", "Image": "Images/collection",
"TitleAlignment": "middle", "TitleAlignment": "middle",
"FontSize": 11 "FontSize": 11
} }
], ],
"SupportedInMultiActions": false, "SupportedInMultiActions": false,
"Tooltip": "Selects which sub folder to use and opens effect profile", "Tooltip": "Selects which collection to use",
"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/page_nav_icon",
"Name": "Page Navigator",
"States": [
{
"Image": "Images/page_nav",
"TitleAlignment": "middle",
"FontSize": 16
}
],
"SupportedInMultiActions": false,
"Tooltip": "Navigates pages",
"UUID": "com.michal-courson.cliptrim.page-navigator",
"PropertyInspectorPath": "PropertyInspector/file_player.html"
},
{
"Icon": "Images/record_icon",
"Name": "Save Clip",
"States": [
{
"Image": "Images/record",
"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",
"Name": "ClipTrimDotNet", "Name": "ClipTrimDotNet",
"Description": "Pairs with cliptrim for easy voice recording and trimming", "Description": "Pairs with cliptrim for easy voice recording and trimming",
"Icon": "Images/pluginIcon", "Icon": "Images/app_icon",
"Version": "0.1.0.0", "Version": "0.1.0.0",
"CodePath": "ClipTrimDotNet.exe", "CodePath": "ClipTrimDotNet.exe",
"Category": "ClipTrimDotNet", "Category": "ClipTrim",
"CategoryIcon": "Images/categoryIcon", "CategoryIcon": "Images/category_icon",
"UUID": "com.michal-courson.cliptrim", "UUID": "com.michal-courson.cliptrim",
"OS": [ "OS": [
{ {