Compare commits
22 Commits
plugin_mig
...
69c9d80a82
| Author | SHA1 | Date | |
|---|---|---|---|
| 69c9d80a82 | |||
| 8c83819a17 | |||
| ad07bf7fe6 | |||
| bc40f9abe3 | |||
| e7f649ae0b | |||
| e34903b20f | |||
| 192c959d39 | |||
| 60123d7450 | |||
| 8265951bd4 | |||
| 757d5ef1a7 | |||
| d78c49d0ad | |||
| 089023e7cf | |||
| db97747f2e | |||
| 1e7141c43f | |||
| 8fda2a03af | |||
| 47cdaa76b6 | |||
| d49ac95fa2 | |||
| f2718282c7 | |||
| 86e30e6ec3 | |||
| b8f26496a0 | |||
| a761b81dd1 | |||
| c1948182ec |
70
.github/upgrades/dotnet-upgrade-plan.md
vendored
Normal file
70
.github/upgrades/dotnet-upgrade-plan.md
vendored
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
# .NET 8.0 Upgrade Plan
|
||||||
|
|
||||||
|
## Execution Steps
|
||||||
|
|
||||||
|
Execute steps below sequentially one by one in the order they are listed.
|
||||||
|
|
||||||
|
1. Validate that an .NET 8.0 SDK required for this upgrade is installed on the machine and if not, help to get it installed.
|
||||||
|
2. Ensure that the SDK version specified in global.json files is compatible with the .NET 8.0 upgrade.
|
||||||
|
3. Upgrade ClipTrimDotNet\ClipTrimDotNet.csproj
|
||||||
|
4. Run unit tests to validate upgrade in the projects listed below:
|
||||||
|
- ClientTest\ClientTest.csproj
|
||||||
|
|
||||||
|
## Settings
|
||||||
|
|
||||||
|
This section contains settings and data used by execution steps.
|
||||||
|
|
||||||
|
### Excluded projects
|
||||||
|
|
||||||
|
| Project name | Description |
|
||||||
|
|:-----------------------------------------------|:---------------------------:|
|
||||||
|
|
||||||
|
### Aggregate NuGet packages modifications across all projects
|
||||||
|
|
||||||
|
NuGet packages used across all selected projects or their dependencies that need version update in projects that reference them.
|
||||||
|
|
||||||
|
| Package Name | Current Version | New Version | Description |
|
||||||
|
|:------------------------------------|:---------------:|:-----------:|:----------------------------------------------|
|
||||||
|
| Microsoft.Bcl.AsyncInterfaces | 10.0.2 | 8.0.0 | Recommended for .NET 8.0 |
|
||||||
|
| Microsoft.Extensions.DependencyInjection | 10.0.2 | 8.0.1 | Recommended for .NET 8.0 |
|
||||||
|
| Microsoft.Extensions.DependencyInjection.Abstractions | 10.0.2 | 8.0.2 | Recommended for .NET 8.0 |
|
||||||
|
| Microsoft.Extensions.Logging | 10.0.2 | 8.0.1 | Recommended for .NET 8.0 |
|
||||||
|
| Microsoft.Extensions.Logging.Abstractions | 10.0.2 | 8.0.3 | Recommended for .NET 8.0 |
|
||||||
|
| Microsoft.Extensions.Options | 10.0.2 | 8.0.2 | Recommended for .NET 8.0 |
|
||||||
|
| Microsoft.Extensions.Primitives | 10.0.2 | 8.0.0 | Recommended for .NET 8.0 |
|
||||||
|
| System.Diagnostics.DiagnosticSource | 10.0.2 | 8.0.1 | Recommended for .NET 8.0 |
|
||||||
|
| System.Drawing.Common | 9.0.10 | 8.0.24 | Recommended for .NET 8.0 |
|
||||||
|
| System.IO.Pipelines | 10.0.2 | 8.0.0 | Recommended for .NET 8.0 |
|
||||||
|
| System.Security.AccessControl | 4.7.0 | 6.0.1 | Recommended for .NET 8.0 |
|
||||||
|
| System.Text.Encodings.Web | 10.0.2 | 8.0.0 | Recommended for .NET 8.0 |
|
||||||
|
| System.Text.Json | 10.0.2 | 8.0.6 | Recommended for .NET 8.0 |
|
||||||
|
| Microsoft.Win32.Registry | 4.7.0 | | Functionality included with framework |
|
||||||
|
| System.Buffers | 4.6.1 | | Functionality included with framework |
|
||||||
|
| System.IO | 4.3.0 | | Functionality included with framework |
|
||||||
|
| System.Memory | 4.6.3 | | Functionality included with framework |
|
||||||
|
| System.Net.Http | 4.3.4 | | Functionality included with framework |
|
||||||
|
| System.Numerics.Vectors | 4.6.1 | | Functionality included with framework |
|
||||||
|
| System.Runtime | 4.3.0 | | Functionality included with framework |
|
||||||
|
| System.Security.Cryptography.Algorithms | 4.3.0 | | Functionality included with framework |
|
||||||
|
| System.Security.Cryptography.Encoding | 4.3.0 | | Functionality included with framework |
|
||||||
|
| System.Security.Cryptography.Primitives | 4.3.0 | | Functionality included with framework |
|
||||||
|
| System.Security.Cryptography.X509Certificates | 4.3.0 | | Functionality included with framework |
|
||||||
|
| System.Security.Principal.Windows | 4.7.0 | | Functionality included with framework |
|
||||||
|
| System.Threading.Tasks.Extensions | 4.6.3 | | Functionality included with framework |
|
||||||
|
| System.ValueTuple | 4.6.1 | | Functionality included with framework |
|
||||||
|
|
||||||
|
### Project upgrade details
|
||||||
|
|
||||||
|
#### ClipTrimDotNet\ClipTrimDotNet.csproj modifications
|
||||||
|
|
||||||
|
Project properties changes:
|
||||||
|
- Target framework should be changed from `.NETFramework,Version=v4.8` to `net8.0`
|
||||||
|
- Project file should be converted to SDK-style
|
||||||
|
|
||||||
|
NuGet packages changes:
|
||||||
|
- Update all packages listed in the NuGet packages table above as recommended
|
||||||
|
- Remove packages whose functionality is now included with the framework
|
||||||
|
|
||||||
|
Other changes:
|
||||||
|
- Ensure compatibility with .NET 8.0 APIs and features
|
||||||
|
- Update code as needed for breaking changes
|
||||||
4
.vscode/settings.json
vendored
4
.vscode/settings.json
vendored
@ -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"
|
||||||
}
|
}
|
||||||
@ -1,34 +1,116 @@
|
|||||||
[
|
[
|
||||||
{
|
{
|
||||||
"name": "Uncategorized",
|
"name": "Uncategorized",
|
||||||
"id": 0,
|
"id": 0,
|
||||||
"clips": []
|
"clips": [
|
||||||
},
|
{
|
||||||
{
|
"filename": "C:\\Users\\mickl\\Desktop\\cliptrim-ui\\ClipTrimApp\\audio-service\\recordings\\audio_capture_20260226_195932.wav",
|
||||||
"name": "Test",
|
"name": "Clip 20260226_195932",
|
||||||
"id": 1,
|
"playbackType": "playOverlap",
|
||||||
"clips": []
|
"volume": 1
|
||||||
},
|
}
|
||||||
{
|
]
|
||||||
"name": "New",
|
},
|
||||||
"id": 2,
|
{
|
||||||
"clips": [
|
"name": "Test",
|
||||||
{
|
"id": 1,
|
||||||
"endTime": 30,
|
"clips": [
|
||||||
"filename": "C:\\Users\\mickl\\Desktop\\cliptrim-ui\\ClipTrimApp\\audio-service\\recordings\\audio_capture_20260220_193822.wav",
|
{
|
||||||
"name": "Pee pee\npoo poo",
|
"filename": "C:\\Users\\mickl\\Desktop\\cliptrim-ui\\ClipTrimApp\\audio-service\\recordings\\audio_capture_20260226_183607.wav",
|
||||||
"playbackType": "playStop",
|
"name": "Clip 20260226_183607",
|
||||||
"startTime": 27.756510985786615,
|
"playbackType": "playStop",
|
||||||
"volume": 1
|
"volume": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"endTime": 28.597210828548004,
|
"filename": "C:\\Users\\mickl\\Desktop\\cliptrim-ui\\ClipTrimApp\\audio-service\\recordings\\audio_capture_20260226_183812.wav",
|
||||||
"filename": "C:\\Users\\mickl\\Desktop\\cliptrim-ui\\ClipTrimApp\\audio-service\\recordings\\audio_capture_20260220_200442.wav",
|
"name": "Clip 20260226_183812",
|
||||||
"name": "Clip 20260220_200442",
|
"playbackType": "playStop",
|
||||||
"playbackType": "playStop",
|
"volume": 1
|
||||||
"startTime": 26.1853978671042,
|
},
|
||||||
"volume": 1
|
{
|
||||||
}
|
"filename": "C:\\Users\\mickl\\Desktop\\cliptrim-ui\\ClipTrimApp\\audio-service\\recordings\\audio_capture_20260226_183822.wav",
|
||||||
]
|
"name": "Clip 20260226_183822",
|
||||||
}
|
"playbackType": "playStop",
|
||||||
|
"volume": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "C:\\Users\\mickl\\Desktop\\cliptrim-ui\\ClipTrimApp\\audio-service\\recordings\\audio_capture_20260226_184028.wav",
|
||||||
|
"name": "Clip 20260226_184028",
|
||||||
|
"playbackType": "playStop",
|
||||||
|
"volume": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "C:\\Users\\mickl\\Desktop\\cliptrim-ui\\ClipTrimApp\\audio-service\\recordings\\audio_capture_20260226_184030.wav",
|
||||||
|
"name": "Clip 20260226_184030",
|
||||||
|
"playbackType": "playStop",
|
||||||
|
"volume": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "C:\\Users\\mickl\\Desktop\\cliptrim-ui\\ClipTrimApp\\audio-service\\recordings\\audio_capture_20260226_184032.wav",
|
||||||
|
"name": "Clip 20260226_184032",
|
||||||
|
"playbackType": "playStop",
|
||||||
|
"volume": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "C:\\Users\\mickl\\Desktop\\cliptrim-ui\\ClipTrimApp\\audio-service\\recordings\\audio_capture_20260226_184037.wav",
|
||||||
|
"name": "Clip 20260226_184037",
|
||||||
|
"playbackType": "playStop",
|
||||||
|
"volume": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "C:\\Users\\mickl\\Desktop\\cliptrim-ui\\ClipTrimApp\\audio-service\\recordings\\audio_capture_20260226_184040.wav",
|
||||||
|
"name": "Clip 20260226_184040",
|
||||||
|
"playbackType": "playStop",
|
||||||
|
"volume": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "C:\\Users\\mickl\\Desktop\\cliptrim-ui\\ClipTrimApp\\audio-service\\recordings\\audio_capture_20260226_184041.wav",
|
||||||
|
"name": "Clip 20260226_184041",
|
||||||
|
"playbackType": "playStop",
|
||||||
|
"volume": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "C:\\Users\\mickl\\Desktop\\cliptrim-ui\\ClipTrimApp\\audio-service\\recordings\\audio_capture_20260226_184042.wav",
|
||||||
|
"name": "Clip 20260226_184042",
|
||||||
|
"playbackType": "playStop",
|
||||||
|
"volume": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "C:\\Users\\mickl\\Desktop\\cliptrim-ui\\ClipTrimApp\\audio-service\\recordings\\audio_capture_20260226_184043.wav",
|
||||||
|
"name": "Clip 20260226_184043",
|
||||||
|
"playbackType": "playStop",
|
||||||
|
"volume": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"endTime": 14.772566995768694,
|
||||||
|
"filename": "C:\\Users\\mickl\\Desktop\\cliptrim-ui\\ClipTrimApp\\audio-service\\recordings\\audio_capture_20260226_194441.wav",
|
||||||
|
"name": "Test",
|
||||||
|
"playbackType": "playStop",
|
||||||
|
"startTime": 9.8548571932299,
|
||||||
|
"volume": 0.6
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "New",
|
||||||
|
"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
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
]
|
]
|
||||||
@ -1,7 +1,6 @@
|
|||||||
sounddevice==0.5.1
|
Flask==3.1.3
|
||||||
numpy==1.22.3
|
flask_cors==6.0.2
|
||||||
python-osc==1.9.3
|
flask_socketio==5.6.1
|
||||||
scipy==1.10.1
|
numpy==2.4.2
|
||||||
comtypes==1.4.8
|
scipy==1.17.1
|
||||||
pycaw==20240210
|
sounddevice==0.5.5
|
||||||
Flask==3.1.2
|
|
||||||
|
|||||||
@ -1,10 +1,17 @@
|
|||||||
{
|
{
|
||||||
"input_device": {
|
"input_device": {
|
||||||
"default_samplerate": 44100.0,
|
"channels": 2,
|
||||||
"index": 1,
|
"default_samplerate": 48000,
|
||||||
"max_input_channels": 8,
|
"index": 55,
|
||||||
"name": "VM Mic mix (VB-Audio Voicemeete"
|
"name": "VM Mic mix (VB-Audio Voicemeeter VAIO)"
|
||||||
},
|
},
|
||||||
"save_path": "C:\\Users\\mickl\\Desktop\\cliptrim-ui\\ClipTrimApp\\audio-service\\recordings",
|
"save_path": "C:\\Users\\mickl\\Desktop\\cliptrim-ui\\ClipTrimApp\\audio-service\\recordings",
|
||||||
"recording_length": 30
|
"recording_length": 30,
|
||||||
|
"output_device": {
|
||||||
|
"channels": 2,
|
||||||
|
"default_samplerate": 48000,
|
||||||
|
"index": 45,
|
||||||
|
"name": "VM to Discord (VB-Audio Voicemeeter VAIO)"
|
||||||
|
},
|
||||||
|
"http_port": 5010
|
||||||
}
|
}
|
||||||
BIN
audio-service/src/__pycache__/audio_clip.cpython-313.pyc
Normal file
BIN
audio-service/src/__pycache__/audio_clip.cpython-313.pyc
Normal file
Binary file not shown.
BIN
audio-service/src/__pycache__/audio_io.cpython-313.pyc
Normal file
BIN
audio-service/src/__pycache__/audio_io.cpython-313.pyc
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
64
audio-service/src/audio_clip.py
Normal file
64
audio-service/src/audio_clip.py
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
import scipy.signal
|
||||||
|
import scipy.io.wavfile as wavfile
|
||||||
|
import numpy as np
|
||||||
|
import os
|
||||||
|
|
||||||
|
class AudioClip:
|
||||||
|
def __init__(self, metadata, target_sample_rate=44100):
|
||||||
|
"""
|
||||||
|
metadata: dict with keys 'filename', 'start', 'end' (seconds)
|
||||||
|
target_sample_rate: sample rate for playback
|
||||||
|
"""
|
||||||
|
self.metadata = metadata
|
||||||
|
self.file_path = metadata['filename']
|
||||||
|
self.start = metadata.get('startTime', 0)
|
||||||
|
self.end = metadata.get('endTime', None)
|
||||||
|
self.target_sample_rate = target_sample_rate
|
||||||
|
self.volume = metadata.get('volume', 1.0)
|
||||||
|
self.finished = False
|
||||||
|
self.audio_data, self.sample_rate = self._load_and_process_audio()
|
||||||
|
print(f"AudioClip created for {self.file_path} with start={self.start}s, end={self.end}s, sample_rate={self.sample_rate}Hz, length={len(self.audio_data)/self.sample_rate:.2f}s")
|
||||||
|
self.position = 0 # sample index for playback
|
||||||
|
|
||||||
|
def _load_and_process_audio(self):
|
||||||
|
# Load audio file
|
||||||
|
sample_rate, data = wavfile.read(self.file_path)
|
||||||
|
# Convert to float32
|
||||||
|
if data.dtype != np.float32:
|
||||||
|
data = data.astype(np.float32) / np.max(np.abs(data))
|
||||||
|
# Convert to mono if needed
|
||||||
|
if len(data.shape) > 1:
|
||||||
|
data = np.mean(data, axis=1)
|
||||||
|
# Resample if needed
|
||||||
|
if sample_rate != self.target_sample_rate:
|
||||||
|
num_samples = int(len(data) * self.target_sample_rate / sample_rate)
|
||||||
|
data = scipy.signal.resample(data, num_samples)
|
||||||
|
sample_rate = self.target_sample_rate
|
||||||
|
# Cache only the clip region
|
||||||
|
start_sample = int(self.start * sample_rate)
|
||||||
|
end_sample = int(self.end * sample_rate) if self.end else len(data)
|
||||||
|
cached = data[start_sample:end_sample]
|
||||||
|
cached *= self.volume # Apply volume
|
||||||
|
return cached, sample_rate
|
||||||
|
|
||||||
|
def get_samples(self, num_samples):
|
||||||
|
# Return next chunk for playback
|
||||||
|
if self.position >= len(self.audio_data):
|
||||||
|
self.finished = True
|
||||||
|
return np.zeros(num_samples, dtype=np.float32)
|
||||||
|
end_pos = min(self.position + num_samples, len(self.audio_data))
|
||||||
|
chunk = self.audio_data[self.position:end_pos]
|
||||||
|
self.position = end_pos
|
||||||
|
if self.position >= len(self.audio_data):
|
||||||
|
self.finished = True
|
||||||
|
# Pad if chunk is short
|
||||||
|
if len(chunk) < num_samples:
|
||||||
|
chunk = np.pad(chunk, (0, num_samples - len(chunk)), mode='constant')
|
||||||
|
return chunk
|
||||||
|
|
||||||
|
def is_finished(self):
|
||||||
|
return self.finished
|
||||||
|
|
||||||
|
def reset(self):
|
||||||
|
self.position = 0
|
||||||
|
self.finished = False
|
||||||
168
audio-service/src/audio_io.py
Normal file
168
audio-service/src/audio_io.py
Normal file
@ -0,0 +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.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 )
|
||||||
|
|
||||||
|
|
||||||
|
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))
|
||||||
@ -1,156 +0,0 @@
|
|||||||
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
|
|
||||||
|
|
||||||
class AudioRecorder:
|
|
||||||
_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):
|
|
||||||
"""
|
|
||||||
Initialize audio recorder with configurable parameters.
|
|
||||||
|
|
||||||
:param duration: Length of audio buffer in seconds
|
|
||||||
:param sample_rate: Audio sample rate (if None, use default device sample rate)
|
|
||||||
:param channels: Number of audio channels
|
|
||||||
"""
|
|
||||||
print(f"Initializing AudioRecorder")
|
|
||||||
self.duration = 30
|
|
||||||
self.sample_rate = 44100
|
|
||||||
self.channels = 2
|
|
||||||
self.buffer = np.zeros((int(self.duration * self.sample_rate), self.channels), dtype=np.float32)
|
|
||||||
self.recordings_dir = "recordings"
|
|
||||||
self.stream = sd.InputStream(
|
|
||||||
samplerate=self.sample_rate,
|
|
||||||
channels=self.channels,
|
|
||||||
callback=self.record_callback
|
|
||||||
)
|
|
||||||
|
|
||||||
def refresh_stream(self):
|
|
||||||
"""
|
|
||||||
Refresh the audio stream with updated parameters.
|
|
||||||
"""
|
|
||||||
was_active = self.stream.active
|
|
||||||
if was_active:
|
|
||||||
self.stream.stop()
|
|
||||||
|
|
||||||
self.buffer = np.zeros((int(self.duration * self.sample_rate), self.channels), dtype=np.float32)
|
|
||||||
|
|
||||||
self.stream = sd.InputStream(
|
|
||||||
samplerate=self.sample_rate,
|
|
||||||
channels=self.channels,
|
|
||||||
callback=self.record_callback
|
|
||||||
)
|
|
||||||
|
|
||||||
if was_active:
|
|
||||||
self.stream.start()
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def record_callback(self, indata, frames, time, status):
|
|
||||||
"""
|
|
||||||
Circular buffer callback for continuous recording.
|
|
||||||
|
|
||||||
:param indata: Input audio data
|
|
||||||
:param frames: Number of frames
|
|
||||||
:param time: Timestamp
|
|
||||||
:param status: Recording status
|
|
||||||
"""
|
|
||||||
if status:
|
|
||||||
print(f"Recording status: {status}")
|
|
||||||
|
|
||||||
# Circular buffer implementation
|
|
||||||
self.buffer = np.roll(self.buffer, -frames, axis=0)
|
|
||||||
self.buffer[-frames:] = indata
|
|
||||||
|
|
||||||
def save_last_n_seconds(self):
|
|
||||||
"""
|
|
||||||
Save the last n seconds of audio to a file.
|
|
||||||
|
|
||||||
:param output_dir: Directory to save recordings
|
|
||||||
:return: Path to saved audio file
|
|
||||||
"""
|
|
||||||
# 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.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):
|
|
||||||
"""
|
|
||||||
Set the duration of the audio buffer.
|
|
||||||
|
|
||||||
:param duration: New buffer duration in seconds
|
|
||||||
"""
|
|
||||||
self.duration = duration
|
|
||||||
self.buffer = np.zeros((int(duration * self.sample_rate), self.channels), dtype=np.float32)
|
|
||||||
|
|
||||||
def set_recording_directory(self, directory):
|
|
||||||
"""
|
|
||||||
Set the directory where recordings will be saved.
|
|
||||||
|
|
||||||
:param directory: Path to the recordings directory
|
|
||||||
"""
|
|
||||||
self.recordings_dir = directory
|
|
||||||
|
|
||||||
def start_recording(self):
|
|
||||||
"""
|
|
||||||
Start continuous audio recording with circular buffer.
|
|
||||||
"""
|
|
||||||
if(self.stream.active):
|
|
||||||
print("Already recording")
|
|
||||||
return
|
|
||||||
print('number of channels', self.channels)
|
|
||||||
|
|
||||||
self.stream.start()
|
|
||||||
|
|
||||||
def stop_recording(self):
|
|
||||||
"""
|
|
||||||
Stop continuous audio recording with circular buffer.
|
|
||||||
"""
|
|
||||||
if(not self.stream.active):
|
|
||||||
print("Already stopped")
|
|
||||||
return
|
|
||||||
|
|
||||||
self.stream.stop()
|
|
||||||
|
|
||||||
def is_recording(self):
|
|
||||||
"""
|
|
||||||
Check if the audio stream is currently active.
|
|
||||||
|
|
||||||
:return: True if recording, False otherwise
|
|
||||||
"""
|
|
||||||
return self.stream.active
|
|
||||||
@ -1,69 +1,94 @@
|
|||||||
import argparse
|
import argparse
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
from audio_recorder import AudioRecorder
|
from audio_io import AudioIO
|
||||||
from windows_audio import WindowsAudioManager
|
from windows_audio import WindowsAudioManager
|
||||||
import sounddevice as sd
|
import sounddevice as sd
|
||||||
from metadata_manager import MetaDataManager
|
from metadata_manager import MetaDataManager
|
||||||
from settings import SettingsManager
|
from settings import SettingsManager
|
||||||
|
|
||||||
from flask import Flask
|
from flask import Flask
|
||||||
from flask_cors import CORS
|
from flask_cors import CORS
|
||||||
|
|
||||||
from routes.recording import recording_bp
|
from routes.recording import recording_bp
|
||||||
from routes.device import device_bp
|
from routes.device import device_bp
|
||||||
from routes.metadata import metadata_bp
|
from routes.metadata import metadata_bp
|
||||||
from routes.settings import settings_bp
|
from routes.settings import settings_bp
|
||||||
from flask_socketio import SocketIO
|
from flask_socketio import SocketIO, emit
|
||||||
import threading
|
import threading
|
||||||
|
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
CORS(app)
|
CORS(app)
|
||||||
# socketio = SocketIO(app, cors_allowed_origins="*")
|
socketio = SocketIO(app, cors_allowed_origins="*", logger=True, engineio_logger=True, async_mode='eventlet')
|
||||||
# CORS(socketio)
|
|
||||||
|
@socketio.on('connect')
|
||||||
def main():
|
def handle_connect():
|
||||||
# Create argument parser
|
print("Client connected")
|
||||||
parser = argparse.ArgumentParser(description='Audio Recording Service')
|
emit('full_data', MetaDataManager().collections)
|
||||||
|
|
||||||
# OSC port argument
|
@socketio.on('record_clip')
|
||||||
parser.add_argument('--osc-port',
|
def record_clip():
|
||||||
type=int,
|
io = AudioIO()
|
||||||
help='OSC server port number',
|
io.save_last_n_seconds();
|
||||||
default=5010)
|
|
||||||
|
@socketio.on('play_clip')
|
||||||
# Parse arguments
|
def play_clip(data):
|
||||||
args = parser.parse_args()
|
io = AudioIO()
|
||||||
audio_manager = WindowsAudioManager()
|
print(f"Received play_clip event with data: {data}")
|
||||||
settings = SettingsManager()
|
if data:
|
||||||
|
io.play_clip(data)
|
||||||
# Ensure save path exists
|
|
||||||
os.makedirs(settings.get_settings('save_path'), exist_ok=True)
|
|
||||||
|
def main():
|
||||||
|
# Create argument parser
|
||||||
# Register blueprints
|
parser = argparse.ArgumentParser(description='Audio Recording Service')
|
||||||
app.register_blueprint(recording_bp)
|
|
||||||
app.register_blueprint(device_bp)
|
# OSC port argument
|
||||||
app.register_blueprint(metadata_bp)
|
parser.add_argument('--osc-port',
|
||||||
app.register_blueprint(settings_bp)
|
type=int,
|
||||||
app.run(host='127.0.0.1', port=args.osc_port, debug=False, use_reloader=True)
|
help='OSC server port number',
|
||||||
# socketio.run(app, host='127.0.0.1', port=args.osc_port, debug=False, use_reloader=True)
|
default=5010)
|
||||||
|
|
||||||
|
# Parse arguments
|
||||||
|
args = parser.parse_args()
|
||||||
# Run the OSC server
|
audio_manager = WindowsAudioManager()
|
||||||
try:
|
settings = SettingsManager()
|
||||||
print(f"Starting OSC Recording Server on port {args.osc_port}")
|
meta = MetaDataManager()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# osc_server.run_server()
|
# Ensure save path exists
|
||||||
except KeyboardInterrupt:
|
os.makedirs(settings.get_settings('save_path'), exist_ok=True)
|
||||||
print("\nServer stopped by user.")
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Error starting server: {e}")
|
io = AudioIO()
|
||||||
sys.exit(1)
|
io.start_recording()
|
||||||
|
|
||||||
|
# settings.socket = socketio
|
||||||
if __name__ == "__main__":
|
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()
|
main()
|
||||||
@ -1,20 +1,20 @@
|
|||||||
{
|
{
|
||||||
"Uncategorized": [
|
"Uncategorized": [
|
||||||
{
|
{
|
||||||
"endTime": 12.489270386266055,
|
"endTime": 12.489270386266055,
|
||||||
"filename": "C:\\Users\\mickl\\Desktop\\cliptrim-ui\\ClipTrimApp\\audio-service\\src\\recordings\\audio_capture_20260214_133540.wav",
|
"filename": "C:\\Users\\mickl\\Desktop\\cliptrim-ui\\ClipTrimApp\\audio-service\\src\\recordings\\audio_capture_20260214_133540.wav",
|
||||||
"name": "Clip 20260214_133540",
|
"name": "Clip 20260214_133540",
|
||||||
"playbackType": "playStop",
|
"playbackType": "playStop",
|
||||||
"startTime": 10.622317596566523,
|
"startTime": 10.622317596566523,
|
||||||
"volume": 1
|
"volume": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"endTime": 6.824034334763957,
|
"endTime": 6.824034334763957,
|
||||||
"filename": "C:\\Users\\mickl\\Desktop\\cliptrim-ui\\ClipTrimApp\\audio-service\\src\\recordings\\audio_capture_20260214_133137.wav",
|
"filename": "C:\\Users\\mickl\\Desktop\\cliptrim-ui\\ClipTrimApp\\audio-service\\src\\recordings\\audio_capture_20260214_133137.wav",
|
||||||
"name": "Clip 20260214_133137",
|
"name": "Clip 20260214_133137",
|
||||||
"playbackType": "playStop",
|
"playbackType": "playStop",
|
||||||
"startTime": 3.7982832618025753,
|
"startTime": 3.7982832618025753,
|
||||||
"volume": 1
|
"volume": 1
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@ -1,100 +1,114 @@
|
|||||||
import os
|
import os
|
||||||
import json
|
import json
|
||||||
|
|
||||||
class MetaDataManager:
|
class MetaDataManager:
|
||||||
_instance = None
|
_instance = None
|
||||||
|
|
||||||
def __new__(cls, *args, **kwargs):
|
def __new__(cls, *args, **kwargs):
|
||||||
if cls._instance is None:
|
if cls._instance is None:
|
||||||
cls._instance = super().__new__(cls)
|
cls._instance = super().__new__(cls)
|
||||||
cls._instance.init()
|
cls._instance.init()
|
||||||
return cls._instance
|
return cls._instance
|
||||||
def init(self):
|
def init(self):
|
||||||
# read metadata file from executing directory
|
self.socket = None
|
||||||
self.metadata_file = os.path.join(os.getcwd(), "metadata.json")
|
# read metadata file from executing directory
|
||||||
if os.path.exists(self.metadata_file):
|
self.metadata_file = os.path.join(os.getcwd(), "metadata.json")
|
||||||
with open(self.metadata_file, "r") as f:
|
if os.path.exists(self.metadata_file):
|
||||||
self.collections = json.load(f)
|
with open(self.metadata_file, "r") as f:
|
||||||
else:
|
self.collections = json.load(f)
|
||||||
self.collections = {}
|
else:
|
||||||
if(collections := next((c for c in self.collections if c.get("name") == "Uncategorized"), None)) is None:
|
self.collections = {}
|
||||||
self.collections.append({"name": "Uncategorized", "id": 0, "clips": []})
|
if(collections := next((c for c in self.collections if c.get("name") == "Uncategorized"), None)) is None:
|
||||||
self.save_metadata()
|
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):
|
def create_collection(self, name):
|
||||||
raise ValueError(f"Collection '{name}' already exists.")
|
if any(c.get("name") == name for c in self.collections):
|
||||||
new_id = max((c.get("id", 0) for c in self.collections), default=0) + 1
|
raise ValueError(f"Collection '{name}' already exists.")
|
||||||
self.collections.append({"name": name, "id": new_id, "clips": []})
|
new_id = max((c.get("id", 0) for c in self.collections), default=0) + 1
|
||||||
self.save_metadata()
|
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)
|
def delete_collection(self, name):
|
||||||
if collection is None:
|
collection = next((c for c in self.collections if c.get("name") == name), None)
|
||||||
raise ValueError(f"Collection '{name}' does not exist.")
|
if collection is None:
|
||||||
self.collections.remove(collection)
|
raise ValueError(f"Collection '{name}' does not exist.")
|
||||||
self.save_metadata()
|
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)
|
def add_clip_to_collection(self, collection_name, clip_metadata):
|
||||||
if collection is None:
|
collection = next((c for c in self.collections if c.get("name") == collection_name), None)
|
||||||
raise ValueError(f"Collection '{collection_name}' does not exist.")
|
if collection is None:
|
||||||
collection["clips"].append(clip_metadata)
|
raise ValueError(f"Collection '{collection_name}' does not exist.")
|
||||||
self.save_metadata()
|
collection["clips"].append(clip_metadata)
|
||||||
|
if not self.socket is None:
|
||||||
def remove_clip_from_collection(self, collection_name, clip_metadata):
|
self.socket.emit('collection_updated', collection)
|
||||||
collection = next((c for c in self.collections if c.get("name") == collection_name), None)
|
self.save_metadata()
|
||||||
if collection is None:
|
|
||||||
raise ValueError(f"Collection '{collection_name}' does not exist.")
|
def remove_clip_from_collection(self, collection_name, clip_metadata):
|
||||||
# Remove all clips with the same file name as clip_metadata["file_name"]
|
collection = next((c for c in self.collections if c.get("name") == collection_name), None)
|
||||||
in_list = any(clip.get("filename") == clip_metadata.get("filename") for clip in collection["clips"])
|
if collection is None:
|
||||||
if not in_list:
|
raise ValueError(f"Collection '{collection_name}' does not exist.")
|
||||||
raise ValueError(f"Clip with filename '{clip_metadata.get('filename')}' not found in collection '{collection_name}'.")
|
# 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"])
|
||||||
collection["clips"] = [
|
if not in_list:
|
||||||
clip for clip in collection["clips"]
|
raise ValueError(f"Clip with filename '{clip_metadata.get('filename')}' not found in collection '{collection_name}'.")
|
||||||
if clip.get("filename") != clip_metadata.get("filename")
|
|
||||||
]
|
collection["clips"] = [
|
||||||
self.save_metadata()
|
clip for clip in collection["clips"]
|
||||||
|
if clip.get("filename") != clip_metadata.get("filename")
|
||||||
def move_clip_to_collection(self, source_collection, target_collection, clip_metadata):
|
]
|
||||||
self.remove_clip_from_collection(source_collection, clip_metadata)
|
if not self.socket is None:
|
||||||
self.add_clip_to_collection(target_collection, clip_metadata)
|
self.socket.emit('collection_updated', collection)
|
||||||
|
self.save_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:
|
def move_clip_to_collection(self, source_collection, target_collection, clip_metadata):
|
||||||
raise ValueError(f"Collection '{collection_name}' does not exist.")
|
self.remove_clip_from_collection(source_collection, clip_metadata)
|
||||||
# Find the index of the clip with the same file name as old_clip_metadata["file_name"]
|
self.add_clip_to_collection(target_collection, clip_metadata)
|
||||||
index = next((i for i, clip in enumerate(collection["clips"]) if clip.get("filename") == new_clip_metadata.get("filename")), None)
|
if not self.socket is None:
|
||||||
if index is None:
|
self.socket.emit('collection_updated', source_collection)
|
||||||
raise ValueError(f"Clip with filename '{new_clip_metadata.get('filename')}' not found in collection '{collection_name}'.")
|
self.socket.emit('collection_updated', target_collection)
|
||||||
|
|
||||||
collection["clips"][index] = new_clip_metadata
|
|
||||||
self.save_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)
|
||||||
def get_collections(self):
|
if collection is None:
|
||||||
return list(map(lambda c: {"name": c.get("name"), "id": c.get("id")}, self.collections))
|
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"]
|
||||||
def get_clips_in_collection(self, collection_name):
|
index = next((i for i, clip in enumerate(collection["clips"]) if clip.get("filename") == new_clip_metadata.get("filename")), None)
|
||||||
collection = next((c for c in self.collections if c.get("name") == collection_name), None)
|
if index is None:
|
||||||
if collection is None:
|
raise ValueError(f"Clip with filename '{new_clip_metadata.get('filename')}' not found in collection '{collection_name}'.")
|
||||||
raise ValueError(f"Collection '{collection_name}' does not exist.")
|
|
||||||
return collection["clips"]
|
collection["clips"][index] = new_clip_metadata
|
||||||
|
if not self.socket is None:
|
||||||
def reorder_clips_in_collection(self, collection_name, new_order):
|
self.socket.emit('collection_updated', collection)
|
||||||
collection = next((c for c in self.collections if c.get("name") == collection_name), None)
|
self.save_metadata()
|
||||||
if collection is None:
|
|
||||||
raise ValueError(f"Collection '{collection_name}' does not exist.")
|
def get_collections(self):
|
||||||
existing_filenames = {clip.get("filename") for clip in collection["clips"]}
|
return list(map(lambda c: {"name": c.get("name"), "id": c.get("id")}, self.collections))
|
||||||
new_filenames = {clip.get("filename") for clip in new_order}
|
|
||||||
|
def get_clips_in_collection(self, collection_name):
|
||||||
if not new_filenames.issubset(existing_filenames):
|
collection = next((c for c in self.collections if c.get("name") == collection_name), None)
|
||||||
raise ValueError("New order contains clips that do not exist in the collection.")
|
if collection is None:
|
||||||
|
raise ValueError(f"Collection '{collection_name}' does not exist.")
|
||||||
collection["clips"] = new_order
|
return collection["clips"]
|
||||||
self.save_metadata()
|
|
||||||
|
def reorder_clips_in_collection(self, collection_name, new_order):
|
||||||
def save_metadata(self):
|
collection = next((c for c in self.collections if c.get("name") == collection_name), None)
|
||||||
with open(self.metadata_file, "w") as f:
|
if collection is None:
|
||||||
json.dump(self.collections, f, indent=4)
|
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)
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
[ViewState]
|
[ViewState]
|
||||||
Mode=
|
Mode=
|
||||||
Vid=
|
Vid=
|
||||||
FolderType=Generic
|
FolderType=Generic
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -1,11 +1,11 @@
|
|||||||
from flask import Blueprint, request, jsonify
|
from flask import Blueprint, request, jsonify
|
||||||
from windows_audio import WindowsAudioManager
|
from windows_audio import WindowsAudioManager
|
||||||
from audio_recorder import AudioRecorder
|
from audio_io import AudioIO
|
||||||
|
|
||||||
device_bp = Blueprint('device', __name__)
|
device_bp = Blueprint('device', __name__)
|
||||||
|
|
||||||
audio_manager = WindowsAudioManager()
|
audio_manager = WindowsAudioManager()
|
||||||
recorder = AudioRecorder()
|
recorder = AudioIO()
|
||||||
|
|
||||||
# @device_bp.route('/device/set', methods=['POST'])
|
# @device_bp.route('/device/set', methods=['POST'])
|
||||||
# def set_audio_device():
|
# def set_audio_device():
|
||||||
@ -19,13 +19,13 @@ recorder = AudioRecorder()
|
|||||||
# except Exception as e:
|
# except Exception as e:
|
||||||
# return jsonify({'status': 'error', 'message': str(e)}), 400
|
# return jsonify({'status': 'error', 'message': str(e)}), 400
|
||||||
|
|
||||||
@device_bp.route('/device/get', methods=['GET'])
|
# @device_bp.route('/device/get', methods=['GET'])
|
||||||
def get_audio_device():
|
# def get_audio_device():
|
||||||
try:
|
# try:
|
||||||
device_info = audio_manager.get_default_device('input')
|
# device_info = audio_manager.get_default_device('input')
|
||||||
return jsonify({'status': 'success', 'device_info': device_info})
|
# return jsonify({'status': 'success', 'device_info': device_info})
|
||||||
except Exception as e:
|
# except Exception as e:
|
||||||
return jsonify({'status': 'error', 'message': str(e)}), 400
|
# return jsonify({'status': 'error', 'message': str(e)}), 400
|
||||||
|
|
||||||
@device_bp.route('/device/list', methods=['GET'])
|
@device_bp.route('/device/list', methods=['GET'])
|
||||||
def list_audio_devices():
|
def list_audio_devices():
|
||||||
|
|||||||
@ -91,10 +91,16 @@ def edit_clip_in_collection():
|
|||||||
meta_manager = MetaDataManager()
|
meta_manager = MetaDataManager()
|
||||||
collection_name = request.json.get('name')
|
collection_name = request.json.get('name')
|
||||||
clip_metadata = request.json.get('clip')
|
clip_metadata = request.json.get('clip')
|
||||||
print(f"Received request to edit clip in collection '{collection_name}': {clip_metadata}")
|
# print(f"Received request to edit clip in collection '{collection_name}': {clip_metadata}")
|
||||||
try:
|
try:
|
||||||
meta_manager.edit_clip_in_collection(collection_name, clip_metadata)
|
meta_manager.edit_clip_in_collection(collection_name, clip_metadata)
|
||||||
collections = meta_manager.collections
|
collections = meta_manager.collections
|
||||||
return jsonify({'status': 'success', 'collections': collections})
|
return jsonify({'status': 'success', 'collections': collections})
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
return jsonify({'status': 'error', 'message': str(e)}), 400
|
return jsonify({'status': 'error', 'message': str(e)}), 400
|
||||||
|
|
||||||
|
|
||||||
|
@metadata_bp.route('/ws/test', methods=['POST'])
|
||||||
|
def test_websocket():
|
||||||
|
MetaDataManager().socket.emit('test_event', {'data': 'Test message from metadata route'})
|
||||||
|
return jsonify({'status': 'success'})
|
||||||
@ -1,53 +1,57 @@
|
|||||||
|
|
||||||
from flask import Blueprint, request, jsonify
|
from flask import Blueprint, request, jsonify
|
||||||
from audio_recorder import AudioRecorder
|
from audio_io import AudioIO
|
||||||
import os
|
import os
|
||||||
|
|
||||||
recording_bp = Blueprint('recording', __name__)
|
recording_bp = Blueprint('recording', __name__)
|
||||||
|
|
||||||
@recording_bp.route('/record/start', methods=['POST'])
|
@recording_bp.route('/record/start', methods=['POST'])
|
||||||
def start_recording():
|
def start_recording():
|
||||||
recorder = AudioRecorder()
|
recorder = AudioIO()
|
||||||
print('HTTP: Starting audio recording')
|
print('HTTP: Starting audio recording')
|
||||||
recorder.start_recording()
|
recorder.start_recording()
|
||||||
return jsonify({'status': 'recording started'})
|
return jsonify({'status': 'recording started'})
|
||||||
|
|
||||||
@recording_bp.route('/record/stop', methods=['POST'])
|
@recording_bp.route('/record/stop', methods=['POST'])
|
||||||
def stop_recording():
|
def stop_recording():
|
||||||
recorder = AudioRecorder()
|
recorder = AudioIO()
|
||||||
print('HTTP: Stopping audio recording')
|
# print('HTTP: Stopping audio recording')
|
||||||
recorder.stop_recording()
|
recorder.stop_recording()
|
||||||
return jsonify({'status': 'recording stopped'})
|
return jsonify({'status': 'recording stopped'})
|
||||||
|
|
||||||
@recording_bp.route('/record/save', methods=['POST'])
|
@recording_bp.route('/record/save', methods=['POST'])
|
||||||
def save_recording():
|
def save_recording():
|
||||||
recorder = AudioRecorder()
|
recorder = AudioIO()
|
||||||
print('HTTP: Saving audio recording')
|
# print('HTTP: Saving audio recording')
|
||||||
saved_file = recorder.save_last_n_seconds()
|
saved_file = recorder.save_last_n_seconds()
|
||||||
return jsonify({'status': 'recording saved', 'file': saved_file})
|
return jsonify({'status': 'recording saved', 'file': saved_file})
|
||||||
|
|
||||||
|
|
||||||
@recording_bp.route('/record/status', methods=['GET'])
|
@recording_bp.route('/record/status', methods=['GET'])
|
||||||
def recording_status():
|
def recording_status():
|
||||||
recorder = AudioRecorder()
|
recorder = AudioIO()
|
||||||
print('HTTP: Checking recording status')
|
# print('HTTP: Checking recording status')
|
||||||
status = 'recording' if recorder.is_recording() else 'stopped'
|
status = 'recording' if recorder.is_recording() else 'stopped'
|
||||||
return jsonify({'status': status})
|
return jsonify({'status': status})
|
||||||
|
|
||||||
@recording_bp.route('/record/delete', methods=['POST'])
|
@recording_bp.route('/record/delete', methods=['POST'])
|
||||||
def recording_delete():
|
def recording_delete():
|
||||||
filename = request.json.get('filename')
|
filename = request.json.get('filename')
|
||||||
try:
|
try:
|
||||||
os.remove(filename)
|
os.remove(filename)
|
||||||
return jsonify({'status': 'success'})
|
return jsonify({'status': 'success'})
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return jsonify({'status': 'error', 'message': str(e)}), 400
|
return jsonify({'status': 'error', 'message': str(e)}), 400
|
||||||
|
|
||||||
@recording_bp.route('/playback/start', methods=['POST'])
|
@recording_bp.route('/playback/start', methods=['POST'])
|
||||||
def playback_start():
|
def playback_start():
|
||||||
print('HTTP: Starting audio playback')
|
print(f"Playing clip")
|
||||||
try:
|
# print('HTTP: Starting audio playback')
|
||||||
# os.remove(filename)
|
clip = request.json
|
||||||
return jsonify({'status': 'success'})
|
try:
|
||||||
except Exception as e:
|
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
|
return jsonify({'status': 'error', 'message': str(e)}), 400
|
||||||
@ -1,25 +1,32 @@
|
|||||||
from flask import Blueprint, request, jsonify
|
from flask import Blueprint, request, jsonify
|
||||||
from settings import SettingsManager
|
from settings import SettingsManager
|
||||||
|
|
||||||
settings_bp = Blueprint('settings', __name__)
|
settings_bp = Blueprint('settings', __name__)
|
||||||
|
|
||||||
|
|
||||||
@settings_bp.route('/settings', methods=['GET'])
|
@settings_bp.route('/settings', methods=['GET'])
|
||||||
def get_all_settings():
|
def get_all_settings():
|
||||||
return jsonify({'status': 'success', 'settings': SettingsManager().get_all_settings()})
|
return jsonify({'status': 'success', 'settings': SettingsManager().get_all_settings()})
|
||||||
|
|
||||||
@settings_bp.route('/settings/<name>', methods=['GET'])
|
@settings_bp.route('/settings/<name>', methods=['GET'])
|
||||||
def get_setting(name):
|
def get_setting(name):
|
||||||
value = SettingsManager().get_settings(name)
|
value = SettingsManager().get_settings(name)
|
||||||
if value is not None:
|
if value is not None:
|
||||||
return jsonify({'status': 'success', 'name': name, 'value': value})
|
return jsonify({'status': 'success', 'name': name, 'value': value})
|
||||||
else:
|
else:
|
||||||
return jsonify({'status': 'error', 'message': f'Setting "{name}" not found'}), 404
|
return jsonify({'status': 'error', 'message': f'Setting "{name}" not found'}), 404
|
||||||
|
|
||||||
@settings_bp.route('/settings/<name>', methods=['POST'])
|
@settings_bp.route('/settings/update', methods=['POST'])
|
||||||
def set_setting(name):
|
def set_all_settings():
|
||||||
value = request.json.get('value')
|
settings = request.json.get('settings')
|
||||||
if value is None:
|
print (f"Received settings update: {settings}")
|
||||||
return jsonify({'status': 'error', 'message': 'Value is required'}), 400
|
if settings is None:
|
||||||
SettingsManager().set_settings(name, value)
|
return jsonify({'status': 'error', 'message': 'Settings are required'}), 400
|
||||||
return jsonify({'status': 'success', 'name': name, 'value': value})
|
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
|
||||||
|
|
||||||
@ -1,10 +1,10 @@
|
|||||||
{
|
{
|
||||||
"input_device": {
|
"input_device": {
|
||||||
"index": 0,
|
"index": 0,
|
||||||
"name": "Microsoft Sound Mapper - Input",
|
"name": "Microsoft Sound Mapper - Input",
|
||||||
"max_input_channels": 2,
|
"max_input_channels": 2,
|
||||||
"default_samplerate": 44100.0
|
"default_samplerate": 44100.0
|
||||||
},
|
},
|
||||||
"save_path": "C:\\Users\\mickl\\Desktop\\cliptrim-ui\\ClipTrimApp\\audio-service\\src\\recordings",
|
"save_path": "C:\\Users\\mickl\\Desktop\\cliptrim-ui\\ClipTrimApp\\audio-service\\src\\recordings",
|
||||||
"recording_length": 15
|
"recording_length": 15
|
||||||
}
|
}
|
||||||
@ -1,6 +1,6 @@
|
|||||||
import os
|
import os
|
||||||
import json
|
import json
|
||||||
from audio_recorder import AudioRecorder
|
from audio_io import AudioIO
|
||||||
from windows_audio import WindowsAudioManager
|
from windows_audio import WindowsAudioManager
|
||||||
|
|
||||||
class SettingsManager:
|
class SettingsManager:
|
||||||
@ -13,6 +13,7 @@ class SettingsManager:
|
|||||||
return cls._instance
|
return cls._instance
|
||||||
def init(self):
|
def init(self):
|
||||||
# read settings file from executing directory
|
# read settings file from executing directory
|
||||||
|
print("Initializing SettingsManager", os.getcwd())
|
||||||
self.settings_file = os.path.join(os.getcwd(), "settings.json")
|
self.settings_file = os.path.join(os.getcwd(), "settings.json")
|
||||||
if os.path.exists(self.settings_file):
|
if os.path.exists(self.settings_file):
|
||||||
with open(self.settings_file, "r") as f:
|
with open(self.settings_file, "r") as f:
|
||||||
@ -20,21 +21,49 @@ class SettingsManager:
|
|||||||
else:
|
else:
|
||||||
self.settings = {
|
self.settings = {
|
||||||
"input_device": None,
|
"input_device": None,
|
||||||
|
"output_device": None,
|
||||||
"save_path": os.path.join(os.getcwd(), "recordings"),
|
"save_path": os.path.join(os.getcwd(), "recordings"),
|
||||||
"recording_length": 15
|
"recording_length": 15
|
||||||
}
|
}
|
||||||
audio_manager = WindowsAudioManager()
|
audio_manager = WindowsAudioManager()
|
||||||
|
|
||||||
devices = audio_manager.list_audio_devices('input')
|
input_devices = audio_manager.list_audio_devices('input')
|
||||||
print(f"Available input devices: {self.settings}")
|
output_devices = audio_manager.list_audio_devices('output')
|
||||||
input = self.settings["input_device"]
|
# print("Available input devices:")
|
||||||
|
# for i, dev in enumerate(input_devices):
|
||||||
|
# print(i, dev['name'])
|
||||||
|
# print("Available output devices:")
|
||||||
|
# for i, dev in enumerate(output_devices):
|
||||||
|
# print(i, dev['name'])
|
||||||
|
# print(f"Available input devices: {input_devices}")
|
||||||
|
# print(f"Available output devices: {output_devices}")
|
||||||
|
input = None
|
||||||
|
output = None
|
||||||
|
|
||||||
|
if("input_device" in self.settings):
|
||||||
|
input = self.settings["input_device"]
|
||||||
|
if("output_device" in self.settings):
|
||||||
|
output = self.settings["output_device"]
|
||||||
#see if input device is in "devices", if not set to the first index
|
#see if input device is in "devices", if not set to the first index
|
||||||
if input is not None and any(d['name'] == input["name"] for d in devices):
|
if input is not None and any(d['name'] == input["name"] for d in input_devices):
|
||||||
print(f"Using saved input device index: {input}")
|
# print(f"Using saved input device index: {input}")
|
||||||
|
pass
|
||||||
else:
|
else:
|
||||||
input = devices[0] if devices else None
|
input = input_devices[0] if input_devices else None
|
||||||
self.settings["input_device"] = input
|
self.settings["input_device"] = input
|
||||||
|
|
||||||
|
#see if output device is in "devices", if not set to the first index
|
||||||
|
if output is not None and any(d['name'] == output["name"] for d in output_devices):
|
||||||
|
# print(f"Using saved output device index: {output}")
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
output = output_devices[0] if output_devices else None
|
||||||
|
self.settings["output_device"] = output
|
||||||
|
|
||||||
|
if not "http_port" in self.settings:
|
||||||
|
self.settings["http_port"] = 5010
|
||||||
|
|
||||||
|
|
||||||
self.save_settings()
|
self.save_settings()
|
||||||
|
|
||||||
|
|
||||||
@ -48,6 +77,8 @@ class SettingsManager:
|
|||||||
return self.settings
|
return self.settings
|
||||||
|
|
||||||
def set_settings(self, name, value):
|
def set_settings(self, name, value):
|
||||||
|
if(name not in self.settings):
|
||||||
|
raise ValueError(f"Setting '{name}' not found.")
|
||||||
self.settings[name] = value
|
self.settings[name] = value
|
||||||
self.save_settings()
|
self.save_settings()
|
||||||
|
|
||||||
@ -57,13 +88,14 @@ class SettingsManager:
|
|||||||
json.dump(self.settings, f, indent=4)
|
json.dump(self.settings, f, indent=4)
|
||||||
|
|
||||||
def refresh_settings(self):
|
def refresh_settings(self):
|
||||||
recorder = AudioRecorder()
|
recorder = AudioIO()
|
||||||
# Update recorder parameters based on new setting
|
# Update recorder parameters based on new setting
|
||||||
recorder.set_buffer_duration(self.get_settings('recording_length'))
|
recorder.set_buffer_duration(self.get_settings('recording_length'))
|
||||||
recorder.recordings_dir = self.get_settings('save_path')
|
recorder.recordings_dir = self.get_settings('save_path')
|
||||||
|
|
||||||
audio_manager = WindowsAudioManager()
|
audio_manager = WindowsAudioManager()
|
||||||
audio_manager.set_default_input_device(self.get_settings('input_device')['index'])
|
audio_manager.set_default_input_device(self.get_settings('input_device')['index'])
|
||||||
|
audio_manager.set_default_output_device(self.get_settings('output_device')['index'])
|
||||||
|
|
||||||
recorder.refresh_stream()
|
recorder.refresh_streams()
|
||||||
|
|
||||||
|
|||||||
@ -1,123 +1,108 @@
|
|||||||
import sounddevice as sd
|
import sounddevice as sd
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import comtypes
|
import json
|
||||||
import comtypes.client
|
|
||||||
from comtypes import CLSCTX_ALL
|
class WindowsAudioManager:
|
||||||
from pycaw.pycaw import AudioUtilities, IAudioEndpointVolume
|
_instance = None
|
||||||
import json
|
|
||||||
|
def __new__(cls, *args, **kwargs):
|
||||||
class WindowsAudioManager:
|
if cls._instance is None:
|
||||||
_instance = None
|
cls._instance = super().__new__(cls)
|
||||||
|
cls._instance.init()
|
||||||
def __new__(cls, *args, **kwargs):
|
return cls._instance
|
||||||
if cls._instance is None:
|
def init(self):
|
||||||
cls._instance = super().__new__(cls)
|
"""
|
||||||
cls._instance.init()
|
Initialize Windows audio device and volume management.
|
||||||
return cls._instance
|
"""
|
||||||
def init(self):
|
host_apis = sd.query_hostapis()
|
||||||
"""
|
wasapi_device_indexes = None
|
||||||
Initialize Windows audio device and volume management.
|
for api in host_apis:
|
||||||
"""
|
if api['name'].lower() == 'Windows WASAPI'.lower():
|
||||||
self.devices = sd.query_devices()
|
wasapi_device_indexes = api['devices']
|
||||||
self.default_input = sd.default.device[0]
|
break
|
||||||
self.default_output = sd.default.device[1]
|
# print(f"Host APIs: {host_apis}")
|
||||||
|
# print(f"WASAPI Device Indexes: {wasapi_device_indexes}")
|
||||||
def list_audio_devices(self, kind='input'):
|
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]
|
||||||
List available audio devices.
|
# self.devices = sd.query_devices()
|
||||||
|
# print(f"devices: {self.devices}")
|
||||||
:param kind: 'input' or 'output'
|
|
||||||
:return: List of audio devices
|
self.default_input = sd.default.device[0]
|
||||||
"""
|
self.default_output = sd.default.device[1]
|
||||||
if kind == 'input':
|
|
||||||
return [
|
def list_audio_devices(self, kind='input'):
|
||||||
{
|
"""
|
||||||
'index': dev['index'],
|
List available audio devices.
|
||||||
'name': dev['name'],
|
|
||||||
'max_input_channels': dev['max_input_channels'],
|
:param kind: 'input' or 'output'
|
||||||
'default_samplerate': dev['default_samplerate']
|
:return: List of audio devices
|
||||||
}
|
"""
|
||||||
for dev in self.devices if dev['max_input_channels'] > 0
|
if kind == 'input':
|
||||||
]
|
return [
|
||||||
elif kind == 'output':
|
{
|
||||||
return [
|
'index': dev['index'],
|
||||||
{
|
'name': dev['name'],
|
||||||
'index': dev['index'],
|
'channels': dev['max_input_channels'],
|
||||||
'name': dev['name'],
|
'default_samplerate': dev['default_samplerate']
|
||||||
'max_output_channels': dev['max_output_channels'],
|
}
|
||||||
'default_samplerate': dev['default_samplerate']
|
for dev in self.devices if dev['max_input_channels'] > 0
|
||||||
}
|
]
|
||||||
for dev in self.devices if dev['max_output_channels'] > 0
|
elif kind == 'output':
|
||||||
]
|
return [
|
||||||
def get_default_device(self, kind='input'):
|
{
|
||||||
"""
|
'index': dev['index'],
|
||||||
Get the default audio device.
|
'name': dev['name'],
|
||||||
|
'channels': dev['max_output_channels'],
|
||||||
:param kind: 'input' or 'output'
|
'default_samplerate': dev['default_samplerate']
|
||||||
:return: Default audio device information
|
}
|
||||||
"""
|
for dev in self.devices if dev['max_output_channels'] > 0
|
||||||
if kind == 'input':
|
]
|
||||||
dev = self.devices[self.default_input]
|
def get_default_device(self, kind='input'):
|
||||||
return [
|
"""
|
||||||
{
|
Get the default audio device.
|
||||||
'index': dev['index'],
|
|
||||||
'name': dev['name'],
|
:param kind: 'input' or 'output'
|
||||||
'max_input_channels': dev['max_input_channels'],
|
:return: Default audio device information
|
||||||
'default_samplerate': dev['default_samplerate']
|
"""
|
||||||
}
|
if kind == 'input':
|
||||||
]
|
dev = self.devices[self.default_input]
|
||||||
|
return [
|
||||||
def set_default_input_device(self, device_index):
|
{
|
||||||
if(device_index is None):
|
'index': dev['index'],
|
||||||
return self.get_current_input_device_sample_rate()
|
'name': dev['name'],
|
||||||
"""
|
'max_input_channels': dev['max_input_channels'],
|
||||||
Set the default input audio device.
|
'default_samplerate': dev['default_samplerate']
|
||||||
|
}
|
||||||
:param device_index: Index of the audio device
|
]
|
||||||
:return: Sample rate of the selected device
|
|
||||||
"""
|
def set_default_input_device(self, device_index):
|
||||||
sd.default.device[0] = device_index
|
if(device_index is None):
|
||||||
self.default_input = device_index
|
return 0
|
||||||
|
"""
|
||||||
# Get the sample rate of the selected device
|
Set the default input audio device.
|
||||||
device_info = sd.query_devices(device_index)
|
|
||||||
return device_info['default_samplerate']
|
:param device_index: Index of the audio device
|
||||||
|
:return: Sample rate of the selected device
|
||||||
def get_current_input_device_sample_rate(self):
|
"""
|
||||||
"""
|
sd.default.device[0] = device_index
|
||||||
Get the sample rate of the current input device.
|
self.default_input = device_index
|
||||||
|
|
||||||
:return: Sample rate of the current input device
|
# Get the sample rate of the selected device
|
||||||
"""
|
device_info = sd.query_devices(device_index)
|
||||||
device_info = sd.query_devices(self.default_input)
|
return device_info['default_samplerate']
|
||||||
return device_info['default_samplerate']
|
|
||||||
|
def set_default_output_device(self, device_index):
|
||||||
def get_system_volume(self):
|
if(device_index is None):
|
||||||
"""
|
return self.get_current_output_device_sample_rate()
|
||||||
Get the system master volume.
|
"""
|
||||||
|
Set the default output audio device.
|
||||||
:return: Current system volume (0.0 to 1.0)
|
|
||||||
"""
|
:param device_index: Index of the audio device
|
||||||
devices = AudioUtilities.GetSpeakers()
|
:return: Sample rate of the selected device
|
||||||
interface = devices.Activate(
|
"""
|
||||||
IAudioEndpointVolume._iid_,
|
sd.default.device[1] = device_index
|
||||||
CLSCTX_ALL,
|
self.default_output = device_index
|
||||||
None
|
|
||||||
)
|
# Get the sample rate of the selected device
|
||||||
volume = interface.QueryInterface(IAudioEndpointVolume)
|
device_info = sd.query_devices(device_index)
|
||||||
return volume.GetMasterVolumeLevelScalar()
|
return device_info['default_samplerate']
|
||||||
|
|
||||||
def set_system_volume(self, volume_level):
|
|
||||||
"""
|
|
||||||
Set the system master volume.
|
|
||||||
|
|
||||||
:param volume_level: Volume level (0.0 to 1.0)
|
|
||||||
"""
|
|
||||||
devices = AudioUtilities.GetSpeakers()
|
|
||||||
interface = devices.Activate(
|
|
||||||
IAudioEndpointVolume._iid_,
|
|
||||||
CLSCTX_ALL,
|
|
||||||
None
|
|
||||||
)
|
|
||||||
volume = interface.QueryInterface(IAudioEndpointVolume)
|
|
||||||
volume.SetMasterVolumeLevelScalar(volume_level, None)
|
|
||||||
16
electron-ui/settings.json
Normal file
16
electron-ui/settings.json
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"input_device": {
|
||||||
|
"index": 49,
|
||||||
|
"name": "Microphone (Logi C615 HD WebCam)",
|
||||||
|
"channels": 1,
|
||||||
|
"default_samplerate": 48000.0
|
||||||
|
},
|
||||||
|
"output_device": {
|
||||||
|
"index": 40,
|
||||||
|
"name": "Speakers (Realtek(R) Audio)",
|
||||||
|
"channels": 2,
|
||||||
|
"default_samplerate": 48000.0
|
||||||
|
},
|
||||||
|
"save_path": "C:\\Users\\mickl\\Desktop\\cliptrim-ui\\ClipTrimApp\\electron-ui\\recordings",
|
||||||
|
"recording_length": 15
|
||||||
|
}
|
||||||
@ -1,5 +1,7 @@
|
|||||||
const AudioChannels = {
|
const AudioChannels = {
|
||||||
LOAD_AUDIO_BUFFER: 'audio:loadAudioBuffer',
|
LOAD_AUDIO_BUFFER: 'audio:loadAudioBuffer',
|
||||||
|
GET_PORT: 'audio:getPort',
|
||||||
|
RESTART_SERVICE: 'audio:restartService',
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export default AudioChannels;
|
export default AudioChannels;
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import { ipcMain } from 'electron';
|
|||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import AudioChannels from './channels';
|
import AudioChannels from './channels';
|
||||||
import { LoadAudioBufferArgs, LoadAudioBufferResult } from './types';
|
import { LoadAudioBufferArgs, LoadAudioBufferResult } from './types';
|
||||||
|
import PythonSubprocessManager from '../../main/service';
|
||||||
|
|
||||||
export default function registerAudioIpcHandlers() {
|
export default function registerAudioIpcHandlers() {
|
||||||
ipcMain.handle(
|
ipcMain.handle(
|
||||||
@ -15,4 +16,25 @@ export default function registerAudioIpcHandlers() {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
ipcMain.handle(AudioChannels.GET_PORT, async () => {
|
||||||
|
try {
|
||||||
|
if (PythonSubprocessManager.instance?.portNumber) {
|
||||||
|
return { port: PythonSubprocessManager.instance.portNumber };
|
||||||
|
}
|
||||||
|
|
||||||
|
return { error: 'Port number not available yet.' };
|
||||||
|
} catch (err: any) {
|
||||||
|
return { error: err.message };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.handle(AudioChannels.RESTART_SERVICE, async () => {
|
||||||
|
try {
|
||||||
|
PythonSubprocessManager.instance?.restart();
|
||||||
|
return { success: true };
|
||||||
|
} catch (err: any) {
|
||||||
|
return { success: false, error: err.message };
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,3 +6,22 @@ export interface LoadAudioBufferResult {
|
|||||||
buffer?: Buffer;
|
buffer?: Buffer;
|
||||||
error?: string;
|
error?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface GetPortResult {
|
||||||
|
port?: number;
|
||||||
|
error?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SetPortArgs {
|
||||||
|
port: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SetPortResult {
|
||||||
|
success: boolean;
|
||||||
|
error?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RestartServiceResult {
|
||||||
|
success: boolean;
|
||||||
|
error?: string;
|
||||||
|
}
|
||||||
|
|||||||
@ -16,6 +16,7 @@ import log from 'electron-log';
|
|||||||
import MenuBuilder from './menu';
|
import MenuBuilder from './menu';
|
||||||
import { resolveHtmlPath } from './util';
|
import { resolveHtmlPath } from './util';
|
||||||
import registerFileIpcHandlers from '../ipc/audio/main';
|
import registerFileIpcHandlers from '../ipc/audio/main';
|
||||||
|
import PythonSubprocessManager from './service';
|
||||||
|
|
||||||
class AppUpdater {
|
class AppUpdater {
|
||||||
constructor() {
|
constructor() {
|
||||||
@ -110,6 +111,7 @@ const createWindow = async () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
registerFileIpcHandlers();
|
registerFileIpcHandlers();
|
||||||
|
|
||||||
// Remove this if your app does not use auto updates
|
// Remove this if your app does not use auto updates
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
new AppUpdater();
|
new AppUpdater();
|
||||||
@ -130,6 +132,8 @@ app.on('window-all-closed', () => {
|
|||||||
app
|
app
|
||||||
.whenReady()
|
.whenReady()
|
||||||
.then(() => {
|
.then(() => {
|
||||||
|
const pythonManager = new PythonSubprocessManager('src/main.py');
|
||||||
|
pythonManager.start();
|
||||||
createWindow();
|
createWindow();
|
||||||
app.on('activate', () => {
|
app.on('activate', () => {
|
||||||
// On macOS it's common to re-create a window in the app when the
|
// On macOS it's common to re-create a window in the app when the
|
||||||
|
|||||||
@ -1,8 +1,7 @@
|
|||||||
// Disable no-unused-vars, broken for spread args
|
// Disable no-unused-vars, broken for spread args
|
||||||
/* eslint no-unused-vars: off */
|
/* eslint no-unused-vars: off */
|
||||||
import { contextBridge, ipcRenderer, IpcRendererEvent } from 'electron';
|
import { contextBridge, ipcRenderer, IpcRendererEvent } from 'electron';
|
||||||
import FileChannels from '../ipc/audio/channels';
|
import { LoadAudioBufferArgs } from '../ipc/audio/types';
|
||||||
import { LoadAudioBufferArgs, ReadTextArgs } from '../ipc/audio/types';
|
|
||||||
import AudioChannels from '../ipc/audio/channels';
|
import AudioChannels from '../ipc/audio/channels';
|
||||||
// import '../ipc/file/preload'; // Import file API preload to ensure it runs and exposes the API
|
// import '../ipc/file/preload'; // Import file API preload to ensure it runs and exposes the API
|
||||||
|
|
||||||
@ -41,10 +40,8 @@ const audioHandler = {
|
|||||||
filePath,
|
filePath,
|
||||||
} satisfies LoadAudioBufferArgs),
|
} satisfies LoadAudioBufferArgs),
|
||||||
|
|
||||||
readText: (filePath: string) =>
|
getPort: () => ipcRenderer.invoke(AudioChannels.GET_PORT),
|
||||||
ipcRenderer.invoke(AudioChannels.READ_TEXT, {
|
restartService: () => ipcRenderer.invoke(AudioChannels.RESTART_SERVICE),
|
||||||
filePath,
|
|
||||||
} satisfies ReadTextArgs),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
contextBridge.exposeInMainWorld('audio', audioHandler);
|
contextBridge.exposeInMainWorld('audio', audioHandler);
|
||||||
|
|||||||
79
electron-ui/src/main/service.ts
Normal file
79
electron-ui/src/main/service.ts
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
import { spawn, ChildProcessWithoutNullStreams } from 'child_process';
|
||||||
|
import path from 'path';
|
||||||
|
|
||||||
|
export default class PythonSubprocessManager {
|
||||||
|
// eslint-disable-next-line no-use-before-define
|
||||||
|
public static instance: PythonSubprocessManager | null = null;
|
||||||
|
|
||||||
|
private process: ChildProcessWithoutNullStreams | null = null;
|
||||||
|
|
||||||
|
private scriptPath: string;
|
||||||
|
|
||||||
|
private working_dir: string = path.join(
|
||||||
|
__dirname,
|
||||||
|
'..',
|
||||||
|
'..',
|
||||||
|
'..',
|
||||||
|
'audio-service',
|
||||||
|
);
|
||||||
|
|
||||||
|
public portNumber: number | null = null;
|
||||||
|
|
||||||
|
constructor(scriptPath: string) {
|
||||||
|
this.scriptPath = scriptPath;
|
||||||
|
PythonSubprocessManager.instance = this;
|
||||||
|
}
|
||||||
|
|
||||||
|
start(args: string[] = []): void {
|
||||||
|
if (this.process) {
|
||||||
|
throw new Error('Process already running.');
|
||||||
|
}
|
||||||
|
console.log(`Using Python working directory at: ${this.working_dir}`);
|
||||||
|
console.log(`Starting Python subprocess with script: ${this.scriptPath}`);
|
||||||
|
this.process = spawn(
|
||||||
|
'venv/Scripts/python.exe',
|
||||||
|
[this.scriptPath, ...args],
|
||||||
|
{
|
||||||
|
cwd: this.working_dir,
|
||||||
|
detached: false,
|
||||||
|
stdio: 'pipe',
|
||||||
|
},
|
||||||
|
);
|
||||||
|
this.process.stdout.on('data', (data: Buffer) => {
|
||||||
|
// console.log(`Python stdout: ${data.toString()}`);
|
||||||
|
});
|
||||||
|
this.process.stderr.on('data', (data: Buffer) => {
|
||||||
|
// console.error(`Python stderr: ${data.toString()}`);
|
||||||
|
const lines = data.toString().split('\n');
|
||||||
|
// eslint-disable-next-line no-restricted-syntax
|
||||||
|
for (const line of lines) {
|
||||||
|
const match = line.match(/Running on .*:(\d+)/);
|
||||||
|
if (match) {
|
||||||
|
const port = parseInt(match[1], 10);
|
||||||
|
console.log(`Detected port: ${port}`);
|
||||||
|
this.portNumber = port;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.process.on('exit', () => {
|
||||||
|
console.log('Python subprocess exited.');
|
||||||
|
this.process = null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
stop(): void {
|
||||||
|
if (this.process) {
|
||||||
|
this.process.kill();
|
||||||
|
this.process = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
restart(args: string[] = []): void {
|
||||||
|
this.stop();
|
||||||
|
this.start(args);
|
||||||
|
}
|
||||||
|
|
||||||
|
isHealthy(): boolean {
|
||||||
|
return !!this.process && !this.process.killed;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -2,6 +2,8 @@ import { MemoryRouter as Router, Routes, Route } from 'react-router-dom';
|
|||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { Provider } from 'react-redux';
|
import { Provider } from 'react-redux';
|
||||||
import Dialog from '@mui/material/Dialog';
|
import Dialog from '@mui/material/Dialog';
|
||||||
|
|
||||||
|
import { ThemeProvider, createTheme } from '@mui/material/styles';
|
||||||
import DialogTitle from '@mui/material/DialogTitle';
|
import DialogTitle from '@mui/material/DialogTitle';
|
||||||
import DialogContent from '@mui/material/DialogContent';
|
import DialogContent from '@mui/material/DialogContent';
|
||||||
import DialogActions from '@mui/material/DialogActions';
|
import DialogActions from '@mui/material/DialogActions';
|
||||||
@ -10,6 +12,9 @@ import './App.css';
|
|||||||
import ClipList from './components/ClipList';
|
import ClipList from './components/ClipList';
|
||||||
import { useAppDispatch, useAppSelector } from './hooks';
|
import { useAppDispatch, useAppSelector } from './hooks';
|
||||||
import { store } from '../redux/main';
|
import { store } from '../redux/main';
|
||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
import SettingsPage from './Settings';
|
||||||
|
import apiFetch from './api';
|
||||||
|
|
||||||
function MainPage() {
|
function MainPage() {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
@ -21,11 +26,12 @@ function MainPage() {
|
|||||||
);
|
);
|
||||||
const [newCollectionOpen, setNewCollectionOpen] = useState(false);
|
const [newCollectionOpen, setNewCollectionOpen] = useState(false);
|
||||||
const [newCollectionName, setNewCollectionName] = useState<string>('');
|
const [newCollectionName, setNewCollectionName] = useState<string>('');
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchMetadata = async () => {
|
const fetchMetadata = async () => {
|
||||||
try {
|
try {
|
||||||
const response = await fetch('http://localhost:5010/meta');
|
const response = await apiFetch('meta');
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
dispatch({ type: 'metadata/setAllData', payload: data });
|
dispatch({ type: 'metadata/setAllData', payload: data });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -137,6 +143,22 @@ function MainPage() {
|
|||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
|
{/* Settings Button at Bottom Left */}
|
||||||
|
<div className="mt-auto mb-2">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="w-full rounded px-4 py-2 bg-neutral-800 text-offwhite hover:bg-plumDark text-left"
|
||||||
|
style={{
|
||||||
|
position: 'absolute',
|
||||||
|
bottom: 16,
|
||||||
|
left: 8,
|
||||||
|
width: 'calc(100% - 16px)',
|
||||||
|
}}
|
||||||
|
onClick={() => navigate('/settings')}
|
||||||
|
>
|
||||||
|
⚙️ Settings
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
{/* Main Content */}
|
{/* Main Content */}
|
||||||
<div
|
<div
|
||||||
@ -150,13 +172,39 @@ function MainPage() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function App() {
|
export default function App() {
|
||||||
|
const theme = createTheme({
|
||||||
|
colorSchemes: {
|
||||||
|
light: false,
|
||||||
|
dark: {
|
||||||
|
palette: {
|
||||||
|
primary: {
|
||||||
|
main: '#6e44ba', // plum
|
||||||
|
dark: '#6e44ba', // plum
|
||||||
|
contrastText: '#ffffff',
|
||||||
|
},
|
||||||
|
secondary: {
|
||||||
|
main: '#4f3186', // plumDark
|
||||||
|
dark: '#4f3186', // plumDark
|
||||||
|
contrastText: '#ffffff',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// colorSchemes: {
|
||||||
|
// light: false,
|
||||||
|
// dark: true,
|
||||||
|
// },
|
||||||
|
});
|
||||||
return (
|
return (
|
||||||
<Provider store={store}>
|
<Provider store={store}>
|
||||||
<Router>
|
<ThemeProvider theme={theme}>
|
||||||
<Routes>
|
<Router>
|
||||||
<Route path="/" element={<MainPage />} />
|
<Routes>
|
||||||
</Routes>
|
<Route path="/" element={<MainPage />} />
|
||||||
</Router>
|
<Route path="/settings" element={<SettingsPage />} />
|
||||||
|
</Routes>
|
||||||
|
</Router>
|
||||||
|
</ThemeProvider>
|
||||||
</Provider>
|
</Provider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
274
electron-ui/src/renderer/Settings.tsx
Normal file
274
electron-ui/src/renderer/Settings.tsx
Normal file
@ -0,0 +1,274 @@
|
|||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
|
||||||
|
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';
|
||||||
|
|
||||||
|
type AudioDevice = {
|
||||||
|
index: number;
|
||||||
|
name: string;
|
||||||
|
default_sample_rate: number;
|
||||||
|
channels: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
type Settings = {
|
||||||
|
http_port: number;
|
||||||
|
input_device: AudioDevice;
|
||||||
|
output_device: AudioDevice;
|
||||||
|
recording_length: number;
|
||||||
|
save_path: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const defaultSettings: Settings = {
|
||||||
|
http_port: 0,
|
||||||
|
input_device: { index: 0, name: '', default_sample_rate: 0, channels: 0 },
|
||||||
|
output_device: { index: 0, name: '', default_sample_rate: 0, channels: 0 },
|
||||||
|
recording_length: 0,
|
||||||
|
save_path: '',
|
||||||
|
};
|
||||||
|
|
||||||
|
async function fetchAudioDevices(
|
||||||
|
type: 'input' | 'output',
|
||||||
|
): Promise<AudioDevice[]> {
|
||||||
|
// Replace with actual backend call
|
||||||
|
// Example: return window.api.getAudioDevices();
|
||||||
|
return apiFetch(`device/list?device_type=${type}`)
|
||||||
|
.then((res) => res.json())
|
||||||
|
.then((data) => data.devices as AudioDevice[])
|
||||||
|
.catch((error) => {
|
||||||
|
console.error('Error fetching audio devices:', error);
|
||||||
|
return [];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchSettings(): Promise<Settings> {
|
||||||
|
// Replace with actual backend call
|
||||||
|
// Example: return window.api.getAudioDevices();
|
||||||
|
console.log('Fetching settings from backend...');
|
||||||
|
return apiFetch('settings')
|
||||||
|
.then((res) => res.json())
|
||||||
|
.then((data) => data.settings as Settings)
|
||||||
|
.catch((error) => {
|
||||||
|
console.error('Error fetching settings:', error);
|
||||||
|
return defaultSettings;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const sendSettingsToBackend = async (settings: Settings) => {
|
||||||
|
// Replace with actual backend call
|
||||||
|
// Example: window.api.updateSettings(settings);
|
||||||
|
console.log('Settings updated:', settings);
|
||||||
|
await apiFetch('settings/update', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ settings }),
|
||||||
|
})
|
||||||
|
.then((res) => res.json())
|
||||||
|
.then((data) => {
|
||||||
|
console.log('Settings update response:', data);
|
||||||
|
if (data.status === 'success') {
|
||||||
|
window.audio.restartService();
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error('Error updating settings:', error);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function SettingsPage() {
|
||||||
|
const [settings, setSettings] = useState<Settings>(defaultSettings);
|
||||||
|
const [inputDevices, setInputDevices] = useState<AudioDevice[]>([]);
|
||||||
|
const [outputDevices, setOutputDevices] = useState<AudioDevice[]>([]);
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetchSettings()
|
||||||
|
.then((fetchedSettings) => {
|
||||||
|
console.log('Fetched settings:', fetchedSettings);
|
||||||
|
setSettings(fetchedSettings);
|
||||||
|
return null;
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
return fetchAudioDevices('input');
|
||||||
|
})
|
||||||
|
.then((devices) => {
|
||||||
|
setInputDevices(devices);
|
||||||
|
// console.log('Input devices:', devices);
|
||||||
|
return fetchAudioDevices('output');
|
||||||
|
})
|
||||||
|
.then((devices) => {
|
||||||
|
setOutputDevices(devices);
|
||||||
|
|
||||||
|
// console.log('Output devices:', devices);
|
||||||
|
return devices;
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error('Error fetching audio devices:', error);
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {}, [settings]);
|
||||||
|
|
||||||
|
const handleChange = () => {
|
||||||
|
sendSettingsToBackend(settings);
|
||||||
|
// const { name, value } = e.target;
|
||||||
|
// setSettings((prev) => ({
|
||||||
|
// ...prev,
|
||||||
|
// [name]: value,
|
||||||
|
// }));
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleFolderChange = async () => {
|
||||||
|
// Replace with actual folder picker
|
||||||
|
// Example: const folder = await window.api.selectFolder();
|
||||||
|
// const folder = window.prompt(
|
||||||
|
// 'Enter output folder path:',
|
||||||
|
// settings.outputFolder,
|
||||||
|
// );
|
||||||
|
// if (folder !== null) {
|
||||||
|
// setSettings((prev) => ({
|
||||||
|
// ...prev,
|
||||||
|
// outputFolder: folder,
|
||||||
|
// }));
|
||||||
|
// }
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="min-w-screen min-h-screen bg-midnight text-offwhite flex items-center justify-center relative">
|
||||||
|
<div className="w-3/4 min-w-[600px] max-w-[800px] self-start flex flex-col font-sans bg-midnight text-offwhite p-6 rounded-lg relative">
|
||||||
|
{/* X Close Button */}
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="absolute top-6 right-6 text-3xl font-bold text-offwhite bg-transparent hover:text-plumDark"
|
||||||
|
aria-label="Close settings"
|
||||||
|
onClick={() => navigate('/')}
|
||||||
|
>
|
||||||
|
×
|
||||||
|
</button>
|
||||||
|
<span className="text-2xl font-bold mb-4">Settings</span>
|
||||||
|
<div className="mb-4 flex justify-between">
|
||||||
|
<span>HTTP Port:</span>
|
||||||
|
<TextField
|
||||||
|
variant="standard"
|
||||||
|
type="text"
|
||||||
|
name="httpPort"
|
||||||
|
value={settings.http_port}
|
||||||
|
onBlur={() => handleChange()}
|
||||||
|
onChange={(e) => {
|
||||||
|
if (!Number.isNaN(Number(e.target.value))) {
|
||||||
|
setSettings((prev) => ({
|
||||||
|
...prev,
|
||||||
|
http_port: Number(e.target.value),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
className="ml-2 text-white w-[150px]"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="mb-4 flex justify-between">
|
||||||
|
<span>Input Audio Device:</span>
|
||||||
|
<Select
|
||||||
|
variant="standard"
|
||||||
|
name="inputDevice"
|
||||||
|
value={settings.input_device.index}
|
||||||
|
onChange={(e) => {
|
||||||
|
const newDevice = inputDevices.find(
|
||||||
|
(dev) => dev.index === Number(e.target.value),
|
||||||
|
);
|
||||||
|
console.log('Selected input device index:', newDevice);
|
||||||
|
if (newDevice) {
|
||||||
|
setSettings((prev) => ({
|
||||||
|
...prev,
|
||||||
|
input_device: newDevice,
|
||||||
|
}));
|
||||||
|
sendSettingsToBackend({
|
||||||
|
...settings,
|
||||||
|
input_device: newDevice,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
className="ml-2 w-64"
|
||||||
|
>
|
||||||
|
{inputDevices.map((dev) => (
|
||||||
|
<MenuItem key={dev.index} value={dev.index}>
|
||||||
|
{dev.name}
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
<div className="mb-4 flex justify-between">
|
||||||
|
<span>Output Audio Device:</span>
|
||||||
|
<Select
|
||||||
|
variant="standard"
|
||||||
|
name="outputDevice"
|
||||||
|
value={settings.output_device.index}
|
||||||
|
onChange={(e) => {
|
||||||
|
const newDevice = outputDevices.find(
|
||||||
|
(dev) => dev.index === Number(e.target.value),
|
||||||
|
);
|
||||||
|
if (newDevice) {
|
||||||
|
setSettings((prev) => ({
|
||||||
|
...prev,
|
||||||
|
output_device: newDevice,
|
||||||
|
}));
|
||||||
|
sendSettingsToBackend({
|
||||||
|
...settings,
|
||||||
|
output_device: newDevice,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
className="ml-2 w-64"
|
||||||
|
>
|
||||||
|
{outputDevices.map((dev) => (
|
||||||
|
<MenuItem key={dev.index} value={dev.index}>
|
||||||
|
{dev.name}
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
<div className="mb-4 flex justify-between">
|
||||||
|
<span>Recording Length (seconds):</span>
|
||||||
|
<TextField
|
||||||
|
variant="standard"
|
||||||
|
type="text"
|
||||||
|
name="recordingLength"
|
||||||
|
value={settings.recording_length}
|
||||||
|
onChange={(e) => {
|
||||||
|
if (!Number.isNaN(Number(e.target.value))) {
|
||||||
|
setSettings((prev) => ({
|
||||||
|
...prev,
|
||||||
|
recording_length: Number(e.target.value),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onBlur={() => handleChange()}
|
||||||
|
className="ml-2 w-[150px]"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="mb-4 flex justify-between">
|
||||||
|
<span>Clip Output Folder:</span>
|
||||||
|
<div className="flex justify-end">
|
||||||
|
<TextField
|
||||||
|
variant="standard"
|
||||||
|
type="text"
|
||||||
|
name="savePath"
|
||||||
|
value={settings.save_path}
|
||||||
|
className="ml-2 w-[300px]"
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={handleFolderChange}
|
||||||
|
className="ml-2 px-3 py-1 rounded bg-plumDark text-offwhite hover:bg-plum"
|
||||||
|
>
|
||||||
|
...
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
13
electron-ui/src/renderer/api.ts
Normal file
13
electron-ui/src/renderer/api.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
const getBaseUrl = async () => {
|
||||||
|
const port = await window.audio.getPort();
|
||||||
|
if (port.error || !port.port) {
|
||||||
|
return `http://localhost:5010`;
|
||||||
|
}
|
||||||
|
// You can store the base URL in localStorage, a config file, or state
|
||||||
|
return `http://localhost:${port.port}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default async function apiFetch(endpoint: string, options = {}) {
|
||||||
|
const url = `${await getBaseUrl()}/${endpoint}`;
|
||||||
|
return fetch(url, options);
|
||||||
|
}
|
||||||
@ -9,6 +9,9 @@ import Dialog from '@mui/material/Dialog';
|
|||||||
import DialogTitle from '@mui/material/DialogTitle';
|
import DialogTitle from '@mui/material/DialogTitle';
|
||||||
import DialogContent from '@mui/material/DialogContent';
|
import DialogContent from '@mui/material/DialogContent';
|
||||||
import DialogActions from '@mui/material/DialogActions';
|
import DialogActions from '@mui/material/DialogActions';
|
||||||
|
import Slider from '@mui/material/Slider';
|
||||||
|
import ToggleButton from '@mui/material/ToggleButton';
|
||||||
|
import ToggleButtonGroup from '@mui/material/ToggleButtonGroup';
|
||||||
import { useWavesurfer } from '@wavesurfer/react';
|
import { useWavesurfer } from '@wavesurfer/react';
|
||||||
import RegionsPlugin from 'wavesurfer.js/dist/plugins/regions.esm.js';
|
import RegionsPlugin from 'wavesurfer.js/dist/plugins/regions.esm.js';
|
||||||
import ZoomPlugin from 'wavesurfer.js/dist/plugins/zoom.esm.js';
|
import ZoomPlugin from 'wavesurfer.js/dist/plugins/zoom.esm.js';
|
||||||
@ -19,8 +22,10 @@ import ArrowDownwardIcon from '@mui/icons-material/ArrowDownward';
|
|||||||
import DeleteIcon from '@mui/icons-material/Delete';
|
import DeleteIcon from '@mui/icons-material/Delete';
|
||||||
import { useSortable } from '@dnd-kit/sortable';
|
import { useSortable } from '@dnd-kit/sortable';
|
||||||
import { CSS } from '@dnd-kit/utilities';
|
import { CSS } from '@dnd-kit/utilities';
|
||||||
import { ClipMetadata } from '../../redux/types';
|
import { ClipMetadata, PlaybackType } from '../../redux/types';
|
||||||
import { useAppSelector } from '../hooks';
|
import { useAppSelector } from '../hooks';
|
||||||
|
import PlayStopIcon from './playStopIcon';
|
||||||
|
import PlayOverlapIcon from './playOverlapIcon';
|
||||||
|
|
||||||
export interface AudioTrimmerProps {
|
export interface AudioTrimmerProps {
|
||||||
metadata: ClipMetadata;
|
metadata: ClipMetadata;
|
||||||
@ -43,6 +48,7 @@ export default function AudioTrimmer({
|
|||||||
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
|
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
|
||||||
const [dropdownOpen, setDropdownOpen] = useState(false);
|
const [dropdownOpen, setDropdownOpen] = useState(false);
|
||||||
const [nameInput, setNameInput] = useState<string>(metadata.name);
|
const [nameInput, setNameInput] = useState<string>(metadata.name);
|
||||||
|
const [volumeInput, setVolumeInput] = useState<number>(metadata.volume ?? 1);
|
||||||
const collectionNames = useAppSelector((state) =>
|
const collectionNames = useAppSelector((state) =>
|
||||||
state.collections.map((col) => col.name),
|
state.collections.map((col) => col.name),
|
||||||
);
|
);
|
||||||
@ -223,6 +229,7 @@ export default function AudioTrimmer({
|
|||||||
} else {
|
} else {
|
||||||
const allRegions = (plugins[0] as RegionsPlugin).getRegions();
|
const allRegions = (plugins[0] as RegionsPlugin).getRegions();
|
||||||
if (allRegions.length > 0) {
|
if (allRegions.length > 0) {
|
||||||
|
wavesurfer.setVolume(metadata.volume ?? 1);
|
||||||
wavesurfer.play(allRegions[0].start, allRegions[0].end);
|
wavesurfer.play(allRegions[0].start, allRegions[0].end);
|
||||||
} else {
|
} else {
|
||||||
wavesurfer.play();
|
wavesurfer.play();
|
||||||
@ -401,12 +408,74 @@ export default function AudioTrimmer({
|
|||||||
<div className="m-1 wavesurfer-scroll-container">
|
<div className="m-1 wavesurfer-scroll-container">
|
||||||
<div ref={containerRef} className="wavesurfer-inner" />
|
<div ref={containerRef} className="wavesurfer-inner" />
|
||||||
</div>
|
</div>
|
||||||
<div className="grid justify-items-stretch grid-cols-2 text-neutral-500">
|
<div className="flex justify-between mt-2">
|
||||||
<div className="m-1 flex justify-start">
|
<span className="w-1/5 flex-none text-sm text-neutral-500 self-center">
|
||||||
<text className="text-sm ">
|
Clip: {formatTime(metadata.startTime ?? 0)} -{' '}
|
||||||
Clip: {formatTime(metadata.startTime ?? 0)} -{' '}
|
{formatTime(metadata.endTime ?? 0)}
|
||||||
{formatTime(metadata.endTime ?? 0)}
|
</span>
|
||||||
</text>
|
<div className="w-3/5 flex-1 flex justify-center items-center">
|
||||||
|
<Slider
|
||||||
|
value={volumeInput}
|
||||||
|
min={0}
|
||||||
|
max={1}
|
||||||
|
step={0.01}
|
||||||
|
onChange={(e, newValue) => setVolumeInput(newValue as number)}
|
||||||
|
onChangeCommitted={(e, newValue) => {
|
||||||
|
const newVolume = newValue as number;
|
||||||
|
console.log('Volume change:', newVolume);
|
||||||
|
if (onSave) onSave({ ...metadata, volume: newVolume });
|
||||||
|
}}
|
||||||
|
color="secondary"
|
||||||
|
className="p-0 m-0"
|
||||||
|
/>
|
||||||
|
{/* <input
|
||||||
|
type="range"
|
||||||
|
min={0}
|
||||||
|
max={1}
|
||||||
|
step={0.01}
|
||||||
|
value={volumeInput}
|
||||||
|
onChange={(e) => {
|
||||||
|
const newVolume = parseFloat(e.target.value);
|
||||||
|
setVolumeInput(newVolume);
|
||||||
|
}}
|
||||||
|
onDragEnd={(e) => {
|
||||||
|
console.log('Volume change:');
|
||||||
|
// const newVolume = parseFloat(e.target.value);
|
||||||
|
// if (onSave) onSave({ ...metadata, volume: newVolume });
|
||||||
|
}}
|
||||||
|
className="mx-2 w-full accent-plum"
|
||||||
|
aria-label="Volume slider"
|
||||||
|
/> */}
|
||||||
|
</div>
|
||||||
|
<div className="w-1/5 flex justify-end text-sm text-neutral-500">
|
||||||
|
<ToggleButtonGroup value={metadata.playbackType}>
|
||||||
|
<ToggleButton
|
||||||
|
value="playStop"
|
||||||
|
color="primary"
|
||||||
|
onClick={() => {
|
||||||
|
if (onSave)
|
||||||
|
onSave({
|
||||||
|
...metadata,
|
||||||
|
playbackType: PlaybackType.PlayStop,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<PlayStopIcon />
|
||||||
|
</ToggleButton>
|
||||||
|
<ToggleButton
|
||||||
|
value="playOverlap"
|
||||||
|
color="primary"
|
||||||
|
onClick={() => {
|
||||||
|
if (onSave)
|
||||||
|
onSave({
|
||||||
|
...metadata,
|
||||||
|
playbackType: PlaybackType.PlayOverlap,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<PlayOverlapIcon />
|
||||||
|
</ToggleButton>
|
||||||
|
</ToggleButtonGroup>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -15,6 +15,7 @@ import { restrictToVerticalAxis } from '@dnd-kit/modifiers';
|
|||||||
import AudioTrimmer from './AudioTrimer';
|
import AudioTrimmer from './AudioTrimer';
|
||||||
import { ClipMetadata } from '../../redux/types';
|
import { ClipMetadata } from '../../redux/types';
|
||||||
import { useAppDispatch, useAppSelector } from '../hooks';
|
import { useAppDispatch, useAppSelector } from '../hooks';
|
||||||
|
import apiFetch from '../api';
|
||||||
|
|
||||||
export interface ClipListProps {
|
export interface ClipListProps {
|
||||||
collection: string;
|
collection: string;
|
||||||
@ -31,6 +32,33 @@ export default function ClipList({ collection }: ClipListProps) {
|
|||||||
useSensor(PointerSensor, { activationConstraint: { distance: 5 } }),
|
useSensor(PointerSensor, { activationConstraint: { distance: 5 } }),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const handleDrop = (event: React.DragEvent<HTMLDivElement>) => {
|
||||||
|
event.preventDefault();
|
||||||
|
console.log('Files dropped:', event.dataTransfer.files);
|
||||||
|
const files = Array.from(event.dataTransfer.files).filter((file) =>
|
||||||
|
file.type.startsWith('audio/'),
|
||||||
|
);
|
||||||
|
if (files.length > 0) {
|
||||||
|
const formData = new FormData();
|
||||||
|
files.forEach((file) => formData.append('files', file));
|
||||||
|
|
||||||
|
// todo send the file to the backend and add to the collection
|
||||||
|
|
||||||
|
// fetch('http://localhost:5010/file/upload', {
|
||||||
|
// method: 'POST',
|
||||||
|
// body: formData,
|
||||||
|
// })
|
||||||
|
// .then((res) => res.json())
|
||||||
|
// .catch((err) => console.error('Error uploading files:', err));
|
||||||
|
// Implement your onDrop logic here
|
||||||
|
// onDrop(files, selectedCollection);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDragOver = (event: React.DragEvent<HTMLDivElement>) => {
|
||||||
|
event.preventDefault();
|
||||||
|
};
|
||||||
|
|
||||||
async function handleDragEnd(event: any) {
|
async function handleDragEnd(event: any) {
|
||||||
const { active, over } = event;
|
const { active, over } = event;
|
||||||
if (active.id !== over?.id) {
|
if (active.id !== over?.id) {
|
||||||
@ -50,19 +78,16 @@ export default function ClipList({ collection }: ClipListProps) {
|
|||||||
payload: { collection, newMetadata },
|
payload: { collection, newMetadata },
|
||||||
});
|
});
|
||||||
try {
|
try {
|
||||||
const response = await fetch(
|
const response = await apiFetch('meta/collection/clips/reorder', {
|
||||||
'http://localhost:5010/meta/collection/clips/reorder',
|
method: 'POST',
|
||||||
{
|
headers: {
|
||||||
method: 'POST',
|
'Content-Type': 'application/json',
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
name: collection,
|
|
||||||
clips: newMetadata.clips,
|
|
||||||
}),
|
|
||||||
},
|
},
|
||||||
);
|
body: JSON.stringify({
|
||||||
|
name: collection,
|
||||||
|
clips: newMetadata.clips,
|
||||||
|
}),
|
||||||
|
});
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
console.log('handle reorder return:', data.collections);
|
console.log('handle reorder return:', data.collections);
|
||||||
dispatch({ type: 'metadata/setAllData', payload: data });
|
dispatch({ type: 'metadata/setAllData', payload: data });
|
||||||
@ -78,7 +103,7 @@ export default function ClipList({ collection }: ClipListProps) {
|
|||||||
type: 'metadata/deleteClip',
|
type: 'metadata/deleteClip',
|
||||||
payload: { collection, clip: meta },
|
payload: { collection, clip: meta },
|
||||||
});
|
});
|
||||||
fetch('http://localhost:5010/meta/collection/clips/remove', {
|
apiFetch('meta/collection/clips/remove', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
@ -99,7 +124,7 @@ export default function ClipList({ collection }: ClipListProps) {
|
|||||||
type: 'metadata/moveClip',
|
type: 'metadata/moveClip',
|
||||||
payload: { sourceCollection: collection, targetCollection, clip: meta },
|
payload: { sourceCollection: collection, targetCollection, clip: meta },
|
||||||
});
|
});
|
||||||
fetch('http://localhost:5010/meta/collection/clips/move', {
|
apiFetch('meta/collection/clips/move', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
@ -120,19 +145,16 @@ export default function ClipList({ collection }: ClipListProps) {
|
|||||||
type: 'metadata/editClip',
|
type: 'metadata/editClip',
|
||||||
payload: { collection, clip: meta },
|
payload: { collection, clip: meta },
|
||||||
});
|
});
|
||||||
const response = await fetch(
|
const response = await apiFetch('meta/collection/clips/edit', {
|
||||||
'http://localhost:5010/meta/collection/clips/edit',
|
method: 'POST',
|
||||||
{
|
headers: {
|
||||||
method: 'POST',
|
'Content-Type': 'application/json',
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
name: collection,
|
|
||||||
clip: meta,
|
|
||||||
}),
|
|
||||||
},
|
},
|
||||||
);
|
body: JSON.stringify({
|
||||||
|
name: collection,
|
||||||
|
clip: meta,
|
||||||
|
}),
|
||||||
|
});
|
||||||
await response.json();
|
await response.json();
|
||||||
// console.log('handle clip save return:', data.collections);
|
// console.log('handle clip save return:', data.collections);
|
||||||
dispatch({
|
dispatch({
|
||||||
@ -145,7 +167,11 @@ export default function ClipList({ collection }: ClipListProps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-full flex flex-col justify-start bg-midnight text-offwhite">
|
<div
|
||||||
|
className="min-h-full flex flex-col justify-start bg-midnight text-offwhite"
|
||||||
|
onDrop={handleDrop}
|
||||||
|
onDragOver={handleDragOver}
|
||||||
|
>
|
||||||
<DndContext
|
<DndContext
|
||||||
sensors={sensors}
|
sensors={sensors}
|
||||||
collisionDetection={closestCenter}
|
collisionDetection={closestCenter}
|
||||||
|
|||||||
29
electron-ui/src/renderer/components/playOverlapIcon.tsx
Normal file
29
electron-ui/src/renderer/components/playOverlapIcon.tsx
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
export default function PlayOverlapIcon({
|
||||||
|
size = 24,
|
||||||
|
color = 'currentColor',
|
||||||
|
}: {
|
||||||
|
size?: number;
|
||||||
|
color?: string;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
width={size}
|
||||||
|
height={size}
|
||||||
|
viewBox="0 0 32 24"
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
{/* Filled play arrow */}
|
||||||
|
<polygon points="4,4 4,20 16,12" fill={color} />
|
||||||
|
{/* Outlined play arrow (underneath and to the right) */}
|
||||||
|
<polygon
|
||||||
|
points="12,4 12,20 24,12"
|
||||||
|
fill="none"
|
||||||
|
stroke={color}
|
||||||
|
strokeWidth={1}
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
23
electron-ui/src/renderer/components/playStopIcon.tsx
Normal file
23
electron-ui/src/renderer/components/playStopIcon.tsx
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
export default function PlayStopIcon({
|
||||||
|
size = 24,
|
||||||
|
color = 'currentColor',
|
||||||
|
}: {
|
||||||
|
size?: number;
|
||||||
|
color?: string;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
width={size}
|
||||||
|
height={size}
|
||||||
|
viewBox="0 0 48 24"
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
aria-label="Play/Stop Icon"
|
||||||
|
>
|
||||||
|
{/* Play Arrow */}
|
||||||
|
<polygon points="4,4 20,12 4,20" fill={color} />
|
||||||
|
{/* Stop Square */}
|
||||||
|
<rect x="28" y="4" width="16" height="16" rx="2" fill={color} />
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
4
electron-ui/src/renderer/preload.d.ts
vendored
4
electron-ui/src/renderer/preload.d.ts
vendored
@ -1,10 +1,10 @@
|
|||||||
import { ElectronHandler, FileHandler } from '../main/preload';
|
import { ElectronHandler, AudioHandler } from '../main/preload';
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
// eslint-disable-next-line no-unused-vars
|
// eslint-disable-next-line no-unused-vars
|
||||||
interface Window {
|
interface Window {
|
||||||
electron: ElectronHandler;
|
electron: ElectronHandler;
|
||||||
audio: FileHandler;
|
audio: AudioHandler;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
3
stream_deck_plugin/.gitignore
vendored
3
stream_deck_plugin/.gitignore
vendored
@ -4,4 +4,5 @@ packages/
|
|||||||
ClipTrimDotNet/bin/
|
ClipTrimDotNet/bin/
|
||||||
ClipTrimDotNet/obj/
|
ClipTrimDotNet/obj/
|
||||||
ClipTrimDotNet/dist/
|
ClipTrimDotNet/dist/
|
||||||
ClipTrimDotNet/node_modules/
|
ClipTrimDotNet/node_modules/
|
||||||
|
.vs/
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
@ -1,96 +0,0 @@
|
|||||||
{
|
|
||||||
"Version": 1,
|
|
||||||
"WorkspaceRootPath": "C:\\Users\\mickl\\Desktop\\cliptrim-ui\\ClipTrimApp\\stream_deck_plugin\\",
|
|
||||||
"Documents": [
|
|
||||||
{
|
|
||||||
"AbsoluteMoniker": "D:0:0:{4635D874-69C0-4010-BE46-77EF92EB1553}|ClipTrimDotNet\\ClipTrimDotNet.csproj|c:\\users\\mickl\\desktop\\cliptrim-ui\\cliptrimapp\\stream_deck_plugin\\cliptrimdotnet\\client\\cliptrimclient.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
|
|
||||||
"RelativeMoniker": "D:0:0:{4635D874-69C0-4010-BE46-77EF92EB1553}|ClipTrimDotNet\\ClipTrimDotNet.csproj|solutionrelative:cliptrimdotnet\\client\\cliptrimclient.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"AbsoluteMoniker": "D:0:0:{4635D874-69C0-4010-BE46-77EF92EB1553}|ClipTrimDotNet\\ClipTrimDotNet.csproj|c:\\users\\mickl\\desktop\\cliptrim-ui\\cliptrimapp\\stream_deck_plugin\\cliptrimdotnet\\player.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
|
|
||||||
"RelativeMoniker": "D:0:0:{4635D874-69C0-4010-BE46-77EF92EB1553}|ClipTrimDotNet\\ClipTrimDotNet.csproj|solutionrelative:cliptrimdotnet\\player.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"AbsoluteMoniker": "D:0:0:{4635D874-69C0-4010-BE46-77EF92EB1553}|ClipTrimDotNet\\ClipTrimDotNet.csproj|c:\\users\\mickl\\desktop\\cliptrim-ui\\cliptrimapp\\stream_deck_plugin\\cliptrimdotnet\\profileswitcher.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
|
|
||||||
"RelativeMoniker": "D:0:0:{4635D874-69C0-4010-BE46-77EF92EB1553}|ClipTrimDotNet\\ClipTrimDotNet.csproj|solutionrelative:cliptrimdotnet\\profileswitcher.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"AbsoluteMoniker": "D:0:0:{4635D874-69C0-4010-BE46-77EF92EB1553}|ClipTrimDotNet\\ClipTrimDotNet.csproj|c:\\users\\mickl\\desktop\\cliptrim-ui\\cliptrimapp\\stream_deck_plugin\\cliptrimdotnet\\client\\collectionmetadata.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
|
|
||||||
"RelativeMoniker": "D:0:0:{4635D874-69C0-4010-BE46-77EF92EB1553}|ClipTrimDotNet\\ClipTrimDotNet.csproj|solutionrelative:cliptrimdotnet\\client\\collectionmetadata.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"DocumentGroupContainers": [
|
|
||||||
{
|
|
||||||
"Orientation": 0,
|
|
||||||
"VerticalTabListWidth": 256,
|
|
||||||
"DocumentGroups": [
|
|
||||||
{
|
|
||||||
"DockedWidth": 297,
|
|
||||||
"SelectedChildIndex": 2,
|
|
||||||
"Children": [
|
|
||||||
{
|
|
||||||
"$type": "Bookmark",
|
|
||||||
"Name": "ST:0:0:{57d563b6-44a5-47df-85be-f4199ad6b651}"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"$type": "Document",
|
|
||||||
"DocumentIndex": 2,
|
|
||||||
"Title": "ProfileSwitcher.cs",
|
|
||||||
"DocumentMoniker": "C:\\Users\\mickl\\Desktop\\cliptrim-ui\\ClipTrimApp\\stream_deck_plugin\\ClipTrimDotNet\\ProfileSwitcher.cs",
|
|
||||||
"RelativeDocumentMoniker": "ClipTrimDotNet\\ProfileSwitcher.cs",
|
|
||||||
"ToolTip": "C:\\Users\\mickl\\Desktop\\cliptrim-ui\\ClipTrimApp\\stream_deck_plugin\\ClipTrimDotNet\\ProfileSwitcher.cs",
|
|
||||||
"RelativeToolTip": "ClipTrimDotNet\\ProfileSwitcher.cs",
|
|
||||||
"ViewState": "AgIAAFkAAAAAAAAAAAAlwG8AAABKAAAAAAAAAA==",
|
|
||||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
|
|
||||||
"WhenOpened": "2026-02-21T15:06:24.045Z",
|
|
||||||
"EditorCaption": ""
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"$type": "Document",
|
|
||||||
"DocumentIndex": 0,
|
|
||||||
"Title": "ClipTrimClient.cs",
|
|
||||||
"DocumentMoniker": "C:\\Users\\mickl\\Desktop\\cliptrim-ui\\ClipTrimApp\\stream_deck_plugin\\ClipTrimDotNet\\Client\\ClipTrimClient.cs",
|
|
||||||
"RelativeDocumentMoniker": "ClipTrimDotNet\\Client\\ClipTrimClient.cs",
|
|
||||||
"ToolTip": "C:\\Users\\mickl\\Desktop\\cliptrim-ui\\ClipTrimApp\\stream_deck_plugin\\ClipTrimDotNet\\Client\\ClipTrimClient.cs",
|
|
||||||
"RelativeToolTip": "ClipTrimDotNet\\Client\\ClipTrimClient.cs",
|
|
||||||
"ViewState": "AgIAAEgAAAAAAAAAAAAuwGMAAAAJAAAAAAAAAA==",
|
|
||||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
|
|
||||||
"WhenOpened": "2026-02-21T15:03:49.814Z",
|
|
||||||
"EditorCaption": ""
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"$type": "Document",
|
|
||||||
"DocumentIndex": 3,
|
|
||||||
"Title": "CollectionMetaData.cs",
|
|
||||||
"DocumentMoniker": "C:\\Users\\mickl\\Desktop\\cliptrim-ui\\ClipTrimApp\\stream_deck_plugin\\ClipTrimDotNet\\Client\\CollectionMetaData.cs",
|
|
||||||
"RelativeDocumentMoniker": "ClipTrimDotNet\\Client\\CollectionMetaData.cs",
|
|
||||||
"ToolTip": "C:\\Users\\mickl\\Desktop\\cliptrim-ui\\ClipTrimApp\\stream_deck_plugin\\ClipTrimDotNet\\Client\\CollectionMetaData.cs",
|
|
||||||
"RelativeToolTip": "ClipTrimDotNet\\Client\\CollectionMetaData.cs",
|
|
||||||
"ViewState": "AgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==",
|
|
||||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
|
|
||||||
"WhenOpened": "2026-02-21T15:03:47.862Z",
|
|
||||||
"EditorCaption": ""
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"$type": "Document",
|
|
||||||
"DocumentIndex": 1,
|
|
||||||
"Title": "Player.cs",
|
|
||||||
"DocumentMoniker": "C:\\Users\\mickl\\Desktop\\cliptrim-ui\\ClipTrimApp\\stream_deck_plugin\\ClipTrimDotNet\\Player.cs",
|
|
||||||
"RelativeDocumentMoniker": "ClipTrimDotNet\\Player.cs",
|
|
||||||
"ToolTip": "C:\\Users\\mickl\\Desktop\\cliptrim-ui\\ClipTrimApp\\stream_deck_plugin\\ClipTrimDotNet\\Player.cs*",
|
|
||||||
"RelativeToolTip": "ClipTrimDotNet\\Player.cs*",
|
|
||||||
"ViewState": "AgIAAHIAAAAAAAAAAAA3wIYAAABMAAAAAAAAAA==",
|
|
||||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
|
|
||||||
"WhenOpened": "2026-02-21T15:00:23.762Z",
|
|
||||||
"EditorCaption": ""
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"$type": "Bookmark",
|
|
||||||
"Name": "ST:0:0:{cce594b6-0c39-4442-ba28-10c64ac7e89f}"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@ -1,113 +0,0 @@
|
|||||||
{
|
|
||||||
"Version": 1,
|
|
||||||
"WorkspaceRootPath": "C:\\Users\\mickl\\Desktop\\cliptrim-ui\\ClipTrimApp\\stream_deck_plugin\\",
|
|
||||||
"Documents": [
|
|
||||||
{
|
|
||||||
"AbsoluteMoniker": "D:0:0:{4635D874-69C0-4010-BE46-77EF92EB1553}|ClipTrimDotNet\\ClipTrimDotNet.csproj|c:\\users\\mickl\\desktop\\cliptrim-ui\\cliptrimapp\\stream_deck_plugin\\cliptrimdotnet\\wavplayer.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
|
|
||||||
"RelativeMoniker": "D:0:0:{4635D874-69C0-4010-BE46-77EF92EB1553}|ClipTrimDotNet\\ClipTrimDotNet.csproj|solutionrelative:cliptrimdotnet\\wavplayer.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"AbsoluteMoniker": "D:0:0:{4635D874-69C0-4010-BE46-77EF92EB1553}|ClipTrimDotNet\\ClipTrimDotNet.csproj|c:\\users\\mickl\\desktop\\cliptrim-ui\\cliptrimapp\\stream_deck_plugin\\cliptrimdotnet\\player.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
|
|
||||||
"RelativeMoniker": "D:0:0:{4635D874-69C0-4010-BE46-77EF92EB1553}|ClipTrimDotNet\\ClipTrimDotNet.csproj|solutionrelative:cliptrimdotnet\\player.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"AbsoluteMoniker": "D:0:0:{4635D874-69C0-4010-BE46-77EF92EB1553}|ClipTrimDotNet\\ClipTrimDotNet.csproj|c:\\users\\mickl\\desktop\\cliptrim-ui\\cliptrimapp\\stream_deck_plugin\\cliptrimdotnet\\profileswitcher.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
|
|
||||||
"RelativeMoniker": "D:0:0:{4635D874-69C0-4010-BE46-77EF92EB1553}|ClipTrimDotNet\\ClipTrimDotNet.csproj|solutionrelative:cliptrimdotnet\\profileswitcher.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"AbsoluteMoniker": "D:0:0:{4635D874-69C0-4010-BE46-77EF92EB1553}|ClipTrimDotNet\\ClipTrimDotNet.csproj|c:\\users\\mickl\\desktop\\cliptrim-ui\\cliptrimapp\\stream_deck_plugin\\cliptrimdotnet\\client\\cliptrimclient.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
|
|
||||||
"RelativeMoniker": "D:0:0:{4635D874-69C0-4010-BE46-77EF92EB1553}|ClipTrimDotNet\\ClipTrimDotNet.csproj|solutionrelative:cliptrimdotnet\\client\\cliptrimclient.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"AbsoluteMoniker": "D:0:0:{4635D874-69C0-4010-BE46-77EF92EB1553}|ClipTrimDotNet\\ClipTrimDotNet.csproj|c:\\users\\mickl\\desktop\\cliptrim-ui\\cliptrimapp\\stream_deck_plugin\\cliptrimdotnet\\client\\collectionmetadata.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
|
|
||||||
"RelativeMoniker": "D:0:0:{4635D874-69C0-4010-BE46-77EF92EB1553}|ClipTrimDotNet\\ClipTrimDotNet.csproj|solutionrelative:cliptrimdotnet\\client\\collectionmetadata.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"DocumentGroupContainers": [
|
|
||||||
{
|
|
||||||
"Orientation": 0,
|
|
||||||
"VerticalTabListWidth": 256,
|
|
||||||
"DocumentGroups": [
|
|
||||||
{
|
|
||||||
"DockedWidth": 297,
|
|
||||||
"SelectedChildIndex": 1,
|
|
||||||
"Children": [
|
|
||||||
{
|
|
||||||
"$type": "Bookmark",
|
|
||||||
"Name": "ST:0:0:{57d563b6-44a5-47df-85be-f4199ad6b651}"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"$type": "Document",
|
|
||||||
"DocumentIndex": 0,
|
|
||||||
"Title": "WavPlayer.cs",
|
|
||||||
"DocumentMoniker": "C:\\Users\\mickl\\Desktop\\cliptrim-ui\\ClipTrimApp\\stream_deck_plugin\\ClipTrimDotNet\\WavPlayer.cs",
|
|
||||||
"RelativeDocumentMoniker": "ClipTrimDotNet\\WavPlayer.cs",
|
|
||||||
"ToolTip": "C:\\Users\\mickl\\Desktop\\cliptrim-ui\\ClipTrimApp\\stream_deck_plugin\\ClipTrimDotNet\\WavPlayer.cs",
|
|
||||||
"RelativeToolTip": "ClipTrimDotNet\\WavPlayer.cs",
|
|
||||||
"ViewState": "AgIAALYAAAAAAAAAAAAAALsAAAANAAAAAAAAAA==",
|
|
||||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
|
|
||||||
"WhenOpened": "2026-02-21T15:16:26.477Z",
|
|
||||||
"EditorCaption": ""
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"$type": "Document",
|
|
||||||
"DocumentIndex": 2,
|
|
||||||
"Title": "ProfileSwitcher.cs",
|
|
||||||
"DocumentMoniker": "C:\\Users\\mickl\\Desktop\\cliptrim-ui\\ClipTrimApp\\stream_deck_plugin\\ClipTrimDotNet\\ProfileSwitcher.cs",
|
|
||||||
"RelativeDocumentMoniker": "ClipTrimDotNet\\ProfileSwitcher.cs",
|
|
||||||
"ToolTip": "C:\\Users\\mickl\\Desktop\\cliptrim-ui\\ClipTrimApp\\stream_deck_plugin\\ClipTrimDotNet\\ProfileSwitcher.cs",
|
|
||||||
"RelativeToolTip": "ClipTrimDotNet\\ProfileSwitcher.cs",
|
|
||||||
"ViewState": "AgIAAG8AAAAAAAAAAAAWwG8AAABKAAAAAAAAAA==",
|
|
||||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
|
|
||||||
"WhenOpened": "2026-02-21T15:06:24.045Z",
|
|
||||||
"EditorCaption": ""
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"$type": "Document",
|
|
||||||
"DocumentIndex": 3,
|
|
||||||
"Title": "ClipTrimClient.cs",
|
|
||||||
"DocumentMoniker": "C:\\Users\\mickl\\Desktop\\cliptrim-ui\\ClipTrimApp\\stream_deck_plugin\\ClipTrimDotNet\\Client\\ClipTrimClient.cs",
|
|
||||||
"RelativeDocumentMoniker": "ClipTrimDotNet\\Client\\ClipTrimClient.cs",
|
|
||||||
"ToolTip": "C:\\Users\\mickl\\Desktop\\cliptrim-ui\\ClipTrimApp\\stream_deck_plugin\\ClipTrimDotNet\\Client\\ClipTrimClient.cs",
|
|
||||||
"RelativeToolTip": "ClipTrimDotNet\\Client\\ClipTrimClient.cs",
|
|
||||||
"ViewState": "AgIAAEgAAAAAAAAAAAAuwGIAAAApAAAAAAAAAA==",
|
|
||||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
|
|
||||||
"WhenOpened": "2026-02-21T15:03:49.814Z",
|
|
||||||
"EditorCaption": ""
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"$type": "Document",
|
|
||||||
"DocumentIndex": 4,
|
|
||||||
"Title": "CollectionMetaData.cs",
|
|
||||||
"DocumentMoniker": "C:\\Users\\mickl\\Desktop\\cliptrim-ui\\ClipTrimApp\\stream_deck_plugin\\ClipTrimDotNet\\Client\\CollectionMetaData.cs",
|
|
||||||
"RelativeDocumentMoniker": "ClipTrimDotNet\\Client\\CollectionMetaData.cs",
|
|
||||||
"ToolTip": "C:\\Users\\mickl\\Desktop\\cliptrim-ui\\ClipTrimApp\\stream_deck_plugin\\ClipTrimDotNet\\Client\\CollectionMetaData.cs",
|
|
||||||
"RelativeToolTip": "ClipTrimDotNet\\Client\\CollectionMetaData.cs",
|
|
||||||
"ViewState": "AgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==",
|
|
||||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
|
|
||||||
"WhenOpened": "2026-02-21T15:03:47.862Z",
|
|
||||||
"EditorCaption": ""
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"$type": "Document",
|
|
||||||
"DocumentIndex": 1,
|
|
||||||
"Title": "Player.cs",
|
|
||||||
"DocumentMoniker": "C:\\Users\\mickl\\Desktop\\cliptrim-ui\\ClipTrimApp\\stream_deck_plugin\\ClipTrimDotNet\\Player.cs",
|
|
||||||
"RelativeDocumentMoniker": "ClipTrimDotNet\\Player.cs",
|
|
||||||
"ToolTip": "C:\\Users\\mickl\\Desktop\\cliptrim-ui\\ClipTrimApp\\stream_deck_plugin\\ClipTrimDotNet\\Player.cs",
|
|
||||||
"RelativeToolTip": "ClipTrimDotNet\\Player.cs",
|
|
||||||
"ViewState": "AgIAAHoAAAAAAAAAAAAswIwAAAAbAAAAAAAAAA==",
|
|
||||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
|
|
||||||
"WhenOpened": "2026-02-21T15:00:23.762Z",
|
|
||||||
"EditorCaption": ""
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"$type": "Bookmark",
|
|
||||||
"Name": "ST:0:0:{cce594b6-0c39-4442-ba28-10c64ac7e89f}"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@ -1,25 +1,25 @@
|
|||||||
|
|
||||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
# Visual Studio Version 17
|
# Visual Studio Version 17
|
||||||
VisualStudioVersion = 17.8.34330.188
|
VisualStudioVersion = 17.8.34330.188
|
||||||
MinimumVisualStudioVersion = 10.0.40219.1
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ClipTrimDotNet", "ClipTrimDotNet\ClipTrimDotNet.csproj", "{4635D874-69C0-4010-BE46-77EF92EB1553}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ClipTrimDotNet", "ClipTrimDotNet\ClipTrimDotNet.csproj", "{4635D874-69C0-4010-BE46-77EF92EB1553}"
|
||||||
EndProject
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
Release|Any CPU = Release|Any CPU
|
Release|Any CPU = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
{4635D874-69C0-4010-BE46-77EF92EB1553}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{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}.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.ActiveCfg = Release|Any CPU
|
||||||
{4635D874-69C0-4010-BE46-77EF92EB1553}.Release|Any CPU.Build.0 = Release|Any CPU
|
{4635D874-69C0-4010-BE46-77EF92EB1553}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
SolutionGuid = {926C6896-F36A-4F3B-A9DD-CA3AA48AD99F}
|
SolutionGuid = {926C6896-F36A-4F3B-A9DD-CA3AA48AD99F}
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
EndGlobal
|
EndGlobal
|
||||||
|
|||||||
@ -1,14 +1,14 @@
|
|||||||
To use:
|
To use:
|
||||||
1. Right click the project and choose "Manage Nuget Packages"
|
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)
|
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
|
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)
|
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
|
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...)
|
6. Before releasing, change the Assembly Information (Right click the project -> Properties -> Application -> Assembly Information...)
|
||||||
|
|
||||||
For help with StreamDeck-Tools:
|
For help with StreamDeck-Tools:
|
||||||
Discord Server: http://discord.barraider.com
|
Discord Server: http://discord.barraider.com
|
||||||
Resources:
|
Resources:
|
||||||
* StreamDeck-Tools samples and tutorial: https://github.com/BarRaider/streamdeck-tools
|
* StreamDeck-Tools samples and tutorial: https://github.com/BarRaider/streamdeck-tools
|
||||||
* EasyPI library (for working with Property Inspector): https://github.com/BarRaider/streamdeck-easypi
|
* EasyPI library (for working with Property Inspector): https://github.com/BarRaider/streamdeck-easypi
|
||||||
|
|
||||||
|
|||||||
@ -1,22 +1,42 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<configuration>
|
<configuration>
|
||||||
<startup>
|
<startup>
|
||||||
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.8"/>
|
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.8" />
|
||||||
</startup>
|
</startup>
|
||||||
<runtime>
|
<runtime>
|
||||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||||
<dependentAssembly>
|
<dependentAssembly>
|
||||||
<assemblyIdentity name="CommandLine" publicKeyToken="5a870481e358d379" culture="neutral"/>
|
<assemblyIdentity name="CommandLine" publicKeyToken="5a870481e358d379" culture="neutral" />
|
||||||
<bindingRedirect oldVersion="0.0.0.0-2.6.0.0" newVersion="2.6.0.0"/>
|
<bindingRedirect oldVersion="0.0.0.0-2.6.0.0" newVersion="2.6.0.0" />
|
||||||
</dependentAssembly>
|
</dependentAssembly>
|
||||||
<dependentAssembly>
|
<dependentAssembly>
|
||||||
<assemblyIdentity name="System.Drawing.Common" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral"/>
|
<assemblyIdentity name="System.Drawing.Common" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
|
||||||
<bindingRedirect oldVersion="0.0.0.0-8.0.0.0" newVersion="8.0.0.0"/>
|
<bindingRedirect oldVersion="0.0.0.0-8.0.0.0" newVersion="8.0.0.0" />
|
||||||
</dependentAssembly>
|
</dependentAssembly>
|
||||||
<dependentAssembly>
|
<dependentAssembly>
|
||||||
<assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral"/>
|
<assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral" />
|
||||||
<bindingRedirect oldVersion="0.0.0.0-13.0.0.0" newVersion="13.0.0.0"/>
|
<bindingRedirect oldVersion="0.0.0.0-13.0.0.0" newVersion="13.0.0.0" />
|
||||||
</dependentAssembly>
|
</dependentAssembly>
|
||||||
</assemblyBinding>
|
<dependentAssembly>
|
||||||
</runtime>
|
<assemblyIdentity name="Microsoft.Extensions.DependencyInjection.Abstractions" publicKeyToken="adb9793829ddae60" culture="neutral" />
|
||||||
</configuration>
|
<bindingRedirect oldVersion="0.0.0.0-10.0.0.2" newVersion="10.0.0.2" />
|
||||||
|
</dependentAssembly>
|
||||||
|
<dependentAssembly>
|
||||||
|
<assemblyIdentity name="System.Text.Json" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
|
||||||
|
<bindingRedirect oldVersion="0.0.0.0-10.0.0.2" newVersion="10.0.0.2" />
|
||||||
|
</dependentAssembly>
|
||||||
|
<dependentAssembly>
|
||||||
|
<assemblyIdentity name="Microsoft.Extensions.Logging.Abstractions" publicKeyToken="adb9793829ddae60" culture="neutral" />
|
||||||
|
<bindingRedirect oldVersion="0.0.0.0-10.0.0.2" newVersion="10.0.0.2" />
|
||||||
|
</dependentAssembly>
|
||||||
|
<dependentAssembly>
|
||||||
|
<assemblyIdentity name="Microsoft.Extensions.Logging" publicKeyToken="adb9793829ddae60" culture="neutral" />
|
||||||
|
<bindingRedirect oldVersion="0.0.0.0-10.0.0.2" newVersion="10.0.0.2" />
|
||||||
|
</dependentAssembly>
|
||||||
|
<dependentAssembly>
|
||||||
|
<assemblyIdentity name="Microsoft.Extensions.DependencyInjection" publicKeyToken="adb9793829ddae60" culture="neutral" />
|
||||||
|
<bindingRedirect oldVersion="0.0.0.0-10.0.0.2" newVersion="10.0.0.2" />
|
||||||
|
</dependentAssembly>
|
||||||
|
</assemblyBinding>
|
||||||
|
</runtime>
|
||||||
|
</configuration>
|
||||||
|
|||||||
@ -1 +1 @@
|
|||||||
|
|
||||||
|
|||||||
@ -1,42 +1,53 @@
|
|||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using Newtonsoft.Json.Converters;
|
using Newtonsoft.Json.Serialization;
|
||||||
using System;
|
using Newtonsoft.Json.Converters;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
using System;
|
||||||
using System.Text;
|
using System.Collections.Generic;
|
||||||
using System.Threading.Tasks;
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
namespace ClipTrimDotNet.Client
|
using System.Threading.Tasks;
|
||||||
{
|
using System.Text.Json.Serialization;
|
||||||
public enum PlaybackType
|
|
||||||
{
|
namespace ClipTrimDotNet.Client
|
||||||
playStop,
|
{
|
||||||
playOverlap
|
public enum PlaybackType
|
||||||
}
|
{
|
||||||
public class ClipMetadata
|
playStop,
|
||||||
{
|
playOverlap
|
||||||
[JsonProperty(PropertyName = "filename")]
|
}
|
||||||
public string Filename { get; set; }
|
public class ClipMetadata
|
||||||
|
{
|
||||||
|
[JsonProperty(PropertyName = "filename")]
|
||||||
[JsonProperty(PropertyName = "name")]
|
[JsonPropertyName("filename")]
|
||||||
public string Name { get; set; }
|
|
||||||
|
public string Filename { get; set; }
|
||||||
|
|
||||||
[JsonProperty(PropertyName = "volume")]
|
|
||||||
public double Volume { get; set; } = 1.0;
|
[JsonProperty(PropertyName = "name")]
|
||||||
|
[JsonPropertyName("name")]
|
||||||
|
public string Name { get; set; }
|
||||||
[JsonProperty(PropertyName = "startTime")]
|
|
||||||
public double StartTime { get; set; } = 0.0;
|
|
||||||
|
[JsonProperty(PropertyName = "volume")]
|
||||||
|
[JsonPropertyName("volume")]
|
||||||
[JsonProperty(PropertyName = "endTime")]
|
public double Volume { get; set; } = 1.0;
|
||||||
public double EndTime { get; set; } = 0.0;
|
|
||||||
|
|
||||||
|
[JsonProperty(PropertyName = "startTime")]
|
||||||
[JsonProperty(PropertyName = "playbackType")]
|
[JsonPropertyName("startTime")]
|
||||||
[JsonConverter(typeof(StringEnumConverter))]
|
public double StartTime { get; set; } = 0.0;
|
||||||
public PlaybackType PlaybackType { get; set; } = PlaybackType.playStop;
|
|
||||||
}
|
|
||||||
}
|
[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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -1,110 +1,237 @@
|
|||||||
using System;
|
using BarRaider.SdTools;
|
||||||
using System.Collections.Generic;
|
using ClipTrimDotNet.Keys;
|
||||||
using System.Linq;
|
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||||
using System.Net.Http;
|
using Newtonsoft.Json;
|
||||||
using System.Text;
|
using SocketIOClient;
|
||||||
using System.Threading.Tasks;
|
using System;
|
||||||
using Newtonsoft.Json;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
namespace ClipTrimDotNet.Client
|
using System.Net.Http;
|
||||||
{
|
using System.Runtime.CompilerServices;
|
||||||
public class ClipTrimClient
|
using System.Text;
|
||||||
{
|
using System.Threading.Tasks;
|
||||||
private static ClipTrimClient? instance;
|
using static System.Runtime.InteropServices.JavaScript.JSType;
|
||||||
public static ClipTrimClient Instance
|
|
||||||
{
|
namespace ClipTrimDotNet.Client
|
||||||
get
|
{
|
||||||
{
|
public class ClipTrimClient
|
||||||
if (instance == null)
|
{
|
||||||
{
|
private static ClipTrimClient? instance;
|
||||||
instance = new ClipTrimClient();
|
public static ClipTrimClient Instance
|
||||||
}
|
{
|
||||||
return instance;
|
get
|
||||||
}
|
{
|
||||||
}
|
if (instance == null)
|
||||||
|
{
|
||||||
private HttpClient httpClient;
|
instance = new ClipTrimClient();
|
||||||
|
}
|
||||||
public ClipTrimClient()
|
return instance;
|
||||||
{
|
}
|
||||||
httpClient = new HttpClient()
|
}
|
||||||
{
|
|
||||||
BaseAddress = new Uri("http://localhost:5010/"),
|
//private HttpClient httpClient;
|
||||||
Timeout = TimeSpan.FromSeconds(10)
|
private SocketIO socket;
|
||||||
};
|
|
||||||
Task.Run(ShortPoll);
|
public int PortNumber { get; set; } = 5010;
|
||||||
}
|
|
||||||
|
public ClipTrimClient()
|
||||||
public async Task ShortPoll()
|
{
|
||||||
{
|
//httpClient = new HttpClient()
|
||||||
while (true)
|
//{
|
||||||
{
|
// BaseAddress = new Uri("http://localhost:5010/"),
|
||||||
await GetMetadata();
|
// Timeout = TimeSpan.FromSeconds(10)
|
||||||
await Task.Delay(TimeSpan.FromSeconds(5)); await Task.Delay(TimeSpan.FromSeconds(5));
|
//};
|
||||||
|
Logger.Instance.LogMessage(TracingLevel.INFO, $"Starting ClipTrimClient on port {PortNumber}");
|
||||||
}
|
socket = new SocketIO(new Uri($"http://localhost:5010/"));
|
||||||
}
|
socket.Options.AutoUpgrade = false;
|
||||||
|
socket.Options.ConnectionTimeout = TimeSpan.FromSeconds(10);
|
||||||
public List<CollectionMetaData> Collections { get; private set; } = new List<CollectionMetaData>();
|
socket.Options.Reconnection = true;
|
||||||
public CollectionMetaData? SelectedCollection { get; private set; }
|
socket.On("full_data", ctx =>
|
||||||
public int PageIndex { get; private set; } = 0;
|
{
|
||||||
private async Task GetMetadata()
|
try
|
||||||
{
|
{
|
||||||
try
|
var response = ctx.GetValue<List<CollectionMetaData>>(0);
|
||||||
{
|
Logger.Instance.LogMessage(TracingLevel.INFO, $"full_data event {JsonConvert.SerializeObject(response)}");
|
||||||
var response = await httpClient.GetAsync("meta");
|
Collections = response!;
|
||||||
if (response.IsSuccessStatusCode)
|
//Logger.Instance.LogMessage(TracingLevel.INFO, $"Collections {JsonConvert.SerializeObject(Collections)}");
|
||||||
{
|
}
|
||||||
var json = await response.Content.ReadAsStringAsync();
|
catch (Exception ex)
|
||||||
dynamic collections = JsonConvert.DeserializeObject(json);
|
{
|
||||||
collections = collections.collections;
|
Logger.Instance.LogMessage(TracingLevel.INFO, $"full_data error {ex.ToString()}");
|
||||||
Collections = JsonConvert.DeserializeObject<List<CollectionMetaData>>(collections.ToString());
|
}
|
||||||
}
|
return Task.CompletedTask;
|
||||||
}
|
});
|
||||||
catch (Exception ex)
|
socket.On("collection_updated", ctx =>
|
||||||
{
|
{
|
||||||
//Logger.Instance.LogMessage(TracingLevel.INFO, $"Error pinging ClipTrim API: {ex.Message}");
|
try
|
||||||
return;
|
{
|
||||||
}
|
var response = ctx.GetValue<CollectionMetaData>(0)!;
|
||||||
|
Logger.Instance.LogMessage(TracingLevel.INFO, $"collection_updated event {JsonConvert.SerializeObject(response)}");
|
||||||
}
|
int index = Collections.FindIndex(x => x.Id == response.Id);
|
||||||
|
if (index != -1)
|
||||||
public List<string> GetCollectionNames()
|
{
|
||||||
{
|
Collections[index] = response;
|
||||||
//await GetMetadata();
|
}
|
||||||
return Collections.Select(x => x.Name).ToList();
|
}
|
||||||
}
|
catch
|
||||||
|
{
|
||||||
public void SetSelectedCollectionByName(string name)
|
|
||||||
{
|
}
|
||||||
var collection = Collections.FirstOrDefault(x => x.Name == name);
|
|
||||||
if (collection != null)
|
return Task.CompletedTask;
|
||||||
{
|
});
|
||||||
SelectedCollection = collection;
|
|
||||||
PageIndex = 0;
|
socket.OnDisconnected += (sender, e) =>
|
||||||
}
|
{
|
||||||
}
|
Logger.Instance.LogMessage(TracingLevel.INFO, $"Socket disconnected: {e}");
|
||||||
|
Task.Run(async () => await Connect());
|
||||||
public ClipMetadata? GetClipByPagedIndex(int index)
|
};
|
||||||
{
|
Task.Run(async () => await Connect());
|
||||||
if (SelectedCollection == null) return null;
|
}
|
||||||
int clipIndex = PageIndex * 10 + index;
|
|
||||||
if (clipIndex >= 0 && clipIndex < SelectedCollection.Clips.Count)
|
public async Task Connect()
|
||||||
{
|
{
|
||||||
return SelectedCollection.Clips[clipIndex];
|
while (!socket.Connected)
|
||||||
}
|
{
|
||||||
return null;
|
try
|
||||||
}
|
{
|
||||||
|
await socket.ConnectAsync();
|
||||||
public async void PlayClip(ClipMetadata? metadata)
|
}
|
||||||
{
|
catch
|
||||||
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}");
|
|
||||||
}
|
public List<CollectionMetaData> Collections { get; private set; } = new List<CollectionMetaData>();
|
||||||
}
|
public int SelectedCollection { get; private set; } = -1;
|
||||||
}
|
|
||||||
}
|
public Dictionary<int, int> CollectionIndexes { get; private set; } = new();
|
||||||
|
public int PageIndex
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (SelectedCollection == -1) return 0;
|
||||||
|
if (!CollectionIndexes.ContainsKey(SelectedCollection))
|
||||||
|
{
|
||||||
|
CollectionIndexes[SelectedCollection] = 0;
|
||||||
|
}
|
||||||
|
return CollectionIndexes[SelectedCollection];
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (SelectedCollection == -1) return;
|
||||||
|
CollectionIndexes[SelectedCollection] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public bool PageMode { get; set; } = false;
|
||||||
|
|
||||||
|
public int PageCount
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (SelectedCollection == -1) return 0;
|
||||||
|
var collection = Collections[SelectedCollection];
|
||||||
|
return (collection.Clips.Count - 1) / 10 + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public bool CanPageUp
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if(PageMode) return false;
|
||||||
|
if (SelectedCollection == -1) return false;
|
||||||
|
return PageCount - PageIndex > 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool CanPageDown
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (PageMode) return false;
|
||||||
|
return PageIndex > 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void PageDown()
|
||||||
|
{
|
||||||
|
if (CanPageDown)
|
||||||
|
{
|
||||||
|
PageIndex--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void PageUp()
|
||||||
|
{
|
||||||
|
if (CanPageUp)
|
||||||
|
{
|
||||||
|
PageIndex++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public List<string> GetCollectionNames()
|
||||||
|
{
|
||||||
|
//await GetMetadata();
|
||||||
|
return Collections.Where(x => x.Name != "Uncategorized").Select(x => x.Name).ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
public string 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
|
||||||
|
{
|
||||||
|
var metadata = GetClipByPagedIndex(index);
|
||||||
|
if (metadata == null) return;
|
||||||
|
Logger.Instance.LogMessage(TracingLevel.INFO, $"playing clip:");
|
||||||
|
await socket.EmitAsync("play_clip", new List<object>() { metadata });
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public async void SaveClip()
|
||||||
|
{
|
||||||
|
await socket.EmitAsync("record_clip", new List<object>() { });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -1,23 +1,27 @@
|
|||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Text.Json.Serialization;
|
||||||
|
using System.Threading.Tasks;
|
||||||
namespace ClipTrimDotNet.Client
|
|
||||||
{
|
namespace ClipTrimDotNet.Client
|
||||||
public class CollectionMetaData
|
{
|
||||||
{
|
public class CollectionMetaData
|
||||||
[JsonProperty(PropertyName = "name")]
|
{
|
||||||
public string Name { get; set; }
|
[JsonProperty(PropertyName = "name")]
|
||||||
|
[JsonPropertyName("name")]
|
||||||
|
public string Name { get; set; }
|
||||||
[JsonProperty(PropertyName = "clips")]
|
|
||||||
public List<ClipMetadata> Clips { get; set; } = new List<ClipMetadata>();
|
|
||||||
|
[JsonProperty(PropertyName = "clips")]
|
||||||
|
[JsonPropertyName("clips")]
|
||||||
[JsonProperty(PropertyName = "id")]
|
public List<ClipMetadata> Clips { get; set; } = new List<ClipMetadata>();
|
||||||
public int Id { get; set; }
|
|
||||||
}
|
|
||||||
}
|
[JsonProperty(PropertyName = "id")]
|
||||||
|
[JsonPropertyName("id")]
|
||||||
|
public int Id { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -1,162 +1,95 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
<PropertyGroup>
|
||||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
<TargetFramework>net8.0-windows</TargetFramework>
|
||||||
<PropertyGroup>
|
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
<OutputType>Exe</OutputType>
|
||||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
<LangVersion>10</LangVersion>
|
||||||
<ProjectGuid>{4635D874-69C0-4010-BE46-77EF92EB1553}</ProjectGuid>
|
<Nullable>enable</Nullable>
|
||||||
<OutputType>Exe</OutputType>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<RootNamespace>ClipTrimDotNet</RootNamespace>
|
<PreBuildEvent>npm run stop</PreBuildEvent>
|
||||||
<AssemblyName>ClipTrimDotNet</AssemblyName>
|
<PostBuildEvent>npm run start</PostBuildEvent>
|
||||||
<TargetFrameworkVersion>v4.8</TargetFrameworkVersion>
|
<AssemblyTitle>ClipTrimDotNet</AssemblyTitle>
|
||||||
<LangVersion>8</LangVersion>
|
<Product>ClipTrimDotNet</Product>
|
||||||
<FileAlignment>512</FileAlignment>
|
<Copyright>Copyright © 2020</Copyright>
|
||||||
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
|
<AssemblyVersion>1.0.0.0</AssemblyVersion>
|
||||||
<Deterministic>true</Deterministic>
|
<FileVersion>1.0.0.0</FileVersion>
|
||||||
<Nullable>enable</Nullable>
|
</PropertyGroup>
|
||||||
<TargetFrameworkProfile />
|
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||||
</PropertyGroup>
|
<OutputPath>bin\Debug\com.michal-courson.cliptrim.sdPlugin\</OutputPath>
|
||||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
</PropertyGroup>
|
||||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||||
<DebugSymbols>true</DebugSymbols>
|
<OutputPath>bin\Release\ClipTrimDotNet.sdPlugin\</OutputPath>
|
||||||
<DebugType>full</DebugType>
|
</PropertyGroup>
|
||||||
<Optimize>false</Optimize>
|
<ItemGroup>
|
||||||
<OutputPath>bin\Debug\com.michal-courson.cliptrim.sdPlugin\</OutputPath>
|
<PackageReference Include="CommandLineParser" Version="2.9.1" />
|
||||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" Version="8.0.0" />
|
||||||
<ErrorReport>prompt</ErrorReport>
|
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="10.0.2" />
|
||||||
<WarningLevel>4</WarningLevel>
|
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.2" />
|
||||||
</PropertyGroup>
|
<PackageReference Include="Microsoft.Extensions.Logging" Version="10.0.2" />
|
||||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.2" />
|
||||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
<PackageReference Include="Microsoft.Extensions.Options" Version="10.0.2" />
|
||||||
<DebugType>pdbonly</DebugType>
|
<PackageReference Include="Microsoft.Extensions.Primitives" Version="10.0.2" />
|
||||||
<Optimize>true</Optimize>
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.4" />
|
||||||
<OutputPath>bin\Release\ClipTrimDotNet.sdPlugin\</OutputPath>
|
<PackageReference Include="NLog" Version="6.0.5" />
|
||||||
<DefineConstants>TRACE</DefineConstants>
|
<PackageReference Include="SocketIOClient" Version="4.0.0.2" />
|
||||||
<ErrorReport>prompt</ErrorReport>
|
<PackageReference Include="SocketIOClient.Common" Version="4.0.0" />
|
||||||
<WarningLevel>4</WarningLevel>
|
<PackageReference Include="SocketIOClient.Serializer" Version="4.0.0.1" />
|
||||||
</PropertyGroup>
|
<PackageReference Include="SocketIOClient.Serializer.NewtonsoftJson" Version="4.0.0.1" />
|
||||||
<ItemGroup>
|
<PackageReference Include="StreamDeck-Tools" Version="6.3.2" />
|
||||||
<Reference Include="CommandLine, Version=2.9.1.0, Culture=neutral, PublicKeyToken=5a870481e358d379, processorArchitecture=MSIL">
|
<PackageReference Include="System.Diagnostics.DiagnosticSource" Version="10.0.2" />
|
||||||
<HintPath>..\packages\CommandLineParser.2.9.1\lib\net461\CommandLine.dll</HintPath>
|
<PackageReference Include="System.Drawing.Common" Version="9.0.10" />
|
||||||
</Reference>
|
<PackageReference Include="System.IO.Pipelines" Version="10.0.2" />
|
||||||
<Reference Include="Microsoft.Win32.Registry, Version=4.1.3.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="6.1.2" />
|
||||||
<HintPath>..\packages\Microsoft.Win32.Registry.4.7.0\lib\net461\Microsoft.Win32.Registry.dll</HintPath>
|
<PackageReference Include="System.Security.AccessControl" Version="6.0.1" />
|
||||||
</Reference>
|
<PackageReference Include="System.Text.Encodings.Web" Version="10.0.2" />
|
||||||
<Reference Include="NAudio, Version=2.2.1.0, Culture=neutral, PublicKeyToken=e279aa5131008a41, processorArchitecture=MSIL">
|
<PackageReference Include="System.Text.Json" Version="10.0.2" />
|
||||||
<HintPath>..\packages\NAudio.2.2.1\lib\net472\NAudio.dll</HintPath>
|
<PackageReference Include="System.Configuration.ConfigurationManager" Version="10.0.3" />
|
||||||
</Reference>
|
<PackageReference Include="CoreWCF.Primitives" Version="1.8.0" />
|
||||||
<Reference Include="NAudio.Asio, Version=2.2.1.0, Culture=neutral, PublicKeyToken=e279aa5131008a41, processorArchitecture=MSIL">
|
<PackageReference Include="CoreWCF.ConfigurationManager" Version="1.8.0" />
|
||||||
<HintPath>..\packages\NAudio.Asio.2.2.1\lib\netstandard2.0\NAudio.Asio.dll</HintPath>
|
<PackageReference Include="CoreWCF.Http" Version="1.8.0" />
|
||||||
</Reference>
|
<PackageReference Include="CoreWCF.WebHttp" Version="1.8.0" />
|
||||||
<Reference Include="NAudio.Core, Version=2.2.1.0, Culture=neutral, PublicKeyToken=e279aa5131008a41, processorArchitecture=MSIL">
|
<PackageReference Include="CoreWCF.NetTcp" Version="1.8.0" />
|
||||||
<HintPath>..\packages\NAudio.Core.2.2.1\lib\netstandard2.0\NAudio.Core.dll</HintPath>
|
</ItemGroup>
|
||||||
</Reference>
|
<ItemGroup>
|
||||||
<Reference Include="NAudio.Midi, Version=2.2.1.0, Culture=neutral, PublicKeyToken=e279aa5131008a41, processorArchitecture=MSIL">
|
<None Update="DialLayout.json">
|
||||||
<HintPath>..\packages\NAudio.Midi.2.2.1\lib\netstandard2.0\NAudio.Midi.dll</HintPath>
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
</Reference>
|
</None>
|
||||||
<Reference Include="NAudio.Wasapi, Version=2.2.1.0, Culture=neutral, PublicKeyToken=e279aa5131008a41, processorArchitecture=MSIL">
|
<None Update="manifest.json">
|
||||||
<HintPath>..\packages\NAudio.Wasapi.2.2.1\lib\netstandard2.0\NAudio.Wasapi.dll</HintPath>
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
</Reference>
|
</None>
|
||||||
<Reference Include="NAudio.WinForms, Version=2.2.1.0, Culture=neutral, PublicKeyToken=e279aa5131008a41, processorArchitecture=MSIL">
|
</ItemGroup>
|
||||||
<HintPath>..\packages\NAudio.WinForms.2.2.1\lib\net472\NAudio.WinForms.dll</HintPath>
|
<ItemGroup>
|
||||||
</Reference>
|
<Content Include="!!README!!.txt" />
|
||||||
<Reference Include="NAudio.WinMM, Version=2.2.1.0, Culture=neutral, PublicKeyToken=e279aa5131008a41, processorArchitecture=MSIL">
|
<Content Include="Images\categoryIcon%402x.png">
|
||||||
<HintPath>..\packages\NAudio.WinMM.2.2.1\lib\netstandard2.0\NAudio.WinMM.dll</HintPath>
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
</Reference>
|
</Content>
|
||||||
<Reference Include="Newtonsoft.Json, Version=13.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
|
<Content Include="Images\categoryIcon.png">
|
||||||
<HintPath>..\packages\Newtonsoft.Json.13.0.4\lib\net45\Newtonsoft.Json.dll</HintPath>
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
</Reference>
|
</Content>
|
||||||
<Reference Include="NLog, Version=6.0.0.0, Culture=neutral, PublicKeyToken=5120e14c03d0593c, processorArchitecture=MSIL">
|
<Content Include="Images\icon%402x.png">
|
||||||
<HintPath>..\packages\NLog.6.0.5\lib\net46\NLog.dll</HintPath>
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
</Reference>
|
</Content>
|
||||||
<Reference Include="StreamDeckTools, Version=6.3.2.0, Culture=neutral, processorArchitecture=MSIL">
|
<Content Include="Images\icon.png">
|
||||||
<HintPath>..\packages\StreamDeck-Tools.6.3.2\lib\netstandard2.0\StreamDeckTools.dll</HintPath>
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
</Reference>
|
</Content>
|
||||||
<Reference Include="System" />
|
<Content Include="Images\pluginAction%402x.png">
|
||||||
<Reference Include="System.Configuration" />
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
<Reference Include="System.Core" />
|
</Content>
|
||||||
<Reference Include="System.Drawing" />
|
<Content Include="Images\pluginAction.png">
|
||||||
<Reference Include="System.Drawing.Common, Version=9.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
<HintPath>..\packages\System.Drawing.Common.9.0.10\lib\net462\System.Drawing.Common.dll</HintPath>
|
</Content>
|
||||||
</Reference>
|
<Content Include="Images\pluginIcon%402x.png">
|
||||||
<Reference Include="System.IO.Compression" />
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
<Reference Include="System.Runtime.Serialization" />
|
</Content>
|
||||||
<Reference Include="System.Security.AccessControl, Version=4.1.3.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
<Content Include="Images\pluginIcon.png">
|
||||||
<HintPath>..\packages\System.Security.AccessControl.4.7.0\lib\net461\System.Security.AccessControl.dll</HintPath>
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
</Reference>
|
</Content>
|
||||||
<Reference Include="System.Security.Principal.Windows, Version=4.1.3.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
<Content Include="package.json" />
|
||||||
<HintPath>..\packages\System.Security.Principal.Windows.4.7.0\lib\net461\System.Security.Principal.Windows.dll</HintPath>
|
<Content Include="PropertyInspector\profile_swticher.html">
|
||||||
</Reference>
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
<Reference Include="System.ServiceModel" />
|
</Content>
|
||||||
<Reference Include="System.Transactions" />
|
<Content Include="PropertyInspector\file_player.html">
|
||||||
<Reference Include="System.Xml.Linq" />
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
<Reference Include="System.Data.DataSetExtensions" />
|
</Content>
|
||||||
<Reference Include="Microsoft.CSharp" />
|
</ItemGroup>
|
||||||
<Reference Include="System.Data" />
|
|
||||||
<Reference Include="System.Net.Http" />
|
|
||||||
<Reference Include="System.Xml" />
|
|
||||||
</ItemGroup>
|
|
||||||
<ItemGroup>
|
|
||||||
<Compile Include="BaseTest.cs" />
|
|
||||||
<Compile Include="Client\ClipMetadata.cs" />
|
|
||||||
<Compile Include="Client\ClipTrimClient.cs" />
|
|
||||||
<Compile Include="Client\CollectionMetaData.cs" />
|
|
||||||
<Compile Include="GlobalSettings.cs" />
|
|
||||||
<Compile Include="Player.cs" />
|
|
||||||
<Compile Include="ProfileSwitcher.cs" />
|
|
||||||
<Compile Include="Program.cs" />
|
|
||||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
|
||||||
<Compile Include="WavPlayer.cs" />
|
|
||||||
</ItemGroup>
|
|
||||||
<ItemGroup>
|
|
||||||
<None Include="App.config" />
|
|
||||||
<None Include="DialLayout.json">
|
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
|
||||||
</None>
|
|
||||||
<None Include="manifest.json">
|
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
|
||||||
</None>
|
|
||||||
<None Include="packages.config" />
|
|
||||||
</ItemGroup>
|
|
||||||
<ItemGroup>
|
|
||||||
<Content Include="!!README!!.txt" />
|
|
||||||
<Content Include="Images\categoryIcon%402x.png">
|
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
|
||||||
</Content>
|
|
||||||
<Content Include="Images\categoryIcon.png">
|
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
|
||||||
</Content>
|
|
||||||
<Content Include="Images\icon%402x.png">
|
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
|
||||||
</Content>
|
|
||||||
<Content Include="Images\icon.png">
|
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
|
||||||
</Content>
|
|
||||||
<Content Include="Images\pluginAction%402x.png">
|
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
|
||||||
</Content>
|
|
||||||
<Content Include="Images\pluginAction.png">
|
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
|
||||||
</Content>
|
|
||||||
<Content Include="Images\pluginIcon%402x.png">
|
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
|
||||||
</Content>
|
|
||||||
<Content Include="Images\pluginIcon.png">
|
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
|
||||||
</Content>
|
|
||||||
<Content Include="package.json" />
|
|
||||||
<Content Include="PropertyInspector\profile_swticher.html">
|
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
|
||||||
</Content>
|
|
||||||
<Content Include="PropertyInspector\file_player.html">
|
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
|
||||||
</Content>
|
|
||||||
</ItemGroup>
|
|
||||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
|
||||||
<PropertyGroup>
|
|
||||||
<PreBuildEvent>npm run stop</PreBuildEvent>
|
|
||||||
</PropertyGroup>
|
|
||||||
<PropertyGroup>
|
|
||||||
<PostBuildEvent>npm run start</PostBuildEvent>
|
|
||||||
</PropertyGroup>
|
|
||||||
</Project>
|
</Project>
|
||||||
@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|AnyCPU'">
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|AnyCPU'">
|
||||||
<StartArguments>--port 23654 --pluginUUID com.michal-courson.cliptrim --registerEvent restart --info {}</StartArguments>
|
<StartArguments>--port 23654 --pluginUUID com.michal-courson.cliptrim --registerEvent restart --info {}</StartArguments>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
</Project>
|
</Project>
|
||||||
@ -1,40 +1,40 @@
|
|||||||
{
|
{
|
||||||
"id": "sampleDial",
|
"id": "sampleDial",
|
||||||
"items": [
|
"items": [
|
||||||
{
|
{
|
||||||
"key": "title",
|
"key": "title",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"rect": [ 16, 10, 136, 24 ],
|
"rect": [ 16, 10, 136, 24 ],
|
||||||
"font": {
|
"font": {
|
||||||
"size": 16,
|
"size": 16,
|
||||||
"weight": 600
|
"weight": 600
|
||||||
},
|
},
|
||||||
"alignment": "left"
|
"alignment": "left"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"key": "icon",
|
"key": "icon",
|
||||||
"type": "pixmap",
|
"type": "pixmap",
|
||||||
"rect": [ 16, 40, 48, 48 ]
|
"rect": [ 16, 40, 48, 48 ]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"key": "value",
|
"key": "value",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"rect": [ 76, 40, 108, 32 ],
|
"rect": [ 76, 40, 108, 32 ],
|
||||||
"font": {
|
"font": {
|
||||||
"size": 24,
|
"size": 24,
|
||||||
"weight": 600
|
"weight": 600
|
||||||
},
|
},
|
||||||
"alignment": "right"
|
"alignment": "right"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"key": "indicator",
|
"key": "indicator",
|
||||||
"type": "gbar",
|
"type": "gbar",
|
||||||
"rect": [ 76, 74, 108, 20 ],
|
"rect": [ 76, 74, 108, 20 ],
|
||||||
"value": 0,
|
"value": 0,
|
||||||
"subtype": 4,
|
"subtype": 4,
|
||||||
"bar_h": 12,
|
"bar_h": 12,
|
||||||
"border_w": 0,
|
"border_w": 0,
|
||||||
"bar_bg_c": "0:#427018,0.75:#705B1C,0.90:#702735,1:#702735"
|
"bar_bg_c": "0:#427018,0.75:#705B1C,0.90:#702735,1:#702735"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@ -1,109 +1,45 @@
|
|||||||
using BarRaider.SdTools;
|
using BarRaider.SdTools;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using BarRaider.SdTools.Wrappers;
|
using BarRaider.SdTools.Wrappers;
|
||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
using NAudio.MediaFoundation;
|
|
||||||
|
namespace ClipTrimDotNet
|
||||||
namespace ClipTrimDotNet
|
{
|
||||||
{
|
public class GlobalSettings
|
||||||
public class FileEntry
|
{
|
||||||
{
|
public static GlobalSettings? _inst;
|
||||||
public FileEntry()
|
public static GlobalSettings Instance
|
||||||
{
|
{
|
||||||
Volume = 1.0;
|
get
|
||||||
Playtype = "Play/Overlap";
|
{
|
||||||
}
|
_inst ??= CreateDefaultSettings();
|
||||||
[JsonProperty(PropertyName = "Volume")]
|
return _inst;
|
||||||
public double Volume { get; set; }
|
}
|
||||||
[JsonProperty(PropertyName = "Playtype")]
|
set
|
||||||
public string Playtype { get; set; }
|
{
|
||||||
}
|
_inst = value;
|
||||||
public class CollectionEntry
|
}
|
||||||
{
|
}
|
||||||
public CollectionEntry()
|
public static GlobalSettings CreateDefaultSettings()
|
||||||
{
|
{
|
||||||
Files = new Dictionary<string, FileEntry>();
|
GlobalSettings instance = new GlobalSettings();
|
||||||
}
|
instance.ProfileName = null;
|
||||||
[JsonProperty(PropertyName = "Files")]
|
return instance;
|
||||||
public Dictionary<string, FileEntry> Files { get; set; }
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public class GlobalSettings
|
[JsonProperty(PropertyName = "profileName")]
|
||||||
{
|
public string? ProfileName { get; set; }
|
||||||
public static GlobalSettings? _inst;
|
|
||||||
public static GlobalSettings Instance
|
|
||||||
{
|
public void SetCurrentProfile(string profile)
|
||||||
get
|
{
|
||||||
{
|
ProfileName = profile;
|
||||||
_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<string, CollectionEntry>();
|
|
||||||
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<string, CollectionEntry> 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
49
stream_deck_plugin/ClipTrimDotNet/Keys/ClipSave.cs
Normal file
49
stream_deck_plugin/ClipTrimDotNet/Keys/ClipSave.cs
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
using BarRaider.SdTools;
|
||||||
|
using ClipTrimDotNet.Client;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace ClipTrimDotNet.Keys
|
||||||
|
{
|
||||||
|
[PluginActionId("com.michal-courson.cliptrim.clip-save")]
|
||||||
|
public class ClipSave : KeypadBase
|
||||||
|
{
|
||||||
|
public ClipSave(SDConnection connection, InitialPayload payload) : base(connection, payload)
|
||||||
|
{
|
||||||
|
GlobalSettingsManager.Instance.RequestGlobalSettings();
|
||||||
|
}
|
||||||
|
public void Instance_OnReceivedGlobalSettings(object sender, ReceivedGlobalSettingsPayload e)
|
||||||
|
{
|
||||||
|
Tools.AutoPopulateSettings(GlobalSettings.Instance, e.Settings);
|
||||||
|
}
|
||||||
|
public override void Dispose()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
public override void KeyPressed(KeyPayload payload)
|
||||||
|
{
|
||||||
|
ClipTrimClient.Instance.SaveClip();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public override void OnTick()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void KeyReleased(KeyPayload payload)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void ReceivedSettings(ReceivedSettingsPayload payload)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
public override void ReceivedGlobalSettings(ReceivedGlobalSettingsPayload payload)
|
||||||
|
{
|
||||||
|
Tools.AutoPopulateSettings(GlobalSettings.Instance, payload.Settings);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
107
stream_deck_plugin/ClipTrimDotNet/Keys/PageNavigator.cs
Normal file
107
stream_deck_plugin/ClipTrimDotNet/Keys/PageNavigator.cs
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
using BarRaider.SdTools;
|
||||||
|
using ClipTrimDotNet.Client;
|
||||||
|
using Microsoft.AspNetCore.Authentication;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace ClipTrimDotNet.Keys
|
||||||
|
{
|
||||||
|
[PluginActionId("com.michal-courson.cliptrim.page-navigator")]
|
||||||
|
public class PageNavigator : KeypadBase
|
||||||
|
{
|
||||||
|
static List<PageNavigator> instances = new();
|
||||||
|
private KeyCoordinates coordinates;
|
||||||
|
public PageNavigator(SDConnection connection, InitialPayload payload) : base(connection, payload)
|
||||||
|
{
|
||||||
|
coordinates = payload.Coordinates;
|
||||||
|
GlobalSettingsManager.Instance.RequestGlobalSettings();
|
||||||
|
instances.Add(this);
|
||||||
|
OnTick();
|
||||||
|
}
|
||||||
|
public void Instance_OnReceivedGlobalSettings(object sender, ReceivedGlobalSettingsPayload e)
|
||||||
|
{
|
||||||
|
Tools.AutoPopulateSettings(GlobalSettings.Instance, e.Settings);
|
||||||
|
}
|
||||||
|
public int GetIndex()
|
||||||
|
{
|
||||||
|
int index = Math.Min(Math.Max(coordinates.Column - 1, 0), 2);
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
public override void Dispose()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
public override void KeyPressed(KeyPayload payload)
|
||||||
|
{
|
||||||
|
switch (GetIndex())
|
||||||
|
{
|
||||||
|
case 0:
|
||||||
|
ClipTrimClient.Instance.PageDown();
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
ClipTrimClient.Instance.PageMode = !ClipTrimClient.Instance.PageMode;
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
ClipTrimClient.Instance.PageUp();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
Player.TickAll();
|
||||||
|
TickAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void TickAll()
|
||||||
|
{
|
||||||
|
foreach (var instance in instances)
|
||||||
|
{
|
||||||
|
instance.OnTick();
|
||||||
|
}
|
||||||
|
instances.RemoveAll(i => i == null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void SetTitle()
|
||||||
|
{
|
||||||
|
switch (GetIndex())
|
||||||
|
{
|
||||||
|
case 0:
|
||||||
|
await Connection.SetTitleAsync(ClipTrimClient.Instance.CanPageDown ? "<" : "");
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
await Connection.SetTitleAsync((ClipTrimClient.Instance.PageIndex + 1).ToString());
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
await Connection.SetTitleAsync(ClipTrimClient.Instance.CanPageUp ? ">" : "");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnTick()
|
||||||
|
{
|
||||||
|
SetTitle();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void KeyReleased(KeyPayload payload)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void ReceivedSettings(ReceivedSettingsPayload payload)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
public override void ReceivedGlobalSettings(ReceivedGlobalSettingsPayload payload)
|
||||||
|
{
|
||||||
|
Tools.AutoPopulateSettings(GlobalSettings.Instance, payload.Settings);
|
||||||
|
//if (payload.Settings == null || payload.Settings.Count == 0)
|
||||||
|
//{
|
||||||
|
// var inst = GlobalSettings.Instance;
|
||||||
|
// //GlobalSettingsManager.Instance.SetGlobalSettings(JObject.FromObject(inst));
|
||||||
|
//}
|
||||||
|
//else
|
||||||
|
//{
|
||||||
|
// GlobalSettings.Instance = payload.Settings.ToObject<GlobalSettings>();
|
||||||
|
//}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
87
stream_deck_plugin/ClipTrimDotNet/Keys/Player.cs
Normal file
87
stream_deck_plugin/ClipTrimDotNet/Keys/Player.cs
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
using BarRaider.SdTools;
|
||||||
|
using BarRaider.SdTools.Wrappers;
|
||||||
|
using ClipTrimDotNet.Client;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Drawing;
|
||||||
|
using System.Dynamic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace ClipTrimDotNet.Keys
|
||||||
|
{
|
||||||
|
[PluginActionId("com.michal-courson.cliptrim.player")]
|
||||||
|
public class Player : KeypadBase
|
||||||
|
{
|
||||||
|
|
||||||
|
private int coordIndex;
|
||||||
|
private string? current_text = null;
|
||||||
|
private KeyCoordinates coordinates;
|
||||||
|
|
||||||
|
static List<Player> instances = new();
|
||||||
|
|
||||||
|
public Player(SDConnection connection, InitialPayload payload) : base(connection, payload)
|
||||||
|
{
|
||||||
|
coordinates = payload.Coordinates;
|
||||||
|
GlobalSettingsManager.Instance.RequestGlobalSettings();
|
||||||
|
instances.Add(this);
|
||||||
|
coordIndex = Math.Max((coordinates.Row - 1) * 5 + coordinates.Column, 0);
|
||||||
|
OnTick();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void TickAll()
|
||||||
|
{
|
||||||
|
Logger.Instance.LogMessage(TracingLevel.INFO, "Ticking all Player instances" + instances.Count);
|
||||||
|
foreach (var instance in instances)
|
||||||
|
{
|
||||||
|
instance.OnTick();
|
||||||
|
}
|
||||||
|
instances.RemoveAll(i => i == null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void CheckFile()
|
||||||
|
{
|
||||||
|
var next_text = ClipTrimClient.Instance.GetPlayerStringByCoordinateIndex(coordIndex);
|
||||||
|
if(next_text != current_text)
|
||||||
|
{
|
||||||
|
current_text = next_text;
|
||||||
|
await Connection.SetTitleAsync($"{current_text ?? ""}");
|
||||||
|
}
|
||||||
|
current_text = next_text;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Dispose()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void KeyPressed(KeyPayload payload)
|
||||||
|
{
|
||||||
|
ClipTrimClient.Instance.PlayClip(coordIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public override void OnTick() {
|
||||||
|
CheckFile();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public override void ReceivedGlobalSettings(ReceivedGlobalSettingsPayload payload) {
|
||||||
|
Tools.AutoPopulateSettings(GlobalSettings.Instance, payload.Settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void KeyReleased(KeyPayload payload)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void ReceivedSettings(ReceivedSettingsPayload payload)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
136
stream_deck_plugin/ClipTrimDotNet/Keys/ProfileSwitcher.cs
Normal file
136
stream_deck_plugin/ClipTrimDotNet/Keys/ProfileSwitcher.cs
Normal file
@ -0,0 +1,136 @@
|
|||||||
|
using BarRaider.SdTools;
|
||||||
|
using BarRaider.SdTools.Wrappers;
|
||||||
|
using ClipTrimDotNet.Client;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Drawing;
|
||||||
|
using System.Dynamic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace ClipTrimDotNet.Keys
|
||||||
|
{
|
||||||
|
public class DataSourceItem
|
||||||
|
{
|
||||||
|
public string label { get; set; } = "";
|
||||||
|
public string value { get; set; } = "";
|
||||||
|
}
|
||||||
|
[PluginActionId("com.michal-courson.cliptrim.profile-switcher")]
|
||||||
|
public class ProfileSwitcher : KeypadBase
|
||||||
|
{
|
||||||
|
private class PluginSettings
|
||||||
|
{
|
||||||
|
public static PluginSettings CreateDefaultSettings()
|
||||||
|
{
|
||||||
|
PluginSettings instance = new PluginSettings();
|
||||||
|
instance.ProfileName = null;
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
[JsonProperty(PropertyName = "profileName")]
|
||||||
|
public string? ProfileName { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Private Members
|
||||||
|
|
||||||
|
private PluginSettings settings;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
public ProfileSwitcher(SDConnection connection, InitialPayload payload) : base(connection, payload)
|
||||||
|
{
|
||||||
|
if (payload.Settings == null || payload.Settings.Count == 0)
|
||||||
|
{
|
||||||
|
settings = PluginSettings.CreateDefaultSettings();
|
||||||
|
SaveSettings();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
settings = payload.Settings.ToObject<PluginSettings>()!;
|
||||||
|
}
|
||||||
|
Logger.Instance.LogMessage(TracingLevel.INFO, $"ProfileSwitcher initialized with payload {JsonConvert.SerializeObject(payload)}");
|
||||||
|
GlobalSettingsManager.Instance.RequestGlobalSettings();
|
||||||
|
Connection.OnSendToPlugin += Connection_OnSendToPlugin;
|
||||||
|
SetTitle();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void SetTitle()
|
||||||
|
{
|
||||||
|
|
||||||
|
await Connection.SetTitleAsync(settings.ProfileName + " B");
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void Connection_OnSendToPlugin(object? sender, SDEventReceivedEventArgs<BarRaider.SdTools.Events.SendToPlugin> e)
|
||||||
|
{
|
||||||
|
//Logger.Instance.LogMessage(TracingLevel.INFO, "get profiles");
|
||||||
|
if (e.Event.Payload["event"] 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??"");
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void ReceivedGlobalSettings(ReceivedGlobalSettingsPayload payload)
|
||||||
|
{
|
||||||
|
Tools.AutoPopulateSettings(GlobalSettings.Instance, payload.Settings);
|
||||||
|
//if (payload.Settings == null || payload.Settings.Count == 0)
|
||||||
|
//{
|
||||||
|
// var inst = GlobalSettings.Instance;
|
||||||
|
//}
|
||||||
|
//else
|
||||||
|
//{
|
||||||
|
// GlobalSettings.Instance = payload.Settings.ToObject<GlobalSettings>();
|
||||||
|
//}
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Private Methods
|
||||||
|
|
||||||
|
private Task SaveSettings()
|
||||||
|
{
|
||||||
|
return Connection.SetSettingsAsync(JObject.FromObject(settings));
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,193 +0,0 @@
|
|||||||
using BarRaider.SdTools;
|
|
||||||
using BarRaider.SdTools.Wrappers;
|
|
||||||
using ClipTrimDotNet.Client;
|
|
||||||
using NAudio.CoreAudioApi.Interfaces;
|
|
||||||
using Newtonsoft.Json;
|
|
||||||
using Newtonsoft.Json.Linq;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Drawing;
|
|
||||||
using System.Dynamic;
|
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace ClipTrimDotNet
|
|
||||||
{
|
|
||||||
[PluginActionId("com.michal-courson.cliptrim.player")]
|
|
||||||
public class Player : KeypadBase
|
|
||||||
{
|
|
||||||
|
|
||||||
private ClipMetadata? metadata;
|
|
||||||
private KeyCoordinates coordinates;
|
|
||||||
private class PluginSettings
|
|
||||||
{
|
|
||||||
public static PluginSettings CreateDefaultSettings()
|
|
||||||
{
|
|
||||||
PluginSettings instance = new PluginSettings();
|
|
||||||
instance.Path = null;
|
|
||||||
instance.PlayType = "Play/Overlap";
|
|
||||||
instance.Index = 0;
|
|
||||||
instance.Volume = 1;
|
|
||||||
return instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
[FilenameProperty]
|
|
||||||
[JsonProperty(PropertyName = "path")]
|
|
||||||
public string? Path { get; set; }
|
|
||||||
|
|
||||||
[JsonProperty(PropertyName = "index")]
|
|
||||||
public int? Index { get; set; }
|
|
||||||
[JsonProperty(PropertyName = "playtype")]
|
|
||||||
public string PlayType { get; set; }
|
|
||||||
|
|
||||||
[JsonProperty(PropertyName = "volume")]
|
|
||||||
public double Volume { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
#region Private Members
|
|
||||||
|
|
||||||
private PluginSettings settings;
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
public Player(SDConnection connection, InitialPayload payload) : base(connection, payload)
|
|
||||||
{
|
|
||||||
if (payload.Settings == null || payload.Settings.Count == 0)
|
|
||||||
{
|
|
||||||
this.settings = PluginSettings.CreateDefaultSettings();
|
|
||||||
SaveSettings();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
this.settings = payload.Settings.ToObject<PluginSettings>();
|
|
||||||
}
|
|
||||||
this.coordinates = payload.Coordinates;
|
|
||||||
GlobalSettingsManager.Instance.RequestGlobalSettings();
|
|
||||||
CheckFile();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void Instance_OnReceivedGlobalSettings(object sender, ReceivedGlobalSettingsPayload e)
|
|
||||||
{
|
|
||||||
Tools.AutoPopulateSettings(GlobalSettings.Instance, e.Settings);
|
|
||||||
}
|
|
||||||
|
|
||||||
public int GetIndex()
|
|
||||||
{
|
|
||||||
return Math.Max((coordinates.Row - 1) * 5 + coordinates.Column, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async void CheckFile()
|
|
||||||
{
|
|
||||||
|
|
||||||
//if (settings == null || GlobalSettings.Instance.ProfileName ==null) return;
|
|
||||||
metadata = ClipTrimClient.Instance.GetClipByPagedIndex(GetIndex());
|
|
||||||
await Connection.SetTitleAsync($"{metadata?.Name ?? ""}");
|
|
||||||
|
|
||||||
return;
|
|
||||||
|
|
||||||
//var files = Directory.GetFiles(Path.Combine(Path.GetDirectoryName(GlobalSettings.Instance.BasePath), GlobalSettings.Instance.ProfileName), "*.wav", SearchOption.TopDirectoryOnly)
|
|
||||||
// .OrderBy(file => File.GetCreationTime(file))
|
|
||||||
// .ToArray();
|
|
||||||
//int? i = this.settings.Index;
|
|
||||||
//string new_path = "";
|
|
||||||
//if (i != null && i >= 0 && i < files.Length)
|
|
||||||
//{
|
|
||||||
// new_path = files[i ?? 0];
|
|
||||||
//}
|
|
||||||
|
|
||||||
|
|
||||||
//await Connection.SetTitleAsync(Path.GetFileNameWithoutExtension(new_path));
|
|
||||||
//if (new_path != settings.Path)
|
|
||||||
//{
|
|
||||||
// settings.Path = new_path;
|
|
||||||
// if(new_path != "")
|
|
||||||
// {
|
|
||||||
// FileEntry opts = GlobalSettings.Instance.GetFileOptionsInCurrentProfile(new_path);
|
|
||||||
// settings.Volume = opts.Volume;
|
|
||||||
// settings.PlayType = opts.Playtype;
|
|
||||||
// }
|
|
||||||
// await SaveSettings();
|
|
||||||
//}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private async void Connection_OnApplicationDidLaunch(object sender, BarRaider.SdTools.Wrappers.SDEventReceivedEventArgs<BarRaider.SdTools.Events.ApplicationDidLaunch> e)
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private void Connection_OnTitleParametersDidChange(object sender, SDEventReceivedEventArgs<BarRaider.SdTools.Events.TitleParametersDidChange> e)
|
|
||||||
{
|
|
||||||
//titleParameters = e.Event?.Payload?.TitleParameters;
|
|
||||||
//userTitle = e.Event?.Payload?.Title;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Dispose()
|
|
||||||
{
|
|
||||||
Connection.OnTitleParametersDidChange -= Connection_OnTitleParametersDidChange;
|
|
||||||
Connection.OnApplicationDidLaunch -= Connection_OnApplicationDidLaunch;
|
|
||||||
//Logger.Instance.LogMessage(TracingLevel.INFO, $"Destructor called");
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void KeyPressed(KeyPayload payload)
|
|
||||||
{
|
|
||||||
//Logger.Instance.LogMessage(TracingLevel.INFO, "Key Pressedd");
|
|
||||||
Tools.AutoPopulateSettings(settings, payload.Settings);
|
|
||||||
// Logger.Instance.LogMessage(TracingLevel.INFO, JsonConvert.SerializeObject(settings));
|
|
||||||
ClipTrimClient.Instance.PlayClip(metadata);
|
|
||||||
//try
|
|
||||||
//{
|
|
||||||
// WavPlayer.Instance.Play(settings.Path, GlobalSettings.Instance.OutputDevice, settings.Volume, settings.PlayType == "Play/Overlap" ? WavPlayer.PlayMode.PlayOverlap : WavPlayer.PlayMode.PlayStop);
|
|
||||||
//}
|
|
||||||
//catch
|
|
||||||
//{
|
|
||||||
|
|
||||||
//}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void KeyReleased(KeyPayload payload) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void OnTick() {
|
|
||||||
CheckFile();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override async void ReceivedSettings(ReceivedSettingsPayload payload)
|
|
||||||
{
|
|
||||||
Logger.Instance.LogMessage(TracingLevel.INFO, "Player rec settings");
|
|
||||||
Tools.AutoPopulateSettings(settings, payload.Settings);
|
|
||||||
GlobalSettings.Instance.SetFileOptionsCurrentProfile(settings.Path, new FileEntry() { Playtype = settings.PlayType, Volume = settings.Volume });
|
|
||||||
await Connection.SetGlobalSettingsAsync(JObject.FromObject(GlobalSettings.Instance));
|
|
||||||
//SaveSettings();
|
|
||||||
//CheckFile();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void ReceivedGlobalSettings(ReceivedGlobalSettingsPayload payload) {
|
|
||||||
//Logger.Instance.LogMessage(TracingLevel.INFO, "ReceivedGlobalSettings");
|
|
||||||
|
|
||||||
if (payload.Settings == null || payload.Settings.Count == 0)
|
|
||||||
{
|
|
||||||
var inst = GlobalSettings.Instance;
|
|
||||||
//GlobalSettingsManager.Instance.SetGlobalSettings(JObject.FromObject(inst));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
GlobalSettings.Instance = payload.Settings.ToObject<GlobalSettings>();
|
|
||||||
}
|
|
||||||
|
|
||||||
//CheckFile();
|
|
||||||
}
|
|
||||||
|
|
||||||
#region Private Methods
|
|
||||||
|
|
||||||
private Task SaveSettings()
|
|
||||||
{
|
|
||||||
return Connection.SetSettingsAsync(JObject.FromObject(settings));
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,167 +0,0 @@
|
|||||||
using BarRaider.SdTools;
|
|
||||||
using BarRaider.SdTools.Wrappers;
|
|
||||||
using ClipTrimDotNet.Client;
|
|
||||||
using NAudio.CoreAudioApi.Interfaces;
|
|
||||||
using NAudio.Wave;
|
|
||||||
using Newtonsoft.Json;
|
|
||||||
using Newtonsoft.Json.Linq;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Drawing;
|
|
||||||
using System.Dynamic;
|
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace ClipTrimDotNet
|
|
||||||
{
|
|
||||||
public class DataSourceItem
|
|
||||||
{
|
|
||||||
public string label { get; set; }
|
|
||||||
public string value { get; set; }
|
|
||||||
}
|
|
||||||
[PluginActionId("com.michal-courson.cliptrim.profile-switcher")]
|
|
||||||
public class ProfileSwitcher : KeypadBase
|
|
||||||
{
|
|
||||||
private class PluginSettings
|
|
||||||
{
|
|
||||||
public static PluginSettings CreateDefaultSettings()
|
|
||||||
{
|
|
||||||
PluginSettings instance = new PluginSettings();
|
|
||||||
instance.ProfileName = null;
|
|
||||||
return instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
[JsonProperty(PropertyName = "profileName")]
|
|
||||||
public string? ProfileName { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
#region Private Members
|
|
||||||
|
|
||||||
private PluginSettings settings;
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
public ProfileSwitcher(SDConnection connection, InitialPayload payload) : base(connection, payload)
|
|
||||||
{
|
|
||||||
if (payload.Settings == null || payload.Settings.Count == 0)
|
|
||||||
{
|
|
||||||
this.settings = PluginSettings.CreateDefaultSettings();
|
|
||||||
SaveSettings();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
this.settings = payload.Settings.ToObject<PluginSettings>();
|
|
||||||
}
|
|
||||||
GlobalSettingsManager.Instance.RequestGlobalSettings();
|
|
||||||
Connection.OnSendToPlugin += Connection_OnSendToPlugin;
|
|
||||||
SetTitle();
|
|
||||||
}
|
|
||||||
|
|
||||||
private async void SetTitle()
|
|
||||||
{
|
|
||||||
|
|
||||||
await Connection.SetTitleAsync(settings.ProfileName + " A");
|
|
||||||
}
|
|
||||||
|
|
||||||
private async void Connection_OnSendToPlugin(object sender, SDEventReceivedEventArgs<BarRaider.SdTools.Events.SendToPlugin> e)
|
|
||||||
{
|
|
||||||
//Logger.Instance.LogMessage(TracingLevel.INFO, "get profiles");
|
|
||||||
if (e.Event.Payload["event"].ToString() == "getProfiles")
|
|
||||||
{
|
|
||||||
//string basePath = "C:\\Users\\mickl\\Music\\clips";
|
|
||||||
//var files = Directory.GetDirectories(basePath, "*", SearchOption.TopDirectoryOnly).Select(x => Path.GetFileNameWithoutExtension(x)).Where(x => x != "original");
|
|
||||||
var files = ClipTrimClient.Instance.GetCollectionNames();
|
|
||||||
var items = files.Select(x => new DataSourceItem { label = x, value = x});
|
|
||||||
var obj = new JObject();
|
|
||||||
obj["event"] = "getProfiles";
|
|
||||||
obj["items"] = JArray.FromObject(items);
|
|
||||||
//Logger.Instance.LogMessage(TracingLevel.INFO, "get profiles return " + JsonConvert.SerializeObject(obj));
|
|
||||||
await Connection.SendToPropertyInspectorAsync(obj);
|
|
||||||
}
|
|
||||||
if (e.Event.Payload["event"].ToString() == "getOutputDevices")
|
|
||||||
{
|
|
||||||
List<WaveOutCapabilities> devices = new List<WaveOutCapabilities>();
|
|
||||||
for (int n = -1; n < WaveOut.DeviceCount; n++)
|
|
||||||
{
|
|
||||||
var caps = WaveOut.GetCapabilities(n);
|
|
||||||
devices.Add(caps);
|
|
||||||
}
|
|
||||||
var items = devices.Select(x => new DataSourceItem { label = x.ProductName, value = x.ProductName });
|
|
||||||
var obj = new JObject();
|
|
||||||
obj["event"] = "getOutputDevices";
|
|
||||||
obj["items"] = JArray.FromObject(items);
|
|
||||||
//Logger.Instance.LogMessage(TracingLevel.INFO, "get devices return " + JsonConvert.SerializeObject(obj));
|
|
||||||
await Connection.SendToPropertyInspectorAsync(obj);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private void Connection_OnTitleParametersDidChange(object sender, SDEventReceivedEventArgs<BarRaider.SdTools.Events.TitleParametersDidChange> e)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Dispose()
|
|
||||||
{
|
|
||||||
Connection.OnTitleParametersDidChange -= Connection_OnTitleParametersDidChange;
|
|
||||||
//Logger.Instance.LogMessage(TracingLevel.INFO, $"Destructor called");
|
|
||||||
}
|
|
||||||
|
|
||||||
public override async void KeyPressed(KeyPayload payload)
|
|
||||||
{
|
|
||||||
//Logger.Instance.LogMessage(TracingLevel.INFO, "KeyPressed");
|
|
||||||
//Logger.Instance.LogMessage(TracingLevel.INFO, JsonConvert.SerializeObject(settings));
|
|
||||||
//Logger.Instance.LogMessage(TracingLevel.INFO, JsonConvert.SerializeObject(GlobalSettings.Instance));
|
|
||||||
ClipTrimClient.Instance.SetSelectedCollectionByName(settings.ProfileName);
|
|
||||||
GlobalSettings.Instance.SetCurrentProfile(settings.ProfileName);
|
|
||||||
Logger.Instance.LogMessage(TracingLevel.INFO, JsonConvert.SerializeObject(GlobalSettings.Instance));
|
|
||||||
|
|
||||||
await Connection.SetGlobalSettingsAsync(JObject.FromObject(GlobalSettings.Instance));
|
|
||||||
await Connection.SwitchProfileAsync("ClipTrim");
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void KeyReleased(KeyPayload payload)
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void OnTick()
|
|
||||||
{
|
|
||||||
SetTitle();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void ReceivedSettings(ReceivedSettingsPayload payload)
|
|
||||||
{
|
|
||||||
Tools.AutoPopulateSettings(settings, payload.Settings);
|
|
||||||
SaveSettings();
|
|
||||||
//tTitle();
|
|
||||||
//CheckFile();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void ReceivedGlobalSettings(ReceivedGlobalSettingsPayload payload)
|
|
||||||
{
|
|
||||||
//Logger.Instance.LogMessage(TracingLevel.INFO, "ReceivedGlobalSettings");
|
|
||||||
|
|
||||||
if (payload.Settings == null || payload.Settings.Count == 0)
|
|
||||||
{
|
|
||||||
var inst = GlobalSettings.Instance;
|
|
||||||
//GlobalSettingsManager.Instance.SetGlobalSettings(JObject.FromObject(inst));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
GlobalSettings.Instance = payload.Settings.ToObject<GlobalSettings>();
|
|
||||||
}
|
|
||||||
|
|
||||||
//CheckFile();
|
|
||||||
}
|
|
||||||
|
|
||||||
#region Private Methods
|
|
||||||
|
|
||||||
private Task SaveSettings()
|
|
||||||
{
|
|
||||||
return Connection.SetSettingsAsync(JObject.FromObject(settings));
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,19 +1,20 @@
|
|||||||
using BarRaider.SdTools;
|
using BarRaider.SdTools;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace ClipTrimDotNet
|
namespace ClipTrimDotNet
|
||||||
{
|
{
|
||||||
internal class Program
|
internal class Program
|
||||||
{
|
{
|
||||||
static void Main(string[] args)
|
static void Main(string[] args)
|
||||||
{
|
{
|
||||||
// Uncomment this line of code to allow for debugging
|
// Uncomment this line of code to allow for debugging
|
||||||
//while (!System.Diagnostics.Debugger.IsAttached) { System.Threading.Thread.Sleep(100); }
|
//while (!System.Diagnostics.Debugger.IsAttached) { System.Threading.Thread.Sleep(100); }
|
||||||
SDWrapper.Run(args);
|
Client.ClipTrimClient.Instance.PortNumber = 5010;
|
||||||
}
|
SDWrapper.Run(args);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -1,36 +1,13 @@
|
|||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
[assembly: AssemblyTrademark("")]
|
||||||
// General Information about an assembly is controlled through the following
|
[assembly: AssemblyCulture("")]
|
||||||
// set of attributes. Change these attribute values to modify the information
|
|
||||||
// associated with an assembly.
|
// Setting ComVisible to false makes the types in this assembly not visible
|
||||||
[assembly: AssemblyTitle("ClipTrimDotNet")]
|
// to COM components. If you need to access a type in this assembly from
|
||||||
[assembly: AssemblyDescription("")]
|
// COM, set the ComVisible attribute to true on that type.
|
||||||
[assembly: AssemblyConfiguration("")]
|
[assembly: ComVisible(false)]
|
||||||
[assembly: AssemblyCompany("")]
|
|
||||||
[assembly: AssemblyProduct("ClipTrimDotNet")]
|
// The following GUID is for the ID of the typelib if this project is exposed to COM
|
||||||
[assembly: AssemblyCopyright("Copyright © 2020")]
|
[assembly: Guid("1bb90885-9d98-46ef-b983-4a4ef3aea890")]
|
||||||
[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")]
|
|
||||||
|
|||||||
@ -1,45 +1,45 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
|
|
||||||
<head lang="en">
|
<head lang="en">
|
||||||
<title>Increment Counter Settings</title>
|
<title>Increment Counter Settings</title>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<script src="https://sdpi-components.dev/releases/v3/sdpi-components.js"></script>
|
<script src="https://sdpi-components.dev/releases/v3/sdpi-components.js"></script>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<!--
|
<!--
|
||||||
Learn more about property inspector components at https://sdpi-components.dev/docs/components
|
Learn more about property inspector components at https://sdpi-components.dev/docs/components
|
||||||
-->
|
-->
|
||||||
<sdpi-item label="Index">
|
<!--<sdpi-item label="Index">
|
||||||
<sdpi-select setting="index" placeholder="file index">
|
<sdpi-select setting="index" placeholder="file index">
|
||||||
<option value="0">0</option>
|
<option value="0">0</option>
|
||||||
<option value="1">1</option>
|
<option value="1">1</option>
|
||||||
<option value="2">2</option>
|
<option value="2">2</option>
|
||||||
<option value="3">3</option>
|
<option value="3">3</option>
|
||||||
<option value="4">4</option>
|
<option value="4">4</option>
|
||||||
<option value="5">5</option>
|
<option value="5">5</option>
|
||||||
<option value="6">6</option>
|
<option value="6">6</option>
|
||||||
<option value="7">7</option>
|
<option value="7">7</option>
|
||||||
<option value="8">8</option>
|
<option value="8">8</option>
|
||||||
<option value="9">9</option>
|
<option value="9">9</option>
|
||||||
<option value="10">10</option>
|
<option value="10">10</option>
|
||||||
<option value="11">11</option>
|
<option value="11">11</option>
|
||||||
<option value="12">12</option>
|
<option value="12">12</option>
|
||||||
<option value="13">13</option>
|
<option value="13">13</option>
|
||||||
<option value="14">14</option>
|
<option value="14">14</option>
|
||||||
<option value="15">15</option>
|
<option value="15">15</option>
|
||||||
</sdpi-select>
|
</sdpi-select>
|
||||||
</sdpi-item>
|
</sdpi-item>
|
||||||
<sdpi-item label="Play Mode">
|
<sdpi-item label="Play Mode">
|
||||||
<sdpi-select setting="playtype" placeholder="Play Mode">
|
<sdpi-select setting="playtype" placeholder="Play Mode">
|
||||||
<option value="Play/Overlap">Play/Overlap</option>
|
<option value="Play/Overlap">Play/Overlap</option>
|
||||||
<option value="Play/Stop">Play/Stop</option>
|
<option value="Play/Stop">Play/Stop</option>
|
||||||
</sdpi-select>
|
</sdpi-select>
|
||||||
</sdpi-item>
|
</sdpi-item>
|
||||||
<sdpi-item label="Volume">
|
<sdpi-item label="Volume">
|
||||||
<sdpi-range setting="volume" min=".1" max="1" , step="0.05"></sdpi-range>
|
<sdpi-range setting="volume" min=".1" max="1" , step="0.05"></sdpi-range>
|
||||||
</sdpi-item>
|
</sdpi-item>-->
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
@ -1,34 +1,34 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
<head lang="en">
|
<head lang="en">
|
||||||
<title>Increment Counter Settings</title>
|
<title>Increment Counter Settings</title>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<script src="https://sdpi-components.dev/releases/v3/sdpi-components.js"></script>
|
<script src="https://sdpi-components.dev/releases/v3/sdpi-components.js"></script>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<!--
|
<!--
|
||||||
Learn more about property inspector components at https://sdpi-components.dev/docs/components
|
Learn more about property inspector components at https://sdpi-components.dev/docs/components
|
||||||
-->
|
-->
|
||||||
<sdpi-item label="Profile Name">
|
<sdpi-item label="Profile Name">
|
||||||
<sdpi-select setting="profileName"
|
<sdpi-select setting="profileName"
|
||||||
datasource="getProfiles"
|
datasource="getProfiles"
|
||||||
show-refresh="true"
|
show-refresh="true"
|
||||||
placeholder="Select a ClipTrim page"></sdpi-select>
|
placeholder="Select a ClipTrim page"></sdpi-select>
|
||||||
</sdpi-item>
|
</sdpi-item>
|
||||||
<sdpi-item label="Base Path">
|
<!--<sdpi-item label="Base Path">
|
||||||
<sdpi-file setting="basePath"
|
<sdpi-file setting="basePath"
|
||||||
global="true"
|
global="true"
|
||||||
webkitdirectory
|
webkitdirectory
|
||||||
directory
|
directory
|
||||||
multiple></sdpi-file>
|
multiple></sdpi-file>
|
||||||
</sdpi-item>
|
</sdpi-item>
|
||||||
<sdpi-item label="Output Device">
|
<sdpi-item label="Output Device">
|
||||||
<sdpi-select setting="outputDevice"
|
<sdpi-select setting="outputDevice"
|
||||||
global="true"
|
global="true"
|
||||||
datasource="getOutputDevices"
|
datasource="getOutputDevices"
|
||||||
show-refresh="true"
|
show-refresh="true"
|
||||||
placeholder="Select an Ouput Device"></sdpi-select>
|
placeholder="Select an Ouput Device"></sdpi-select>
|
||||||
</sdpi-item>
|
</sdpi-item>-->
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@ -1,225 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Concurrent;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.ServiceModel.Security;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using BarRaider.SdTools;
|
|
||||||
using NAudio.Wave;
|
|
||||||
using NAudio.Wave.SampleProviders;
|
|
||||||
using Newtonsoft.Json;
|
|
||||||
|
|
||||||
class CachedSound
|
|
||||||
{
|
|
||||||
public byte[] AudioData { get; private set; }
|
|
||||||
public WaveFormat WaveFormat { get; private set; }
|
|
||||||
public CachedSound(string audioFileName)
|
|
||||||
{
|
|
||||||
using (var audioFileReader = new AudioFileReader(audioFileName))
|
|
||||||
{
|
|
||||||
// TODO: could add resampling in here if required
|
|
||||||
WaveFormat = audioFileReader.WaveFormat;
|
|
||||||
var wholeFile = new List<byte>((int)(audioFileReader.Length));
|
|
||||||
var readBuffer = new byte[audioFileReader.WaveFormat.SampleRate * audioFileReader.WaveFormat.Channels*4];
|
|
||||||
int samplesRead;
|
|
||||||
while ((samplesRead = audioFileReader.Read(readBuffer, 0, readBuffer.Length)) > 0)
|
|
||||||
{
|
|
||||||
wholeFile.AddRange(readBuffer.Take(samplesRead));
|
|
||||||
}
|
|
||||||
AudioData = wholeFile.ToArray();
|
|
||||||
}
|
|
||||||
//Logger.Instance.LogMessage(TracingLevel.INFO, $"AudioData Length {AudioData.Length}");
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
class CachedSoundSampleProvider : IWaveProvider
|
|
||||||
{
|
|
||||||
private readonly CachedSound cachedSound;
|
|
||||||
private long position;
|
|
||||||
|
|
||||||
~CachedSoundSampleProvider() {
|
|
||||||
//Logger.Instance.LogMessage(TracingLevel.INFO, $"Cache destructor");
|
|
||||||
}
|
|
||||||
|
|
||||||
public CachedSoundSampleProvider(CachedSound cachedSound)
|
|
||||||
{
|
|
||||||
this.cachedSound = cachedSound;
|
|
||||||
position = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int Read(byte[] buffer, int offset, int count)
|
|
||||||
{
|
|
||||||
//Logger.Instance.LogMessage(TracingLevel.INFO, $"Read1 byte");
|
|
||||||
var availableSamples = cachedSound.AudioData.Length - position;
|
|
||||||
var samplesToCopy = Math.Min(availableSamples, count);
|
|
||||||
try
|
|
||||||
{
|
|
||||||
//Logger.Instance.LogMessage(TracingLevel.INFO, $"{cachedSound.AudioData.GetType()} {buffer.GetType()}");
|
|
||||||
Array.Copy(cachedSound.AudioData, position, buffer, offset, samplesToCopy);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
//Logger.Instance.LogMessage(TracingLevel.INFO, $"{ex.ToString()}");
|
|
||||||
}
|
|
||||||
//Logger.Instance.LogMessage(TracingLevel.INFO, $"Read3");
|
|
||||||
position += samplesToCopy;
|
|
||||||
//Logger.Instance.LogMessage(TracingLevel.INFO, $"Sending {samplesToCopy} samples");
|
|
||||||
return (int)samplesToCopy;
|
|
||||||
}
|
|
||||||
|
|
||||||
public WaveFormat WaveFormat => cachedSound.WaveFormat;
|
|
||||||
}
|
|
||||||
|
|
||||||
public class WavPlayer
|
|
||||||
{
|
|
||||||
private static WavPlayer? instance;
|
|
||||||
public static WavPlayer Instance
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
instance ??= new WavPlayer();
|
|
||||||
return instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
public enum PlayMode
|
|
||||||
{
|
|
||||||
PlayOverlap,
|
|
||||||
PlayStop
|
|
||||||
}
|
|
||||||
|
|
||||||
private readonly ConcurrentDictionary<string, List<Tuple<WaveOutEvent, IWaveProvider>>> _activePlayers;
|
|
||||||
|
|
||||||
public WavPlayer()
|
|
||||||
{
|
|
||||||
_activePlayers = new ConcurrentDictionary<string, List<Tuple<WaveOutEvent, IWaveProvider>>>();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Play(string filePath, string id, double volume, PlayMode mode)
|
|
||||||
{
|
|
||||||
if (string.IsNullOrWhiteSpace(filePath))
|
|
||||||
throw new ArgumentException("File path cannot be null or empty.", nameof(filePath));
|
|
||||||
|
|
||||||
if (mode == PlayMode.PlayOverlap)
|
|
||||||
{
|
|
||||||
PlayWithOverlap(filePath, id, volume);
|
|
||||||
}
|
|
||||||
else if (mode == PlayMode.PlayStop)
|
|
||||||
{
|
|
||||||
PlayWithStop(filePath, id, volume);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
throw new ArgumentOutOfRangeException(nameof(mode), "Invalid play mode specified.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void PlayWithOverlap(string filePath, string id, double volume)
|
|
||||||
{
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
//Logger.Instance.LogMessage(TracingLevel.INFO, "Play overlap");
|
|
||||||
var player = CreatePlayer(filePath, id);
|
|
||||||
|
|
||||||
if (!_activePlayers.ContainsKey(filePath))
|
|
||||||
{
|
|
||||||
_activePlayers[filePath] = new List<Tuple<WaveOutEvent, IWaveProvider>>();
|
|
||||||
}
|
|
||||||
|
|
||||||
_activePlayers[filePath].Add(player);
|
|
||||||
player.Item1.Volume = (float)volume;
|
|
||||||
player.Item1.Play();
|
|
||||||
}
|
|
||||||
catch(Exception ex)
|
|
||||||
{
|
|
||||||
//Logger.Instance.LogMessage(TracingLevel.INFO, ex.ToString());
|
|
||||||
}
|
|
||||||
|
|
||||||
//var playersToDispose = _activePlayers[filePath].Where(x => x.Item1.PlaybackState == PlaybackState.Stopped).ToList();
|
|
||||||
//foreach (var p in playersToDispose)
|
|
||||||
//{
|
|
||||||
// p.Item1.Stop();
|
|
||||||
// p.Item1.Dispose();
|
|
||||||
//}
|
|
||||||
//_activePlayers[filePath].RemoveAll(x => x.Item1.PlaybackState == PlaybackState.Stopped);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void PlayWithStop(string filePath, string id, double volume)
|
|
||||||
{
|
|
||||||
if (_activePlayers.TryGetValue(filePath, out var players))
|
|
||||||
{
|
|
||||||
|
|
||||||
// Stop and dispose all current players for this file
|
|
||||||
if (players.Any(x => x.Item1.PlaybackState == PlaybackState.Playing))
|
|
||||||
{
|
|
||||||
var playersToDispose = players.ToList();
|
|
||||||
foreach (var player in playersToDispose)
|
|
||||||
{
|
|
||||||
player.Item1.Stop();
|
|
||||||
player.Item1.Dispose();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
PlayWithOverlap(filePath, id, volume);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Start a new player
|
|
||||||
PlayWithOverlap(filePath, id, volume);
|
|
||||||
}
|
|
||||||
|
|
||||||
_activePlayers[filePath].RemoveAll(x => x.Item1.PlaybackState == PlaybackState.Stopped);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Tuple<WaveOutEvent, IWaveProvider> CreatePlayer(string filePath, string name)
|
|
||||||
{
|
|
||||||
var reader = new CachedSoundSampleProvider(new CachedSound(filePath));
|
|
||||||
//var reader = new AudioFileReader(filePath);
|
|
||||||
int number = -1;
|
|
||||||
for (int i = 0; i < WaveOut.DeviceCount; ++i)
|
|
||||||
{
|
|
||||||
if (WaveOut.GetCapabilities(i).ProductName == name)
|
|
||||||
{
|
|
||||||
number = i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var player = new WaveOutEvent() { DeviceNumber = number };
|
|
||||||
player.Init(reader);
|
|
||||||
return new Tuple<WaveOutEvent, IWaveProvider>(player, reader);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void CleanupPlayer(string filePath)
|
|
||||||
{
|
|
||||||
if (_activePlayers.TryGetValue(filePath, out var players))
|
|
||||||
{
|
|
||||||
var playersToDispose = players.ToList();
|
|
||||||
foreach (var p in playersToDispose)
|
|
||||||
{
|
|
||||||
p.Item1.Stop();
|
|
||||||
p.Item1.Dispose();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_activePlayers[filePath].RemoveAll(x => x.Item1.PlaybackState == PlaybackState.Stopped);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public void StopAll()
|
|
||||||
{
|
|
||||||
foreach (var players in _activePlayers.Values)
|
|
||||||
{
|
|
||||||
var playersToDispose = players.ToList();
|
|
||||||
foreach (var player in playersToDispose)
|
|
||||||
{
|
|
||||||
player.Item1.Stop();
|
|
||||||
player.Item1.Dispose();
|
|
||||||
}
|
|
||||||
players.Clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
_activePlayers.Clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,62 +1,92 @@
|
|||||||
{
|
{
|
||||||
|
|
||||||
"Actions": [
|
"Actions": [
|
||||||
{
|
{
|
||||||
"Icon": "Images/icon",
|
"Icon": "Images/icon",
|
||||||
"Name": "Player",
|
"Name": "Player",
|
||||||
"States": [
|
"States": [
|
||||||
{
|
{
|
||||||
"Image": "Images/pluginAction",
|
"Image": "Images/pluginAction",
|
||||||
"TitleAlignment": "middle",
|
"TitleAlignment": "middle",
|
||||||
"FontSize": 11
|
"FontSize": 11
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"SupportedInMultiActions": false,
|
"SupportedInMultiActions": false,
|
||||||
"Tooltip": "Plays a bound audio file",
|
"Tooltip": "Plays a bound audio file",
|
||||||
"UUID": "com.michal-courson.cliptrim.player",
|
"UUID": "com.michal-courson.cliptrim.player",
|
||||||
"PropertyInspectorPath": "PropertyInspector/file_player.html"
|
"PropertyInspectorPath": "PropertyInspector/file_player.html"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"Icon": "Images/icon",
|
"Icon": "Images/icon",
|
||||||
"Name": "Profile Switcher",
|
"Name": "Profile Switcher",
|
||||||
"States": [
|
"States": [
|
||||||
{
|
{
|
||||||
"Image": "Images/pluginAction",
|
"Image": "Images/pluginAction",
|
||||||
"TitleAlignment": "middle",
|
"TitleAlignment": "middle",
|
||||||
"FontSize": 11
|
"FontSize": 11
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"SupportedInMultiActions": false,
|
"SupportedInMultiActions": false,
|
||||||
"Tooltip": "Selects which sub folder to use and opens effect profile",
|
"Tooltip": "Selects which sub folder to use and opens effect profile",
|
||||||
"UUID": "com.michal-courson.cliptrim.profile-switcher",
|
"UUID": "com.michal-courson.cliptrim.profile-switcher",
|
||||||
"PropertyInspectorPath": "PropertyInspector/profile_swticher.html"
|
"PropertyInspectorPath": "PropertyInspector/profile_swticher.html"
|
||||||
}
|
},
|
||||||
],
|
{
|
||||||
"Author": "Michal Courson",
|
"Icon": "Images/icon",
|
||||||
"Name": "ClipTrimDotNet",
|
"Name": "Page Navigator",
|
||||||
"Description": "Pairs with cliptrim for easy voice recording and trimming",
|
"States": [
|
||||||
"Icon": "Images/pluginIcon",
|
{
|
||||||
"Version": "0.1.0.0",
|
"Image": "Images/pluginAction",
|
||||||
"CodePath": "ClipTrimDotNet.exe",
|
"TitleAlignment": "middle",
|
||||||
"Category": "ClipTrimDotNet",
|
"FontSize": 16
|
||||||
"CategoryIcon": "Images/categoryIcon",
|
}
|
||||||
"UUID": "com.michal-courson.cliptrim",
|
],
|
||||||
"OS": [
|
"SupportedInMultiActions": false,
|
||||||
{
|
"Tooltip": "Navigates pages",
|
||||||
"Platform": "windows",
|
"UUID": "com.michal-courson.cliptrim.page-navigator",
|
||||||
"MinimumVersion": "10"
|
"PropertyInspectorPath": "PropertyInspector/file_player.html"
|
||||||
}
|
},
|
||||||
],
|
{
|
||||||
"SDKVersion": 2,
|
"Icon": "Images/icon",
|
||||||
"Software": {
|
"Name": "Save Clip",
|
||||||
"MinimumVersion": "6.4"
|
"States": [
|
||||||
},
|
{
|
||||||
"Profiles": [
|
"Image": "Images/pluginAction",
|
||||||
{
|
"TitleAlignment": "middle",
|
||||||
"Name": "ClipTrim",
|
"FontSize": 16
|
||||||
"DeviceType": 0,
|
}
|
||||||
"Readonly": false,
|
],
|
||||||
"DontAutoSwitchWhenInstalled": false
|
"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/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
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
544
stream_deck_plugin/ClipTrimDotNet/package-lock.json
generated
544
stream_deck_plugin/ClipTrimDotNet/package-lock.json
generated
@ -1,272 +1,272 @@
|
|||||||
{
|
{
|
||||||
"name": "ClipTrimDotNet",
|
"name": "ClipTrimDotNet",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"shx": "^0.3.4"
|
"shx": "^0.3.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/balanced-match": {
|
"node_modules/balanced-match": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
||||||
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
|
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/brace-expansion": {
|
"node_modules/brace-expansion": {
|
||||||
"version": "1.1.11",
|
"version": "1.1.11",
|
||||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
||||||
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
|
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"balanced-match": "^1.0.0",
|
"balanced-match": "^1.0.0",
|
||||||
"concat-map": "0.0.1"
|
"concat-map": "0.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/concat-map": {
|
"node_modules/concat-map": {
|
||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
||||||
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
|
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/fs.realpath": {
|
"node_modules/fs.realpath": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
|
||||||
"integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
|
"integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
"node_modules/function-bind": {
|
"node_modules/function-bind": {
|
||||||
"version": "1.1.2",
|
"version": "1.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
||||||
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
|
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/glob": {
|
"node_modules/glob": {
|
||||||
"version": "7.2.3",
|
"version": "7.2.3",
|
||||||
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
|
||||||
"integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
|
"integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
|
||||||
"deprecated": "Glob versions prior to v9 are no longer supported",
|
"deprecated": "Glob versions prior to v9 are no longer supported",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"fs.realpath": "^1.0.0",
|
"fs.realpath": "^1.0.0",
|
||||||
"inflight": "^1.0.4",
|
"inflight": "^1.0.4",
|
||||||
"inherits": "2",
|
"inherits": "2",
|
||||||
"minimatch": "^3.1.1",
|
"minimatch": "^3.1.1",
|
||||||
"once": "^1.3.0",
|
"once": "^1.3.0",
|
||||||
"path-is-absolute": "^1.0.0"
|
"path-is-absolute": "^1.0.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "*"
|
"node": "*"
|
||||||
},
|
},
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://github.com/sponsors/isaacs"
|
"url": "https://github.com/sponsors/isaacs"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/hasown": {
|
"node_modules/hasown": {
|
||||||
"version": "2.0.2",
|
"version": "2.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
|
||||||
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
|
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"function-bind": "^1.1.2"
|
"function-bind": "^1.1.2"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/inflight": {
|
"node_modules/inflight": {
|
||||||
"version": "1.0.6",
|
"version": "1.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
|
||||||
"integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
|
"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.",
|
"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,
|
"dev": true,
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"once": "^1.3.0",
|
"once": "^1.3.0",
|
||||||
"wrappy": "1"
|
"wrappy": "1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/inherits": {
|
"node_modules/inherits": {
|
||||||
"version": "2.0.4",
|
"version": "2.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
|
||||||
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
|
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
"node_modules/interpret": {
|
"node_modules/interpret": {
|
||||||
"version": "1.4.0",
|
"version": "1.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz",
|
||||||
"integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==",
|
"integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.10"
|
"node": ">= 0.10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/is-core-module": {
|
"node_modules/is-core-module": {
|
||||||
"version": "2.16.1",
|
"version": "2.16.1",
|
||||||
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz",
|
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz",
|
||||||
"integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==",
|
"integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"hasown": "^2.0.2"
|
"hasown": "^2.0.2"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
},
|
},
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/minimatch": {
|
"node_modules/minimatch": {
|
||||||
"version": "3.1.2",
|
"version": "3.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
||||||
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
|
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"brace-expansion": "^1.1.7"
|
"brace-expansion": "^1.1.7"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "*"
|
"node": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/minimist": {
|
"node_modules/minimist": {
|
||||||
"version": "1.2.8",
|
"version": "1.2.8",
|
||||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
|
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
|
||||||
"integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
|
"integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/once": {
|
"node_modules/once": {
|
||||||
"version": "1.4.0",
|
"version": "1.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
||||||
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
|
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"wrappy": "1"
|
"wrappy": "1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/path-is-absolute": {
|
"node_modules/path-is-absolute": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
|
||||||
"integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
|
"integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/path-parse": {
|
"node_modules/path-parse": {
|
||||||
"version": "1.0.7",
|
"version": "1.0.7",
|
||||||
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
|
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
|
||||||
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
|
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/rechoir": {
|
"node_modules/rechoir": {
|
||||||
"version": "0.6.2",
|
"version": "0.6.2",
|
||||||
"resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz",
|
"resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz",
|
||||||
"integrity": "sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw==",
|
"integrity": "sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"resolve": "^1.1.6"
|
"resolve": "^1.1.6"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.10"
|
"node": ">= 0.10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/resolve": {
|
"node_modules/resolve": {
|
||||||
"version": "1.22.10",
|
"version": "1.22.10",
|
||||||
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz",
|
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz",
|
||||||
"integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==",
|
"integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"is-core-module": "^2.16.0",
|
"is-core-module": "^2.16.0",
|
||||||
"path-parse": "^1.0.7",
|
"path-parse": "^1.0.7",
|
||||||
"supports-preserve-symlinks-flag": "^1.0.0"
|
"supports-preserve-symlinks-flag": "^1.0.0"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
"resolve": "bin/resolve"
|
"resolve": "bin/resolve"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
},
|
},
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/shelljs": {
|
"node_modules/shelljs": {
|
||||||
"version": "0.8.5",
|
"version": "0.8.5",
|
||||||
"resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.5.tgz",
|
"resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.5.tgz",
|
||||||
"integrity": "sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow==",
|
"integrity": "sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "BSD-3-Clause",
|
"license": "BSD-3-Clause",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"glob": "^7.0.0",
|
"glob": "^7.0.0",
|
||||||
"interpret": "^1.0.0",
|
"interpret": "^1.0.0",
|
||||||
"rechoir": "^0.6.2"
|
"rechoir": "^0.6.2"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
"shjs": "bin/shjs"
|
"shjs": "bin/shjs"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=4"
|
"node": ">=4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/shx": {
|
"node_modules/shx": {
|
||||||
"version": "0.3.4",
|
"version": "0.3.4",
|
||||||
"resolved": "https://registry.npmjs.org/shx/-/shx-0.3.4.tgz",
|
"resolved": "https://registry.npmjs.org/shx/-/shx-0.3.4.tgz",
|
||||||
"integrity": "sha512-N6A9MLVqjxZYcVn8hLmtneQWIJtp8IKzMP4eMnx+nqkvXoqinUPCbUFLp2UcWTEIUONhlk0ewxr/jaVGlc+J+g==",
|
"integrity": "sha512-N6A9MLVqjxZYcVn8hLmtneQWIJtp8IKzMP4eMnx+nqkvXoqinUPCbUFLp2UcWTEIUONhlk0ewxr/jaVGlc+J+g==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"minimist": "^1.2.3",
|
"minimist": "^1.2.3",
|
||||||
"shelljs": "^0.8.5"
|
"shelljs": "^0.8.5"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
"shx": "lib/cli.js"
|
"shx": "lib/cli.js"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/supports-preserve-symlinks-flag": {
|
"node_modules/supports-preserve-symlinks-flag": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
|
||||||
"integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
|
"integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
},
|
},
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/wrappy": {
|
"node_modules/wrappy": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
||||||
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
|
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,14 +1,14 @@
|
|||||||
{
|
{
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"stop": "streamdeck stop com.michal-courson.cliptrim",
|
"stop": "streamdeck stop com.michal-courson.cliptrim",
|
||||||
"copy": "@powershell robocopy bin/Debug/ClipTrimDotNet.sdPlugin bin/Debug/com.michal-courson.cliptrim.sdPlugin",
|
"copy": "@powershell robocopy bin/Debug/ClipTrimDotNet.sdPlugin bin/Debug/com.michal-courson.cliptrim.sdPlugin/net8.0-windows",
|
||||||
"link": "streamdeck link bin/Debug/com.michal-courson.cliptrim.sdPlugin",
|
"link": "streamdeck link bin/Debug/com.michal-courson.cliptrim.sdPlugin",
|
||||||
"restart": "streamdeck restart com.michal-courson.cliptrim",
|
"restart": "streamdeck restart com.michal-courson.cliptrim",
|
||||||
"start": "npm run link && npm run restart",
|
"start": "npm run link && npm run restart",
|
||||||
"all": "npm run stop && npm run copy && npm run link && npm run restart",
|
"all": "npm run stop && npm run copy && npm run link && npm run restart",
|
||||||
"pack": "streamdeck pack bin/Debug/com.michal-courson.cliptrim.sdPlugin/"
|
"pack": "streamdeck pack bin/Debug/com.michal-courson.cliptrim.sdPlugin/net8.0-windows/"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"shx": "^0.3.4"
|
"shx": "^0.3.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,18 +1,46 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<packages>
|
<packages>
|
||||||
<package id="CommandLineParser" version="2.9.1" targetFramework="net472" />
|
<package id="CommandLineParser" version="2.9.1" targetFramework="net472" />
|
||||||
<package id="Microsoft.Win32.Registry" version="4.7.0" targetFramework="net48" />
|
<package id="Microsoft.Bcl.AsyncInterfaces" version="10.0.2" targetFramework="net48" />
|
||||||
<package id="NAudio" version="2.2.1" targetFramework="net48" />
|
<package id="Microsoft.Extensions.DependencyInjection" version="10.0.2" targetFramework="net48" />
|
||||||
<package id="NAudio.Asio" version="2.2.1" targetFramework="net48" />
|
<package id="Microsoft.Extensions.DependencyInjection.Abstractions" version="10.0.2" targetFramework="net48" />
|
||||||
<package id="NAudio.Core" version="2.2.1" targetFramework="net48" />
|
<package id="Microsoft.Extensions.Logging" version="10.0.2" targetFramework="net48" />
|
||||||
<package id="NAudio.Midi" version="2.2.1" targetFramework="net48" />
|
<package id="Microsoft.Extensions.Logging.Abstractions" version="10.0.2" targetFramework="net48" />
|
||||||
<package id="NAudio.Wasapi" version="2.2.1" targetFramework="net48" />
|
<package id="Microsoft.Extensions.Options" version="10.0.2" targetFramework="net48" />
|
||||||
<package id="NAudio.WinForms" version="2.2.1" targetFramework="net48" />
|
<package id="Microsoft.Extensions.Primitives" version="10.0.2" targetFramework="net48" />
|
||||||
<package id="NAudio.WinMM" version="2.2.1" targetFramework="net48" />
|
<package id="Microsoft.Win32.Registry" version="4.7.0" targetFramework="net48" />
|
||||||
<package id="Newtonsoft.Json" version="13.0.4" targetFramework="net48" />
|
<package id="NAudio" version="2.2.1" targetFramework="net48" />
|
||||||
<package id="NLog" version="6.0.5" targetFramework="net48" />
|
<package id="NAudio.Asio" version="2.2.1" targetFramework="net48" />
|
||||||
<package id="StreamDeck-Tools" version="6.3.2" targetFramework="net48" />
|
<package id="NAudio.Core" version="2.2.1" targetFramework="net48" />
|
||||||
<package id="System.Drawing.Common" version="9.0.10" targetFramework="net48" />
|
<package id="NAudio.Midi" version="2.2.1" targetFramework="net48" />
|
||||||
<package id="System.Security.AccessControl" version="4.7.0" targetFramework="net48" />
|
<package id="NAudio.Wasapi" version="2.2.1" targetFramework="net48" />
|
||||||
<package id="System.Security.Principal.Windows" version="4.7.0" targetFramework="net48" />
|
<package id="NAudio.WinForms" version="2.2.1" targetFramework="net48" />
|
||||||
|
<package id="NAudio.WinMM" version="2.2.1" targetFramework="net48" />
|
||||||
|
<package id="Newtonsoft.Json" version="13.0.4" targetFramework="net48" />
|
||||||
|
<package id="NLog" version="6.0.5" targetFramework="net48" />
|
||||||
|
<package id="SocketIOClient" version="4.0.0.2" targetFramework="net48" />
|
||||||
|
<package id="SocketIOClient.Common" version="4.0.0" targetFramework="net48" />
|
||||||
|
<package id="SocketIOClient.Serializer" version="4.0.0.1" targetFramework="net48" />
|
||||||
|
<package id="SocketIOClient.Serializer.NewtonsoftJson" version="4.0.0.1" targetFramework="net48" />
|
||||||
|
<package id="StreamDeck-Tools" version="6.3.2" targetFramework="net48" />
|
||||||
|
<package id="System.Buffers" version="4.6.1" targetFramework="net48" />
|
||||||
|
<package id="System.Diagnostics.DiagnosticSource" version="10.0.2" targetFramework="net48" />
|
||||||
|
<package id="System.Drawing.Common" version="9.0.10" targetFramework="net48" />
|
||||||
|
<package id="System.IO" version="4.3.0" targetFramework="net48" />
|
||||||
|
<package id="System.IO.Pipelines" version="10.0.2" targetFramework="net48" />
|
||||||
|
<package id="System.Memory" version="4.6.3" targetFramework="net48" />
|
||||||
|
<package id="System.Net.Http" version="4.3.4" targetFramework="net48" />
|
||||||
|
<package id="System.Numerics.Vectors" version="4.6.1" targetFramework="net48" />
|
||||||
|
<package id="System.Runtime" version="4.3.0" targetFramework="net48" />
|
||||||
|
<package id="System.Runtime.CompilerServices.Unsafe" version="6.1.2" targetFramework="net48" />
|
||||||
|
<package id="System.Security.AccessControl" version="4.7.0" targetFramework="net48" />
|
||||||
|
<package id="System.Security.Cryptography.Algorithms" version="4.3.0" targetFramework="net48" />
|
||||||
|
<package id="System.Security.Cryptography.Encoding" version="4.3.0" targetFramework="net48" />
|
||||||
|
<package id="System.Security.Cryptography.Primitives" version="4.3.0" targetFramework="net48" />
|
||||||
|
<package id="System.Security.Cryptography.X509Certificates" version="4.3.0" targetFramework="net48" />
|
||||||
|
<package id="System.Security.Principal.Windows" version="4.7.0" targetFramework="net48" />
|
||||||
|
<package id="System.Text.Encodings.Web" version="10.0.2" targetFramework="net48" />
|
||||||
|
<package id="System.Text.Json" version="10.0.2" targetFramework="net48" />
|
||||||
|
<package id="System.Threading.Tasks.Extensions" version="4.6.3" targetFramework="net48" />
|
||||||
|
<package id="System.ValueTuple" version="4.6.1" targetFramework="net48" />
|
||||||
</packages>
|
</packages>
|
||||||
Reference in New Issue
Block a user