diff --git a/.github/upgrades/dotnet-upgrade-plan.md b/.github/upgrades/dotnet-upgrade-plan.md new file mode 100644 index 0000000..24be8a9 --- /dev/null +++ b/.github/upgrades/dotnet-upgrade-plan.md @@ -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 diff --git a/.vscode/settings.json b/.vscode/settings.json index 5cc61c2..d289b15 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,3 +1,3 @@ -{ - "idf.pythonInstallPath": "C:\\Users\\mickl\\.espressif\\tools\\idf-python\\3.11.2\\python.exe" +{ + "idf.pythonInstallPath": "C:\\Users\\mickl\\.espressif\\tools\\idf-python\\3.11.2\\python.exe" } \ No newline at end of file diff --git a/audio-service/metadata.json b/audio-service/metadata.json index 386665c..a13a6f4 100644 --- a/audio-service/metadata.json +++ b/audio-service/metadata.json @@ -1,34 +1,134 @@ -[ - { - "name": "Uncategorized", - "id": 0, - "clips": [] - }, - { - "name": "Test", - "id": 1, - "clips": [] - }, - { - "name": "New", - "id": 2, - "clips": [ - { - "endTime": 30, - "filename": "C:\\Users\\mickl\\Desktop\\cliptrim-ui\\ClipTrimApp\\audio-service\\recordings\\audio_capture_20260220_193822.wav", - "name": "Pee pee\npoo poo", - "playbackType": "playOverlap", - "startTime": 27.76674010920584, - "volume": 0.25 - }, - { - "endTime": 27.516843118383072, - "filename": "C:\\Users\\mickl\\Desktop\\cliptrim-ui\\ClipTrimApp\\audio-service\\recordings\\audio_capture_20260220_200442.wav", - "name": "Clip 20260220_200442", - "playbackType": "playOverlap", - "startTime": 25.120307988450435, - "volume": 0.64 - } - ] - } +[ + { + "name": "Uncategorized", + "id": 0, + "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", + "id": 1, + "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", + "id": 2, + "clips": [ + { + "endTime": 30, + "filename": "C:\\Users\\mickl\\Desktop\\cliptrim-ui\\ClipTrimApp\\audio-service\\recordings\\audio_capture_20260220_193822.wav", + "name": "Pee pee\npoo poo", + "playbackType": "playOverlap", + "startTime": 27.64044943820222, + "volume": 0.31 + }, + { + "endTime": 30, + "filename": "C:\\Users\\mickl\\Desktop\\cliptrim-ui\\ClipTrimApp\\audio-service\\recordings\\audio_capture_20260220_200442.wav", + "name": "Test", + "playbackType": "playOverlap", + "startTime": 26.14685314685314, + "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 + } + ] + } ] \ No newline at end of file diff --git a/audio-service/requirements.txt b/audio-service/requirements.txt index 918fe6b..e77454c 100644 --- a/audio-service/requirements.txt +++ b/audio-service/requirements.txt @@ -1,7 +1,6 @@ -sounddevice==0.5.1 -numpy==1.22.3 -python-osc==1.9.3 -scipy==1.10.1 -comtypes==1.4.8 -pycaw==20240210 -Flask==3.1.2 \ No newline at end of file +Flask==3.1.3 +flask_cors==6.0.2 +flask_socketio==5.6.1 +numpy==2.4.2 +scipy==1.17.1 +sounddevice==0.5.5 diff --git a/audio-service/settings.json b/audio-service/settings.json index 6b19aa6..600d5b4 100644 --- a/audio-service/settings.json +++ b/audio-service/settings.json @@ -1,17 +1,17 @@ -{ - "input_device": { - "channels": 2, - "default_samplerate": 48000, - "index": 55, - "name": "VM Mic mix (VB-Audio Voicemeeter VAIO)" - }, - "save_path": "C:\\Users\\mickl\\Desktop\\cliptrim-ui\\ClipTrimApp\\audio-service\\recordings", - "recording_length": 30, - "output_device": { - "channels": 2, - "default_samplerate": 48000, - "index": 45, - "name": "VM to Discord (VB-Audio Voicemeeter VAIO)" - }, - "http_port": 5010 +{ + "input_device": { + "channels": 2, + "default_samplerate": 48000, + "index": 55, + "name": "VM Mic mix (VB-Audio Voicemeeter VAIO)" + }, + "save_path": "C:\\Users\\mickl\\Desktop\\cliptrim-ui\\ClipTrimApp\\audio-service\\recordings", + "recording_length": 30, + "output_device": { + "channels": 2, + "default_samplerate": 48000, + "index": 45, + "name": "VM to Discord (VB-Audio Voicemeeter VAIO)" + }, + "http_port": 5010 } \ No newline at end of file diff --git a/audio-service/src/audio_io.py b/audio-service/src/audio_io.py index 72e52b9..7dd6f24 100644 --- a/audio-service/src/audio_io.py +++ b/audio-service/src/audio_io.py @@ -1,166 +1,168 @@ -import sounddevice as sd -import numpy as np -import os -from datetime import datetime -import scipy.io.wavfile as wavfile -from metadata_manager import MetaDataManager -from audio_clip import AudioClip - - -# AudioClip class for clip playback - - -class AudioIO: - _instance = None - - def __new__(cls, *args, **kwargs): - if cls._instance is None: - # print("Creating new AudioRecorder instance") - cls._instance = super().__new__(cls) - cls._instance.init() - return cls._instance - def init(self): - self.duration = 30 - self.channels = 2 - self.input_sample_rate = 44100 - self.output_sample_rate = 44100 - self.buffer = np.zeros((int(self.duration * self.input_sample_rate), self.channels), dtype=np.float32) - self.recordings_dir = "recordings" - - sd.default.latency = 'low' - - self.in_stream = sd.InputStream( - callback=self.record_callback - ) - - self.out_stream = sd.OutputStream( - callback=self.playback_callback, - latency=3 - ) - - self.clip_map = {} - - - def refresh_streams(self): - was_active = self.in_stream.active - if was_active: - self.in_stream.stop() - self.out_stream.stop() - - self.buffer = np.zeros((int(self.duration * self.input_sample_rate), self.channels), dtype=np.float32) - # print(f"AudioRecorder initialized with duration={self.duration}s, sample_rate={self.sample_rate}Hz, channels={self.channels}") - self.in_stream = sd.InputStream( - callback=self.record_callback - ) - - self.out_stream = sd.OutputStream( - callback=self.playback_callback - ) - - if was_active: - self.in_stream.start() - self.out_stream.start() - - - - def record_callback(self, indata, frames, time, status): - if status: - # print(f"Recording status: {status}") - pass - - # Circular buffer implementation - self.buffer = np.roll(self.buffer, -frames, axis=0) - self.buffer[-frames:] = indata - - def playback_callback(self, outdata, frames, time, status): - if status: - # print(f"Playback status: {status}") - pass - - outdata.fill(0) - - # Iterate over a copy of the items to avoid modifying the dictionary during iteration - for clip_id, clip_list in list(self.clip_map.items()): - for clip in clip_list[:]: # Iterate over a copy of the list - if not clip.is_finished(): - samples = clip.get_samples(frames) - outdata[:] += samples.reshape(-1, 1) # Mix into output - if clip.is_finished(): - self.clip_map[clip_id].remove(clip) - if len(self.clip_map[clip_id]) == 0: - del self.clip_map[clip_id] - break # Exit inner loop since the key is deleted - - - def save_last_n_seconds(self): - # Create output directory if it doesn't exist - os.makedirs(self.recordings_dir, exist_ok=True) - - # Generate filename with timestamp - timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") - filename = os.path.join(self.recordings_dir, f"audio_capture_{timestamp}.wav") - - # Normalize audio to prevent clipping - audio_data = self.buffer / np.max(np.abs(self.buffer)) * .5 - - # Convert float32 to int16 for WAV file - audio_data_int16 = (audio_data * 32767).astype(np.int16) - - # Write buffer to file - wavfile.write(filename, int(self.input_sample_rate), audio_data_int16) - - meta = MetaDataManager() - - clip_metadata = { - "filename": filename, - "name": f"Clip {timestamp}", - "playbackType":"playStop", - "volume": 1.0, - } - - meta.add_clip_to_collection("Uncategorized", clip_metadata ) - - - return clip_metadata - - def set_buffer_duration(self, duration): - self.duration = duration - self.buffer = np.zeros((int(duration * self.input_sample_rate), self.channels), dtype=np.float32) - - def set_recording_directory(self, directory): - self.recordings_dir = directory - - def start_recording(self): - if(self.in_stream.active): - # print("Already recording") - return - # print('number of channels', self.channels) - - self.in_stream.start() - self.out_stream.start() - self.output_sample_rate = self.out_stream.samplerate - self.input_sample_rate = self.in_stream.samplerate - - def stop_recording(self): - if(not self.in_stream.active): - # print("Already stopped") - return - - self.in_stream.stop() - self.out_stream.stop() - - def is_recording(self): - return self.in_stream.active - - def play_clip(self, clip_metadata): - print(f"Playing clip: {clip_metadata}") - clip_id = clip_metadata.get("filename") - if clip_metadata.get("playbackType") == "playStop": - if clip_id in self.clip_map: - del self.clip_map[clip_id] - return - else: - self.clip_map[clip_id] = [] - if clip_id not in self.clip_map: - self.clip_map[clip_id] = [] +import sounddevice as sd +import numpy as np +import os +from datetime import datetime +import scipy.io.wavfile as wavfile +from metadata_manager import MetaDataManager +from audio_clip import AudioClip + + +# AudioClip class for clip playback + + +class AudioIO: + _instance = None + + def __new__(cls, *args, **kwargs): + if cls._instance is None: + # print("Creating new AudioRecorder instance") + cls._instance = super().__new__(cls) + cls._instance.init() + return cls._instance + def init(self): + self.duration = 30 + self.channels = 2 + self.input_sample_rate = 44100 + self.output_sample_rate = 44100 + self.buffer = np.zeros((int(self.duration * self.input_sample_rate), self.channels), dtype=np.float32) + self.recordings_dir = "recordings" + + sd.default.latency = 'low' + + self.socket = None + + self.in_stream = sd.InputStream( + callback=self.record_callback + ) + + self.out_stream = sd.OutputStream( + callback=self.playback_callback, + latency=3 + ) + + self.clip_map = {} + + + def refresh_streams(self): + was_active = self.in_stream.active + if was_active: + self.in_stream.stop() + self.out_stream.stop() + + self.buffer = np.zeros((int(self.duration * self.input_sample_rate), self.channels), dtype=np.float32) + # print(f"AudioRecorder initialized with duration={self.duration}s, sample_rate={self.sample_rate}Hz, channels={self.channels}") + self.in_stream = sd.InputStream( + callback=self.record_callback + ) + + self.out_stream = sd.OutputStream( + callback=self.playback_callback + ) + + if was_active: + self.in_stream.start() + self.out_stream.start() + + + + def record_callback(self, indata, frames, time, status): + if status: + # print(f"Recording status: {status}") + pass + + # Circular buffer implementation + self.buffer = np.roll(self.buffer, -frames, axis=0) + self.buffer[-frames:] = indata + + def playback_callback(self, outdata, frames, time, status): + if status: + # print(f"Playback status: {status}") + pass + + outdata.fill(0) + + # Iterate over a copy of the items to avoid modifying the dictionary during iteration + for clip_id, clip_list in list(self.clip_map.items()): + for clip in clip_list[:]: # Iterate over a copy of the list + if not clip.is_finished(): + samples = clip.get_samples(frames) + outdata[:] += samples.reshape(-1, 1) # Mix into output + if clip.is_finished(): + self.clip_map[clip_id].remove(clip) + if len(self.clip_map[clip_id]) == 0: + del self.clip_map[clip_id] + break # Exit inner loop since the key is deleted + + + def save_last_n_seconds(self): + # Create output directory if it doesn't exist + os.makedirs(self.recordings_dir, exist_ok=True) + + # Generate filename with timestamp + timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") + filename = os.path.join(self.recordings_dir, f"audio_capture_{timestamp}.wav") + + # Normalize audio to prevent clipping + audio_data = self.buffer / np.max(np.abs(self.buffer)) * .5 + + # Convert float32 to int16 for WAV file + audio_data_int16 = (audio_data * 32767).astype(np.int16) + + # Write buffer to file + wavfile.write(filename, int(self.input_sample_rate), audio_data_int16) + + meta = MetaDataManager() + + clip_metadata = { + "filename": filename, + "name": f"Clip {timestamp}", + "playbackType":"playStop", + "volume": 1.0, + } + + meta.add_clip_to_collection("Uncategorized", clip_metadata ) + self.socket.emit('new_clip', clip_metadata) + + return clip_metadata + + def set_buffer_duration(self, duration): + self.duration = duration + self.buffer = np.zeros((int(duration * self.input_sample_rate), self.channels), dtype=np.float32) + + def set_recording_directory(self, directory): + self.recordings_dir = directory + + def start_recording(self): + if(self.in_stream.active): + # print("Already recording") + return + # print('number of channels', self.channels) + + self.in_stream.start() + self.out_stream.start() + self.output_sample_rate = self.out_stream.samplerate + self.input_sample_rate = self.in_stream.samplerate + + def stop_recording(self): + if(not self.in_stream.active): + # print("Already stopped") + return + + self.in_stream.stop() + self.out_stream.stop() + + def is_recording(self): + return self.in_stream.active + + def play_clip(self, clip_metadata): + print(f"Playing clip: {clip_metadata}") + clip_id = clip_metadata.get("filename") + if clip_metadata.get("playbackType") == "playStop": + if clip_id in self.clip_map: + del self.clip_map[clip_id] + return + else: + self.clip_map[clip_id] = [] + if clip_id not in self.clip_map: + self.clip_map[clip_id] = [] self.clip_map[clip_id].append(AudioClip(clip_metadata, target_sample_rate=self.output_sample_rate)) \ No newline at end of file diff --git a/audio-service/src/main.py b/audio-service/src/main.py index b14ff0e..4fe8f4f 100644 --- a/audio-service/src/main.py +++ b/audio-service/src/main.py @@ -1,68 +1,94 @@ -import argparse -import os -import sys -from audio_io import AudioIO -from windows_audio import WindowsAudioManager -import sounddevice as sd -from metadata_manager import MetaDataManager -from settings import SettingsManager - -from flask import Flask -from flask_cors import CORS - -from routes.recording import recording_bp -from routes.device import device_bp -from routes.metadata import metadata_bp -from routes.settings import settings_bp -from flask_socketio import SocketIO -import threading - -app = Flask(__name__) -CORS(app) -# socketio = SocketIO(app, cors_allowed_origins="*") -# CORS(socketio) - -def main(): - # Create argument parser - parser = argparse.ArgumentParser(description='Audio Recording Service') - - # OSC port argument - parser.add_argument('--osc-port', - type=int, - help='OSC server port number', - default=5010) - - # Parse arguments - args = parser.parse_args() - audio_manager = WindowsAudioManager() - settings = SettingsManager() - - # Ensure save path exists - os.makedirs(settings.get_settings('save_path'), exist_ok=True) - - - io = AudioIO() - io.start_recording() - # Register blueprints - app.register_blueprint(recording_bp) - app.register_blueprint(device_bp) - app.register_blueprint(metadata_bp) - app.register_blueprint(settings_bp) - 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=args.osc_port, debug=False, use_reloader=True) - - - - # Run the OSC server - # try: - # print(f"Starting OSC Recording Server on port {args.osc_port}") - # # osc_server.run_server() - # except KeyboardInterrupt: - # print("\nServer stopped by user.") - # except Exception as e: - # print(f"Error starting server: {e}") - # sys.exit(1) - - -if __name__ == "__main__": +import argparse +import os +import sys +from audio_io import AudioIO +from windows_audio import WindowsAudioManager +import sounddevice as sd +from metadata_manager import MetaDataManager +from settings import SettingsManager + +from flask import Flask +from flask_cors import CORS + +from routes.recording import recording_bp +from routes.device import device_bp +from routes.metadata import metadata_bp +from routes.settings import settings_bp +from flask_socketio import SocketIO, emit +import threading + +app = Flask(__name__) +CORS(app) +socketio = SocketIO(app, cors_allowed_origins="*", logger=True, engineio_logger=True, async_mode='eventlet') + +@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(): + # Create argument parser + parser = argparse.ArgumentParser(description='Audio Recording Service') + + # OSC port argument + parser.add_argument('--osc-port', + type=int, + help='OSC server port number', + default=5010) + + # Parse arguments + args = parser.parse_args() + audio_manager = WindowsAudioManager() + settings = SettingsManager() + meta = MetaDataManager() + + + + # Ensure save path exists + os.makedirs(settings.get_settings('save_path'), exist_ok=True) + + + io = AudioIO() + io.start_recording() + + # settings.socket = socketio + io.socket = socketio + meta.socket = socketio + + # Register blueprints + app.register_blueprint(recording_bp) + app.register_blueprint(device_bp) + app.register_blueprint(metadata_bp) + app.register_blueprint(settings_bp) + print(f"Starting Flask server on port {settings.get_settings('http_port')}") + # app.run(host='127.0.0.1', port=settings.get_settings('http_port'), debug=False, use_reloader=True) + socketio.run(app, host='127.0.0.1', port=settings.get_settings('http_port'), debug=False, use_reloader=True, allow_unsafe_werkzeug=True) + + + + # Run the OSC server + # try: + # print(f"Starting OSC Recording Server on port {args.osc_port}") + # # osc_server.run_server() + # except KeyboardInterrupt: + # print("\nServer stopped by user.") + # except Exception as e: + # print(f"Error starting server: {e}") + # sys.exit(1) + + +if __name__ == "__main__": main() \ No newline at end of file diff --git a/audio-service/src/metadata.json b/audio-service/src/metadata.json index cb2d255..802da50 100644 --- a/audio-service/src/metadata.json +++ b/audio-service/src/metadata.json @@ -1,20 +1,20 @@ -{ - "Uncategorized": [ - { - "endTime": 12.489270386266055, - "filename": "C:\\Users\\mickl\\Desktop\\cliptrim-ui\\ClipTrimApp\\audio-service\\src\\recordings\\audio_capture_20260214_133540.wav", - "name": "Clip 20260214_133540", - "playbackType": "playStop", - "startTime": 10.622317596566523, - "volume": 1 - }, - { - "endTime": 6.824034334763957, - "filename": "C:\\Users\\mickl\\Desktop\\cliptrim-ui\\ClipTrimApp\\audio-service\\src\\recordings\\audio_capture_20260214_133137.wav", - "name": "Clip 20260214_133137", - "playbackType": "playStop", - "startTime": 3.7982832618025753, - "volume": 1 - } - ] +{ + "Uncategorized": [ + { + "endTime": 12.489270386266055, + "filename": "C:\\Users\\mickl\\Desktop\\cliptrim-ui\\ClipTrimApp\\audio-service\\src\\recordings\\audio_capture_20260214_133540.wav", + "name": "Clip 20260214_133540", + "playbackType": "playStop", + "startTime": 10.622317596566523, + "volume": 1 + }, + { + "endTime": 6.824034334763957, + "filename": "C:\\Users\\mickl\\Desktop\\cliptrim-ui\\ClipTrimApp\\audio-service\\src\\recordings\\audio_capture_20260214_133137.wav", + "name": "Clip 20260214_133137", + "playbackType": "playStop", + "startTime": 3.7982832618025753, + "volume": 1 + } + ] } \ No newline at end of file diff --git a/audio-service/src/metadata_manager.py b/audio-service/src/metadata_manager.py index cf2cc5c..8c2a4c6 100644 --- a/audio-service/src/metadata_manager.py +++ b/audio-service/src/metadata_manager.py @@ -1,100 +1,114 @@ -import os -import json - -class MetaDataManager: - _instance = None - - def __new__(cls, *args, **kwargs): - if cls._instance is None: - cls._instance = super().__new__(cls) - cls._instance.init() - return cls._instance - def init(self): - # read metadata file from executing directory - self.metadata_file = os.path.join(os.getcwd(), "metadata.json") - if os.path.exists(self.metadata_file): - with open(self.metadata_file, "r") as f: - self.collections = json.load(f) - else: - self.collections = {} - if(collections := next((c for c in self.collections if c.get("name") == "Uncategorized"), None)) is None: - self.collections.append({"name": "Uncategorized", "id": 0, "clips": []}) - self.save_metadata() - - def create_collection(self, name): - if any(c.get("name") == name for c in self.collections): - raise ValueError(f"Collection '{name}' already exists.") - new_id = max((c.get("id", 0) for c in self.collections), default=0) + 1 - self.collections.append({"name": name, "id": new_id, "clips": []}) - self.save_metadata() - - def delete_collection(self, name): - collection = next((c for c in self.collections if c.get("name") == name), None) - if collection is None: - raise ValueError(f"Collection '{name}' does not exist.") - self.collections.remove(collection) - self.save_metadata() - - def add_clip_to_collection(self, collection_name, clip_metadata): - collection = next((c for c in self.collections if c.get("name") == collection_name), None) - if collection is None: - raise ValueError(f"Collection '{collection_name}' does not exist.") - collection["clips"].append(clip_metadata) - self.save_metadata() - - def remove_clip_from_collection(self, collection_name, clip_metadata): - collection = next((c for c in self.collections if c.get("name") == collection_name), None) - if collection is None: - raise ValueError(f"Collection '{collection_name}' does not exist.") - # Remove all clips with the same file name as clip_metadata["file_name"] - in_list = any(clip.get("filename") == clip_metadata.get("filename") for clip in collection["clips"]) - if not in_list: - raise ValueError(f"Clip with filename '{clip_metadata.get('filename')}' not found in collection '{collection_name}'.") - - collection["clips"] = [ - clip for clip in collection["clips"] - if clip.get("filename") != clip_metadata.get("filename") - ] - self.save_metadata() - - def move_clip_to_collection(self, source_collection, target_collection, clip_metadata): - self.remove_clip_from_collection(source_collection, clip_metadata) - self.add_clip_to_collection(target_collection, 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) - if collection is None: - raise ValueError(f"Collection '{collection_name}' does not exist.") - # Find the index of the clip with the same file name as old_clip_metadata["file_name"] - index = next((i for i, clip in enumerate(collection["clips"]) if clip.get("filename") == new_clip_metadata.get("filename")), None) - if index is None: - raise ValueError(f"Clip with filename '{new_clip_metadata.get('filename')}' not found in collection '{collection_name}'.") - - collection["clips"][index] = new_clip_metadata - self.save_metadata() - - def get_collections(self): - return list(map(lambda c: {"name": c.get("name"), "id": c.get("id")}, self.collections)) - - def get_clips_in_collection(self, collection_name): - collection = next((c for c in self.collections if c.get("name") == collection_name), None) - if collection is None: - raise ValueError(f"Collection '{collection_name}' does not exist.") - return collection["clips"] - - def reorder_clips_in_collection(self, collection_name, new_order): - collection = next((c for c in self.collections if c.get("name") == collection_name), None) - if collection is None: - raise ValueError(f"Collection '{collection_name}' does not exist.") - existing_filenames = {clip.get("filename") for clip in collection["clips"]} - new_filenames = {clip.get("filename") for clip in new_order} - - if not new_filenames.issubset(existing_filenames): - raise ValueError("New order contains clips that do not exist in the collection.") - - collection["clips"] = new_order - self.save_metadata() - - def save_metadata(self): - with open(self.metadata_file, "w") as f: - json.dump(self.collections, f, indent=4) +import os +import json + +class MetaDataManager: + _instance = None + + def __new__(cls, *args, **kwargs): + if cls._instance is None: + cls._instance = super().__new__(cls) + cls._instance.init() + return cls._instance + def init(self): + self.socket = None + # read metadata file from executing directory + self.metadata_file = os.path.join(os.getcwd(), "metadata.json") + if os.path.exists(self.metadata_file): + with open(self.metadata_file, "r") as f: + self.collections = json.load(f) + else: + self.collections = {} + if(collections := next((c for c in self.collections if c.get("name") == "Uncategorized"), None)) is None: + self.collections.append({"name": "Uncategorized", "id": 0, "clips": []}) + self.save_metadata() + + def create_collection(self, name): + if any(c.get("name") == name for c in self.collections): + raise ValueError(f"Collection '{name}' already exists.") + new_id = max((c.get("id", 0) for c in self.collections), default=0) + 1 + self.collections.append({"name": name, "id": new_id, "clips": []}) + self.save_metadata() + + def delete_collection(self, name): + collection = next((c for c in self.collections if c.get("name") == name), None) + if collection is None: + raise ValueError(f"Collection '{name}' does not exist.") + self.collections.remove(collection) + self.save_metadata() + + def add_clip_to_collection(self, collection_name, clip_metadata): + collection = next((c for c in self.collections if c.get("name") == collection_name), None) + if collection is None: + raise ValueError(f"Collection '{collection_name}' does not exist.") + collection["clips"].append(clip_metadata) + if not self.socket is None: + self.socket.emit('collection_updated', collection) + self.save_metadata() + + def remove_clip_from_collection(self, collection_name, clip_metadata): + collection = next((c for c in self.collections if c.get("name") == collection_name), None) + if collection is None: + raise ValueError(f"Collection '{collection_name}' does not exist.") + # Remove all clips with the same file name as clip_metadata["file_name"] + in_list = any(clip.get("filename") == clip_metadata.get("filename") for clip in collection["clips"]) + if not in_list: + raise ValueError(f"Clip with filename '{clip_metadata.get('filename')}' not found in collection '{collection_name}'.") + + collection["clips"] = [ + clip for clip in collection["clips"] + if clip.get("filename") != clip_metadata.get("filename") + ] + if not self.socket is None: + self.socket.emit('collection_updated', collection) + self.save_metadata() + + + def move_clip_to_collection(self, source_collection, target_collection, clip_metadata): + self.remove_clip_from_collection(source_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): + collection = next((c for c in self.collections if c.get("name") == collection_name), None) + if collection is None: + raise ValueError(f"Collection '{collection_name}' does not exist.") + # Find the index of the clip with the same file name as old_clip_metadata["file_name"] + index = next((i for i, clip in enumerate(collection["clips"]) if clip.get("filename") == new_clip_metadata.get("filename")), None) + if index is None: + raise ValueError(f"Clip with filename '{new_clip_metadata.get('filename')}' not found in collection '{collection_name}'.") + + collection["clips"][index] = new_clip_metadata + if not self.socket is None: + self.socket.emit('collection_updated', collection) + self.save_metadata() + + def get_collections(self): + return list(map(lambda c: {"name": c.get("name"), "id": c.get("id")}, self.collections)) + + def get_clips_in_collection(self, collection_name): + collection = next((c for c in self.collections if c.get("name") == collection_name), None) + if collection is None: + raise ValueError(f"Collection '{collection_name}' does not exist.") + return collection["clips"] + + def reorder_clips_in_collection(self, collection_name, new_order): + collection = next((c for c in self.collections if c.get("name") == collection_name), None) + if collection is None: + raise ValueError(f"Collection '{collection_name}' does not exist.") + existing_filenames = {clip.get("filename") for clip in collection["clips"]} + new_filenames = {clip.get("filename") for clip in new_order} + + if not new_filenames.issubset(existing_filenames): + raise ValueError("New order contains clips that do not exist in the collection.") + + collection["clips"] = new_order + if not self.socket is None: + self.socket.emit('collection_updated', collection) + self.save_metadata() + + def save_metadata(self): + with open(self.metadata_file, "w") as f: + json.dump(self.collections, f, indent=4) diff --git a/audio-service/src/recordings/desktop.ini b/audio-service/src/recordings/desktop.ini index d957fd1..bb9f3d6 100644 --- a/audio-service/src/recordings/desktop.ini +++ b/audio-service/src/recordings/desktop.ini @@ -1,4 +1,4 @@ -[ViewState] -Mode= -Vid= -FolderType=Generic +[ViewState] +Mode= +Vid= +FolderType=Generic diff --git a/audio-service/src/routes/__pycache__/metadata.cpython-313.pyc b/audio-service/src/routes/__pycache__/metadata.cpython-313.pyc index 9bf59f7..fda7d27 100644 Binary files a/audio-service/src/routes/__pycache__/metadata.cpython-313.pyc and b/audio-service/src/routes/__pycache__/metadata.cpython-313.pyc differ diff --git a/audio-service/src/routes/__pycache__/settings.cpython-313.pyc b/audio-service/src/routes/__pycache__/settings.cpython-313.pyc index 8953471..d859b80 100644 Binary files a/audio-service/src/routes/__pycache__/settings.cpython-313.pyc and b/audio-service/src/routes/__pycache__/settings.cpython-313.pyc differ diff --git a/audio-service/src/routes/metadata.py b/audio-service/src/routes/metadata.py index 1987031..ea5d915 100644 --- a/audio-service/src/routes/metadata.py +++ b/audio-service/src/routes/metadata.py @@ -98,3 +98,9 @@ def edit_clip_in_collection(): return jsonify({'status': 'success', 'collections': collections}) except ValueError as e: 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'}) \ No newline at end of file diff --git a/audio-service/src/routes/recording.py b/audio-service/src/routes/recording.py index 97cd300..0501788 100644 --- a/audio-service/src/routes/recording.py +++ b/audio-service/src/routes/recording.py @@ -1,57 +1,57 @@ - -from flask import Blueprint, request, jsonify -from audio_io import AudioIO -import os - -recording_bp = Blueprint('recording', __name__) - -@recording_bp.route('/record/start', methods=['POST']) -def start_recording(): - recorder = AudioIO() - print('HTTP: Starting audio recording') - recorder.start_recording() - return jsonify({'status': 'recording started'}) - -@recording_bp.route('/record/stop', methods=['POST']) -def stop_recording(): - recorder = AudioIO() - # print('HTTP: Stopping audio recording') - recorder.stop_recording() - return jsonify({'status': 'recording stopped'}) - -@recording_bp.route('/record/save', methods=['POST']) -def save_recording(): - recorder = AudioIO() - # print('HTTP: Saving audio recording') - saved_file = recorder.save_last_n_seconds() - return jsonify({'status': 'recording saved', 'file': saved_file}) - - -@recording_bp.route('/record/status', methods=['GET']) -def recording_status(): - recorder = AudioIO() - # print('HTTP: Checking recording status') - status = 'recording' if recorder.is_recording() else 'stopped' - return jsonify({'status': status}) - -@recording_bp.route('/record/delete', methods=['POST']) -def recording_delete(): - filename = request.json.get('filename') - try: - os.remove(filename) - return jsonify({'status': 'success'}) - except Exception as e: - return jsonify({'status': 'error', 'message': str(e)}), 400 - -@recording_bp.route('/playback/start', methods=['POST']) -def playback_start(): - print(f"Playing clip") - # print('HTTP: Starting audio playback') - clip = request.json - try: - io = AudioIO() - io.play_clip(clip) - # os.remove(filename) - return jsonify({'status': 'success'}) - except Exception as e: + +from flask import Blueprint, request, jsonify +from audio_io import AudioIO +import os + +recording_bp = Blueprint('recording', __name__) + +@recording_bp.route('/record/start', methods=['POST']) +def start_recording(): + recorder = AudioIO() + print('HTTP: Starting audio recording') + recorder.start_recording() + return jsonify({'status': 'recording started'}) + +@recording_bp.route('/record/stop', methods=['POST']) +def stop_recording(): + recorder = AudioIO() + # print('HTTP: Stopping audio recording') + recorder.stop_recording() + return jsonify({'status': 'recording stopped'}) + +@recording_bp.route('/record/save', methods=['POST']) +def save_recording(): + recorder = AudioIO() + # print('HTTP: Saving audio recording') + saved_file = recorder.save_last_n_seconds() + return jsonify({'status': 'recording saved', 'file': saved_file}) + + +@recording_bp.route('/record/status', methods=['GET']) +def recording_status(): + recorder = AudioIO() + # print('HTTP: Checking recording status') + status = 'recording' if recorder.is_recording() else 'stopped' + return jsonify({'status': status}) + +@recording_bp.route('/record/delete', methods=['POST']) +def recording_delete(): + filename = request.json.get('filename') + try: + os.remove(filename) + return jsonify({'status': 'success'}) + except Exception as e: + return jsonify({'status': 'error', 'message': str(e)}), 400 + +@recording_bp.route('/playback/start', methods=['POST']) +def playback_start(): + print(f"Playing clip") + # print('HTTP: Starting audio playback') + clip = request.json + try: + io = AudioIO() + io.play_clip(clip) + # os.remove(filename) + return jsonify({'status': 'success'}) + except Exception as e: return jsonify({'status': 'error', 'message': str(e)}), 400 \ No newline at end of file diff --git a/audio-service/src/routes/settings.py b/audio-service/src/routes/settings.py index 0d153ab..c21fa08 100644 --- a/audio-service/src/routes/settings.py +++ b/audio-service/src/routes/settings.py @@ -1,31 +1,32 @@ -from flask import Blueprint, request, jsonify -from settings import SettingsManager - -settings_bp = Blueprint('settings', __name__) - - -@settings_bp.route('/settings', methods=['GET']) -def get_all_settings(): - return jsonify({'status': 'success', 'settings': SettingsManager().get_all_settings()}) - -@settings_bp.route('/settings/', methods=['GET']) -def get_setting(name): - value = SettingsManager().get_settings(name) - if value is not None: - return jsonify({'status': 'success', 'name': name, 'value': value}) - else: - return jsonify({'status': 'error', 'message': f'Setting "{name}" not found'}), 404 - -@settings_bp.route('/settings/update', methods=['POST']) -def set_all_settings(): - settings = request.json.get('settings') - print (f"Received settings update: {settings}") - if settings is None: - return jsonify({'status': 'error', 'message': 'Settings are required'}), 400 - try: - for name, value in settings.items(): - print(f"Updating setting '{name}' to '{value}'") - SettingsManager().set_settings(name, value) - return jsonify({'status': 'success', 'settings': settings}) - except ValueError as e: - return jsonify({'status': 'error', 'message': str(e)}), 400 \ No newline at end of file +from flask import Blueprint, request, jsonify +from settings import SettingsManager + +settings_bp = Blueprint('settings', __name__) + + +@settings_bp.route('/settings', methods=['GET']) +def get_all_settings(): + return jsonify({'status': 'success', 'settings': SettingsManager().get_all_settings()}) + +@settings_bp.route('/settings/', methods=['GET']) +def get_setting(name): + value = SettingsManager().get_settings(name) + if value is not None: + return jsonify({'status': 'success', 'name': name, 'value': value}) + else: + return jsonify({'status': 'error', 'message': f'Setting "{name}" not found'}), 404 + +@settings_bp.route('/settings/update', methods=['POST']) +def set_all_settings(): + settings = request.json.get('settings') + print (f"Received settings update: {settings}") + if settings is None: + return jsonify({'status': 'error', 'message': 'Settings are required'}), 400 + try: + for name, value in settings.items(): + print(f"Updating setting '{name}' to '{value}'") + SettingsManager().set_settings(name, value) + return jsonify({'status': 'success', 'settings': settings}) + except ValueError as e: + return jsonify({'status': 'error', 'message': str(e)}), 400 + \ No newline at end of file diff --git a/audio-service/src/settings.json b/audio-service/src/settings.json index a767ad6..6f5468c 100644 --- a/audio-service/src/settings.json +++ b/audio-service/src/settings.json @@ -1,10 +1,10 @@ -{ - "input_device": { - "index": 0, - "name": "Microsoft Sound Mapper - Input", - "max_input_channels": 2, - "default_samplerate": 44100.0 - }, - "save_path": "C:\\Users\\mickl\\Desktop\\cliptrim-ui\\ClipTrimApp\\audio-service\\src\\recordings", - "recording_length": 15 +{ + "input_device": { + "index": 0, + "name": "Microsoft Sound Mapper - Input", + "max_input_channels": 2, + "default_samplerate": 44100.0 + }, + "save_path": "C:\\Users\\mickl\\Desktop\\cliptrim-ui\\ClipTrimApp\\audio-service\\src\\recordings", + "recording_length": 15 } \ No newline at end of file diff --git a/audio-service/src/windows_audio.py b/audio-service/src/windows_audio.py index 1724d1b..38f2468 100644 --- a/audio-service/src/windows_audio.py +++ b/audio-service/src/windows_audio.py @@ -1,112 +1,108 @@ -import sounddevice as sd -import numpy as np -import comtypes -import comtypes.client -from comtypes import CLSCTX_ALL -from pycaw.pycaw import AudioUtilities, IAudioEndpointVolume -import json - -class WindowsAudioManager: - _instance = None - - def __new__(cls, *args, **kwargs): - if cls._instance is None: - cls._instance = super().__new__(cls) - cls._instance.init() - return cls._instance - def init(self): - """ - Initialize Windows audio device and volume management. - """ - host_apis = sd.query_hostapis() - wasapi_device_indexes = None - for api in host_apis: - if api['name'].lower() == 'Windows WASAPI'.lower(): - wasapi_device_indexes = api['devices'] - break - # print(f"Host APIs: {host_apis}") - # print(f"WASAPI Device Indexes: {wasapi_device_indexes}") - wasapi_device_indexes = set(wasapi_device_indexes) if wasapi_device_indexes is not None else set() - self.devices = [dev for dev in sd.query_devices() if dev['index'] in wasapi_device_indexes] - # self.devices = sd.query_devices() - # print(f"devices: {self.devices}") - - self.default_input = sd.default.device[0] - self.default_output = sd.default.device[1] - - def list_audio_devices(self, kind='input'): - """ - List available audio devices. - - :param kind: 'input' or 'output' - :return: List of audio devices - """ - if kind == 'input': - return [ - { - 'index': dev['index'], - 'name': dev['name'], - 'channels': dev['max_input_channels'], - 'default_samplerate': dev['default_samplerate'] - } - for dev in self.devices if dev['max_input_channels'] > 0 - ] - elif kind == 'output': - return [ - { - 'index': dev['index'], - 'name': dev['name'], - 'channels': dev['max_output_channels'], - 'default_samplerate': dev['default_samplerate'] - } - for dev in self.devices if dev['max_output_channels'] > 0 - ] - def get_default_device(self, kind='input'): - """ - Get the default audio device. - - :param kind: 'input' or 'output' - :return: Default audio device information - """ - if kind == 'input': - dev = self.devices[self.default_input] - return [ - { - 'index': dev['index'], - 'name': dev['name'], - 'max_input_channels': dev['max_input_channels'], - 'default_samplerate': dev['default_samplerate'] - } - ] - - def set_default_input_device(self, device_index): - if(device_index is None): - return 0 - """ - Set the default input audio device. - - :param device_index: Index of the audio device - :return: Sample rate of the selected device - """ - sd.default.device[0] = device_index - self.default_input = device_index - - # Get the sample rate of the selected device - device_info = sd.query_devices(device_index) - return device_info['default_samplerate'] - - def set_default_output_device(self, device_index): - if(device_index is None): - return self.get_current_output_device_sample_rate() - """ - Set the default output audio device. - - :param device_index: Index of the audio device - :return: Sample rate of the selected device - """ - sd.default.device[1] = device_index - self.default_output = device_index - - # Get the sample rate of the selected device - device_info = sd.query_devices(device_index) +import sounddevice as sd +import numpy as np +import json + +class WindowsAudioManager: + _instance = None + + def __new__(cls, *args, **kwargs): + if cls._instance is None: + cls._instance = super().__new__(cls) + cls._instance.init() + return cls._instance + def init(self): + """ + Initialize Windows audio device and volume management. + """ + host_apis = sd.query_hostapis() + wasapi_device_indexes = None + for api in host_apis: + if api['name'].lower() == 'Windows WASAPI'.lower(): + wasapi_device_indexes = api['devices'] + break + # print(f"Host APIs: {host_apis}") + # print(f"WASAPI Device Indexes: {wasapi_device_indexes}") + wasapi_device_indexes = set(wasapi_device_indexes) if wasapi_device_indexes is not None else set() + self.devices = [dev for dev in sd.query_devices() if dev['index'] in wasapi_device_indexes] + # self.devices = sd.query_devices() + # print(f"devices: {self.devices}") + + self.default_input = sd.default.device[0] + self.default_output = sd.default.device[1] + + def list_audio_devices(self, kind='input'): + """ + List available audio devices. + + :param kind: 'input' or 'output' + :return: List of audio devices + """ + if kind == 'input': + return [ + { + 'index': dev['index'], + 'name': dev['name'], + 'channels': dev['max_input_channels'], + 'default_samplerate': dev['default_samplerate'] + } + for dev in self.devices if dev['max_input_channels'] > 0 + ] + elif kind == 'output': + return [ + { + 'index': dev['index'], + 'name': dev['name'], + 'channels': dev['max_output_channels'], + 'default_samplerate': dev['default_samplerate'] + } + for dev in self.devices if dev['max_output_channels'] > 0 + ] + def get_default_device(self, kind='input'): + """ + Get the default audio device. + + :param kind: 'input' or 'output' + :return: Default audio device information + """ + if kind == 'input': + dev = self.devices[self.default_input] + return [ + { + 'index': dev['index'], + 'name': dev['name'], + 'max_input_channels': dev['max_input_channels'], + 'default_samplerate': dev['default_samplerate'] + } + ] + + def set_default_input_device(self, device_index): + if(device_index is None): + return 0 + """ + Set the default input audio device. + + :param device_index: Index of the audio device + :return: Sample rate of the selected device + """ + sd.default.device[0] = device_index + self.default_input = device_index + + # Get the sample rate of the selected device + device_info = sd.query_devices(device_index) + return device_info['default_samplerate'] + + def set_default_output_device(self, device_index): + if(device_index is None): + return self.get_current_output_device_sample_rate() + """ + Set the default output audio device. + + :param device_index: Index of the audio device + :return: Sample rate of the selected device + """ + sd.default.device[1] = device_index + self.default_output = device_index + + # Get the sample rate of the selected device + device_info = sd.query_devices(device_index) return device_info['default_samplerate'] \ No newline at end of file diff --git a/electron-ui/src/main/main.ts b/electron-ui/src/main/main.ts index caf9382..c4ff4e1 100644 --- a/electron-ui/src/main/main.ts +++ b/electron-ui/src/main/main.ts @@ -18,6 +18,8 @@ import { resolveHtmlPath } from './util'; import registerFileIpcHandlers from '../ipc/audio/main'; import PythonSubprocessManager from './service'; +const pythonManager = new PythonSubprocessManager('src/main.py'); + class AppUpdater { constructor() { log.transports.file.level = 'info'; @@ -112,9 +114,6 @@ const createWindow = async () => { registerFileIpcHandlers(); - const pythonManager = new PythonSubprocessManager('src/main.py'); - - pythonManager.start(); // Remove this if your app does not use auto updates // eslint-disable-next-line new AppUpdater(); @@ -127,6 +126,7 @@ const createWindow = async () => { app.on('window-all-closed', () => { // Respect the OSX convention of having the application in memory even // after all windows have been closed + pythonManager.stop(); if (process.platform !== 'darwin') { app.quit(); } @@ -135,6 +135,7 @@ app.on('window-all-closed', () => { app .whenReady() .then(() => { + // pythonManager.start(); createWindow(); app.on('activate', () => { // On macOS it's common to re-create a window in the app when the diff --git a/electron-ui/src/main/service.ts b/electron-ui/src/main/service.ts index e79633c..7759355 100644 --- a/electron-ui/src/main/service.ts +++ b/electron-ui/src/main/service.ts @@ -40,10 +40,10 @@ export default class PythonSubprocessManager { }, ); 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) => { - // console.error(`Python stderr: ${data.toString()}`); + // console.error(`Python stderr: ${data.toString()}`); const lines = data.toString().split('\n'); // eslint-disable-next-line no-restricted-syntax for (const line of lines) { diff --git a/electron-ui/src/redux/main.ts b/electron-ui/src/redux/main.ts index eaa92bc..7e12d4e 100644 --- a/electron-ui/src/redux/main.ts +++ b/electron-ui/src/redux/main.ts @@ -67,32 +67,11 @@ const metadataSlice = createSlice({ targetState.clips.push(clip); } }, - addNewClips(state, action) { - const { collections } = action.payload; - Object.keys(collections).forEach((collection) => { - const collectionState = state.collections.find( - (col) => col.name === collection, - ); - 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); + addNewClip(state, action) { + const { clip } = action.payload; + state.collections.forEach((collection) => { + if (collection.name === 'Uncategorized') { + collection.clips.push(clip); } }); }, @@ -113,6 +92,6 @@ export type RootState = ReturnType; // Inferred type: {posts: PostsState, comments: CommentsState, users: UsersState} export type AppDispatch = AppStore['dispatch']; -export const { setCollections, addNewClips, addCollection } = +export const { setCollections, addNewClip, addCollection } = metadataSlice.actions; export default metadataSlice.reducer; diff --git a/electron-ui/src/renderer/App.tsx b/electron-ui/src/renderer/App.tsx index 2106d9d..504c5d7 100644 --- a/electron-ui/src/renderer/App.tsx +++ b/electron-ui/src/renderer/App.tsx @@ -7,6 +7,7 @@ import { ThemeProvider, createTheme } from '@mui/material/styles'; import DialogTitle from '@mui/material/DialogTitle'; import DialogContent from '@mui/material/DialogContent'; import DialogActions from '@mui/material/DialogActions'; +import io from 'socket.io-client'; // import 'tailwindcss/tailwind.css'; import './App.css'; import ClipList from './components/ClipList'; @@ -14,7 +15,7 @@ import { useAppDispatch, useAppSelector } from './hooks'; import { store } from '../redux/main'; import { useNavigate } from 'react-router-dom'; import SettingsPage from './Settings'; -import apiFetch from './api'; +import { apiFetch, getBaseUrl } from './api'; function MainPage() { const dispatch = useAppDispatch(); @@ -27,20 +28,46 @@ function MainPage() { const [newCollectionOpen, setNewCollectionOpen] = useState(false); const [newCollectionName, setNewCollectionName] = useState(''); const navigate = useNavigate(); + const [socket, setSocket] = useState(null); + + useEffect(() => {}, []); useEffect(() => { - const fetchMetadata = async () => { - 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); - } + const initializeSocket = async () => { + const baseUrl = await getBaseUrl(); + const newSocket = io(baseUrl); + setSocket(newSocket); + newSocket.on('connect', () => { + console.log('Connected to WebSocket server'); + }); + 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(); - const intervalId = setInterval(fetchMetadata, 5000); - return () => clearInterval(intervalId); + initializeSocket(); + // const fetchMetadata = async () => { + // 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]); useEffect(() => { diff --git a/electron-ui/src/renderer/Settings.tsx b/electron-ui/src/renderer/Settings.tsx index 748f60b..716c58b 100644 --- a/electron-ui/src/renderer/Settings.tsx +++ b/electron-ui/src/renderer/Settings.tsx @@ -5,7 +5,7 @@ import './App.css'; import TextField from '@mui/material/TextField'; import Select from '@mui/material/Select'; import MenuItem from '@mui/material/MenuItem'; -import apiFetch from './api'; +import { apiFetch } from './api'; type AudioDevice = { index: number; diff --git a/electron-ui/src/renderer/api.ts b/electron-ui/src/renderer/api.ts index 3b87142..a059327 100644 --- a/electron-ui/src/renderer/api.ts +++ b/electron-ui/src/renderer/api.ts @@ -1,4 +1,4 @@ -const getBaseUrl = async () => { +export const getBaseUrl = async () => { const port = await window.audio.getPort(); if (port.error || !port.port) { return `http://localhost:5010`; @@ -7,7 +7,7 @@ const getBaseUrl = async () => { 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}`; return fetch(url, options); } diff --git a/electron-ui/src/renderer/components/ClipList.tsx b/electron-ui/src/renderer/components/ClipList.tsx index 80f1f0d..7e450fd 100644 --- a/electron-ui/src/renderer/components/ClipList.tsx +++ b/electron-ui/src/renderer/components/ClipList.tsx @@ -15,7 +15,7 @@ import { restrictToVerticalAxis } from '@dnd-kit/modifiers'; import AudioTrimmer from './AudioTrimer'; import { ClipMetadata } from '../../redux/types'; import { useAppDispatch, useAppSelector } from '../hooks'; -import apiFetch from '../api'; +import { apiFetch } from '../api'; export interface ClipListProps { collection: string; diff --git a/stream_deck_plugin/ClipTrimDotNet.sln b/stream_deck_plugin/ClipTrimDotNet.sln index a71fcb9..e9c2bce 100644 --- a/stream_deck_plugin/ClipTrimDotNet.sln +++ b/stream_deck_plugin/ClipTrimDotNet.sln @@ -1,25 +1,25 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.8.34330.188 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ClipTrimDotNet", "ClipTrimDotNet\ClipTrimDotNet.csproj", "{4635D874-69C0-4010-BE46-77EF92EB1553}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {4635D874-69C0-4010-BE46-77EF92EB1553}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {4635D874-69C0-4010-BE46-77EF92EB1553}.Debug|Any CPU.Build.0 = Debug|Any CPU - {4635D874-69C0-4010-BE46-77EF92EB1553}.Release|Any CPU.ActiveCfg = Release|Any CPU - {4635D874-69C0-4010-BE46-77EF92EB1553}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {926C6896-F36A-4F3B-A9DD-CA3AA48AD99F} - EndGlobalSection -EndGlobal + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.8.34330.188 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ClipTrimDotNet", "ClipTrimDotNet\ClipTrimDotNet.csproj", "{4635D874-69C0-4010-BE46-77EF92EB1553}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {4635D874-69C0-4010-BE46-77EF92EB1553}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4635D874-69C0-4010-BE46-77EF92EB1553}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4635D874-69C0-4010-BE46-77EF92EB1553}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4635D874-69C0-4010-BE46-77EF92EB1553}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {926C6896-F36A-4F3B-A9DD-CA3AA48AD99F} + EndGlobalSection +EndGlobal diff --git a/stream_deck_plugin/ClipTrimDotNet/!!README!!.txt b/stream_deck_plugin/ClipTrimDotNet/!!README!!.txt index b6e1b33..e93e13e 100644 --- a/stream_deck_plugin/ClipTrimDotNet/!!README!!.txt +++ b/stream_deck_plugin/ClipTrimDotNet/!!README!!.txt @@ -1,14 +1,14 @@ -To use: -1. Right click the project and choose "Manage Nuget Packages" -2. Choose the restore option in the Nuget screen (or just install the latest StreamDeck-Tools from Nuget) -3. Update the manifest.json file with the correct details about your plugin -4. Modify PluginAction.cs as needed (it holds the logic for your plugin) -5. Modify the PropertyInspector\PluginActionPI.html and PropertyInspector\PluginActionPI.js as needed to show field in the Property Inspector -6. Before releasing, change the Assembly Information (Right click the project -> Properties -> Application -> Assembly Information...) - -For help with StreamDeck-Tools: - Discord Server: http://discord.barraider.com -Resources: -* StreamDeck-Tools samples and tutorial: https://github.com/BarRaider/streamdeck-tools -* EasyPI library (for working with Property Inspector): https://github.com/BarRaider/streamdeck-easypi - +To use: +1. Right click the project and choose "Manage Nuget Packages" +2. Choose the restore option in the Nuget screen (or just install the latest StreamDeck-Tools from Nuget) +3. Update the manifest.json file with the correct details about your plugin +4. Modify PluginAction.cs as needed (it holds the logic for your plugin) +5. Modify the PropertyInspector\PluginActionPI.html and PropertyInspector\PluginActionPI.js as needed to show field in the Property Inspector +6. Before releasing, change the Assembly Information (Right click the project -> Properties -> Application -> Assembly Information...) + +For help with StreamDeck-Tools: + Discord Server: http://discord.barraider.com +Resources: +* StreamDeck-Tools samples and tutorial: https://github.com/BarRaider/streamdeck-tools +* EasyPI library (for working with Property Inspector): https://github.com/BarRaider/streamdeck-easypi + diff --git a/stream_deck_plugin/ClipTrimDotNet/App.config b/stream_deck_plugin/ClipTrimDotNet/App.config index e9d06ee..23161b3 100644 --- a/stream_deck_plugin/ClipTrimDotNet/App.config +++ b/stream_deck_plugin/ClipTrimDotNet/App.config @@ -1,22 +1,42 @@ - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/stream_deck_plugin/ClipTrimDotNet/BaseTest.cs b/stream_deck_plugin/ClipTrimDotNet/BaseTest.cs index e02abfc..af0744a 100644 --- a/stream_deck_plugin/ClipTrimDotNet/BaseTest.cs +++ b/stream_deck_plugin/ClipTrimDotNet/BaseTest.cs @@ -1 +1 @@ - + diff --git a/stream_deck_plugin/ClipTrimDotNet/Client/ClipMetadata.cs b/stream_deck_plugin/ClipTrimDotNet/Client/ClipMetadata.cs index 36f6643..48b1c7b 100644 --- a/stream_deck_plugin/ClipTrimDotNet/Client/ClipMetadata.cs +++ b/stream_deck_plugin/ClipTrimDotNet/Client/ClipMetadata.cs @@ -1,42 +1,53 @@ -using Newtonsoft.Json; -using Newtonsoft.Json.Converters; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace ClipTrimDotNet.Client -{ - public enum PlaybackType - { - playStop, - playOverlap - } - public class ClipMetadata - { - [JsonProperty(PropertyName = "filename")] - public string Filename { get; set; } - - - [JsonProperty(PropertyName = "name")] - public string Name { get; set; } - - - [JsonProperty(PropertyName = "volume")] - public double Volume { get; set; } = 1.0; - - - [JsonProperty(PropertyName = "startTime")] - public double StartTime { get; set; } = 0.0; - - - [JsonProperty(PropertyName = "endTime")] - public double EndTime { get; set; } = 0.0; - - - [JsonProperty(PropertyName = "playbackType")] - [JsonConverter(typeof(StringEnumConverter))] - public PlaybackType PlaybackType { get; set; } = PlaybackType.playStop; - } -} +using Newtonsoft.Json; +using Newtonsoft.Json.Serialization; +using Newtonsoft.Json.Converters; + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Text.Json.Serialization; + +namespace ClipTrimDotNet.Client +{ + public enum PlaybackType + { + playStop, + playOverlap + } + public class ClipMetadata + { + [JsonProperty(PropertyName = "filename")] + [JsonPropertyName("filename")] + + public string Filename { get; set; } + + + [JsonProperty(PropertyName = "name")] + [JsonPropertyName("name")] + public string Name { get; set; } + + + [JsonProperty(PropertyName = "volume")] + [JsonPropertyName("volume")] + public double Volume { get; set; } = 1.0; + + + [JsonProperty(PropertyName = "startTime")] + [JsonPropertyName("startTime")] + public double StartTime { get; set; } = 0.0; + + + [JsonProperty(PropertyName = "endTime")] + [JsonPropertyName("endTime")] + public double EndTime { get; set; } = 0.0; + + + [JsonProperty(PropertyName = "playbackType")] + [Newtonsoft.Json.JsonConverter(typeof(StringEnumConverter))] + [System.Text.Json.Serialization.JsonConverter(typeof(JsonStringEnumConverter))] + [JsonPropertyName("playbackType")] + public PlaybackType PlaybackType { get; set; } = PlaybackType.playStop; + } +} diff --git a/stream_deck_plugin/ClipTrimDotNet/Client/ClipTrimClient.cs b/stream_deck_plugin/ClipTrimDotNet/Client/ClipTrimClient.cs index b369dc0..1a3177c 100644 --- a/stream_deck_plugin/ClipTrimDotNet/Client/ClipTrimClient.cs +++ b/stream_deck_plugin/ClipTrimDotNet/Client/ClipTrimClient.cs @@ -1,110 +1,287 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net.Http; -using System.Text; -using System.Threading.Tasks; -using Newtonsoft.Json; - -namespace ClipTrimDotNet.Client -{ - public class ClipTrimClient - { - private static ClipTrimClient? instance; - public static ClipTrimClient Instance - { - get - { - if (instance == null) - { - instance = new ClipTrimClient(); - } - return instance; - } - } - - private HttpClient httpClient; - - public ClipTrimClient() - { - httpClient = new HttpClient() - { - BaseAddress = new Uri("http://localhost:5010/"), - Timeout = TimeSpan.FromSeconds(10) - }; - Task.Run(ShortPoll); - } - - public async Task ShortPoll() - { - while (true) - { - await GetMetadata(); - await Task.Delay(TimeSpan.FromSeconds(5)); await Task.Delay(TimeSpan.FromSeconds(5)); - - } - } - - public List Collections { get; private set; } = new List(); - public CollectionMetaData? SelectedCollection { get; private set; } - public int PageIndex { get; private set; } = 0; - private async Task GetMetadata() - { - try - { - var response = await httpClient.GetAsync("meta"); - if (response.IsSuccessStatusCode) - { - var json = await response.Content.ReadAsStringAsync(); - dynamic collections = JsonConvert.DeserializeObject(json); - collections = collections.collections; - Collections = JsonConvert.DeserializeObject>(collections.ToString()); - } - } - catch (Exception ex) - { - //Logger.Instance.LogMessage(TracingLevel.INFO, $"Error pinging ClipTrim API: {ex.Message}"); - return; - } - - } - - public List GetCollectionNames() - { - //await GetMetadata(); - return Collections.Select(x => x.Name).ToList(); - } - - public void SetSelectedCollectionByName(string name) - { - var collection = Collections.FirstOrDefault(x => x.Name == name); - if (collection != null) - { - SelectedCollection = collection; - PageIndex = 0; - } - } - - public ClipMetadata? GetClipByPagedIndex(int index) - { - if (SelectedCollection == null) return null; - int clipIndex = PageIndex * 10 + index; - if (clipIndex >= 0 && clipIndex < SelectedCollection.Clips.Count) - { - return SelectedCollection.Clips[clipIndex]; - } - return null; - } - - public async void PlayClip(ClipMetadata? metadata) - { - if (metadata == null) return; - - var response = await httpClient.PostAsync("playback/start", new StringContent(JsonConvert.SerializeObject(metadata), Encoding.UTF8, "application/json")); - if (!response.IsSuccessStatusCode) - { - //Logger.Instance.LogMessage(TracingLevel.INFO, $"Error playing clip: {response.ReasonPhrase}"); - } - } - } -} +using BarRaider.SdTools; +using ClipTrimDotNet.Keys; +using Microsoft.AspNetCore.Mvc.RazorPages; +using Newtonsoft.Json; +using SocketIOClient; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Runtime.CompilerServices; +using System.Text; +using System.Threading.Tasks; +using static System.Runtime.InteropServices.JavaScript.JSType; + +namespace ClipTrimDotNet.Client +{ + public class ClipTrimClient + { + private static ClipTrimClient? instance; + public static ClipTrimClient Instance + { + get + { + if (instance == null) + { + instance = new ClipTrimClient(); + } + return instance; + } + } + + //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>(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(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() + { + //httpClient = new HttpClient() + //{ + // BaseAddress = new Uri("http://localhost:5010/"), + // Timeout = TimeSpan.FromSeconds(10) + //}; + CreateSocket(); + } + + public async Task Connect() + { + if (socket is null) return; + while (!socket.Connected) + { + try + { + await socket.ConnectAsync(); + } + catch + { + + } + } + } + + public List Collections { get; private set; } = new List(); + public int SelectedCollection { get; private set; } = -1; + + public Dictionary CollectionIndexes { get; private set; } = new(); + public int PageIndex + { + get + { + if (SelectedCollection == -1) return 0; + if (!CollectionIndexes.ContainsKey(SelectedCollection)) + { + CollectionIndexes[SelectedCollection] = 0; + } + return CollectionIndexes[SelectedCollection]; + } + set + { + if (SelectedCollection == -1) return; + CollectionIndexes[SelectedCollection] = value; + } + } + public bool PageMode { get; set; } = false; + + public int PageCount + { + get + { + if (SelectedCollection == -1) return 0; + var collection = Collections[SelectedCollection]; + return (collection.Clips.Count - 1) / 10 + 1; + } + } + public bool CanPageUp + { + get + { + if(PageMode) return false; + if (SelectedCollection == -1) return false; + return PageCount - PageIndex > 1; + } + } + + public bool CanPageDown + { + get + { + if (PageMode) return false; + return PageIndex > 0; + } + } + + public void PageDown() + { + if (CanPageDown) + { + PageIndex--; + } + } + + public void PageUp() + { + if (CanPageUp) + { + PageIndex++; + } + } + public List GetCollectionNames() + { + //await GetMetadata(); + return Collections.Where(x => x.Name != "Uncategorized").Select(x => x.Name).ToList(); + } + + public string GetCurrentCollectionName() + { + if (SelectedCollection == -1) return ""; + return Collections[SelectedCollection].Name; + } + + public string GetPlayerStringByCoordinateIndex(int index) + { + if (PageMode) + { + int pageNumber = index + 1; + if(pageNumber <= PageCount) + { + return pageNumber.ToString(); + } + return ""; + } + else + { + var collection = GetClipByPagedIndex(index); + return collection?.Name ?? ""; + } + } + + + + public ClipMetadata? GetClipByPagedIndex(int index) + { + SelectedCollection = Collections.FindIndex(x => x.Name == GlobalSettings.Instance.ProfileName); + if (SelectedCollection == -1) return null; + int clipIndex = PageIndex * 10 + index; + var collection = Collections[SelectedCollection]; + if (clipIndex >= 0 && clipIndex < collection.Clips.Count) + { + return collection.Clips[clipIndex]; + } + return null; + } + + 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; + //Logger.Instance.LogMessage(TracingLevel.INFO, $"playing clip:"); + await socket.EmitAsync("play_clip", new List() { metadata }); + } + + } + + public async void SaveClip() + { + if (socket is null) return; + await socket.EmitAsync("record_clip", new List() { }); + } + + 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(); + } + } + } +} diff --git a/stream_deck_plugin/ClipTrimDotNet/Client/CollectionMetaData.cs b/stream_deck_plugin/ClipTrimDotNet/Client/CollectionMetaData.cs index f2769b7..0465dac 100644 --- a/stream_deck_plugin/ClipTrimDotNet/Client/CollectionMetaData.cs +++ b/stream_deck_plugin/ClipTrimDotNet/Client/CollectionMetaData.cs @@ -1,23 +1,27 @@ -using Newtonsoft.Json; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace ClipTrimDotNet.Client -{ - public class CollectionMetaData - { - [JsonProperty(PropertyName = "name")] - public string Name { get; set; } - - - [JsonProperty(PropertyName = "clips")] - public List Clips { get; set; } = new List(); - - - [JsonProperty(PropertyName = "id")] - public int Id { get; set; } - } -} +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.Json.Serialization; +using System.Threading.Tasks; + +namespace ClipTrimDotNet.Client +{ + public class CollectionMetaData + { + [JsonProperty(PropertyName = "name")] + [JsonPropertyName("name")] + public string Name { get; set; } + + + [JsonProperty(PropertyName = "clips")] + [JsonPropertyName("clips")] + public List Clips { get; set; } = new List(); + + + [JsonProperty(PropertyName = "id")] + [JsonPropertyName("id")] + public int Id { get; set; } + } +} diff --git a/stream_deck_plugin/ClipTrimDotNet/ClipTrimDotNet.csproj b/stream_deck_plugin/ClipTrimDotNet/ClipTrimDotNet.csproj index 29f0f39..e788b33 100644 --- a/stream_deck_plugin/ClipTrimDotNet/ClipTrimDotNet.csproj +++ b/stream_deck_plugin/ClipTrimDotNet/ClipTrimDotNet.csproj @@ -1,162 +1,118 @@ - - - - - Debug - AnyCPU - {4635D874-69C0-4010-BE46-77EF92EB1553} - Exe - ClipTrimDotNet - ClipTrimDotNet - v4.8 - 8 - 512 - true - true - enable - - - - AnyCPU - true - full - false - bin\Debug\com.michal-courson.cliptrim.sdPlugin\ - DEBUG;TRACE - prompt - 4 - - - AnyCPU - pdbonly - true - bin\Release\ClipTrimDotNet.sdPlugin\ - TRACE - prompt - 4 - - - - ..\packages\CommandLineParser.2.9.1\lib\net461\CommandLine.dll - - - ..\packages\Microsoft.Win32.Registry.4.7.0\lib\net461\Microsoft.Win32.Registry.dll - - - ..\packages\NAudio.2.2.1\lib\net472\NAudio.dll - - - ..\packages\NAudio.Asio.2.2.1\lib\netstandard2.0\NAudio.Asio.dll - - - ..\packages\NAudio.Core.2.2.1\lib\netstandard2.0\NAudio.Core.dll - - - ..\packages\NAudio.Midi.2.2.1\lib\netstandard2.0\NAudio.Midi.dll - - - ..\packages\NAudio.Wasapi.2.2.1\lib\netstandard2.0\NAudio.Wasapi.dll - - - ..\packages\NAudio.WinForms.2.2.1\lib\net472\NAudio.WinForms.dll - - - ..\packages\NAudio.WinMM.2.2.1\lib\netstandard2.0\NAudio.WinMM.dll - - - ..\packages\Newtonsoft.Json.13.0.4\lib\net45\Newtonsoft.Json.dll - - - ..\packages\NLog.6.0.5\lib\net46\NLog.dll - - - ..\packages\StreamDeck-Tools.6.3.2\lib\netstandard2.0\StreamDeckTools.dll - - - - - - - ..\packages\System.Drawing.Common.9.0.10\lib\net462\System.Drawing.Common.dll - - - - - ..\packages\System.Security.AccessControl.4.7.0\lib\net461\System.Security.AccessControl.dll - - - ..\packages\System.Security.Principal.Windows.4.7.0\lib\net461\System.Security.Principal.Windows.dll - - - - - - - - - - - - - - - - - - - - - - - - - - PreserveNewest - - - PreserveNewest - - - - - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - - PreserveNewest - - - PreserveNewest - - - - - npm run stop - - - npm run start - + + + net8.0-windows + false + Exe + 10 + enable + enable + npm run stop + npm run start + ClipTrimDotNet + ClipTrimDotNet + Copyright © 2020 + 1.0.0.0 + 1.0.0.0 + + + bin\Debug\com.michal-courson.cliptrim.sdPlugin\ + + + bin\Release\ClipTrimDotNet.sdPlugin\ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + PreserveNewest + + + + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + + PreserveNewest + + + PreserveNewest + + \ No newline at end of file diff --git a/stream_deck_plugin/ClipTrimDotNet/ClipTrimDotNet.csproj.user b/stream_deck_plugin/ClipTrimDotNet/ClipTrimDotNet.csproj.user index 48d700b..6a46d49 100644 --- a/stream_deck_plugin/ClipTrimDotNet/ClipTrimDotNet.csproj.user +++ b/stream_deck_plugin/ClipTrimDotNet/ClipTrimDotNet.csproj.user @@ -1,6 +1,6 @@ - - - - --port 23654 --pluginUUID com.michal-courson.cliptrim --registerEvent restart --info {} - + + + + --port 23654 --pluginUUID com.michal-courson.cliptrim --registerEvent restart --info {} + \ No newline at end of file diff --git a/stream_deck_plugin/ClipTrimDotNet/DialLayout.json b/stream_deck_plugin/ClipTrimDotNet/DialLayout.json index 014c4e1..d74b16a 100644 --- a/stream_deck_plugin/ClipTrimDotNet/DialLayout.json +++ b/stream_deck_plugin/ClipTrimDotNet/DialLayout.json @@ -1,40 +1,40 @@ -{ - "id": "sampleDial", - "items": [ - { - "key": "title", - "type": "text", - "rect": [ 16, 10, 136, 24 ], - "font": { - "size": 16, - "weight": 600 - }, - "alignment": "left" - }, - { - "key": "icon", - "type": "pixmap", - "rect": [ 16, 40, 48, 48 ] - }, - { - "key": "value", - "type": "text", - "rect": [ 76, 40, 108, 32 ], - "font": { - "size": 24, - "weight": 600 - }, - "alignment": "right" - }, - { - "key": "indicator", - "type": "gbar", - "rect": [ 76, 74, 108, 20 ], - "value": 0, - "subtype": 4, - "bar_h": 12, - "border_w": 0, - "bar_bg_c": "0:#427018,0.75:#705B1C,0.90:#702735,1:#702735" - } - ] +{ + "id": "sampleDial", + "items": [ + { + "key": "title", + "type": "text", + "rect": [ 16, 10, 136, 24 ], + "font": { + "size": 16, + "weight": 600 + }, + "alignment": "left" + }, + { + "key": "icon", + "type": "pixmap", + "rect": [ 16, 40, 48, 48 ] + }, + { + "key": "value", + "type": "text", + "rect": [ 76, 40, 108, 32 ], + "font": { + "size": 24, + "weight": 600 + }, + "alignment": "right" + }, + { + "key": "indicator", + "type": "gbar", + "rect": [ 76, 74, 108, 20 ], + "value": 0, + "subtype": 4, + "bar_h": 12, + "border_w": 0, + "bar_bg_c": "0:#427018,0.75:#705B1C,0.90:#702735,1:#702735" + } + ] } \ No newline at end of file diff --git a/stream_deck_plugin/ClipTrimDotNet/GlobalSettings.cs b/stream_deck_plugin/ClipTrimDotNet/GlobalSettings.cs index 07c3646..639b2f2 100644 --- a/stream_deck_plugin/ClipTrimDotNet/GlobalSettings.cs +++ b/stream_deck_plugin/ClipTrimDotNet/GlobalSettings.cs @@ -1,109 +1,49 @@ -using BarRaider.SdTools; -using Newtonsoft.Json; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using BarRaider.SdTools.Wrappers; -using Newtonsoft.Json.Linq; -using NAudio.MediaFoundation; - -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(); - } - [JsonProperty(PropertyName = "Files")] - public Dictionary Files { get; set; } - } - - public class GlobalSettings - { - public static GlobalSettings? _inst; - public static GlobalSettings Instance - { - get - { - _inst ??= CreateDefaultSettings(); - return _inst; - } - set - { - _inst = value; - } - } - public static GlobalSettings CreateDefaultSettings() - { - GlobalSettings instance = new GlobalSettings(); - instance.BasePath = null; - instance.ProfileName = null; - instance.Collections = new Dictionary(); - return instance; - } - - - [FilenameProperty] - [JsonProperty(PropertyName = "basePath")] - public string? BasePath { get; set; } - - [JsonProperty(PropertyName = "profileName")] - public string? ProfileName { get; set; } - - [JsonProperty(PropertyName = "outputDevice")] - public string? OutputDevice { get; set; } - - [JsonProperty(PropertyName = "collections")] - public Dictionary Collections { get; set; } - - public void SetCurrentProfile(string 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; - } - } -} +using BarRaider.SdTools; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using BarRaider.SdTools.Wrappers; +using Newtonsoft.Json.Linq; + +namespace ClipTrimDotNet +{ + public class GlobalSettings + { + public static GlobalSettings? _inst; + public static GlobalSettings Instance + { + get + { + _inst ??= CreateDefaultSettings(); + return _inst; + } + set + { + _inst = value; + } + } + public static GlobalSettings CreateDefaultSettings() + { + GlobalSettings instance = new GlobalSettings(); + instance.ProfileName = null; + instance.PortNumber = 5010; + return instance; + } + + + [JsonProperty(PropertyName = "profileName")] + public string? ProfileName { get; set; } + + [JsonProperty(PropertyName = "portNumber")] + public int? PortNumber { get; set; } + + + public void SetCurrentProfile(string profile) + { + ProfileName = profile; + } + } +} diff --git a/stream_deck_plugin/ClipTrimDotNet/Images/app_icon.png b/stream_deck_plugin/ClipTrimDotNet/Images/app_icon.png new file mode 100644 index 0000000..2ba9a15 Binary files /dev/null and b/stream_deck_plugin/ClipTrimDotNet/Images/app_icon.png differ diff --git a/stream_deck_plugin/ClipTrimDotNet/Images/app_icon.psd b/stream_deck_plugin/ClipTrimDotNet/Images/app_icon.psd new file mode 100644 index 0000000..425f9eb Binary files /dev/null and b/stream_deck_plugin/ClipTrimDotNet/Images/app_icon.psd differ diff --git a/stream_deck_plugin/ClipTrimDotNet/Images/back.png b/stream_deck_plugin/ClipTrimDotNet/Images/back.png new file mode 100644 index 0000000..8d6cae4 Binary files /dev/null and b/stream_deck_plugin/ClipTrimDotNet/Images/back.png differ diff --git a/stream_deck_plugin/ClipTrimDotNet/Images/back.psd b/stream_deck_plugin/ClipTrimDotNet/Images/back.psd new file mode 100644 index 0000000..2dfd3f3 Binary files /dev/null and b/stream_deck_plugin/ClipTrimDotNet/Images/back.psd differ diff --git a/stream_deck_plugin/ClipTrimDotNet/Images/categoryIcon.png b/stream_deck_plugin/ClipTrimDotNet/Images/categoryIcon.png deleted file mode 100644 index c864463..0000000 Binary files a/stream_deck_plugin/ClipTrimDotNet/Images/categoryIcon.png and /dev/null differ diff --git a/stream_deck_plugin/ClipTrimDotNet/Images/categoryIcon@2x.png b/stream_deck_plugin/ClipTrimDotNet/Images/categoryIcon@2x.png deleted file mode 100644 index 88f7532..0000000 Binary files a/stream_deck_plugin/ClipTrimDotNet/Images/categoryIcon@2x.png and /dev/null differ diff --git a/stream_deck_plugin/ClipTrimDotNet/Images/category_icon.png b/stream_deck_plugin/ClipTrimDotNet/Images/category_icon.png new file mode 100644 index 0000000..560cc86 Binary files /dev/null and b/stream_deck_plugin/ClipTrimDotNet/Images/category_icon.png differ diff --git a/stream_deck_plugin/ClipTrimDotNet/Images/collection.png b/stream_deck_plugin/ClipTrimDotNet/Images/collection.png new file mode 100644 index 0000000..8387adb Binary files /dev/null and b/stream_deck_plugin/ClipTrimDotNet/Images/collection.png differ diff --git a/stream_deck_plugin/ClipTrimDotNet/Images/collection.psd b/stream_deck_plugin/ClipTrimDotNet/Images/collection.psd new file mode 100644 index 0000000..69ff624 Binary files /dev/null and b/stream_deck_plugin/ClipTrimDotNet/Images/collection.psd differ diff --git a/stream_deck_plugin/ClipTrimDotNet/Images/collection_icon.png b/stream_deck_plugin/ClipTrimDotNet/Images/collection_icon.png new file mode 100644 index 0000000..cf90e9c Binary files /dev/null and b/stream_deck_plugin/ClipTrimDotNet/Images/collection_icon.png differ diff --git a/stream_deck_plugin/ClipTrimDotNet/Images/collection_icon.psd b/stream_deck_plugin/ClipTrimDotNet/Images/collection_icon.psd new file mode 100644 index 0000000..c07beb0 Binary files /dev/null and b/stream_deck_plugin/ClipTrimDotNet/Images/collection_icon.psd differ diff --git a/stream_deck_plugin/ClipTrimDotNet/Images/icon.png b/stream_deck_plugin/ClipTrimDotNet/Images/icon.png deleted file mode 100644 index 282cc46..0000000 Binary files a/stream_deck_plugin/ClipTrimDotNet/Images/icon.png and /dev/null differ diff --git a/stream_deck_plugin/ClipTrimDotNet/Images/icon@2x.png b/stream_deck_plugin/ClipTrimDotNet/Images/icon@2x.png deleted file mode 100644 index 580d504..0000000 Binary files a/stream_deck_plugin/ClipTrimDotNet/Images/icon@2x.png and /dev/null differ diff --git a/stream_deck_plugin/ClipTrimDotNet/Images/off_save.png b/stream_deck_plugin/ClipTrimDotNet/Images/off_save.png deleted file mode 100644 index 1667313..0000000 Binary files a/stream_deck_plugin/ClipTrimDotNet/Images/off_save.png and /dev/null differ diff --git a/stream_deck_plugin/ClipTrimDotNet/Images/page_nav.png b/stream_deck_plugin/ClipTrimDotNet/Images/page_nav.png new file mode 100644 index 0000000..f60de75 Binary files /dev/null and b/stream_deck_plugin/ClipTrimDotNet/Images/page_nav.png differ diff --git a/stream_deck_plugin/ClipTrimDotNet/Images/page_nav.psd b/stream_deck_plugin/ClipTrimDotNet/Images/page_nav.psd new file mode 100644 index 0000000..5bbc264 Binary files /dev/null and b/stream_deck_plugin/ClipTrimDotNet/Images/page_nav.psd differ diff --git a/stream_deck_plugin/ClipTrimDotNet/Images/page_nav_icon.png b/stream_deck_plugin/ClipTrimDotNet/Images/page_nav_icon.png new file mode 100644 index 0000000..0e69816 Binary files /dev/null and b/stream_deck_plugin/ClipTrimDotNet/Images/page_nav_icon.png differ diff --git a/stream_deck_plugin/ClipTrimDotNet/Images/page_nav_icon.psd b/stream_deck_plugin/ClipTrimDotNet/Images/page_nav_icon.psd new file mode 100644 index 0000000..7f8c567 Binary files /dev/null and b/stream_deck_plugin/ClipTrimDotNet/Images/page_nav_icon.psd differ diff --git a/stream_deck_plugin/ClipTrimDotNet/Images/player.png b/stream_deck_plugin/ClipTrimDotNet/Images/player.png new file mode 100644 index 0000000..82dc0df Binary files /dev/null and b/stream_deck_plugin/ClipTrimDotNet/Images/player.png differ diff --git a/stream_deck_plugin/ClipTrimDotNet/Images/player.psd b/stream_deck_plugin/ClipTrimDotNet/Images/player.psd new file mode 100644 index 0000000..b30eb41 Binary files /dev/null and b/stream_deck_plugin/ClipTrimDotNet/Images/player.psd differ diff --git a/stream_deck_plugin/ClipTrimDotNet/Images/player_icon.png b/stream_deck_plugin/ClipTrimDotNet/Images/player_icon.png new file mode 100644 index 0000000..e8531fc Binary files /dev/null and b/stream_deck_plugin/ClipTrimDotNet/Images/player_icon.png differ diff --git a/stream_deck_plugin/ClipTrimDotNet/Images/player_icon.psd b/stream_deck_plugin/ClipTrimDotNet/Images/player_icon.psd new file mode 100644 index 0000000..5d956e2 Binary files /dev/null and b/stream_deck_plugin/ClipTrimDotNet/Images/player_icon.psd differ diff --git a/stream_deck_plugin/ClipTrimDotNet/Images/pluginAction.png b/stream_deck_plugin/ClipTrimDotNet/Images/pluginAction.png deleted file mode 100644 index 6f9dea4..0000000 Binary files a/stream_deck_plugin/ClipTrimDotNet/Images/pluginAction.png and /dev/null differ diff --git a/stream_deck_plugin/ClipTrimDotNet/Images/pluginAction@2x.png b/stream_deck_plugin/ClipTrimDotNet/Images/pluginAction@2x.png deleted file mode 100644 index 8f9d3d6..0000000 Binary files a/stream_deck_plugin/ClipTrimDotNet/Images/pluginAction@2x.png and /dev/null differ diff --git a/stream_deck_plugin/ClipTrimDotNet/Images/pluginIcon.png b/stream_deck_plugin/ClipTrimDotNet/Images/pluginIcon.png deleted file mode 100644 index c864463..0000000 Binary files a/stream_deck_plugin/ClipTrimDotNet/Images/pluginIcon.png and /dev/null differ diff --git a/stream_deck_plugin/ClipTrimDotNet/Images/pluginIcon@2x.png b/stream_deck_plugin/ClipTrimDotNet/Images/pluginIcon@2x.png deleted file mode 100644 index e98ca7c..0000000 Binary files a/stream_deck_plugin/ClipTrimDotNet/Images/pluginIcon@2x.png and /dev/null differ diff --git a/stream_deck_plugin/ClipTrimDotNet/Images/record.png b/stream_deck_plugin/ClipTrimDotNet/Images/record.png new file mode 100644 index 0000000..b0e13d4 Binary files /dev/null and b/stream_deck_plugin/ClipTrimDotNet/Images/record.png differ diff --git a/stream_deck_plugin/ClipTrimDotNet/Images/record.psd b/stream_deck_plugin/ClipTrimDotNet/Images/record.psd new file mode 100644 index 0000000..162756b Binary files /dev/null and b/stream_deck_plugin/ClipTrimDotNet/Images/record.psd differ diff --git a/stream_deck_plugin/ClipTrimDotNet/Images/record_icon.png b/stream_deck_plugin/ClipTrimDotNet/Images/record_icon.png new file mode 100644 index 0000000..a9b86df Binary files /dev/null and b/stream_deck_plugin/ClipTrimDotNet/Images/record_icon.png differ diff --git a/stream_deck_plugin/ClipTrimDotNet/Images/record_icon.psd b/stream_deck_plugin/ClipTrimDotNet/Images/record_icon.psd new file mode 100644 index 0000000..b2b0b5c Binary files /dev/null and b/stream_deck_plugin/ClipTrimDotNet/Images/record_icon.psd differ diff --git a/stream_deck_plugin/ClipTrimDotNet/Keys/ClipSave.cs b/stream_deck_plugin/ClipTrimDotNet/Keys/ClipSave.cs new file mode 100644 index 0000000..ea1d55d --- /dev/null +++ b/stream_deck_plugin/ClipTrimDotNet/Keys/ClipSave.cs @@ -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); + } + } +} diff --git a/stream_deck_plugin/ClipTrimDotNet/Keys/PageNavigator.cs b/stream_deck_plugin/ClipTrimDotNet/Keys/PageNavigator.cs new file mode 100644 index 0000000..d277c92 --- /dev/null +++ b/stream_deck_plugin/ClipTrimDotNet/Keys/PageNavigator.cs @@ -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 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(); + //} + } + } +} diff --git a/stream_deck_plugin/ClipTrimDotNet/Keys/Player.cs b/stream_deck_plugin/ClipTrimDotNet/Keys/Player.cs new file mode 100644 index 0000000..44b0269 --- /dev/null +++ b/stream_deck_plugin/ClipTrimDotNet/Keys/Player.cs @@ -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 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) + { + + } + + } +} \ No newline at end of file diff --git a/stream_deck_plugin/ClipTrimDotNet/Keys/ProfileSwitcher.cs b/stream_deck_plugin/ClipTrimDotNet/Keys/ProfileSwitcher.cs new file mode 100644 index 0000000..8f85a5f --- /dev/null +++ b/stream_deck_plugin/ClipTrimDotNet/Keys/ProfileSwitcher.cs @@ -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()!; + } + 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 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(); + //} + } + + #region Private Methods + + private Task SaveSettings() + { + return Connection.SetSettingsAsync(JObject.FromObject(settings)); + } + + #endregion + } +} \ No newline at end of file diff --git a/stream_deck_plugin/ClipTrimDotNet/Player.cs b/stream_deck_plugin/ClipTrimDotNet/Player.cs deleted file mode 100644 index 3c83e9b..0000000 --- a/stream_deck_plugin/ClipTrimDotNet/Player.cs +++ /dev/null @@ -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(); - } - 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 e) - { - - } - - private void Connection_OnTitleParametersDidChange(object sender, SDEventReceivedEventArgs 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(); - } - - //CheckFile(); - } - - #region Private Methods - - private Task SaveSettings() - { - return Connection.SetSettingsAsync(JObject.FromObject(settings)); - } - - #endregion - } -} \ No newline at end of file diff --git a/stream_deck_plugin/ClipTrimDotNet/ProfileSwitcher.cs b/stream_deck_plugin/ClipTrimDotNet/ProfileSwitcher.cs deleted file mode 100644 index e22e515..0000000 --- a/stream_deck_plugin/ClipTrimDotNet/ProfileSwitcher.cs +++ /dev/null @@ -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(); - } - 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 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 devices = new List(); - 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 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(); - } - - //CheckFile(); - } - - #region Private Methods - - private Task SaveSettings() - { - return Connection.SetSettingsAsync(JObject.FromObject(settings)); - } - - #endregion - } -} \ No newline at end of file diff --git a/stream_deck_plugin/ClipTrimDotNet/Program.cs b/stream_deck_plugin/ClipTrimDotNet/Program.cs index 9b01e28..82d615b 100644 --- a/stream_deck_plugin/ClipTrimDotNet/Program.cs +++ b/stream_deck_plugin/ClipTrimDotNet/Program.cs @@ -1,19 +1,20 @@ -using BarRaider.SdTools; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace ClipTrimDotNet -{ - internal class Program - { - static void Main(string[] args) - { - // Uncomment this line of code to allow for debugging - //while (!System.Diagnostics.Debugger.IsAttached) { System.Threading.Thread.Sleep(100); } - SDWrapper.Run(args); - } - } -} +using BarRaider.SdTools; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace ClipTrimDotNet +{ + internal class Program + { + static void Main(string[] args) + { + // Uncomment this line of code to allow for debugging + //while (!System.Diagnostics.Debugger.IsAttached) { System.Threading.Thread.Sleep(100); } + //Client.ClipTrimClient.Instance.PortNumber = 5010; + SDWrapper.Run(args); + } + } +} diff --git a/stream_deck_plugin/ClipTrimDotNet/Properties/AssemblyInfo.cs b/stream_deck_plugin/ClipTrimDotNet/Properties/AssemblyInfo.cs index a5449c8..61d79b6 100644 --- a/stream_deck_plugin/ClipTrimDotNet/Properties/AssemblyInfo.cs +++ b/stream_deck_plugin/ClipTrimDotNet/Properties/AssemblyInfo.cs @@ -1,36 +1,13 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -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: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[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")] +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("1bb90885-9d98-46ef-b983-4a4ef3aea890")] diff --git a/stream_deck_plugin/ClipTrimDotNet/PropertyInspector/file_player.html b/stream_deck_plugin/ClipTrimDotNet/PropertyInspector/file_player.html index a33211e..c088656 100644 --- a/stream_deck_plugin/ClipTrimDotNet/PropertyInspector/file_player.html +++ b/stream_deck_plugin/ClipTrimDotNet/PropertyInspector/file_player.html @@ -1,45 +1,45 @@ - - - - - Increment Counter Settings - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + Increment Counter Settings + + + + + + + + + \ No newline at end of file diff --git a/stream_deck_plugin/ClipTrimDotNet/PropertyInspector/profile_swticher.html b/stream_deck_plugin/ClipTrimDotNet/PropertyInspector/profile_swticher.html index 10af32e..5928145 100644 --- a/stream_deck_plugin/ClipTrimDotNet/PropertyInspector/profile_swticher.html +++ b/stream_deck_plugin/ClipTrimDotNet/PropertyInspector/profile_swticher.html @@ -1,34 +1,41 @@ - - - - Increment Counter Settings - - - - - - - - - - - - - - - - - + + + + Increment Counter Settings + + + + + + + + + + + + + + + diff --git a/stream_deck_plugin/ClipTrimDotNet/WavPlayer.cs b/stream_deck_plugin/ClipTrimDotNet/WavPlayer.cs deleted file mode 100644 index 68036b6..0000000 --- a/stream_deck_plugin/ClipTrimDotNet/WavPlayer.cs +++ /dev/null @@ -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((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>> _activePlayers; - - public WavPlayer() - { - _activePlayers = new ConcurrentDictionary>>(); - } - - 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>(); - } - - _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 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(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(); - } -} diff --git a/stream_deck_plugin/ClipTrimDotNet/manifest.json b/stream_deck_plugin/ClipTrimDotNet/manifest.json index 9ff9b8c..ff3b350 100644 --- a/stream_deck_plugin/ClipTrimDotNet/manifest.json +++ b/stream_deck_plugin/ClipTrimDotNet/manifest.json @@ -1,62 +1,92 @@ -{ - - "Actions": [ - { - "Icon": "Images/icon", - "Name": "Player", - "States": [ - { - "Image": "Images/pluginAction", - "TitleAlignment": "middle", - "FontSize": 11 - } - ], - "SupportedInMultiActions": false, - "Tooltip": "Plays a bound audio file", - "UUID": "com.michal-courson.cliptrim.player", - "PropertyInspectorPath": "PropertyInspector/file_player.html" - }, - { - "Icon": "Images/icon", - "Name": "Profile Switcher", - "States": [ - { - "Image": "Images/pluginAction", - "TitleAlignment": "middle", - "FontSize": 11 - } - ], - "SupportedInMultiActions": false, - "Tooltip": "Selects which sub folder to use and opens effect profile", - "UUID": "com.michal-courson.cliptrim.profile-switcher", - "PropertyInspectorPath": "PropertyInspector/profile_swticher.html" - } - ], - "Author": "Michal Courson", - "Name": "ClipTrimDotNet", - "Description": "Pairs with cliptrim for easy voice recording and trimming", - "Icon": "Images/pluginIcon", - "Version": "0.1.0.0", - "CodePath": "ClipTrimDotNet.exe", - "Category": "ClipTrimDotNet", - "CategoryIcon": "Images/categoryIcon", - "UUID": "com.michal-courson.cliptrim", - "OS": [ - { - "Platform": "windows", - "MinimumVersion": "10" - } - ], - "SDKVersion": 2, - "Software": { - "MinimumVersion": "6.4" - }, - "Profiles": [ - { - "Name": "ClipTrim", - "DeviceType": 0, - "Readonly": false, - "DontAutoSwitchWhenInstalled": false - } - ] +{ + + "Actions": [ + { + "Icon": "Images/player_icon", + "Name": "Player", + "States": [ + { + "Image": "Images/player", + "TitleAlignment": "middle", + "FontSize": 11 + } + ], + "SupportedInMultiActions": false, + "Tooltip": "Plays a bound audio file", + "UUID": "com.michal-courson.cliptrim.player", + "PropertyInspectorPath": "PropertyInspector/file_player.html" + }, + { + "Icon": "Images/collection_icon", + "Name": "Collection Selector", + "States": [ + { + "Image": "Images/collection", + "TitleAlignment": "middle", + "FontSize": 11 + } + ], + "SupportedInMultiActions": false, + "Tooltip": "Selects which collection to use", + "UUID": "com.michal-courson.cliptrim.profile-switcher", + "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", + "Name": "ClipTrimDotNet", + "Description": "Pairs with cliptrim for easy voice recording and trimming", + "Icon": "Images/app_icon", + "Version": "0.1.0.0", + "CodePath": "ClipTrimDotNet.exe", + "Category": "ClipTrim", + "CategoryIcon": "Images/category_icon", + "UUID": "com.michal-courson.cliptrim", + "OS": [ + { + "Platform": "windows", + "MinimumVersion": "10" + } + ], + "SDKVersion": 2, + "Software": { + "MinimumVersion": "6.4" + }, + "Profiles": [ + { + "Name": "ClipTrim", + "DeviceType": 0, + "Readonly": false, + "DontAutoSwitchWhenInstalled": false + } + ] } \ No newline at end of file diff --git a/stream_deck_plugin/ClipTrimDotNet/package-lock.json b/stream_deck_plugin/ClipTrimDotNet/package-lock.json index 8d76e11..eb84d5a 100644 --- a/stream_deck_plugin/ClipTrimDotNet/package-lock.json +++ b/stream_deck_plugin/ClipTrimDotNet/package-lock.json @@ -1,272 +1,272 @@ -{ - "name": "ClipTrimDotNet", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "devDependencies": { - "shx": "^0.3.4" - } - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, - "license": "MIT" - }, - "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true, - "license": "MIT" - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true, - "license": "ISC" - }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Glob versions prior to v9 are no longer supported", - "dev": true, - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", - "dev": true, - "license": "ISC", - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/interpret": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", - "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/is-core-module": { - "version": "2.16.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", - "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", - "dev": true, - "license": "MIT", - "dependencies": { - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/minimist": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", - "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, - "license": "ISC", - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true, - "license": "MIT" - }, - "node_modules/rechoir": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", - "integrity": "sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw==", - "dev": true, - "dependencies": { - "resolve": "^1.1.6" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/resolve": { - "version": "1.22.10", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", - "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-core-module": "^2.16.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/shelljs": { - "version": "0.8.5", - "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.5.tgz", - "integrity": "sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "glob": "^7.0.0", - "interpret": "^1.0.0", - "rechoir": "^0.6.2" - }, - "bin": { - "shjs": "bin/shjs" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/shx": { - "version": "0.3.4", - "resolved": "https://registry.npmjs.org/shx/-/shx-0.3.4.tgz", - "integrity": "sha512-N6A9MLVqjxZYcVn8hLmtneQWIJtp8IKzMP4eMnx+nqkvXoqinUPCbUFLp2UcWTEIUONhlk0ewxr/jaVGlc+J+g==", - "dev": true, - "license": "MIT", - "dependencies": { - "minimist": "^1.2.3", - "shelljs": "^0.8.5" - }, - "bin": { - "shx": "lib/cli.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true, - "license": "ISC" - } - } -} +{ + "name": "ClipTrimDotNet", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "devDependencies": { + "shx": "^0.3.4" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true, + "license": "ISC" + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/interpret": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", + "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT" + }, + "node_modules/rechoir": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", + "integrity": "sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw==", + "dev": true, + "dependencies": { + "resolve": "^1.1.6" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/resolve": { + "version": "1.22.10", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", + "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/shelljs": { + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.5.tgz", + "integrity": "sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "glob": "^7.0.0", + "interpret": "^1.0.0", + "rechoir": "^0.6.2" + }, + "bin": { + "shjs": "bin/shjs" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/shx": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/shx/-/shx-0.3.4.tgz", + "integrity": "sha512-N6A9MLVqjxZYcVn8hLmtneQWIJtp8IKzMP4eMnx+nqkvXoqinUPCbUFLp2UcWTEIUONhlk0ewxr/jaVGlc+J+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "minimist": "^1.2.3", + "shelljs": "^0.8.5" + }, + "bin": { + "shx": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, + "license": "ISC" + } + } +} diff --git a/stream_deck_plugin/ClipTrimDotNet/package.json b/stream_deck_plugin/ClipTrimDotNet/package.json index c55417b..a66cde7 100644 --- a/stream_deck_plugin/ClipTrimDotNet/package.json +++ b/stream_deck_plugin/ClipTrimDotNet/package.json @@ -1,14 +1,14 @@ -{ - "scripts": { - "stop": "streamdeck stop com.michal-courson.cliptrim", - "copy": "@powershell robocopy bin/Debug/ClipTrimDotNet.sdPlugin bin/Debug/com.michal-courson.cliptrim.sdPlugin", - "link": "streamdeck link bin/Debug/com.michal-courson.cliptrim.sdPlugin", - "restart": "streamdeck restart com.michal-courson.cliptrim", - "start": "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/" - }, - "devDependencies": { - "shx": "^0.3.4" - } -} +{ + "scripts": { + "stop": "streamdeck stop com.michal-courson.cliptrim", + "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", + "restart": "streamdeck restart com.michal-courson.cliptrim", + "start": "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/net8.0-windows/" + }, + "devDependencies": { + "shx": "^0.3.4" + } +} diff --git a/stream_deck_plugin/ClipTrimDotNet/packages.config b/stream_deck_plugin/ClipTrimDotNet/packages.config index 3166d2a..fe5d425 100644 --- a/stream_deck_plugin/ClipTrimDotNet/packages.config +++ b/stream_deck_plugin/ClipTrimDotNet/packages.config @@ -1,18 +1,46 @@ - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file