Merge branch 'socket' into react_migration

This commit is contained in:
michalcourson
2026-02-28 17:04:44 -05:00
80 changed files with 2471 additions and 2301 deletions

70
.github/upgrades/dotnet-upgrade-plan.md vendored Normal file
View File

@ -0,0 +1,70 @@
# .NET 8.0 Upgrade Plan
## Execution Steps
Execute steps below sequentially one by one in the order they are listed.
1. Validate that an .NET 8.0 SDK required for this upgrade is installed on the machine and if not, help to get it installed.
2. Ensure that the SDK version specified in global.json files is compatible with the .NET 8.0 upgrade.
3. Upgrade ClipTrimDotNet\ClipTrimDotNet.csproj
4. Run unit tests to validate upgrade in the projects listed below:
- ClientTest\ClientTest.csproj
## Settings
This section contains settings and data used by execution steps.
### Excluded projects
| Project name | Description |
|:-----------------------------------------------|:---------------------------:|
### Aggregate NuGet packages modifications across all projects
NuGet packages used across all selected projects or their dependencies that need version update in projects that reference them.
| Package Name | Current Version | New Version | Description |
|:------------------------------------|:---------------:|:-----------:|:----------------------------------------------|
| Microsoft.Bcl.AsyncInterfaces | 10.0.2 | 8.0.0 | Recommended for .NET 8.0 |
| Microsoft.Extensions.DependencyInjection | 10.0.2 | 8.0.1 | Recommended for .NET 8.0 |
| Microsoft.Extensions.DependencyInjection.Abstractions | 10.0.2 | 8.0.2 | Recommended for .NET 8.0 |
| Microsoft.Extensions.Logging | 10.0.2 | 8.0.1 | Recommended for .NET 8.0 |
| Microsoft.Extensions.Logging.Abstractions | 10.0.2 | 8.0.3 | Recommended for .NET 8.0 |
| Microsoft.Extensions.Options | 10.0.2 | 8.0.2 | Recommended for .NET 8.0 |
| Microsoft.Extensions.Primitives | 10.0.2 | 8.0.0 | Recommended for .NET 8.0 |
| System.Diagnostics.DiagnosticSource | 10.0.2 | 8.0.1 | Recommended for .NET 8.0 |
| System.Drawing.Common | 9.0.10 | 8.0.24 | Recommended for .NET 8.0 |
| System.IO.Pipelines | 10.0.2 | 8.0.0 | Recommended for .NET 8.0 |
| System.Security.AccessControl | 4.7.0 | 6.0.1 | Recommended for .NET 8.0 |
| System.Text.Encodings.Web | 10.0.2 | 8.0.0 | Recommended for .NET 8.0 |
| System.Text.Json | 10.0.2 | 8.0.6 | Recommended for .NET 8.0 |
| Microsoft.Win32.Registry | 4.7.0 | | Functionality included with framework |
| System.Buffers | 4.6.1 | | Functionality included with framework |
| System.IO | 4.3.0 | | Functionality included with framework |
| System.Memory | 4.6.3 | | Functionality included with framework |
| System.Net.Http | 4.3.4 | | Functionality included with framework |
| System.Numerics.Vectors | 4.6.1 | | Functionality included with framework |
| System.Runtime | 4.3.0 | | Functionality included with framework |
| System.Security.Cryptography.Algorithms | 4.3.0 | | Functionality included with framework |
| System.Security.Cryptography.Encoding | 4.3.0 | | Functionality included with framework |
| System.Security.Cryptography.Primitives | 4.3.0 | | Functionality included with framework |
| System.Security.Cryptography.X509Certificates | 4.3.0 | | Functionality included with framework |
| System.Security.Principal.Windows | 4.7.0 | | Functionality included with framework |
| System.Threading.Tasks.Extensions | 4.6.3 | | Functionality included with framework |
| System.ValueTuple | 4.6.1 | | Functionality included with framework |
### Project upgrade details
#### ClipTrimDotNet\ClipTrimDotNet.csproj modifications
Project properties changes:
- Target framework should be changed from `.NETFramework,Version=v4.8` to `net8.0`
- Project file should be converted to SDK-style
NuGet packages changes:
- Update all packages listed in the NuGet packages table above as recommended
- Remove packages whose functionality is now included with the framework
Other changes:
- Ensure compatibility with .NET 8.0 APIs and features
- Update code as needed for breaking changes

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

@ -29,6 +29,8 @@ class AudioIO:
sd.default.latency = 'low' sd.default.latency = 'low'
self.socket = None
self.in_stream = sd.InputStream( self.in_stream = sd.InputStream(
callback=self.record_callback callback=self.record_callback
) )
@ -119,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

@ -14,13 +14,30 @@ from routes.recording import recording_bp
from routes.device import device_bp from routes.device import device_bp
from routes.metadata import metadata_bp from routes.metadata import metadata_bp
from routes.settings import settings_bp from routes.settings import settings_bp
from flask_socketio import SocketIO from flask_socketio import SocketIO, emit
import threading 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')
# CORS(socketio)
@socketio.on('connect')
def handle_connect():
print("Client connected")
emit('full_data', MetaDataManager().collections)
@socketio.on('record_clip')
def record_clip():
io = AudioIO()
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
@ -36,6 +53,9 @@ def main():
args = parser.parse_args() args = parser.parse_args()
audio_manager = WindowsAudioManager() audio_manager = WindowsAudioManager()
settings = SettingsManager() settings = SettingsManager()
meta = MetaDataManager()
# Ensure save path exists # Ensure save path exists
os.makedirs(settings.get_settings('save_path'), exist_ok=True) os.makedirs(settings.get_settings('save_path'), exist_ok=True)
@ -43,13 +63,19 @@ def main():
io = AudioIO() io = AudioIO()
io.start_recording() io.start_recording()
# settings.socket = socketio
io.socket = socketio
meta.socket = socketio
# Register blueprints # Register blueprints
app.register_blueprint(recording_bp) app.register_blueprint(recording_bp)
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)
app.run(host='127.0.0.1', port=settings.get_settings('http_port'), debug=False, use_reloader=True) print(f"Starting Flask server on port {settings.get_settings('http_port')}")
# socketio.run(app, host='127.0.0.1', port=args.osc_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, allow_unsafe_werkzeug=True)

View File

@ -10,6 +10,7 @@ class MetaDataManager:
cls._instance.init() cls._instance.init()
return cls._instance return cls._instance
def init(self): def init(self):
self.socket = None
# read metadata file from executing directory # read metadata file from executing directory
self.metadata_file = os.path.join(os.getcwd(), "metadata.json") self.metadata_file = os.path.join(os.getcwd(), "metadata.json")
if os.path.exists(self.metadata_file): if os.path.exists(self.metadata_file):
@ -40,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):
@ -55,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)
@ -71,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):
@ -93,6 +105,8 @@ class MetaDataManager:
raise ValueError("New order contains clips that do not exist in the collection.") raise ValueError("New order contains clips that do not exist in the collection.")
collection["clips"] = new_order collection["clips"] = new_order
if not self.socket is None:
self.socket.emit('collection_updated', collection)
self.save_metadata() self.save_metadata()
def save_metadata(self): def save_metadata(self):

View File

@ -98,3 +98,9 @@ def edit_clip_in_collection():
return jsonify({'status': 'success', 'collections': collections}) return jsonify({'status': 'success', 'collections': collections})
except ValueError as e: except ValueError as e:
return jsonify({'status': 'error', 'message': str(e)}), 400 return jsonify({'status': 'error', 'message': str(e)}), 400
@metadata_bp.route('/ws/test', methods=['POST'])
def test_websocket():
MetaDataManager().socket.emit('test_event', {'data': 'Test message from metadata route'})
return jsonify({'status': 'success'})

View File

@ -29,3 +29,4 @@ def set_all_settings():
return jsonify({'status': 'success', 'settings': settings}) return jsonify({'status': 'success', 'settings': settings})
except ValueError as e: except ValueError as e:
return jsonify({'status': 'error', 'message': str(e)}), 400 return jsonify({'status': 'error', 'message': str(e)}), 400

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,7 +40,7 @@ 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()}`);

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,21 +1,41 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<configuration> <configuration>
<startup> <startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.8"/> <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.8" />
</startup> </startup>
<runtime> <runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1"> <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly> <dependentAssembly>
<assemblyIdentity name="CommandLine" publicKeyToken="5a870481e358d379" culture="neutral"/> <assemblyIdentity name="CommandLine" publicKeyToken="5a870481e358d379" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-2.6.0.0" newVersion="2.6.0.0"/> <bindingRedirect oldVersion="0.0.0.0-2.6.0.0" newVersion="2.6.0.0" />
</dependentAssembly> </dependentAssembly>
<dependentAssembly> <dependentAssembly>
<assemblyIdentity name="System.Drawing.Common" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral"/> <assemblyIdentity name="System.Drawing.Common" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-8.0.0.0" newVersion="8.0.0.0"/> <bindingRedirect oldVersion="0.0.0.0-8.0.0.0" newVersion="8.0.0.0" />
</dependentAssembly> </dependentAssembly>
<dependentAssembly> <dependentAssembly>
<assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral"/> <assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-13.0.0.0" newVersion="13.0.0.0"/> <bindingRedirect oldVersion="0.0.0.0-13.0.0.0" newVersion="13.0.0.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="Microsoft.Extensions.DependencyInjection.Abstractions" publicKeyToken="adb9793829ddae60" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-10.0.0.2" newVersion="10.0.0.2" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Text.Json" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-10.0.0.2" newVersion="10.0.0.2" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="Microsoft.Extensions.Logging.Abstractions" publicKeyToken="adb9793829ddae60" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-10.0.0.2" newVersion="10.0.0.2" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="Microsoft.Extensions.Logging" publicKeyToken="adb9793829ddae60" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-10.0.0.2" newVersion="10.0.0.2" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="Microsoft.Extensions.DependencyInjection" publicKeyToken="adb9793829ddae60" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-10.0.0.2" newVersion="10.0.0.2" />
</dependentAssembly> </dependentAssembly>
</assemblyBinding> </assemblyBinding>
</runtime> </runtime>

View File

@ -1,10 +1,13 @@
using Newtonsoft.Json; using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using Newtonsoft.Json.Converters; using Newtonsoft.Json.Converters;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Text.Json.Serialization;
namespace ClipTrimDotNet.Client namespace ClipTrimDotNet.Client
{ {
@ -16,27 +19,35 @@ namespace ClipTrimDotNet.Client
public class ClipMetadata public class ClipMetadata
{ {
[JsonProperty(PropertyName = "filename")] [JsonProperty(PropertyName = "filename")]
[JsonPropertyName("filename")]
public string Filename { get; set; } public string Filename { get; set; }
[JsonProperty(PropertyName = "name")] [JsonProperty(PropertyName = "name")]
[JsonPropertyName("name")]
public string Name { get; set; } public string Name { get; set; }
[JsonProperty(PropertyName = "volume")] [JsonProperty(PropertyName = "volume")]
[JsonPropertyName("volume")]
public double Volume { get; set; } = 1.0; public double Volume { get; set; } = 1.0;
[JsonProperty(PropertyName = "startTime")] [JsonProperty(PropertyName = "startTime")]
[JsonPropertyName("startTime")]
public double StartTime { get; set; } = 0.0; public double StartTime { get; set; } = 0.0;
[JsonProperty(PropertyName = "endTime")] [JsonProperty(PropertyName = "endTime")]
[JsonPropertyName("endTime")]
public double EndTime { get; set; } = 0.0; public double EndTime { get; set; } = 0.0;
[JsonProperty(PropertyName = "playbackType")] [JsonProperty(PropertyName = "playbackType")]
[JsonConverter(typeof(StringEnumConverter))] [Newtonsoft.Json.JsonConverter(typeof(StringEnumConverter))]
[System.Text.Json.Serialization.JsonConverter(typeof(JsonStringEnumConverter))]
[JsonPropertyName("playbackType")]
public PlaybackType PlaybackType { get; set; } = PlaybackType.playStop; public PlaybackType PlaybackType { get; set; } = PlaybackType.playStop;
} }
} }

View File

@ -1,10 +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;
namespace ClipTrimDotNet.Client namespace ClipTrimDotNet.Client
{ {
@ -23,87 +29,258 @@ namespace ClipTrimDotNet.Client
} }
} }
private HttpClient httpClient; //private HttpClient httpClient;
private SocketIO? socket;
public string HostName
{
get
{
//return $"http://localhost:5010/";
return $"http://localhost:{GlobalSettings.Instance.PortNumber}/";
}
}
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.Path = "/socket.io";
socket.Options.ConnectionTimeout = TimeSpan.FromSeconds(10);
socket.Options.Reconnection = true;
socket.On("full_data", ctx =>
{
try
{
var response = ctx.GetValue<List<CollectionMetaData>>(0);
Logger.Instance.LogMessage(TracingLevel.INFO, $"full_data event {JsonConvert.SerializeObject(response)}");
Collections = response!;
Player.TickAll();
PageNavigator.TickAll();
//Logger.Instance.LogMessage(TracingLevel.INFO, $"Collections {JsonConvert.SerializeObject(Collections)}");
}
catch (Exception ex)
{
Logger.Instance.LogMessage(TracingLevel.INFO, $"full_data error {ex.ToString()}");
}
return Task.CompletedTask;
});
socket.On("collection_updated", ctx =>
{
try
{
var response = ctx.GetValue<CollectionMetaData>(0)!;
Logger.Instance.LogMessage(TracingLevel.INFO, $"collection_updated event {JsonConvert.SerializeObject(response)}");
int index = Collections.FindIndex(x => x.Id == response.Id);
if (index != -1)
{
Collections[index] = response;
Player.TickAll();
PageNavigator.TickAll();
}
}
catch
{
}
return Task.CompletedTask;
});
socket.OnConnected += (sender, e) =>
{
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() public ClipTrimClient()
{ {
httpClient = new HttpClient() //httpClient = new HttpClient()
{ //{
BaseAddress = new Uri("http://localhost:5010/"), // BaseAddress = new Uri("http://localhost:5010/"),
Timeout = TimeSpan.FromSeconds(10) // Timeout = TimeSpan.FromSeconds(10)
}; //};
Task.Run(ShortPoll); CreateSocket();
} }
public async Task ShortPoll() public async Task Connect()
{ {
while (true) if (socket is null) return;
while (!socket.Connected)
{
try
{
await socket.ConnectAsync();
}
catch
{ {
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 List<CollectionMetaData> Collections { get; private set; } = new List<CollectionMetaData>();
public CollectionMetaData? SelectedCollection { get; private set; } public int SelectedCollection { get; private set; } = -1;
public int PageIndex { get; private set; } = 0;
private async Task GetMetadata() public Dictionary<int, int> CollectionIndexes { get; private set; } = new();
public int PageIndex
{ {
try get
{ {
var response = await httpClient.GetAsync("meta"); if (SelectedCollection == -1) return 0;
if (response.IsSuccessStatusCode) if (!CollectionIndexes.ContainsKey(SelectedCollection))
{ {
var json = await response.Content.ReadAsStringAsync(); CollectionIndexes[SelectedCollection] = 0;
dynamic collections = JsonConvert.DeserializeObject(json); }
collections = collections.collections; return CollectionIndexes[SelectedCollection];
Collections = JsonConvert.DeserializeObject<List<CollectionMetaData>>(collections.ToString()); }
set
{
if (SelectedCollection == -1) return;
CollectionIndexes[SelectedCollection] = value;
} }
} }
catch (Exception ex) public bool PageMode { get; set; } = false;
public int PageCount
{ {
//Logger.Instance.LogMessage(TracingLevel.INFO, $"Error pinging ClipTrim API: {ex.Message}"); get
return; {
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 GetCurrentCollectionName()
{ {
var collection = Collections.FirstOrDefault(x => x.Name == name); if (SelectedCollection == -1) return "";
if (collection != null) return Collections[SelectedCollection].Name;
}
public string GetPlayerStringByCoordinateIndex(int index)
{ {
SelectedCollection = collection; if (PageMode)
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)
{ {
if (SelectedCollection == null) return null; SelectedCollection = Collections.FindIndex(x => x.Name == GlobalSettings.Instance.ProfileName);
if (SelectedCollection == -1) return null;
int clipIndex = PageIndex * 10 + index; int clipIndex = PageIndex * 10 + index;
if (clipIndex >= 0 && clipIndex < SelectedCollection.Clips.Count) var collection = Collections[SelectedCollection];
if (clipIndex >= 0 && clipIndex < collection.Clips.Count)
{ {
return SelectedCollection.Clips[clipIndex]; return collection.Clips[clipIndex];
} }
return null; return null;
} }
public async void PlayClip(ClipMetadata? metadata) public async void PlayClip(int index)
{ {
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; 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

@ -3,6 +3,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using System.Text.Json.Serialization;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace ClipTrimDotNet.Client namespace ClipTrimDotNet.Client
@ -10,14 +11,17 @@ namespace ClipTrimDotNet.Client
public class CollectionMetaData public class CollectionMetaData
{ {
[JsonProperty(PropertyName = "name")] [JsonProperty(PropertyName = "name")]
[JsonPropertyName("name")]
public string Name { get; set; } public string Name { get; set; }
[JsonProperty(PropertyName = "clips")] [JsonProperty(PropertyName = "clips")]
[JsonPropertyName("clips")]
public List<ClipMetadata> Clips { get; set; } = new List<ClipMetadata>(); public List<ClipMetadata> Clips { get; set; } = new List<ClipMetadata>();
[JsonProperty(PropertyName = "id")] [JsonProperty(PropertyName = "id")]
[JsonPropertyName("id")]
public int Id { get; set; } public int Id { get; set; }
} }
} }

View File

@ -1,147 +1,110 @@
<?xml version="1.0" encoding="utf-8"?> <Project Sdk="Microsoft.NET.Sdk">
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup> <PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> <TargetFramework>net8.0-windows</TargetFramework>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> <AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<ProjectGuid>{4635D874-69C0-4010-BE46-77EF92EB1553}</ProjectGuid>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<RootNamespace>ClipTrimDotNet</RootNamespace> <LangVersion>10</LangVersion>
<AssemblyName>ClipTrimDotNet</AssemblyName>
<TargetFrameworkVersion>v4.8</TargetFrameworkVersion>
<LangVersion>8</LangVersion>
<FileAlignment>512</FileAlignment>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<Deterministic>true</Deterministic>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<TargetFrameworkProfile /> <ImplicitUsings>enable</ImplicitUsings>
<PreBuildEvent>npm run stop</PreBuildEvent>
<PostBuildEvent>npm run start</PostBuildEvent>
<AssemblyTitle>ClipTrimDotNet</AssemblyTitle>
<Product>ClipTrimDotNet</Product>
<Copyright>Copyright © 2020</Copyright>
<AssemblyVersion>1.0.0.0</AssemblyVersion>
<FileVersion>1.0.0.0</FileVersion>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\com.michal-courson.cliptrim.sdPlugin\</OutputPath> <OutputPath>bin\Debug\com.michal-courson.cliptrim.sdPlugin\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\ClipTrimDotNet.sdPlugin\</OutputPath> <OutputPath>bin\Release\ClipTrimDotNet.sdPlugin\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Reference Include="CommandLine, Version=2.9.1.0, Culture=neutral, PublicKeyToken=5a870481e358d379, processorArchitecture=MSIL"> <None Remove="Images\app_icon.png" />
<HintPath>..\packages\CommandLineParser.2.9.1\lib\net461\CommandLine.dll</HintPath> <None Remove="Images\back.png" />
</Reference> <None Remove="Images\category_icon.png" />
<Reference Include="Microsoft.Win32.Registry, Version=4.1.3.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> <None Remove="Images\collection.png" />
<HintPath>..\packages\Microsoft.Win32.Registry.4.7.0\lib\net461\Microsoft.Win32.Registry.dll</HintPath> <None Remove="Images\collection_icon.png" />
</Reference> <None Remove="Images\page_nav.png" />
<Reference Include="NAudio, Version=2.2.1.0, Culture=neutral, PublicKeyToken=e279aa5131008a41, processorArchitecture=MSIL"> <None Remove="Images\page_nav_icon.png" />
<HintPath>..\packages\NAudio.2.2.1\lib\net472\NAudio.dll</HintPath> <None Remove="Images\player.png" />
</Reference> <None Remove="Images\player_icon.png" />
<Reference Include="NAudio.Asio, Version=2.2.1.0, Culture=neutral, PublicKeyToken=e279aa5131008a41, processorArchitecture=MSIL"> <None Remove="Images\record.png" />
<HintPath>..\packages\NAudio.Asio.2.2.1\lib\netstandard2.0\NAudio.Asio.dll</HintPath> <None Remove="Images\record_icon.png" />
</Reference> <None Remove="manifest.json" />
<Reference Include="NAudio.Core, Version=2.2.1.0, Culture=neutral, PublicKeyToken=e279aa5131008a41, processorArchitecture=MSIL">
<HintPath>..\packages\NAudio.Core.2.2.1\lib\netstandard2.0\NAudio.Core.dll</HintPath>
</Reference>
<Reference Include="NAudio.Midi, Version=2.2.1.0, Culture=neutral, PublicKeyToken=e279aa5131008a41, processorArchitecture=MSIL">
<HintPath>..\packages\NAudio.Midi.2.2.1\lib\netstandard2.0\NAudio.Midi.dll</HintPath>
</Reference>
<Reference Include="NAudio.Wasapi, Version=2.2.1.0, Culture=neutral, PublicKeyToken=e279aa5131008a41, processorArchitecture=MSIL">
<HintPath>..\packages\NAudio.Wasapi.2.2.1\lib\netstandard2.0\NAudio.Wasapi.dll</HintPath>
</Reference>
<Reference Include="NAudio.WinForms, Version=2.2.1.0, Culture=neutral, PublicKeyToken=e279aa5131008a41, processorArchitecture=MSIL">
<HintPath>..\packages\NAudio.WinForms.2.2.1\lib\net472\NAudio.WinForms.dll</HintPath>
</Reference>
<Reference Include="NAudio.WinMM, Version=2.2.1.0, Culture=neutral, PublicKeyToken=e279aa5131008a41, processorArchitecture=MSIL">
<HintPath>..\packages\NAudio.WinMM.2.2.1\lib\netstandard2.0\NAudio.WinMM.dll</HintPath>
</Reference>
<Reference Include="Newtonsoft.Json, Version=13.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
<HintPath>..\packages\Newtonsoft.Json.13.0.4\lib\net45\Newtonsoft.Json.dll</HintPath>
</Reference>
<Reference Include="NLog, Version=6.0.0.0, Culture=neutral, PublicKeyToken=5120e14c03d0593c, processorArchitecture=MSIL">
<HintPath>..\packages\NLog.6.0.5\lib\net46\NLog.dll</HintPath>
</Reference>
<Reference Include="StreamDeckTools, Version=6.3.2.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\StreamDeck-Tools.6.3.2\lib\netstandard2.0\StreamDeckTools.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Configuration" />
<Reference Include="System.Core" />
<Reference Include="System.Drawing" />
<Reference Include="System.Drawing.Common, Version=9.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\System.Drawing.Common.9.0.10\lib\net462\System.Drawing.Common.dll</HintPath>
</Reference>
<Reference Include="System.IO.Compression" />
<Reference Include="System.Runtime.Serialization" />
<Reference Include="System.Security.AccessControl, Version=4.1.3.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\System.Security.AccessControl.4.7.0\lib\net461\System.Security.AccessControl.dll</HintPath>
</Reference>
<Reference Include="System.Security.Principal.Windows, Version=4.1.3.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\System.Security.Principal.Windows.4.7.0\lib\net461\System.Security.Principal.Windows.dll</HintPath>
</Reference>
<Reference Include="System.ServiceModel" />
<Reference Include="System.Transactions" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Xml" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="BaseTest.cs" /> <PackageReference Include="CommandLineParser" Version="2.9.1" />
<Compile Include="Client\ClipMetadata.cs" /> <PackageReference Include="Microsoft.Bcl.AsyncInterfaces" Version="8.0.0" />
<Compile Include="Client\ClipTrimClient.cs" /> <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="10.0.2" />
<Compile Include="Client\CollectionMetaData.cs" /> <PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.2" />
<Compile Include="GlobalSettings.cs" /> <PackageReference Include="Microsoft.Extensions.Logging" Version="10.0.2" />
<Compile Include="Player.cs" /> <PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.2" />
<Compile Include="ProfileSwitcher.cs" /> <PackageReference Include="Microsoft.Extensions.Options" Version="10.0.2" />
<Compile Include="Program.cs" /> <PackageReference Include="Microsoft.Extensions.Primitives" Version="10.0.2" />
<Compile Include="Properties\AssemblyInfo.cs" /> <PackageReference Include="Newtonsoft.Json" Version="13.0.4" />
<Compile Include="WavPlayer.cs" /> <PackageReference Include="NLog" Version="6.0.5" />
<PackageReference Include="SocketIOClient" Version="4.0.0.2" />
<PackageReference Include="SocketIOClient.Common" Version="4.0.0" />
<PackageReference Include="SocketIOClient.Serializer" Version="4.0.0.1" />
<PackageReference Include="SocketIOClient.Serializer.NewtonsoftJson" Version="4.0.0.1" />
<PackageReference Include="StreamDeck-Tools" Version="6.3.2" />
<PackageReference Include="System.Diagnostics.DiagnosticSource" Version="10.0.2" />
<PackageReference Include="System.Drawing.Common" Version="9.0.10" />
<PackageReference Include="System.IO.Pipelines" Version="10.0.2" />
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="6.1.2" />
<PackageReference Include="System.Security.AccessControl" Version="6.0.1" />
<PackageReference Include="System.Text.Encodings.Web" Version="10.0.2" />
<PackageReference Include="System.Text.Json" Version="10.0.2" />
<PackageReference Include="System.Configuration.ConfigurationManager" Version="10.0.3" />
<PackageReference Include="CoreWCF.Primitives" Version="1.8.0" />
<PackageReference Include="CoreWCF.ConfigurationManager" Version="1.8.0" />
<PackageReference Include="CoreWCF.Http" Version="1.8.0" />
<PackageReference Include="CoreWCF.WebHttp" Version="1.8.0" />
<PackageReference Include="CoreWCF.NetTcp" Version="1.8.0" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<None Include="App.config" /> <None Update="DialLayout.json">
<None Include="DialLayout.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None> </None>
<None Include="manifest.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="packages.config" />
</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" />
@ -152,11 +115,4 @@
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content> </Content>
</ItemGroup> </ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<PropertyGroup>
<PreBuildEvent>npm run stop</PreBuildEvent>
</PropertyGroup>
<PropertyGroup>
<PostBuildEvent>npm run start</PostBuildEvent>
</PropertyGroup>
</Project> </Project>

View File

@ -7,32 +7,9 @@ using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using BarRaider.SdTools.Wrappers; using BarRaider.SdTools.Wrappers;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using NAudio.MediaFoundation;
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;
@ -51,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,193 +0,0 @@
using BarRaider.SdTools;
using BarRaider.SdTools.Wrappers;
using ClipTrimDotNet.Client;
using NAudio.CoreAudioApi.Interfaces;
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
{
[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]
[JsonProperty(PropertyName = "path")]
public string? Path { get; set; }
[JsonProperty(PropertyName = "index")]
public int? Index { get; set; }
[JsonProperty(PropertyName = "playtype")]
public string PlayType { get; set; }
[JsonProperty(PropertyName = "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 ?? ""}");
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,167 +0,0 @@
using BarRaider.SdTools;
using BarRaider.SdTools.Wrappers;
using ClipTrimDotNet.Client;
using NAudio.CoreAudioApi.Interfaces;
using NAudio.Wave;
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 + " A");
}
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,6 +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;
SDWrapper.Run(args); SDWrapper.Run(args);
} }
} }

View File

@ -1,16 +1,6 @@
using System.Reflection; using System.Reflection;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("ClipTrimDotNet")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("ClipTrimDotNet")]
[assembly: AssemblyCopyright("Copyright © 2020")]
[assembly: AssemblyTrademark("")] [assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")] [assembly: AssemblyCulture("")]
@ -21,16 +11,3 @@ using System.Runtime.InteropServices;
// The following GUID is for the ID of the typelib if this project is exposed to COM // The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("1bb90885-9d98-46ef-b983-4a4ef3aea890")] [assembly: Guid("1bb90885-9d98-46ef-b983-4a4ef3aea890")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

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": [
{ {

View File

@ -1,12 +1,12 @@
{ {
"scripts": { "scripts": {
"stop": "streamdeck stop com.michal-courson.cliptrim", "stop": "streamdeck stop com.michal-courson.cliptrim",
"copy": "@powershell robocopy bin/Debug/ClipTrimDotNet.sdPlugin bin/Debug/com.michal-courson.cliptrim.sdPlugin", "copy": "@powershell robocopy bin/Debug/ClipTrimDotNet.sdPlugin bin/Debug/com.michal-courson.cliptrim.sdPlugin/net8.0-windows",
"link": "streamdeck link bin/Debug/com.michal-courson.cliptrim.sdPlugin", "link": "streamdeck link bin/Debug/com.michal-courson.cliptrim.sdPlugin",
"restart": "streamdeck restart com.michal-courson.cliptrim", "restart": "streamdeck restart com.michal-courson.cliptrim",
"start": "npm run link && npm run restart", "start": "npm run link && npm run restart",
"all": "npm run stop && npm run copy && npm run link && npm run restart", "all": "npm run stop && npm run copy && npm run link && npm run restart",
"pack": "streamdeck pack bin/Debug/com.michal-courson.cliptrim.sdPlugin/" "pack": "streamdeck pack bin/Debug/com.michal-courson.cliptrim.sdPlugin/net8.0-windows/"
}, },
"devDependencies": { "devDependencies": {
"shx": "^0.3.4" "shx": "^0.3.4"

View File

@ -1,6 +1,13 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<packages> <packages>
<package id="CommandLineParser" version="2.9.1" targetFramework="net472" /> <package id="CommandLineParser" version="2.9.1" targetFramework="net472" />
<package id="Microsoft.Bcl.AsyncInterfaces" version="10.0.2" targetFramework="net48" />
<package id="Microsoft.Extensions.DependencyInjection" version="10.0.2" targetFramework="net48" />
<package id="Microsoft.Extensions.DependencyInjection.Abstractions" version="10.0.2" targetFramework="net48" />
<package id="Microsoft.Extensions.Logging" version="10.0.2" targetFramework="net48" />
<package id="Microsoft.Extensions.Logging.Abstractions" version="10.0.2" targetFramework="net48" />
<package id="Microsoft.Extensions.Options" version="10.0.2" targetFramework="net48" />
<package id="Microsoft.Extensions.Primitives" version="10.0.2" targetFramework="net48" />
<package id="Microsoft.Win32.Registry" version="4.7.0" targetFramework="net48" /> <package id="Microsoft.Win32.Registry" version="4.7.0" targetFramework="net48" />
<package id="NAudio" version="2.2.1" targetFramework="net48" /> <package id="NAudio" version="2.2.1" targetFramework="net48" />
<package id="NAudio.Asio" version="2.2.1" targetFramework="net48" /> <package id="NAudio.Asio" version="2.2.1" targetFramework="net48" />
@ -11,8 +18,29 @@
<package id="NAudio.WinMM" version="2.2.1" targetFramework="net48" /> <package id="NAudio.WinMM" version="2.2.1" targetFramework="net48" />
<package id="Newtonsoft.Json" version="13.0.4" targetFramework="net48" /> <package id="Newtonsoft.Json" version="13.0.4" targetFramework="net48" />
<package id="NLog" version="6.0.5" targetFramework="net48" /> <package id="NLog" version="6.0.5" targetFramework="net48" />
<package id="SocketIOClient" version="4.0.0.2" targetFramework="net48" />
<package id="SocketIOClient.Common" version="4.0.0" targetFramework="net48" />
<package id="SocketIOClient.Serializer" version="4.0.0.1" targetFramework="net48" />
<package id="SocketIOClient.Serializer.NewtonsoftJson" version="4.0.0.1" targetFramework="net48" />
<package id="StreamDeck-Tools" version="6.3.2" targetFramework="net48" /> <package id="StreamDeck-Tools" version="6.3.2" targetFramework="net48" />
<package id="System.Buffers" version="4.6.1" targetFramework="net48" />
<package id="System.Diagnostics.DiagnosticSource" version="10.0.2" targetFramework="net48" />
<package id="System.Drawing.Common" version="9.0.10" targetFramework="net48" /> <package id="System.Drawing.Common" version="9.0.10" targetFramework="net48" />
<package id="System.IO" version="4.3.0" targetFramework="net48" />
<package id="System.IO.Pipelines" version="10.0.2" targetFramework="net48" />
<package id="System.Memory" version="4.6.3" targetFramework="net48" />
<package id="System.Net.Http" version="4.3.4" targetFramework="net48" />
<package id="System.Numerics.Vectors" version="4.6.1" targetFramework="net48" />
<package id="System.Runtime" version="4.3.0" targetFramework="net48" />
<package id="System.Runtime.CompilerServices.Unsafe" version="6.1.2" targetFramework="net48" />
<package id="System.Security.AccessControl" version="4.7.0" targetFramework="net48" /> <package id="System.Security.AccessControl" version="4.7.0" targetFramework="net48" />
<package id="System.Security.Cryptography.Algorithms" version="4.3.0" targetFramework="net48" />
<package id="System.Security.Cryptography.Encoding" version="4.3.0" targetFramework="net48" />
<package id="System.Security.Cryptography.Primitives" version="4.3.0" targetFramework="net48" />
<package id="System.Security.Cryptography.X509Certificates" version="4.3.0" targetFramework="net48" />
<package id="System.Security.Principal.Windows" version="4.7.0" targetFramework="net48" /> <package id="System.Security.Principal.Windows" version="4.7.0" targetFramework="net48" />
<package id="System.Text.Encodings.Web" version="10.0.2" targetFramework="net48" />
<package id="System.Text.Json" version="10.0.2" targetFramework="net48" />
<package id="System.Threading.Tasks.Extensions" version="4.6.3" targetFramework="net48" />
<package id="System.ValueTuple" version="4.6.1" targetFramework="net48" />
</packages> </packages>