Compare commits
18 Commits
8fda2a03af
...
aefb3f2648
| Author | SHA1 | Date | |
|---|---|---|---|
| aefb3f2648 | |||
| a613b26ba8 | |||
| 7a471041e7 | |||
| ab57d8ef22 | |||
| 69c9d80a82 | |||
| 8c83819a17 | |||
| ad07bf7fe6 | |||
| bc40f9abe3 | |||
| e7f649ae0b | |||
| e34903b20f | |||
| 192c959d39 | |||
| 60123d7450 | |||
| 8265951bd4 | |||
| 757d5ef1a7 | |||
| d78c49d0ad | |||
| 089023e7cf | |||
| db97747f2e | |||
| 1e7141c43f |
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
@ -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"
|
||||
}
|
||||
3
audio-service/.gitignore
vendored
@ -1 +1,2 @@
|
||||
recordings/
|
||||
recordings/
|
||||
__pycache__/
|
||||
@ -1,34 +1,134 @@
|
||||
[
|
||||
{
|
||||
"name": "Uncategorized",
|
||||
"id": 0,
|
||||
"clips": []
|
||||
},
|
||||
{
|
||||
"name": "Test",
|
||||
"id": 1,
|
||||
"clips": []
|
||||
},
|
||||
{
|
||||
"name": "New",
|
||||
"id": 2,
|
||||
"clips": [
|
||||
{
|
||||
"endTime": 30,
|
||||
"filename": "C:\\Users\\mickl\\Desktop\\cliptrim-ui\\ClipTrimApp\\audio-service\\recordings\\audio_capture_20260220_193822.wav",
|
||||
"name": "Pee pee\npoo poo",
|
||||
"playbackType": "playOverlap",
|
||||
"startTime": 27.76674010920584,
|
||||
"volume": 0.25
|
||||
},
|
||||
{
|
||||
"endTime": 27.516843118383072,
|
||||
"filename": "C:\\Users\\mickl\\Desktop\\cliptrim-ui\\ClipTrimApp\\audio-service\\recordings\\audio_capture_20260220_200442.wav",
|
||||
"name": "Clip 20260220_200442",
|
||||
"playbackType": "playOverlap",
|
||||
"startTime": 25.120307988450435,
|
||||
"volume": 0.64
|
||||
}
|
||||
]
|
||||
}
|
||||
[
|
||||
{
|
||||
"name": "Uncategorized",
|
||||
"id": 0,
|
||||
"clips": [
|
||||
{
|
||||
"filename": "C:\\Users\\mickl\\Desktop\\cliptrim-ui\\ClipTrimApp\\audio-service\\recordings\\audio_capture_20260226_195932.wav",
|
||||
"name": "Clip 20260226_195932",
|
||||
"playbackType": "playOverlap",
|
||||
"volume": 1
|
||||
},
|
||||
{
|
||||
"filename": "C:\\Users\\mickl\\Desktop\\cliptrim-ui\\ClipTrimApp\\audio-service\\recordings\\audio_capture_20260228_165611.wav",
|
||||
"name": "Clip 20260228_165611",
|
||||
"playbackType": "playStop",
|
||||
"volume": 1.0
|
||||
},
|
||||
{
|
||||
"filename": "C:\\Users\\mickl\\Desktop\\cliptrim-ui\\ClipTrimApp\\audio-service\\recordings\\audio_capture_20260228_165646.wav",
|
||||
"name": "Clip 20260228_165646",
|
||||
"playbackType": "playStop",
|
||||
"volume": 1.0
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Test",
|
||||
"id": 1,
|
||||
"clips": [
|
||||
{
|
||||
"filename": "C:\\Users\\mickl\\Desktop\\cliptrim-ui\\ClipTrimApp\\audio-service\\recordings\\audio_capture_20260226_183812.wav",
|
||||
"name": "Clip 20260226_183812",
|
||||
"playbackType": "playStop",
|
||||
"volume": 1
|
||||
},
|
||||
{
|
||||
"filename": "C:\\Users\\mickl\\Desktop\\cliptrim-ui\\ClipTrimApp\\audio-service\\recordings\\audio_capture_20260226_183607.wav",
|
||||
"name": "Clip 20260226_183607",
|
||||
"playbackType": "playStop",
|
||||
"volume": 1
|
||||
},
|
||||
{
|
||||
"filename": "C:\\Users\\mickl\\Desktop\\cliptrim-ui\\ClipTrimApp\\audio-service\\recordings\\audio_capture_20260226_183822.wav",
|
||||
"name": "Clip 20260226_183822",
|
||||
"playbackType": "playStop",
|
||||
"volume": 1
|
||||
},
|
||||
{
|
||||
"filename": "C:\\Users\\mickl\\Desktop\\cliptrim-ui\\ClipTrimApp\\audio-service\\recordings\\audio_capture_20260226_184028.wav",
|
||||
"name": "Clip 20260226_184028",
|
||||
"playbackType": "playStop",
|
||||
"volume": 1
|
||||
},
|
||||
{
|
||||
"filename": "C:\\Users\\mickl\\Desktop\\cliptrim-ui\\ClipTrimApp\\audio-service\\recordings\\audio_capture_20260226_184030.wav",
|
||||
"name": "Clip 20260226_184030",
|
||||
"playbackType": "playStop",
|
||||
"volume": 1
|
||||
},
|
||||
{
|
||||
"filename": "C:\\Users\\mickl\\Desktop\\cliptrim-ui\\ClipTrimApp\\audio-service\\recordings\\audio_capture_20260226_184032.wav",
|
||||
"name": "Clip 20260226_184032",
|
||||
"playbackType": "playStop",
|
||||
"volume": 1
|
||||
},
|
||||
{
|
||||
"filename": "C:\\Users\\mickl\\Desktop\\cliptrim-ui\\ClipTrimApp\\audio-service\\recordings\\audio_capture_20260226_184037.wav",
|
||||
"name": "Clip 20260226_184037",
|
||||
"playbackType": "playStop",
|
||||
"volume": 1
|
||||
},
|
||||
{
|
||||
"filename": "C:\\Users\\mickl\\Desktop\\cliptrim-ui\\ClipTrimApp\\audio-service\\recordings\\audio_capture_20260226_184040.wav",
|
||||
"name": "Clip 20260226_184040",
|
||||
"playbackType": "playStop",
|
||||
"volume": 1
|
||||
},
|
||||
{
|
||||
"filename": "C:\\Users\\mickl\\Desktop\\cliptrim-ui\\ClipTrimApp\\audio-service\\recordings\\audio_capture_20260226_184041.wav",
|
||||
"name": "Clip 20260226_184041",
|
||||
"playbackType": "playStop",
|
||||
"volume": 1
|
||||
},
|
||||
{
|
||||
"filename": "C:\\Users\\mickl\\Desktop\\cliptrim-ui\\ClipTrimApp\\audio-service\\recordings\\audio_capture_20260226_184042.wav",
|
||||
"name": "Clip 20260226_184042",
|
||||
"playbackType": "playStop",
|
||||
"volume": 1
|
||||
},
|
||||
{
|
||||
"filename": "C:\\Users\\mickl\\Desktop\\cliptrim-ui\\ClipTrimApp\\audio-service\\recordings\\audio_capture_20260228_092721.wav",
|
||||
"name": "Clip 20260228_092721",
|
||||
"playbackType": "playStop",
|
||||
"volume": 1,
|
||||
"startTime": 6.438382145377559,
|
||||
"endTime": 14.277258292166426
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "New",
|
||||
"id": 2,
|
||||
"clips": [
|
||||
{
|
||||
"endTime": 30,
|
||||
"filename": "C:\\Users\\mickl\\Desktop\\cliptrim-ui\\ClipTrimApp\\audio-service\\recordings\\audio_capture_20260220_193822.wav",
|
||||
"name": "Pee pee\npoo poo",
|
||||
"playbackType": "playOverlap",
|
||||
"startTime": 27.64044943820222,
|
||||
"volume": 0.31
|
||||
},
|
||||
{
|
||||
"endTime": 30,
|
||||
"filename": "C:\\Users\\mickl\\Desktop\\cliptrim-ui\\ClipTrimApp\\audio-service\\recordings\\audio_capture_20260220_200442.wav",
|
||||
"name": "Test",
|
||||
"playbackType": "playOverlap",
|
||||
"startTime": 26.14685314685314,
|
||||
"volume": 0.64
|
||||
},
|
||||
{
|
||||
"filename": "C:\\Users\\mickl\\Desktop\\cliptrim-ui\\ClipTrimApp\\audio-service\\recordings\\audio_capture_20260228_085116.wav",
|
||||
"name": "pp",
|
||||
"playbackType": "playStop",
|
||||
"volume": 1
|
||||
},
|
||||
{
|
||||
"filename": "C:\\Users\\mickl\\Desktop\\cliptrim-ui\\ClipTrimApp\\audio-service\\recordings\\audio_capture_20260228_120955.wav",
|
||||
"name": "nose",
|
||||
"playbackType": "playStop",
|
||||
"volume": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
@ -1,7 +1,6 @@
|
||||
sounddevice==0.5.1
|
||||
numpy==1.22.3
|
||||
python-osc==1.9.3
|
||||
scipy==1.10.1
|
||||
comtypes==1.4.8
|
||||
pycaw==20240210
|
||||
Flask==3.1.2
|
||||
Flask==3.1.3
|
||||
flask_cors==6.0.2
|
||||
flask_socketio==5.6.1
|
||||
numpy==2.4.2
|
||||
scipy==1.17.1
|
||||
sounddevice==0.5.5
|
||||
|
||||
@ -1,17 +1,17 @@
|
||||
{
|
||||
"input_device": {
|
||||
"channels": 2,
|
||||
"default_samplerate": 48000,
|
||||
"index": 55,
|
||||
"name": "VM Mic mix (VB-Audio Voicemeeter VAIO)"
|
||||
},
|
||||
"save_path": "C:\\Users\\mickl\\Desktop\\cliptrim-ui\\ClipTrimApp\\audio-service\\recordings",
|
||||
"recording_length": 30,
|
||||
"output_device": {
|
||||
"channels": 2,
|
||||
"default_samplerate": 48000,
|
||||
"index": 45,
|
||||
"name": "VM to Discord (VB-Audio Voicemeeter VAIO)"
|
||||
},
|
||||
"http_port": 5010
|
||||
{
|
||||
"input_device": {
|
||||
"channels": 2,
|
||||
"default_samplerate": 48000,
|
||||
"index": 55,
|
||||
"name": "VM Mic mix (VB-Audio Voicemeeter VAIO)"
|
||||
},
|
||||
"save_path": "C:\\Users\\mickl\\Desktop\\cliptrim-ui\\ClipTrimApp\\audio-service\\recordings",
|
||||
"recording_length": 30,
|
||||
"output_device": {
|
||||
"channels": 2,
|
||||
"default_samplerate": 48000,
|
||||
"index": 45,
|
||||
"name": "VM to Discord (VB-Audio Voicemeeter VAIO)"
|
||||
},
|
||||
"http_port": 5010
|
||||
}
|
||||
@ -1,166 +1,168 @@
|
||||
import sounddevice as sd
|
||||
import numpy as np
|
||||
import os
|
||||
from datetime import datetime
|
||||
import scipy.io.wavfile as wavfile
|
||||
from metadata_manager import MetaDataManager
|
||||
from audio_clip import AudioClip
|
||||
|
||||
|
||||
# AudioClip class for clip playback
|
||||
|
||||
|
||||
class AudioIO:
|
||||
_instance = None
|
||||
|
||||
def __new__(cls, *args, **kwargs):
|
||||
if cls._instance is None:
|
||||
# print("Creating new AudioRecorder instance")
|
||||
cls._instance = super().__new__(cls)
|
||||
cls._instance.init()
|
||||
return cls._instance
|
||||
def init(self):
|
||||
self.duration = 30
|
||||
self.channels = 2
|
||||
self.input_sample_rate = 44100
|
||||
self.output_sample_rate = 44100
|
||||
self.buffer = np.zeros((int(self.duration * self.input_sample_rate), self.channels), dtype=np.float32)
|
||||
self.recordings_dir = "recordings"
|
||||
|
||||
sd.default.latency = 'low'
|
||||
|
||||
self.in_stream = sd.InputStream(
|
||||
callback=self.record_callback
|
||||
)
|
||||
|
||||
self.out_stream = sd.OutputStream(
|
||||
callback=self.playback_callback,
|
||||
latency=3
|
||||
)
|
||||
|
||||
self.clip_map = {}
|
||||
|
||||
|
||||
def refresh_streams(self):
|
||||
was_active = self.in_stream.active
|
||||
if was_active:
|
||||
self.in_stream.stop()
|
||||
self.out_stream.stop()
|
||||
|
||||
self.buffer = np.zeros((int(self.duration * self.input_sample_rate), self.channels), dtype=np.float32)
|
||||
# print(f"AudioRecorder initialized with duration={self.duration}s, sample_rate={self.sample_rate}Hz, channels={self.channels}")
|
||||
self.in_stream = sd.InputStream(
|
||||
callback=self.record_callback
|
||||
)
|
||||
|
||||
self.out_stream = sd.OutputStream(
|
||||
callback=self.playback_callback
|
||||
)
|
||||
|
||||
if was_active:
|
||||
self.in_stream.start()
|
||||
self.out_stream.start()
|
||||
|
||||
|
||||
|
||||
def record_callback(self, indata, frames, time, status):
|
||||
if status:
|
||||
# print(f"Recording status: {status}")
|
||||
pass
|
||||
|
||||
# Circular buffer implementation
|
||||
self.buffer = np.roll(self.buffer, -frames, axis=0)
|
||||
self.buffer[-frames:] = indata
|
||||
|
||||
def playback_callback(self, outdata, frames, time, status):
|
||||
if status:
|
||||
# print(f"Playback status: {status}")
|
||||
pass
|
||||
|
||||
outdata.fill(0)
|
||||
|
||||
# Iterate over a copy of the items to avoid modifying the dictionary during iteration
|
||||
for clip_id, clip_list in list(self.clip_map.items()):
|
||||
for clip in clip_list[:]: # Iterate over a copy of the list
|
||||
if not clip.is_finished():
|
||||
samples = clip.get_samples(frames)
|
||||
outdata[:] += samples.reshape(-1, 1) # Mix into output
|
||||
if clip.is_finished():
|
||||
self.clip_map[clip_id].remove(clip)
|
||||
if len(self.clip_map[clip_id]) == 0:
|
||||
del self.clip_map[clip_id]
|
||||
break # Exit inner loop since the key is deleted
|
||||
|
||||
|
||||
def save_last_n_seconds(self):
|
||||
# Create output directory if it doesn't exist
|
||||
os.makedirs(self.recordings_dir, exist_ok=True)
|
||||
|
||||
# Generate filename with timestamp
|
||||
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||
filename = os.path.join(self.recordings_dir, f"audio_capture_{timestamp}.wav")
|
||||
|
||||
# Normalize audio to prevent clipping
|
||||
audio_data = self.buffer / np.max(np.abs(self.buffer)) * .5
|
||||
|
||||
# Convert float32 to int16 for WAV file
|
||||
audio_data_int16 = (audio_data * 32767).astype(np.int16)
|
||||
|
||||
# Write buffer to file
|
||||
wavfile.write(filename, int(self.input_sample_rate), audio_data_int16)
|
||||
|
||||
meta = MetaDataManager()
|
||||
|
||||
clip_metadata = {
|
||||
"filename": filename,
|
||||
"name": f"Clip {timestamp}",
|
||||
"playbackType":"playStop",
|
||||
"volume": 1.0,
|
||||
}
|
||||
|
||||
meta.add_clip_to_collection("Uncategorized", clip_metadata )
|
||||
|
||||
|
||||
return clip_metadata
|
||||
|
||||
def set_buffer_duration(self, duration):
|
||||
self.duration = duration
|
||||
self.buffer = np.zeros((int(duration * self.input_sample_rate), self.channels), dtype=np.float32)
|
||||
|
||||
def set_recording_directory(self, directory):
|
||||
self.recordings_dir = directory
|
||||
|
||||
def start_recording(self):
|
||||
if(self.in_stream.active):
|
||||
# print("Already recording")
|
||||
return
|
||||
# print('number of channels', self.channels)
|
||||
|
||||
self.in_stream.start()
|
||||
self.out_stream.start()
|
||||
self.output_sample_rate = self.out_stream.samplerate
|
||||
self.input_sample_rate = self.in_stream.samplerate
|
||||
|
||||
def stop_recording(self):
|
||||
if(not self.in_stream.active):
|
||||
# print("Already stopped")
|
||||
return
|
||||
|
||||
self.in_stream.stop()
|
||||
self.out_stream.stop()
|
||||
|
||||
def is_recording(self):
|
||||
return self.in_stream.active
|
||||
|
||||
def play_clip(self, clip_metadata):
|
||||
print(f"Playing clip: {clip_metadata}")
|
||||
clip_id = clip_metadata.get("filename")
|
||||
if clip_metadata.get("playbackType") == "playStop":
|
||||
if clip_id in self.clip_map:
|
||||
del self.clip_map[clip_id]
|
||||
return
|
||||
else:
|
||||
self.clip_map[clip_id] = []
|
||||
if clip_id not in self.clip_map:
|
||||
self.clip_map[clip_id] = []
|
||||
import sounddevice as sd
|
||||
import numpy as np
|
||||
import os
|
||||
from datetime import datetime
|
||||
import scipy.io.wavfile as wavfile
|
||||
from metadata_manager import MetaDataManager
|
||||
from audio_clip import AudioClip
|
||||
|
||||
|
||||
# AudioClip class for clip playback
|
||||
|
||||
|
||||
class AudioIO:
|
||||
_instance = None
|
||||
|
||||
def __new__(cls, *args, **kwargs):
|
||||
if cls._instance is None:
|
||||
# print("Creating new AudioRecorder instance")
|
||||
cls._instance = super().__new__(cls)
|
||||
cls._instance.init()
|
||||
return cls._instance
|
||||
def init(self):
|
||||
self.duration = 30
|
||||
self.channels = 2
|
||||
self.input_sample_rate = 44100
|
||||
self.output_sample_rate = 44100
|
||||
self.buffer = np.zeros((int(self.duration * self.input_sample_rate), self.channels), dtype=np.float32)
|
||||
self.recordings_dir = "recordings"
|
||||
|
||||
sd.default.latency = 'low'
|
||||
|
||||
self.socket = None
|
||||
|
||||
self.in_stream = sd.InputStream(
|
||||
callback=self.record_callback
|
||||
)
|
||||
|
||||
self.out_stream = sd.OutputStream(
|
||||
callback=self.playback_callback,
|
||||
latency=3
|
||||
)
|
||||
|
||||
self.clip_map = {}
|
||||
|
||||
|
||||
def refresh_streams(self):
|
||||
was_active = self.in_stream.active
|
||||
if was_active:
|
||||
self.in_stream.stop()
|
||||
self.out_stream.stop()
|
||||
|
||||
self.buffer = np.zeros((int(self.duration * self.input_sample_rate), self.channels), dtype=np.float32)
|
||||
# print(f"AudioRecorder initialized with duration={self.duration}s, sample_rate={self.sample_rate}Hz, channels={self.channels}")
|
||||
self.in_stream = sd.InputStream(
|
||||
callback=self.record_callback
|
||||
)
|
||||
|
||||
self.out_stream = sd.OutputStream(
|
||||
callback=self.playback_callback
|
||||
)
|
||||
|
||||
if was_active:
|
||||
self.in_stream.start()
|
||||
self.out_stream.start()
|
||||
|
||||
|
||||
|
||||
def record_callback(self, indata, frames, time, status):
|
||||
if status:
|
||||
# print(f"Recording status: {status}")
|
||||
pass
|
||||
|
||||
# Circular buffer implementation
|
||||
self.buffer = np.roll(self.buffer, -frames, axis=0)
|
||||
self.buffer[-frames:] = indata
|
||||
|
||||
def playback_callback(self, outdata, frames, time, status):
|
||||
if status:
|
||||
# print(f"Playback status: {status}")
|
||||
pass
|
||||
|
||||
outdata.fill(0)
|
||||
|
||||
# Iterate over a copy of the items to avoid modifying the dictionary during iteration
|
||||
for clip_id, clip_list in list(self.clip_map.items()):
|
||||
for clip in clip_list[:]: # Iterate over a copy of the list
|
||||
if not clip.is_finished():
|
||||
samples = clip.get_samples(frames)
|
||||
outdata[:] += samples.reshape(-1, 1) # Mix into output
|
||||
if clip.is_finished():
|
||||
self.clip_map[clip_id].remove(clip)
|
||||
if len(self.clip_map[clip_id]) == 0:
|
||||
del self.clip_map[clip_id]
|
||||
break # Exit inner loop since the key is deleted
|
||||
|
||||
|
||||
def save_last_n_seconds(self):
|
||||
# Create output directory if it doesn't exist
|
||||
os.makedirs(self.recordings_dir, exist_ok=True)
|
||||
|
||||
# Generate filename with timestamp
|
||||
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||
filename = os.path.join(self.recordings_dir, f"audio_capture_{timestamp}.wav")
|
||||
|
||||
# Normalize audio to prevent clipping
|
||||
audio_data = self.buffer / np.max(np.abs(self.buffer)) * .5
|
||||
|
||||
# Convert float32 to int16 for WAV file
|
||||
audio_data_int16 = (audio_data * 32767).astype(np.int16)
|
||||
|
||||
# Write buffer to file
|
||||
wavfile.write(filename, int(self.input_sample_rate), audio_data_int16)
|
||||
|
||||
meta = MetaDataManager()
|
||||
|
||||
clip_metadata = {
|
||||
"filename": filename,
|
||||
"name": f"Clip {timestamp}",
|
||||
"playbackType":"playStop",
|
||||
"volume": 1.0,
|
||||
}
|
||||
|
||||
meta.add_clip_to_collection("Uncategorized", clip_metadata )
|
||||
self.socket.emit('new_clip', clip_metadata)
|
||||
|
||||
return clip_metadata
|
||||
|
||||
def set_buffer_duration(self, duration):
|
||||
self.duration = duration
|
||||
self.buffer = np.zeros((int(duration * self.input_sample_rate), self.channels), dtype=np.float32)
|
||||
|
||||
def set_recording_directory(self, directory):
|
||||
self.recordings_dir = directory
|
||||
|
||||
def start_recording(self):
|
||||
if(self.in_stream.active):
|
||||
# print("Already recording")
|
||||
return
|
||||
# print('number of channels', self.channels)
|
||||
|
||||
self.in_stream.start()
|
||||
self.out_stream.start()
|
||||
self.output_sample_rate = self.out_stream.samplerate
|
||||
self.input_sample_rate = self.in_stream.samplerate
|
||||
|
||||
def stop_recording(self):
|
||||
if(not self.in_stream.active):
|
||||
# print("Already stopped")
|
||||
return
|
||||
|
||||
self.in_stream.stop()
|
||||
self.out_stream.stop()
|
||||
|
||||
def is_recording(self):
|
||||
return self.in_stream.active
|
||||
|
||||
def play_clip(self, clip_metadata):
|
||||
print(f"Playing clip: {clip_metadata}")
|
||||
clip_id = clip_metadata.get("filename")
|
||||
if clip_metadata.get("playbackType") == "playStop":
|
||||
if clip_id in self.clip_map:
|
||||
del self.clip_map[clip_id]
|
||||
return
|
||||
else:
|
||||
self.clip_map[clip_id] = []
|
||||
if clip_id not in self.clip_map:
|
||||
self.clip_map[clip_id] = []
|
||||
self.clip_map[clip_id].append(AudioClip(clip_metadata, target_sample_rate=self.output_sample_rate))
|
||||
@ -1,68 +1,94 @@
|
||||
import argparse
|
||||
import os
|
||||
import sys
|
||||
from audio_io import AudioIO
|
||||
from windows_audio import WindowsAudioManager
|
||||
import sounddevice as sd
|
||||
from metadata_manager import MetaDataManager
|
||||
from settings import SettingsManager
|
||||
|
||||
from flask import Flask
|
||||
from flask_cors import CORS
|
||||
|
||||
from routes.recording import recording_bp
|
||||
from routes.device import device_bp
|
||||
from routes.metadata import metadata_bp
|
||||
from routes.settings import settings_bp
|
||||
from flask_socketio import SocketIO
|
||||
import threading
|
||||
|
||||
app = Flask(__name__)
|
||||
CORS(app)
|
||||
# socketio = SocketIO(app, cors_allowed_origins="*")
|
||||
# CORS(socketio)
|
||||
|
||||
def main():
|
||||
# Create argument parser
|
||||
parser = argparse.ArgumentParser(description='Audio Recording Service')
|
||||
|
||||
# OSC port argument
|
||||
parser.add_argument('--osc-port',
|
||||
type=int,
|
||||
help='OSC server port number',
|
||||
default=5010)
|
||||
|
||||
# Parse arguments
|
||||
args = parser.parse_args()
|
||||
audio_manager = WindowsAudioManager()
|
||||
settings = SettingsManager()
|
||||
|
||||
# Ensure save path exists
|
||||
os.makedirs(settings.get_settings('save_path'), exist_ok=True)
|
||||
|
||||
|
||||
io = AudioIO()
|
||||
io.start_recording()
|
||||
# Register blueprints
|
||||
app.register_blueprint(recording_bp)
|
||||
app.register_blueprint(device_bp)
|
||||
app.register_blueprint(metadata_bp)
|
||||
app.register_blueprint(settings_bp)
|
||||
app.run(host='127.0.0.1', port=settings.get_settings('http_port'), debug=False, use_reloader=True)
|
||||
# socketio.run(app, host='127.0.0.1', port=args.osc_port, debug=False, use_reloader=True)
|
||||
|
||||
|
||||
|
||||
# Run the OSC server
|
||||
# try:
|
||||
# print(f"Starting OSC Recording Server on port {args.osc_port}")
|
||||
# # osc_server.run_server()
|
||||
# except KeyboardInterrupt:
|
||||
# print("\nServer stopped by user.")
|
||||
# except Exception as e:
|
||||
# print(f"Error starting server: {e}")
|
||||
# sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import argparse
|
||||
import os
|
||||
import sys
|
||||
from audio_io import AudioIO
|
||||
from windows_audio import WindowsAudioManager
|
||||
import sounddevice as sd
|
||||
from metadata_manager import MetaDataManager
|
||||
from settings import SettingsManager
|
||||
|
||||
from flask import Flask
|
||||
from flask_cors import CORS
|
||||
|
||||
from routes.recording import recording_bp
|
||||
from routes.device import device_bp
|
||||
from routes.metadata import metadata_bp
|
||||
from routes.settings import settings_bp
|
||||
from flask_socketio import SocketIO, emit
|
||||
import threading
|
||||
|
||||
app = Flask(__name__)
|
||||
CORS(app)
|
||||
socketio = SocketIO(app, cors_allowed_origins="*", logger=True, engineio_logger=True, async_mode='eventlet')
|
||||
|
||||
@socketio.on('connect')
|
||||
def handle_connect():
|
||||
print("Client connected")
|
||||
emit('full_data', MetaDataManager().collections)
|
||||
|
||||
@socketio.on('record_clip')
|
||||
def record_clip():
|
||||
io = AudioIO()
|
||||
io.save_last_n_seconds();
|
||||
|
||||
@socketio.on('play_clip')
|
||||
def play_clip(data):
|
||||
io = AudioIO()
|
||||
print(f"Received play_clip event with data: {data}")
|
||||
if data:
|
||||
io.play_clip(data)
|
||||
|
||||
|
||||
def main():
|
||||
# Create argument parser
|
||||
parser = argparse.ArgumentParser(description='Audio Recording Service')
|
||||
|
||||
# OSC port argument
|
||||
parser.add_argument('--osc-port',
|
||||
type=int,
|
||||
help='OSC server port number',
|
||||
default=5010)
|
||||
|
||||
# Parse arguments
|
||||
args = parser.parse_args()
|
||||
audio_manager = WindowsAudioManager()
|
||||
settings = SettingsManager()
|
||||
meta = MetaDataManager()
|
||||
|
||||
|
||||
|
||||
# Ensure save path exists
|
||||
os.makedirs(settings.get_settings('save_path'), exist_ok=True)
|
||||
|
||||
|
||||
io = AudioIO()
|
||||
io.start_recording()
|
||||
|
||||
# settings.socket = socketio
|
||||
io.socket = socketio
|
||||
meta.socket = socketio
|
||||
|
||||
# Register blueprints
|
||||
app.register_blueprint(recording_bp)
|
||||
app.register_blueprint(device_bp)
|
||||
app.register_blueprint(metadata_bp)
|
||||
app.register_blueprint(settings_bp)
|
||||
print(f"Starting Flask server on port {settings.get_settings('http_port')}")
|
||||
# app.run(host='127.0.0.1', port=settings.get_settings('http_port'), debug=False, use_reloader=True)
|
||||
socketio.run(app, host='127.0.0.1', port=settings.get_settings('http_port'), debug=False, use_reloader=True, allow_unsafe_werkzeug=True)
|
||||
|
||||
|
||||
|
||||
# Run the OSC server
|
||||
# try:
|
||||
# print(f"Starting OSC Recording Server on port {args.osc_port}")
|
||||
# # osc_server.run_server()
|
||||
# except KeyboardInterrupt:
|
||||
# print("\nServer stopped by user.")
|
||||
# except Exception as e:
|
||||
# print(f"Error starting server: {e}")
|
||||
# sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@ -1,20 +1,20 @@
|
||||
{
|
||||
"Uncategorized": [
|
||||
{
|
||||
"endTime": 12.489270386266055,
|
||||
"filename": "C:\\Users\\mickl\\Desktop\\cliptrim-ui\\ClipTrimApp\\audio-service\\src\\recordings\\audio_capture_20260214_133540.wav",
|
||||
"name": "Clip 20260214_133540",
|
||||
"playbackType": "playStop",
|
||||
"startTime": 10.622317596566523,
|
||||
"volume": 1
|
||||
},
|
||||
{
|
||||
"endTime": 6.824034334763957,
|
||||
"filename": "C:\\Users\\mickl\\Desktop\\cliptrim-ui\\ClipTrimApp\\audio-service\\src\\recordings\\audio_capture_20260214_133137.wav",
|
||||
"name": "Clip 20260214_133137",
|
||||
"playbackType": "playStop",
|
||||
"startTime": 3.7982832618025753,
|
||||
"volume": 1
|
||||
}
|
||||
]
|
||||
{
|
||||
"Uncategorized": [
|
||||
{
|
||||
"endTime": 12.489270386266055,
|
||||
"filename": "C:\\Users\\mickl\\Desktop\\cliptrim-ui\\ClipTrimApp\\audio-service\\src\\recordings\\audio_capture_20260214_133540.wav",
|
||||
"name": "Clip 20260214_133540",
|
||||
"playbackType": "playStop",
|
||||
"startTime": 10.622317596566523,
|
||||
"volume": 1
|
||||
},
|
||||
{
|
||||
"endTime": 6.824034334763957,
|
||||
"filename": "C:\\Users\\mickl\\Desktop\\cliptrim-ui\\ClipTrimApp\\audio-service\\src\\recordings\\audio_capture_20260214_133137.wav",
|
||||
"name": "Clip 20260214_133137",
|
||||
"playbackType": "playStop",
|
||||
"startTime": 3.7982832618025753,
|
||||
"volume": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -1,100 +1,114 @@
|
||||
import os
|
||||
import json
|
||||
|
||||
class MetaDataManager:
|
||||
_instance = None
|
||||
|
||||
def __new__(cls, *args, **kwargs):
|
||||
if cls._instance is None:
|
||||
cls._instance = super().__new__(cls)
|
||||
cls._instance.init()
|
||||
return cls._instance
|
||||
def init(self):
|
||||
# read metadata file from executing directory
|
||||
self.metadata_file = os.path.join(os.getcwd(), "metadata.json")
|
||||
if os.path.exists(self.metadata_file):
|
||||
with open(self.metadata_file, "r") as f:
|
||||
self.collections = json.load(f)
|
||||
else:
|
||||
self.collections = {}
|
||||
if(collections := next((c for c in self.collections if c.get("name") == "Uncategorized"), None)) is None:
|
||||
self.collections.append({"name": "Uncategorized", "id": 0, "clips": []})
|
||||
self.save_metadata()
|
||||
|
||||
def create_collection(self, name):
|
||||
if any(c.get("name") == name for c in self.collections):
|
||||
raise ValueError(f"Collection '{name}' already exists.")
|
||||
new_id = max((c.get("id", 0) for c in self.collections), default=0) + 1
|
||||
self.collections.append({"name": name, "id": new_id, "clips": []})
|
||||
self.save_metadata()
|
||||
|
||||
def delete_collection(self, name):
|
||||
collection = next((c for c in self.collections if c.get("name") == name), None)
|
||||
if collection is None:
|
||||
raise ValueError(f"Collection '{name}' does not exist.")
|
||||
self.collections.remove(collection)
|
||||
self.save_metadata()
|
||||
|
||||
def add_clip_to_collection(self, collection_name, clip_metadata):
|
||||
collection = next((c for c in self.collections if c.get("name") == collection_name), None)
|
||||
if collection is None:
|
||||
raise ValueError(f"Collection '{collection_name}' does not exist.")
|
||||
collection["clips"].append(clip_metadata)
|
||||
self.save_metadata()
|
||||
|
||||
def remove_clip_from_collection(self, collection_name, clip_metadata):
|
||||
collection = next((c for c in self.collections if c.get("name") == collection_name), None)
|
||||
if collection is None:
|
||||
raise ValueError(f"Collection '{collection_name}' does not exist.")
|
||||
# Remove all clips with the same file name as clip_metadata["file_name"]
|
||||
in_list = any(clip.get("filename") == clip_metadata.get("filename") for clip in collection["clips"])
|
||||
if not in_list:
|
||||
raise ValueError(f"Clip with filename '{clip_metadata.get('filename')}' not found in collection '{collection_name}'.")
|
||||
|
||||
collection["clips"] = [
|
||||
clip for clip in collection["clips"]
|
||||
if clip.get("filename") != clip_metadata.get("filename")
|
||||
]
|
||||
self.save_metadata()
|
||||
|
||||
def move_clip_to_collection(self, source_collection, target_collection, clip_metadata):
|
||||
self.remove_clip_from_collection(source_collection, clip_metadata)
|
||||
self.add_clip_to_collection(target_collection, clip_metadata)
|
||||
|
||||
def edit_clip_in_collection(self, collection_name, new_clip_metadata):
|
||||
collection = next((c for c in self.collections if c.get("name") == collection_name), None)
|
||||
if collection is None:
|
||||
raise ValueError(f"Collection '{collection_name}' does not exist.")
|
||||
# Find the index of the clip with the same file name as old_clip_metadata["file_name"]
|
||||
index = next((i for i, clip in enumerate(collection["clips"]) if clip.get("filename") == new_clip_metadata.get("filename")), None)
|
||||
if index is None:
|
||||
raise ValueError(f"Clip with filename '{new_clip_metadata.get('filename')}' not found in collection '{collection_name}'.")
|
||||
|
||||
collection["clips"][index] = new_clip_metadata
|
||||
self.save_metadata()
|
||||
|
||||
def get_collections(self):
|
||||
return list(map(lambda c: {"name": c.get("name"), "id": c.get("id")}, self.collections))
|
||||
|
||||
def get_clips_in_collection(self, collection_name):
|
||||
collection = next((c for c in self.collections if c.get("name") == collection_name), None)
|
||||
if collection is None:
|
||||
raise ValueError(f"Collection '{collection_name}' does not exist.")
|
||||
return collection["clips"]
|
||||
|
||||
def reorder_clips_in_collection(self, collection_name, new_order):
|
||||
collection = next((c for c in self.collections if c.get("name") == collection_name), None)
|
||||
if collection is None:
|
||||
raise ValueError(f"Collection '{collection_name}' does not exist.")
|
||||
existing_filenames = {clip.get("filename") for clip in collection["clips"]}
|
||||
new_filenames = {clip.get("filename") for clip in new_order}
|
||||
|
||||
if not new_filenames.issubset(existing_filenames):
|
||||
raise ValueError("New order contains clips that do not exist in the collection.")
|
||||
|
||||
collection["clips"] = new_order
|
||||
self.save_metadata()
|
||||
|
||||
def save_metadata(self):
|
||||
with open(self.metadata_file, "w") as f:
|
||||
json.dump(self.collections, f, indent=4)
|
||||
import os
|
||||
import json
|
||||
|
||||
class MetaDataManager:
|
||||
_instance = None
|
||||
|
||||
def __new__(cls, *args, **kwargs):
|
||||
if cls._instance is None:
|
||||
cls._instance = super().__new__(cls)
|
||||
cls._instance.init()
|
||||
return cls._instance
|
||||
def init(self):
|
||||
self.socket = None
|
||||
# read metadata file from executing directory
|
||||
self.metadata_file = os.path.join(os.getcwd(), "metadata.json")
|
||||
if os.path.exists(self.metadata_file):
|
||||
with open(self.metadata_file, "r") as f:
|
||||
self.collections = json.load(f)
|
||||
else:
|
||||
self.collections = {}
|
||||
if(collections := next((c for c in self.collections if c.get("name") == "Uncategorized"), None)) is None:
|
||||
self.collections.append({"name": "Uncategorized", "id": 0, "clips": []})
|
||||
self.save_metadata()
|
||||
|
||||
def create_collection(self, name):
|
||||
if any(c.get("name") == name for c in self.collections):
|
||||
raise ValueError(f"Collection '{name}' already exists.")
|
||||
new_id = max((c.get("id", 0) for c in self.collections), default=0) + 1
|
||||
self.collections.append({"name": name, "id": new_id, "clips": []})
|
||||
self.save_metadata()
|
||||
|
||||
def delete_collection(self, name):
|
||||
collection = next((c for c in self.collections if c.get("name") == name), None)
|
||||
if collection is None:
|
||||
raise ValueError(f"Collection '{name}' does not exist.")
|
||||
self.collections.remove(collection)
|
||||
self.save_metadata()
|
||||
|
||||
def add_clip_to_collection(self, collection_name, clip_metadata):
|
||||
collection = next((c for c in self.collections if c.get("name") == collection_name), None)
|
||||
if collection is None:
|
||||
raise ValueError(f"Collection '{collection_name}' does not exist.")
|
||||
collection["clips"].append(clip_metadata)
|
||||
if not self.socket is None:
|
||||
self.socket.emit('collection_updated', collection)
|
||||
self.save_metadata()
|
||||
|
||||
def remove_clip_from_collection(self, collection_name, clip_metadata):
|
||||
collection = next((c for c in self.collections if c.get("name") == collection_name), None)
|
||||
if collection is None:
|
||||
raise ValueError(f"Collection '{collection_name}' does not exist.")
|
||||
# Remove all clips with the same file name as clip_metadata["file_name"]
|
||||
in_list = any(clip.get("filename") == clip_metadata.get("filename") for clip in collection["clips"])
|
||||
if not in_list:
|
||||
raise ValueError(f"Clip with filename '{clip_metadata.get('filename')}' not found in collection '{collection_name}'.")
|
||||
|
||||
collection["clips"] = [
|
||||
clip for clip in collection["clips"]
|
||||
if clip.get("filename") != clip_metadata.get("filename")
|
||||
]
|
||||
if not self.socket is None:
|
||||
self.socket.emit('collection_updated', collection)
|
||||
self.save_metadata()
|
||||
|
||||
|
||||
def move_clip_to_collection(self, source_collection, target_collection, clip_metadata):
|
||||
self.remove_clip_from_collection(source_collection, clip_metadata)
|
||||
self.add_clip_to_collection(target_collection, clip_metadata)
|
||||
if not self.socket is None:
|
||||
self.socket.emit('collection_updated', source_collection)
|
||||
self.socket.emit('collection_updated', target_collection)
|
||||
|
||||
|
||||
def edit_clip_in_collection(self, collection_name, new_clip_metadata):
|
||||
collection = next((c for c in self.collections if c.get("name") == collection_name), None)
|
||||
if collection is None:
|
||||
raise ValueError(f"Collection '{collection_name}' does not exist.")
|
||||
# Find the index of the clip with the same file name as old_clip_metadata["file_name"]
|
||||
index = next((i for i, clip in enumerate(collection["clips"]) if clip.get("filename") == new_clip_metadata.get("filename")), None)
|
||||
if index is None:
|
||||
raise ValueError(f"Clip with filename '{new_clip_metadata.get('filename')}' not found in collection '{collection_name}'.")
|
||||
|
||||
collection["clips"][index] = new_clip_metadata
|
||||
if not self.socket is None:
|
||||
self.socket.emit('collection_updated', collection)
|
||||
self.save_metadata()
|
||||
|
||||
def get_collections(self):
|
||||
return list(map(lambda c: {"name": c.get("name"), "id": c.get("id")}, self.collections))
|
||||
|
||||
def get_clips_in_collection(self, collection_name):
|
||||
collection = next((c for c in self.collections if c.get("name") == collection_name), None)
|
||||
if collection is None:
|
||||
raise ValueError(f"Collection '{collection_name}' does not exist.")
|
||||
return collection["clips"]
|
||||
|
||||
def reorder_clips_in_collection(self, collection_name, new_order):
|
||||
collection = next((c for c in self.collections if c.get("name") == collection_name), None)
|
||||
if collection is None:
|
||||
raise ValueError(f"Collection '{collection_name}' does not exist.")
|
||||
existing_filenames = {clip.get("filename") for clip in collection["clips"]}
|
||||
new_filenames = {clip.get("filename") for clip in new_order}
|
||||
|
||||
if not new_filenames.issubset(existing_filenames):
|
||||
raise ValueError("New order contains clips that do not exist in the collection.")
|
||||
|
||||
collection["clips"] = new_order
|
||||
if not self.socket is None:
|
||||
self.socket.emit('collection_updated', collection)
|
||||
self.save_metadata()
|
||||
|
||||
def save_metadata(self):
|
||||
with open(self.metadata_file, "w") as f:
|
||||
json.dump(self.collections, f, indent=4)
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
[ViewState]
|
||||
Mode=
|
||||
Vid=
|
||||
FolderType=Generic
|
||||
[ViewState]
|
||||
Mode=
|
||||
Vid=
|
||||
FolderType=Generic
|
||||
|
||||
@ -98,3 +98,9 @@ def edit_clip_in_collection():
|
||||
return jsonify({'status': 'success', 'collections': collections})
|
||||
except ValueError as e:
|
||||
return jsonify({'status': 'error', 'message': str(e)}), 400
|
||||
|
||||
|
||||
@metadata_bp.route('/ws/test', methods=['POST'])
|
||||
def test_websocket():
|
||||
MetaDataManager().socket.emit('test_event', {'data': 'Test message from metadata route'})
|
||||
return jsonify({'status': 'success'})
|
||||
@ -1,57 +1,57 @@
|
||||
|
||||
from flask import Blueprint, request, jsonify
|
||||
from audio_io import AudioIO
|
||||
import os
|
||||
|
||||
recording_bp = Blueprint('recording', __name__)
|
||||
|
||||
@recording_bp.route('/record/start', methods=['POST'])
|
||||
def start_recording():
|
||||
recorder = AudioIO()
|
||||
print('HTTP: Starting audio recording')
|
||||
recorder.start_recording()
|
||||
return jsonify({'status': 'recording started'})
|
||||
|
||||
@recording_bp.route('/record/stop', methods=['POST'])
|
||||
def stop_recording():
|
||||
recorder = AudioIO()
|
||||
# print('HTTP: Stopping audio recording')
|
||||
recorder.stop_recording()
|
||||
return jsonify({'status': 'recording stopped'})
|
||||
|
||||
@recording_bp.route('/record/save', methods=['POST'])
|
||||
def save_recording():
|
||||
recorder = AudioIO()
|
||||
# print('HTTP: Saving audio recording')
|
||||
saved_file = recorder.save_last_n_seconds()
|
||||
return jsonify({'status': 'recording saved', 'file': saved_file})
|
||||
|
||||
|
||||
@recording_bp.route('/record/status', methods=['GET'])
|
||||
def recording_status():
|
||||
recorder = AudioIO()
|
||||
# print('HTTP: Checking recording status')
|
||||
status = 'recording' if recorder.is_recording() else 'stopped'
|
||||
return jsonify({'status': status})
|
||||
|
||||
@recording_bp.route('/record/delete', methods=['POST'])
|
||||
def recording_delete():
|
||||
filename = request.json.get('filename')
|
||||
try:
|
||||
os.remove(filename)
|
||||
return jsonify({'status': 'success'})
|
||||
except Exception as e:
|
||||
return jsonify({'status': 'error', 'message': str(e)}), 400
|
||||
|
||||
@recording_bp.route('/playback/start', methods=['POST'])
|
||||
def playback_start():
|
||||
print(f"Playing clip")
|
||||
# print('HTTP: Starting audio playback')
|
||||
clip = request.json
|
||||
try:
|
||||
io = AudioIO()
|
||||
io.play_clip(clip)
|
||||
# os.remove(filename)
|
||||
return jsonify({'status': 'success'})
|
||||
except Exception as e:
|
||||
|
||||
from flask import Blueprint, request, jsonify
|
||||
from audio_io import AudioIO
|
||||
import os
|
||||
|
||||
recording_bp = Blueprint('recording', __name__)
|
||||
|
||||
@recording_bp.route('/record/start', methods=['POST'])
|
||||
def start_recording():
|
||||
recorder = AudioIO()
|
||||
print('HTTP: Starting audio recording')
|
||||
recorder.start_recording()
|
||||
return jsonify({'status': 'recording started'})
|
||||
|
||||
@recording_bp.route('/record/stop', methods=['POST'])
|
||||
def stop_recording():
|
||||
recorder = AudioIO()
|
||||
# print('HTTP: Stopping audio recording')
|
||||
recorder.stop_recording()
|
||||
return jsonify({'status': 'recording stopped'})
|
||||
|
||||
@recording_bp.route('/record/save', methods=['POST'])
|
||||
def save_recording():
|
||||
recorder = AudioIO()
|
||||
# print('HTTP: Saving audio recording')
|
||||
saved_file = recorder.save_last_n_seconds()
|
||||
return jsonify({'status': 'recording saved', 'file': saved_file})
|
||||
|
||||
|
||||
@recording_bp.route('/record/status', methods=['GET'])
|
||||
def recording_status():
|
||||
recorder = AudioIO()
|
||||
# print('HTTP: Checking recording status')
|
||||
status = 'recording' if recorder.is_recording() else 'stopped'
|
||||
return jsonify({'status': status})
|
||||
|
||||
@recording_bp.route('/record/delete', methods=['POST'])
|
||||
def recording_delete():
|
||||
filename = request.json.get('filename')
|
||||
try:
|
||||
os.remove(filename)
|
||||
return jsonify({'status': 'success'})
|
||||
except Exception as e:
|
||||
return jsonify({'status': 'error', 'message': str(e)}), 400
|
||||
|
||||
@recording_bp.route('/playback/start', methods=['POST'])
|
||||
def playback_start():
|
||||
print(f"Playing clip")
|
||||
# print('HTTP: Starting audio playback')
|
||||
clip = request.json
|
||||
try:
|
||||
io = AudioIO()
|
||||
io.play_clip(clip)
|
||||
# os.remove(filename)
|
||||
return jsonify({'status': 'success'})
|
||||
except Exception as e:
|
||||
return jsonify({'status': 'error', 'message': str(e)}), 400
|
||||
@ -1,31 +1,32 @@
|
||||
from flask import Blueprint, request, jsonify
|
||||
from settings import SettingsManager
|
||||
|
||||
settings_bp = Blueprint('settings', __name__)
|
||||
|
||||
|
||||
@settings_bp.route('/settings', methods=['GET'])
|
||||
def get_all_settings():
|
||||
return jsonify({'status': 'success', 'settings': SettingsManager().get_all_settings()})
|
||||
|
||||
@settings_bp.route('/settings/<name>', methods=['GET'])
|
||||
def get_setting(name):
|
||||
value = SettingsManager().get_settings(name)
|
||||
if value is not None:
|
||||
return jsonify({'status': 'success', 'name': name, 'value': value})
|
||||
else:
|
||||
return jsonify({'status': 'error', 'message': f'Setting "{name}" not found'}), 404
|
||||
|
||||
@settings_bp.route('/settings/update', methods=['POST'])
|
||||
def set_all_settings():
|
||||
settings = request.json.get('settings')
|
||||
print (f"Received settings update: {settings}")
|
||||
if settings is None:
|
||||
return jsonify({'status': 'error', 'message': 'Settings are required'}), 400
|
||||
try:
|
||||
for name, value in settings.items():
|
||||
print(f"Updating setting '{name}' to '{value}'")
|
||||
SettingsManager().set_settings(name, value)
|
||||
return jsonify({'status': 'success', 'settings': settings})
|
||||
except ValueError as e:
|
||||
return jsonify({'status': 'error', 'message': str(e)}), 400
|
||||
from flask import Blueprint, request, jsonify
|
||||
from settings import SettingsManager
|
||||
|
||||
settings_bp = Blueprint('settings', __name__)
|
||||
|
||||
|
||||
@settings_bp.route('/settings', methods=['GET'])
|
||||
def get_all_settings():
|
||||
return jsonify({'status': 'success', 'settings': SettingsManager().get_all_settings()})
|
||||
|
||||
@settings_bp.route('/settings/<name>', methods=['GET'])
|
||||
def get_setting(name):
|
||||
value = SettingsManager().get_settings(name)
|
||||
if value is not None:
|
||||
return jsonify({'status': 'success', 'name': name, 'value': value})
|
||||
else:
|
||||
return jsonify({'status': 'error', 'message': f'Setting "{name}" not found'}), 404
|
||||
|
||||
@settings_bp.route('/settings/update', methods=['POST'])
|
||||
def set_all_settings():
|
||||
settings = request.json.get('settings')
|
||||
print (f"Received settings update: {settings}")
|
||||
if settings is None:
|
||||
return jsonify({'status': 'error', 'message': 'Settings are required'}), 400
|
||||
try:
|
||||
for name, value in settings.items():
|
||||
print(f"Updating setting '{name}' to '{value}'")
|
||||
SettingsManager().set_settings(name, value)
|
||||
return jsonify({'status': 'success', 'settings': settings})
|
||||
except ValueError as e:
|
||||
return jsonify({'status': 'error', 'message': str(e)}), 400
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
{
|
||||
"input_device": {
|
||||
"index": 0,
|
||||
"name": "Microsoft Sound Mapper - Input",
|
||||
"max_input_channels": 2,
|
||||
"default_samplerate": 44100.0
|
||||
},
|
||||
"save_path": "C:\\Users\\mickl\\Desktop\\cliptrim-ui\\ClipTrimApp\\audio-service\\src\\recordings",
|
||||
"recording_length": 15
|
||||
{
|
||||
"input_device": {
|
||||
"index": 0,
|
||||
"name": "Microsoft Sound Mapper - Input",
|
||||
"max_input_channels": 2,
|
||||
"default_samplerate": 44100.0
|
||||
},
|
||||
"save_path": "C:\\Users\\mickl\\Desktop\\cliptrim-ui\\ClipTrimApp\\audio-service\\src\\recordings",
|
||||
"recording_length": 15
|
||||
}
|
||||
@ -1,112 +1,108 @@
|
||||
import sounddevice as sd
|
||||
import numpy as np
|
||||
import comtypes
|
||||
import comtypes.client
|
||||
from comtypes import CLSCTX_ALL
|
||||
from pycaw.pycaw import AudioUtilities, IAudioEndpointVolume
|
||||
import json
|
||||
|
||||
class WindowsAudioManager:
|
||||
_instance = None
|
||||
|
||||
def __new__(cls, *args, **kwargs):
|
||||
if cls._instance is None:
|
||||
cls._instance = super().__new__(cls)
|
||||
cls._instance.init()
|
||||
return cls._instance
|
||||
def init(self):
|
||||
"""
|
||||
Initialize Windows audio device and volume management.
|
||||
"""
|
||||
host_apis = sd.query_hostapis()
|
||||
wasapi_device_indexes = None
|
||||
for api in host_apis:
|
||||
if api['name'].lower() == 'Windows WASAPI'.lower():
|
||||
wasapi_device_indexes = api['devices']
|
||||
break
|
||||
# print(f"Host APIs: {host_apis}")
|
||||
# print(f"WASAPI Device Indexes: {wasapi_device_indexes}")
|
||||
wasapi_device_indexes = set(wasapi_device_indexes) if wasapi_device_indexes is not None else set()
|
||||
self.devices = [dev for dev in sd.query_devices() if dev['index'] in wasapi_device_indexes]
|
||||
# self.devices = sd.query_devices()
|
||||
# print(f"devices: {self.devices}")
|
||||
|
||||
self.default_input = sd.default.device[0]
|
||||
self.default_output = sd.default.device[1]
|
||||
|
||||
def list_audio_devices(self, kind='input'):
|
||||
"""
|
||||
List available audio devices.
|
||||
|
||||
:param kind: 'input' or 'output'
|
||||
:return: List of audio devices
|
||||
"""
|
||||
if kind == 'input':
|
||||
return [
|
||||
{
|
||||
'index': dev['index'],
|
||||
'name': dev['name'],
|
||||
'channels': dev['max_input_channels'],
|
||||
'default_samplerate': dev['default_samplerate']
|
||||
}
|
||||
for dev in self.devices if dev['max_input_channels'] > 0
|
||||
]
|
||||
elif kind == 'output':
|
||||
return [
|
||||
{
|
||||
'index': dev['index'],
|
||||
'name': dev['name'],
|
||||
'channels': dev['max_output_channels'],
|
||||
'default_samplerate': dev['default_samplerate']
|
||||
}
|
||||
for dev in self.devices if dev['max_output_channels'] > 0
|
||||
]
|
||||
def get_default_device(self, kind='input'):
|
||||
"""
|
||||
Get the default audio device.
|
||||
|
||||
:param kind: 'input' or 'output'
|
||||
:return: Default audio device information
|
||||
"""
|
||||
if kind == 'input':
|
||||
dev = self.devices[self.default_input]
|
||||
return [
|
||||
{
|
||||
'index': dev['index'],
|
||||
'name': dev['name'],
|
||||
'max_input_channels': dev['max_input_channels'],
|
||||
'default_samplerate': dev['default_samplerate']
|
||||
}
|
||||
]
|
||||
|
||||
def set_default_input_device(self, device_index):
|
||||
if(device_index is None):
|
||||
return 0
|
||||
"""
|
||||
Set the default input audio device.
|
||||
|
||||
:param device_index: Index of the audio device
|
||||
:return: Sample rate of the selected device
|
||||
"""
|
||||
sd.default.device[0] = device_index
|
||||
self.default_input = device_index
|
||||
|
||||
# Get the sample rate of the selected device
|
||||
device_info = sd.query_devices(device_index)
|
||||
return device_info['default_samplerate']
|
||||
|
||||
def set_default_output_device(self, device_index):
|
||||
if(device_index is None):
|
||||
return self.get_current_output_device_sample_rate()
|
||||
"""
|
||||
Set the default output audio device.
|
||||
|
||||
:param device_index: Index of the audio device
|
||||
:return: Sample rate of the selected device
|
||||
"""
|
||||
sd.default.device[1] = device_index
|
||||
self.default_output = device_index
|
||||
|
||||
# Get the sample rate of the selected device
|
||||
device_info = sd.query_devices(device_index)
|
||||
import sounddevice as sd
|
||||
import numpy as np
|
||||
import json
|
||||
|
||||
class WindowsAudioManager:
|
||||
_instance = None
|
||||
|
||||
def __new__(cls, *args, **kwargs):
|
||||
if cls._instance is None:
|
||||
cls._instance = super().__new__(cls)
|
||||
cls._instance.init()
|
||||
return cls._instance
|
||||
def init(self):
|
||||
"""
|
||||
Initialize Windows audio device and volume management.
|
||||
"""
|
||||
host_apis = sd.query_hostapis()
|
||||
wasapi_device_indexes = None
|
||||
for api in host_apis:
|
||||
if api['name'].lower() == 'Windows WASAPI'.lower():
|
||||
wasapi_device_indexes = api['devices']
|
||||
break
|
||||
# print(f"Host APIs: {host_apis}")
|
||||
# print(f"WASAPI Device Indexes: {wasapi_device_indexes}")
|
||||
wasapi_device_indexes = set(wasapi_device_indexes) if wasapi_device_indexes is not None else set()
|
||||
self.devices = [dev for dev in sd.query_devices() if dev['index'] in wasapi_device_indexes]
|
||||
# self.devices = sd.query_devices()
|
||||
# print(f"devices: {self.devices}")
|
||||
|
||||
self.default_input = sd.default.device[0]
|
||||
self.default_output = sd.default.device[1]
|
||||
|
||||
def list_audio_devices(self, kind='input'):
|
||||
"""
|
||||
List available audio devices.
|
||||
|
||||
:param kind: 'input' or 'output'
|
||||
:return: List of audio devices
|
||||
"""
|
||||
if kind == 'input':
|
||||
return [
|
||||
{
|
||||
'index': dev['index'],
|
||||
'name': dev['name'],
|
||||
'channels': dev['max_input_channels'],
|
||||
'default_samplerate': dev['default_samplerate']
|
||||
}
|
||||
for dev in self.devices if dev['max_input_channels'] > 0
|
||||
]
|
||||
elif kind == 'output':
|
||||
return [
|
||||
{
|
||||
'index': dev['index'],
|
||||
'name': dev['name'],
|
||||
'channels': dev['max_output_channels'],
|
||||
'default_samplerate': dev['default_samplerate']
|
||||
}
|
||||
for dev in self.devices if dev['max_output_channels'] > 0
|
||||
]
|
||||
def get_default_device(self, kind='input'):
|
||||
"""
|
||||
Get the default audio device.
|
||||
|
||||
:param kind: 'input' or 'output'
|
||||
:return: Default audio device information
|
||||
"""
|
||||
if kind == 'input':
|
||||
dev = self.devices[self.default_input]
|
||||
return [
|
||||
{
|
||||
'index': dev['index'],
|
||||
'name': dev['name'],
|
||||
'max_input_channels': dev['max_input_channels'],
|
||||
'default_samplerate': dev['default_samplerate']
|
||||
}
|
||||
]
|
||||
|
||||
def set_default_input_device(self, device_index):
|
||||
if(device_index is None):
|
||||
return 0
|
||||
"""
|
||||
Set the default input audio device.
|
||||
|
||||
:param device_index: Index of the audio device
|
||||
:return: Sample rate of the selected device
|
||||
"""
|
||||
sd.default.device[0] = device_index
|
||||
self.default_input = device_index
|
||||
|
||||
# Get the sample rate of the selected device
|
||||
device_info = sd.query_devices(device_index)
|
||||
return device_info['default_samplerate']
|
||||
|
||||
def set_default_output_device(self, device_index):
|
||||
if(device_index is None):
|
||||
return self.get_current_output_device_sample_rate()
|
||||
"""
|
||||
Set the default output audio device.
|
||||
|
||||
:param device_index: Index of the audio device
|
||||
:return: Sample rate of the selected device
|
||||
"""
|
||||
sd.default.device[1] = device_index
|
||||
self.default_output = device_index
|
||||
|
||||
# Get the sample rate of the selected device
|
||||
device_info = sd.query_devices(device_index)
|
||||
return device_info['default_samplerate']
|
||||
@ -18,6 +18,8 @@ import { resolveHtmlPath } from './util';
|
||||
import registerFileIpcHandlers from '../ipc/audio/main';
|
||||
import PythonSubprocessManager from './service';
|
||||
|
||||
const pythonManager = new PythonSubprocessManager('src/main.py');
|
||||
|
||||
class AppUpdater {
|
||||
constructor() {
|
||||
log.transports.file.level = 'info';
|
||||
@ -112,9 +114,6 @@ const createWindow = async () => {
|
||||
|
||||
registerFileIpcHandlers();
|
||||
|
||||
const pythonManager = new PythonSubprocessManager('src/main.py');
|
||||
|
||||
pythonManager.start();
|
||||
// Remove this if your app does not use auto updates
|
||||
// eslint-disable-next-line
|
||||
new AppUpdater();
|
||||
@ -127,6 +126,7 @@ const createWindow = async () => {
|
||||
app.on('window-all-closed', () => {
|
||||
// Respect the OSX convention of having the application in memory even
|
||||
// after all windows have been closed
|
||||
pythonManager.stop();
|
||||
if (process.platform !== 'darwin') {
|
||||
app.quit();
|
||||
}
|
||||
@ -135,6 +135,7 @@ app.on('window-all-closed', () => {
|
||||
app
|
||||
.whenReady()
|
||||
.then(() => {
|
||||
// pythonManager.start();
|
||||
createWindow();
|
||||
app.on('activate', () => {
|
||||
// On macOS it's common to re-create a window in the app when the
|
||||
|
||||
@ -40,10 +40,10 @@ export default class PythonSubprocessManager {
|
||||
},
|
||||
);
|
||||
this.process.stdout.on('data', (data: Buffer) => {
|
||||
console.log(`Python stdout: ${data.toString()}`);
|
||||
// console.log(`Python stdout: ${data.toString()}`);
|
||||
});
|
||||
this.process.stderr.on('data', (data: Buffer) => {
|
||||
// console.error(`Python stderr: ${data.toString()}`);
|
||||
// console.error(`Python stderr: ${data.toString()}`);
|
||||
const lines = data.toString().split('\n');
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
for (const line of lines) {
|
||||
|
||||
@ -67,32 +67,11 @@ const metadataSlice = createSlice({
|
||||
targetState.clips.push(clip);
|
||||
}
|
||||
},
|
||||
addNewClips(state, action) {
|
||||
const { collections } = action.payload;
|
||||
Object.keys(collections).forEach((collection) => {
|
||||
const collectionState = state.collections.find(
|
||||
(col) => col.name === collection,
|
||||
);
|
||||
if (!collectionState) {
|
||||
state.collections.push({
|
||||
name: collection,
|
||||
id: Date.now(),
|
||||
clips: [],
|
||||
});
|
||||
}
|
||||
const existingFilenames = new Set(
|
||||
state.collections
|
||||
.find((col) => col.name === collection)
|
||||
?.clips.map((clip) => clip.filename) || [],
|
||||
);
|
||||
const newClips = collections[collection].filter(
|
||||
(clip: ClipMetadata) => !existingFilenames.has(clip.filename),
|
||||
);
|
||||
// const collectionState = state.collections.find(
|
||||
// (col) => col.name === collection,
|
||||
// );
|
||||
if (collectionState) {
|
||||
collectionState.clips.push(...newClips);
|
||||
addNewClip(state, action) {
|
||||
const { clip } = action.payload;
|
||||
state.collections.forEach((collection) => {
|
||||
if (collection.name === 'Uncategorized') {
|
||||
collection.clips.push(clip);
|
||||
}
|
||||
});
|
||||
},
|
||||
@ -113,6 +92,6 @@ export type RootState = ReturnType<AppStore['getState']>;
|
||||
// Inferred type: {posts: PostsState, comments: CommentsState, users: UsersState}
|
||||
export type AppDispatch = AppStore['dispatch'];
|
||||
|
||||
export const { setCollections, addNewClips, addCollection } =
|
||||
export const { setCollections, addNewClip, addCollection } =
|
||||
metadataSlice.actions;
|
||||
export default metadataSlice.reducer;
|
||||
|
||||
@ -7,6 +7,7 @@ import { ThemeProvider, createTheme } from '@mui/material/styles';
|
||||
import DialogTitle from '@mui/material/DialogTitle';
|
||||
import DialogContent from '@mui/material/DialogContent';
|
||||
import DialogActions from '@mui/material/DialogActions';
|
||||
import io from 'socket.io-client';
|
||||
// import 'tailwindcss/tailwind.css';
|
||||
import './App.css';
|
||||
import ClipList from './components/ClipList';
|
||||
@ -14,7 +15,7 @@ import { useAppDispatch, useAppSelector } from './hooks';
|
||||
import { store } from '../redux/main';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import SettingsPage from './Settings';
|
||||
import apiFetch from './api';
|
||||
import { apiFetch, getBaseUrl } from './api';
|
||||
|
||||
function MainPage() {
|
||||
const dispatch = useAppDispatch();
|
||||
@ -27,20 +28,46 @@ function MainPage() {
|
||||
const [newCollectionOpen, setNewCollectionOpen] = useState(false);
|
||||
const [newCollectionName, setNewCollectionName] = useState<string>('');
|
||||
const navigate = useNavigate();
|
||||
const [socket, setSocket] = useState<any>(null);
|
||||
|
||||
useEffect(() => {}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchMetadata = async () => {
|
||||
try {
|
||||
const response = await apiFetch('meta');
|
||||
const data = await response.json();
|
||||
dispatch({ type: 'metadata/setAllData', payload: data });
|
||||
} catch (error) {
|
||||
console.error('Error fetching metadata:', error);
|
||||
}
|
||||
const initializeSocket = async () => {
|
||||
const baseUrl = await getBaseUrl();
|
||||
const newSocket = io(baseUrl);
|
||||
setSocket(newSocket);
|
||||
newSocket.on('connect', () => {
|
||||
console.log('Connected to WebSocket server');
|
||||
});
|
||||
newSocket.on('full_data', (data: any) => {
|
||||
console.log('Received full_data from server:', data);
|
||||
dispatch({
|
||||
type: 'metadata/setAllData',
|
||||
payload: { collections: data },
|
||||
});
|
||||
});
|
||||
newSocket.on('new_clip', (data: any) => {
|
||||
console.log('Received new_clips from server:', data);
|
||||
dispatch({
|
||||
type: 'metadata/addNewClip',
|
||||
payload: { clip: data },
|
||||
});
|
||||
});
|
||||
};
|
||||
fetchMetadata();
|
||||
const intervalId = setInterval(fetchMetadata, 5000);
|
||||
return () => clearInterval(intervalId);
|
||||
initializeSocket();
|
||||
// const fetchMetadata = async () => {
|
||||
// try {
|
||||
// const response = await apiFetch('meta');
|
||||
// const data = await response.json();
|
||||
// dispatch({ type: 'metadata/setAllData', payload: data });
|
||||
// } catch (error) {
|
||||
// console.error('Error fetching metadata:', error);
|
||||
// }
|
||||
// };
|
||||
// fetchMetadata();
|
||||
// const intervalId = setInterval(fetchMetadata, 5000);
|
||||
// return () => clearInterval(intervalId);
|
||||
}, [dispatch]);
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@ -5,7 +5,7 @@ import './App.css';
|
||||
import TextField from '@mui/material/TextField';
|
||||
import Select from '@mui/material/Select';
|
||||
import MenuItem from '@mui/material/MenuItem';
|
||||
import apiFetch from './api';
|
||||
import { apiFetch } from './api';
|
||||
|
||||
type AudioDevice = {
|
||||
index: number;
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
const getBaseUrl = async () => {
|
||||
export const getBaseUrl = async () => {
|
||||
const port = await window.audio.getPort();
|
||||
if (port.error || !port.port) {
|
||||
return `http://localhost:5010`;
|
||||
@ -7,7 +7,7 @@ const getBaseUrl = async () => {
|
||||
return `http://localhost:${port.port}`;
|
||||
};
|
||||
|
||||
export default async function apiFetch(endpoint: string, options = {}) {
|
||||
export async function apiFetch(endpoint: string, options = {}) {
|
||||
const url = `${await getBaseUrl()}/${endpoint}`;
|
||||
return fetch(url, options);
|
||||
}
|
||||
|
||||
@ -15,7 +15,7 @@ import { restrictToVerticalAxis } from '@dnd-kit/modifiers';
|
||||
import AudioTrimmer from './AudioTrimer';
|
||||
import { ClipMetadata } from '../../redux/types';
|
||||
import { useAppDispatch, useAppSelector } from '../hooks';
|
||||
import apiFetch from '../api';
|
||||
import { apiFetch } from '../api';
|
||||
|
||||
export interface ClipListProps {
|
||||
collection: string;
|
||||
|
||||
@ -1,25 +1,25 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.8.34330.188
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ClipTrimDotNet", "ClipTrimDotNet\ClipTrimDotNet.csproj", "{4635D874-69C0-4010-BE46-77EF92EB1553}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{4635D874-69C0-4010-BE46-77EF92EB1553}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{4635D874-69C0-4010-BE46-77EF92EB1553}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{4635D874-69C0-4010-BE46-77EF92EB1553}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{4635D874-69C0-4010-BE46-77EF92EB1553}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {926C6896-F36A-4F3B-A9DD-CA3AA48AD99F}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.8.34330.188
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ClipTrimDotNet", "ClipTrimDotNet\ClipTrimDotNet.csproj", "{4635D874-69C0-4010-BE46-77EF92EB1553}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{4635D874-69C0-4010-BE46-77EF92EB1553}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{4635D874-69C0-4010-BE46-77EF92EB1553}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{4635D874-69C0-4010-BE46-77EF92EB1553}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{4635D874-69C0-4010-BE46-77EF92EB1553}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {926C6896-F36A-4F3B-A9DD-CA3AA48AD99F}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
|
||||
@ -1,14 +1,14 @@
|
||||
To use:
|
||||
1. Right click the project and choose "Manage Nuget Packages"
|
||||
2. Choose the restore option in the Nuget screen (or just install the latest StreamDeck-Tools from Nuget)
|
||||
3. Update the manifest.json file with the correct details about your plugin
|
||||
4. Modify PluginAction.cs as needed (it holds the logic for your plugin)
|
||||
5. Modify the PropertyInspector\PluginActionPI.html and PropertyInspector\PluginActionPI.js as needed to show field in the Property Inspector
|
||||
6. Before releasing, change the Assembly Information (Right click the project -> Properties -> Application -> Assembly Information...)
|
||||
|
||||
For help with StreamDeck-Tools:
|
||||
Discord Server: http://discord.barraider.com
|
||||
Resources:
|
||||
* StreamDeck-Tools samples and tutorial: https://github.com/BarRaider/streamdeck-tools
|
||||
* EasyPI library (for working with Property Inspector): https://github.com/BarRaider/streamdeck-easypi
|
||||
|
||||
To use:
|
||||
1. Right click the project and choose "Manage Nuget Packages"
|
||||
2. Choose the restore option in the Nuget screen (or just install the latest StreamDeck-Tools from Nuget)
|
||||
3. Update the manifest.json file with the correct details about your plugin
|
||||
4. Modify PluginAction.cs as needed (it holds the logic for your plugin)
|
||||
5. Modify the PropertyInspector\PluginActionPI.html and PropertyInspector\PluginActionPI.js as needed to show field in the Property Inspector
|
||||
6. Before releasing, change the Assembly Information (Right click the project -> Properties -> Application -> Assembly Information...)
|
||||
|
||||
For help with StreamDeck-Tools:
|
||||
Discord Server: http://discord.barraider.com
|
||||
Resources:
|
||||
* StreamDeck-Tools samples and tutorial: https://github.com/BarRaider/streamdeck-tools
|
||||
* EasyPI library (for working with Property Inspector): https://github.com/BarRaider/streamdeck-easypi
|
||||
|
||||
|
||||
@ -1,22 +1,42 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<configuration>
|
||||
<startup>
|
||||
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.8"/>
|
||||
</startup>
|
||||
<runtime>
|
||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="CommandLine" publicKeyToken="5a870481e358d379" culture="neutral"/>
|
||||
<bindingRedirect oldVersion="0.0.0.0-2.6.0.0" newVersion="2.6.0.0"/>
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.Drawing.Common" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral"/>
|
||||
<bindingRedirect oldVersion="0.0.0.0-8.0.0.0" newVersion="8.0.0.0"/>
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral"/>
|
||||
<bindingRedirect oldVersion="0.0.0.0-13.0.0.0" newVersion="13.0.0.0"/>
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
</runtime>
|
||||
</configuration>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<configuration>
|
||||
<startup>
|
||||
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.8" />
|
||||
</startup>
|
||||
<runtime>
|
||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="CommandLine" publicKeyToken="5a870481e358d379" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-2.6.0.0" newVersion="2.6.0.0" />
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.Drawing.Common" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-8.0.0.0" newVersion="8.0.0.0" />
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-13.0.0.0" newVersion="13.0.0.0" />
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="Microsoft.Extensions.DependencyInjection.Abstractions" publicKeyToken="adb9793829ddae60" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-10.0.0.2" newVersion="10.0.0.2" />
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.Text.Json" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-10.0.0.2" newVersion="10.0.0.2" />
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="Microsoft.Extensions.Logging.Abstractions" publicKeyToken="adb9793829ddae60" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-10.0.0.2" newVersion="10.0.0.2" />
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="Microsoft.Extensions.Logging" publicKeyToken="adb9793829ddae60" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-10.0.0.2" newVersion="10.0.0.2" />
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="Microsoft.Extensions.DependencyInjection" publicKeyToken="adb9793829ddae60" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-10.0.0.2" newVersion="10.0.0.2" />
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
</runtime>
|
||||
</configuration>
|
||||
|
||||
@ -1 +1 @@
|
||||
|
||||
|
||||
|
||||
@ -1,42 +1,53 @@
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Converters;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace ClipTrimDotNet.Client
|
||||
{
|
||||
public enum PlaybackType
|
||||
{
|
||||
playStop,
|
||||
playOverlap
|
||||
}
|
||||
public class ClipMetadata
|
||||
{
|
||||
[JsonProperty(PropertyName = "filename")]
|
||||
public string Filename { get; set; }
|
||||
|
||||
|
||||
[JsonProperty(PropertyName = "name")]
|
||||
public string Name { get; set; }
|
||||
|
||||
|
||||
[JsonProperty(PropertyName = "volume")]
|
||||
public double Volume { get; set; } = 1.0;
|
||||
|
||||
|
||||
[JsonProperty(PropertyName = "startTime")]
|
||||
public double StartTime { get; set; } = 0.0;
|
||||
|
||||
|
||||
[JsonProperty(PropertyName = "endTime")]
|
||||
public double EndTime { get; set; } = 0.0;
|
||||
|
||||
|
||||
[JsonProperty(PropertyName = "playbackType")]
|
||||
[JsonConverter(typeof(StringEnumConverter))]
|
||||
public PlaybackType PlaybackType { get; set; } = PlaybackType.playStop;
|
||||
}
|
||||
}
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Serialization;
|
||||
using Newtonsoft.Json.Converters;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace ClipTrimDotNet.Client
|
||||
{
|
||||
public enum PlaybackType
|
||||
{
|
||||
playStop,
|
||||
playOverlap
|
||||
}
|
||||
public class ClipMetadata
|
||||
{
|
||||
[JsonProperty(PropertyName = "filename")]
|
||||
[JsonPropertyName("filename")]
|
||||
|
||||
public string Filename { get; set; }
|
||||
|
||||
|
||||
[JsonProperty(PropertyName = "name")]
|
||||
[JsonPropertyName("name")]
|
||||
public string Name { get; set; }
|
||||
|
||||
|
||||
[JsonProperty(PropertyName = "volume")]
|
||||
[JsonPropertyName("volume")]
|
||||
public double Volume { get; set; } = 1.0;
|
||||
|
||||
|
||||
[JsonProperty(PropertyName = "startTime")]
|
||||
[JsonPropertyName("startTime")]
|
||||
public double StartTime { get; set; } = 0.0;
|
||||
|
||||
|
||||
[JsonProperty(PropertyName = "endTime")]
|
||||
[JsonPropertyName("endTime")]
|
||||
public double EndTime { get; set; } = 0.0;
|
||||
|
||||
|
||||
[JsonProperty(PropertyName = "playbackType")]
|
||||
[Newtonsoft.Json.JsonConverter(typeof(StringEnumConverter))]
|
||||
[System.Text.Json.Serialization.JsonConverter(typeof(JsonStringEnumConverter))]
|
||||
[JsonPropertyName("playbackType")]
|
||||
public PlaybackType PlaybackType { get; set; } = PlaybackType.playStop;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,110 +1,287 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace ClipTrimDotNet.Client
|
||||
{
|
||||
public class ClipTrimClient
|
||||
{
|
||||
private static ClipTrimClient? instance;
|
||||
public static ClipTrimClient Instance
|
||||
{
|
||||
get
|
||||
{
|
||||
if (instance == null)
|
||||
{
|
||||
instance = new ClipTrimClient();
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
|
||||
private HttpClient httpClient;
|
||||
|
||||
public ClipTrimClient()
|
||||
{
|
||||
httpClient = new HttpClient()
|
||||
{
|
||||
BaseAddress = new Uri("http://localhost:5010/"),
|
||||
Timeout = TimeSpan.FromSeconds(10)
|
||||
};
|
||||
Task.Run(ShortPoll);
|
||||
}
|
||||
|
||||
public async Task ShortPoll()
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
await GetMetadata();
|
||||
await Task.Delay(TimeSpan.FromSeconds(5)); await Task.Delay(TimeSpan.FromSeconds(5));
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public List<CollectionMetaData> Collections { get; private set; } = new List<CollectionMetaData>();
|
||||
public CollectionMetaData? SelectedCollection { get; private set; }
|
||||
public int PageIndex { get; private set; } = 0;
|
||||
private async Task GetMetadata()
|
||||
{
|
||||
try
|
||||
{
|
||||
var response = await httpClient.GetAsync("meta");
|
||||
if (response.IsSuccessStatusCode)
|
||||
{
|
||||
var json = await response.Content.ReadAsStringAsync();
|
||||
dynamic collections = JsonConvert.DeserializeObject(json);
|
||||
collections = collections.collections;
|
||||
Collections = JsonConvert.DeserializeObject<List<CollectionMetaData>>(collections.ToString());
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
//Logger.Instance.LogMessage(TracingLevel.INFO, $"Error pinging ClipTrim API: {ex.Message}");
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public List<string> GetCollectionNames()
|
||||
{
|
||||
//await GetMetadata();
|
||||
return Collections.Select(x => x.Name).ToList();
|
||||
}
|
||||
|
||||
public void SetSelectedCollectionByName(string name)
|
||||
{
|
||||
var collection = Collections.FirstOrDefault(x => x.Name == name);
|
||||
if (collection != null)
|
||||
{
|
||||
SelectedCollection = collection;
|
||||
PageIndex = 0;
|
||||
}
|
||||
}
|
||||
|
||||
public ClipMetadata? GetClipByPagedIndex(int index)
|
||||
{
|
||||
if (SelectedCollection == null) return null;
|
||||
int clipIndex = PageIndex * 10 + index;
|
||||
if (clipIndex >= 0 && clipIndex < SelectedCollection.Clips.Count)
|
||||
{
|
||||
return SelectedCollection.Clips[clipIndex];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public async void PlayClip(ClipMetadata? metadata)
|
||||
{
|
||||
if (metadata == null) return;
|
||||
|
||||
var response = await httpClient.PostAsync("playback/start", new StringContent(JsonConvert.SerializeObject(metadata), Encoding.UTF8, "application/json"));
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
//Logger.Instance.LogMessage(TracingLevel.INFO, $"Error playing clip: {response.ReasonPhrase}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
using BarRaider.SdTools;
|
||||
using ClipTrimDotNet.Keys;
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||
using Newtonsoft.Json;
|
||||
using SocketIOClient;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using static System.Runtime.InteropServices.JavaScript.JSType;
|
||||
|
||||
namespace ClipTrimDotNet.Client
|
||||
{
|
||||
public class ClipTrimClient
|
||||
{
|
||||
private static ClipTrimClient? instance;
|
||||
public static ClipTrimClient Instance
|
||||
{
|
||||
get
|
||||
{
|
||||
if (instance == null)
|
||||
{
|
||||
instance = new ClipTrimClient();
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
|
||||
//private HttpClient httpClient;
|
||||
private SocketIO? socket;
|
||||
|
||||
public string HostName
|
||||
{
|
||||
get
|
||||
{
|
||||
//return $"http://localhost:5010/";
|
||||
return $"http://localhost:{GlobalSettings.Instance.PortNumber}/";
|
||||
}
|
||||
}
|
||||
|
||||
private string? currentHostname = null;
|
||||
|
||||
void CreateSocket()
|
||||
{
|
||||
Logger.Instance.LogMessage(TracingLevel.INFO, $"Starting ClipTrimClient on port {HostName}");
|
||||
socket = new SocketIO(new Uri(HostName));
|
||||
currentHostname = HostName;
|
||||
socket.Options.AutoUpgrade = false;
|
||||
//socket.Options.Path = "/socket.io";
|
||||
socket.Options.ConnectionTimeout = TimeSpan.FromSeconds(10);
|
||||
socket.Options.Reconnection = true;
|
||||
socket.On("full_data", ctx =>
|
||||
{
|
||||
try
|
||||
{
|
||||
var response = ctx.GetValue<List<CollectionMetaData>>(0);
|
||||
Logger.Instance.LogMessage(TracingLevel.INFO, $"full_data event {JsonConvert.SerializeObject(response)}");
|
||||
Collections = response!;
|
||||
Player.TickAll();
|
||||
PageNavigator.TickAll();
|
||||
//Logger.Instance.LogMessage(TracingLevel.INFO, $"Collections {JsonConvert.SerializeObject(Collections)}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Instance.LogMessage(TracingLevel.INFO, $"full_data error {ex.ToString()}");
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
});
|
||||
socket.On("collection_updated", ctx =>
|
||||
{
|
||||
try
|
||||
{
|
||||
var response = ctx.GetValue<CollectionMetaData>(0)!;
|
||||
Logger.Instance.LogMessage(TracingLevel.INFO, $"collection_updated event {JsonConvert.SerializeObject(response)}");
|
||||
int index = Collections.FindIndex(x => x.Id == response.Id);
|
||||
if (index != -1)
|
||||
{
|
||||
Collections[index] = response;
|
||||
Player.TickAll();
|
||||
PageNavigator.TickAll();
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
});
|
||||
|
||||
socket.OnConnected += (sender, e) =>
|
||||
{
|
||||
Logger.Instance.LogMessage(TracingLevel.INFO, $"Socket connected: {e}");
|
||||
};
|
||||
|
||||
socket.OnDisconnected += (sender, e) =>
|
||||
{
|
||||
Logger.Instance.LogMessage(TracingLevel.INFO, $"Socket disconnected: {e}");
|
||||
Task.Run(async () => await Connect());
|
||||
};
|
||||
Task.Run(async () => await Connect());
|
||||
}
|
||||
|
||||
public ClipTrimClient()
|
||||
{
|
||||
//httpClient = new HttpClient()
|
||||
//{
|
||||
// BaseAddress = new Uri("http://localhost:5010/"),
|
||||
// Timeout = TimeSpan.FromSeconds(10)
|
||||
//};
|
||||
CreateSocket();
|
||||
}
|
||||
|
||||
public async Task Connect()
|
||||
{
|
||||
if (socket is null) return;
|
||||
while (!socket.Connected)
|
||||
{
|
||||
try
|
||||
{
|
||||
await socket.ConnectAsync();
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public List<CollectionMetaData> Collections { get; private set; } = new List<CollectionMetaData>();
|
||||
public int SelectedCollection { get; private set; } = -1;
|
||||
|
||||
public Dictionary<int, int> CollectionIndexes { get; private set; } = new();
|
||||
public int PageIndex
|
||||
{
|
||||
get
|
||||
{
|
||||
if (SelectedCollection == -1) return 0;
|
||||
if (!CollectionIndexes.ContainsKey(SelectedCollection))
|
||||
{
|
||||
CollectionIndexes[SelectedCollection] = 0;
|
||||
}
|
||||
return CollectionIndexes[SelectedCollection];
|
||||
}
|
||||
set
|
||||
{
|
||||
if (SelectedCollection == -1) return;
|
||||
CollectionIndexes[SelectedCollection] = value;
|
||||
}
|
||||
}
|
||||
public bool PageMode { get; set; } = false;
|
||||
|
||||
public int PageCount
|
||||
{
|
||||
get
|
||||
{
|
||||
if (SelectedCollection == -1) return 0;
|
||||
var collection = Collections[SelectedCollection];
|
||||
return (collection.Clips.Count - 1) / 10 + 1;
|
||||
}
|
||||
}
|
||||
public bool CanPageUp
|
||||
{
|
||||
get
|
||||
{
|
||||
if(PageMode) return false;
|
||||
if (SelectedCollection == -1) return false;
|
||||
return PageCount - PageIndex > 1;
|
||||
}
|
||||
}
|
||||
|
||||
public bool CanPageDown
|
||||
{
|
||||
get
|
||||
{
|
||||
if (PageMode) return false;
|
||||
return PageIndex > 0;
|
||||
}
|
||||
}
|
||||
|
||||
public void PageDown()
|
||||
{
|
||||
if (CanPageDown)
|
||||
{
|
||||
PageIndex--;
|
||||
}
|
||||
}
|
||||
|
||||
public void PageUp()
|
||||
{
|
||||
if (CanPageUp)
|
||||
{
|
||||
PageIndex++;
|
||||
}
|
||||
}
|
||||
public List<string> GetCollectionNames()
|
||||
{
|
||||
//await GetMetadata();
|
||||
return Collections.Where(x => x.Name != "Uncategorized").Select(x => x.Name).ToList();
|
||||
}
|
||||
|
||||
public string GetCurrentCollectionName()
|
||||
{
|
||||
if (SelectedCollection == -1) return "";
|
||||
return Collections[SelectedCollection].Name;
|
||||
}
|
||||
|
||||
public string GetPlayerStringByCoordinateIndex(int index)
|
||||
{
|
||||
if (PageMode)
|
||||
{
|
||||
int pageNumber = index + 1;
|
||||
if(pageNumber <= PageCount)
|
||||
{
|
||||
return pageNumber.ToString();
|
||||
}
|
||||
return "";
|
||||
}
|
||||
else
|
||||
{
|
||||
var collection = GetClipByPagedIndex(index);
|
||||
return collection?.Name ?? "";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
public ClipMetadata? GetClipByPagedIndex(int index)
|
||||
{
|
||||
SelectedCollection = Collections.FindIndex(x => x.Name == GlobalSettings.Instance.ProfileName);
|
||||
if (SelectedCollection == -1) return null;
|
||||
int clipIndex = PageIndex * 10 + index;
|
||||
var collection = Collections[SelectedCollection];
|
||||
if (clipIndex >= 0 && clipIndex < collection.Clips.Count)
|
||||
{
|
||||
return collection.Clips[clipIndex];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public async void PlayClip(int index)
|
||||
{
|
||||
if (PageMode)
|
||||
{
|
||||
if(index < 0 || index >= PageCount) return;
|
||||
PageIndex = index;
|
||||
PageMode = false;
|
||||
Player.TickAll();
|
||||
PageNavigator.TickAll();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (socket is null) return;
|
||||
var metadata = GetClipByPagedIndex(index);
|
||||
if (metadata == null) return;
|
||||
//Logger.Instance.LogMessage(TracingLevel.INFO, $"playing clip:");
|
||||
await socket.EmitAsync("play_clip", new List<object>() { metadata });
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public async void SaveClip()
|
||||
{
|
||||
if (socket is null) return;
|
||||
await socket.EmitAsync("record_clip", new List<object>() { });
|
||||
}
|
||||
|
||||
public async void CheckPort()
|
||||
{
|
||||
if (socket is null) return;
|
||||
//Logger.Instance.LogMessage(TracingLevel.INFO, $"Checking port {socket}");
|
||||
if (currentHostname != HostName)
|
||||
{
|
||||
//Logger.Instance.LogMessage(TracingLevel.INFO, $"port {socket}");
|
||||
if (socket.Connected)
|
||||
{
|
||||
await socket.DisconnectAsync();
|
||||
}
|
||||
socket.Dispose();
|
||||
CreateSocket();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,23 +1,27 @@
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace ClipTrimDotNet.Client
|
||||
{
|
||||
public class CollectionMetaData
|
||||
{
|
||||
[JsonProperty(PropertyName = "name")]
|
||||
public string Name { get; set; }
|
||||
|
||||
|
||||
[JsonProperty(PropertyName = "clips")]
|
||||
public List<ClipMetadata> Clips { get; set; } = new List<ClipMetadata>();
|
||||
|
||||
|
||||
[JsonProperty(PropertyName = "id")]
|
||||
public int Id { get; set; }
|
||||
}
|
||||
}
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace ClipTrimDotNet.Client
|
||||
{
|
||||
public class CollectionMetaData
|
||||
{
|
||||
[JsonProperty(PropertyName = "name")]
|
||||
[JsonPropertyName("name")]
|
||||
public string Name { get; set; }
|
||||
|
||||
|
||||
[JsonProperty(PropertyName = "clips")]
|
||||
[JsonPropertyName("clips")]
|
||||
public List<ClipMetadata> Clips { get; set; } = new List<ClipMetadata>();
|
||||
|
||||
|
||||
[JsonProperty(PropertyName = "id")]
|
||||
[JsonPropertyName("id")]
|
||||
public int Id { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,162 +1,118 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProjectGuid>{4635D874-69C0-4010-BE46-77EF92EB1553}</ProjectGuid>
|
||||
<OutputType>Exe</OutputType>
|
||||
<RootNamespace>ClipTrimDotNet</RootNamespace>
|
||||
<AssemblyName>ClipTrimDotNet</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.8</TargetFrameworkVersion>
|
||||
<LangVersion>8</LangVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
|
||||
<Deterministic>true</Deterministic>
|
||||
<Nullable>enable</Nullable>
|
||||
<TargetFrameworkProfile />
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>bin\Debug\com.michal-courson.cliptrim.sdPlugin\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>bin\Release\ClipTrimDotNet.sdPlugin\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="CommandLine, Version=2.9.1.0, Culture=neutral, PublicKeyToken=5a870481e358d379, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\CommandLineParser.2.9.1\lib\net461\CommandLine.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.Win32.Registry, Version=4.1.3.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Microsoft.Win32.Registry.4.7.0\lib\net461\Microsoft.Win32.Registry.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="NAudio, Version=2.2.1.0, Culture=neutral, PublicKeyToken=e279aa5131008a41, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\NAudio.2.2.1\lib\net472\NAudio.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="NAudio.Asio, Version=2.2.1.0, Culture=neutral, PublicKeyToken=e279aa5131008a41, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\NAudio.Asio.2.2.1\lib\netstandard2.0\NAudio.Asio.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="NAudio.Core, Version=2.2.1.0, Culture=neutral, PublicKeyToken=e279aa5131008a41, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\NAudio.Core.2.2.1\lib\netstandard2.0\NAudio.Core.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="NAudio.Midi, Version=2.2.1.0, Culture=neutral, PublicKeyToken=e279aa5131008a41, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\NAudio.Midi.2.2.1\lib\netstandard2.0\NAudio.Midi.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="NAudio.Wasapi, Version=2.2.1.0, Culture=neutral, PublicKeyToken=e279aa5131008a41, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\NAudio.Wasapi.2.2.1\lib\netstandard2.0\NAudio.Wasapi.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="NAudio.WinForms, Version=2.2.1.0, Culture=neutral, PublicKeyToken=e279aa5131008a41, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\NAudio.WinForms.2.2.1\lib\net472\NAudio.WinForms.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="NAudio.WinMM, Version=2.2.1.0, Culture=neutral, PublicKeyToken=e279aa5131008a41, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\NAudio.WinMM.2.2.1\lib\netstandard2.0\NAudio.WinMM.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Newtonsoft.Json, Version=13.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Newtonsoft.Json.13.0.4\lib\net45\Newtonsoft.Json.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="NLog, Version=6.0.0.0, Culture=neutral, PublicKeyToken=5120e14c03d0593c, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\NLog.6.0.5\lib\net46\NLog.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="StreamDeckTools, Version=6.3.2.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\StreamDeck-Tools.6.3.2\lib\netstandard2.0\StreamDeckTools.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Configuration" />
|
||||
<Reference Include="System.Core" />
|
||||
<Reference Include="System.Drawing" />
|
||||
<Reference Include="System.Drawing.Common, Version=9.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\System.Drawing.Common.9.0.10\lib\net462\System.Drawing.Common.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.IO.Compression" />
|
||||
<Reference Include="System.Runtime.Serialization" />
|
||||
<Reference Include="System.Security.AccessControl, Version=4.1.3.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\System.Security.AccessControl.4.7.0\lib\net461\System.Security.AccessControl.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Security.Principal.Windows, Version=4.1.3.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\System.Security.Principal.Windows.4.7.0\lib\net461\System.Security.Principal.Windows.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.ServiceModel" />
|
||||
<Reference Include="System.Transactions" />
|
||||
<Reference Include="System.Xml.Linq" />
|
||||
<Reference Include="System.Data.DataSetExtensions" />
|
||||
<Reference Include="Microsoft.CSharp" />
|
||||
<Reference Include="System.Data" />
|
||||
<Reference Include="System.Net.Http" />
|
||||
<Reference Include="System.Xml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<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 Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0-windows</TargetFramework>
|
||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||
<OutputType>Exe</OutputType>
|
||||
<LangVersion>10</LangVersion>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<PreBuildEvent>npm run stop</PreBuildEvent>
|
||||
<PostBuildEvent>npm run start</PostBuildEvent>
|
||||
<AssemblyTitle>ClipTrimDotNet</AssemblyTitle>
|
||||
<Product>ClipTrimDotNet</Product>
|
||||
<Copyright>Copyright © 2020</Copyright>
|
||||
<AssemblyVersion>1.0.0.0</AssemblyVersion>
|
||||
<FileVersion>1.0.0.0</FileVersion>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<OutputPath>bin\Debug\com.michal-courson.cliptrim.sdPlugin\</OutputPath>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<OutputPath>bin\Release\ClipTrimDotNet.sdPlugin\</OutputPath>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<None Remove="Images\app_icon.png" />
|
||||
<None Remove="Images\back.png" />
|
||||
<None Remove="Images\category_icon.png" />
|
||||
<None Remove="Images\collection.png" />
|
||||
<None Remove="Images\collection_icon.png" />
|
||||
<None Remove="Images\page_nav.png" />
|
||||
<None Remove="Images\page_nav_icon.png" />
|
||||
<None Remove="Images\player.png" />
|
||||
<None Remove="Images\player_icon.png" />
|
||||
<None Remove="Images\record.png" />
|
||||
<None Remove="Images\record_icon.png" />
|
||||
<None Remove="manifest.json" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="CommandLineParser" Version="2.9.1" />
|
||||
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="10.0.2" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.2" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="10.0.2" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.2" />
|
||||
<PackageReference Include="Microsoft.Extensions.Options" Version="10.0.2" />
|
||||
<PackageReference Include="Microsoft.Extensions.Primitives" Version="10.0.2" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.4" />
|
||||
<PackageReference Include="NLog" Version="6.0.5" />
|
||||
<PackageReference Include="SocketIOClient" Version="4.0.0.2" />
|
||||
<PackageReference Include="SocketIOClient.Common" Version="4.0.0" />
|
||||
<PackageReference Include="SocketIOClient.Serializer" Version="4.0.0.1" />
|
||||
<PackageReference Include="SocketIOClient.Serializer.NewtonsoftJson" Version="4.0.0.1" />
|
||||
<PackageReference Include="StreamDeck-Tools" Version="6.3.2" />
|
||||
<PackageReference Include="System.Diagnostics.DiagnosticSource" Version="10.0.2" />
|
||||
<PackageReference Include="System.Drawing.Common" Version="9.0.10" />
|
||||
<PackageReference Include="System.IO.Pipelines" Version="10.0.2" />
|
||||
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="6.1.2" />
|
||||
<PackageReference Include="System.Security.AccessControl" Version="6.0.1" />
|
||||
<PackageReference Include="System.Text.Encodings.Web" Version="10.0.2" />
|
||||
<PackageReference Include="System.Text.Json" Version="10.0.2" />
|
||||
<PackageReference Include="System.Configuration.ConfigurationManager" Version="10.0.3" />
|
||||
<PackageReference Include="CoreWCF.Primitives" Version="1.8.0" />
|
||||
<PackageReference Include="CoreWCF.ConfigurationManager" Version="1.8.0" />
|
||||
<PackageReference Include="CoreWCF.Http" Version="1.8.0" />
|
||||
<PackageReference Include="CoreWCF.WebHttp" Version="1.8.0" />
|
||||
<PackageReference Include="CoreWCF.NetTcp" Version="1.8.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Update="DialLayout.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Content Include="!!README!!.txt" />
|
||||
<Content Include="Images\app_icon.png">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="Images\back.png">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="Images\category_icon.png">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="Images\collection.png">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="Images\collection_icon.png">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="Images\page_nav.png">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="Images\page_nav_icon.png">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="Images\player_icon.png">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="Images\record.png">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="Images\player.png">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="Images\record_icon.png">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="manifest.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</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>
|
||||
</Project>
|
||||
@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|AnyCPU'">
|
||||
<StartArguments>--port 23654 --pluginUUID com.michal-courson.cliptrim --registerEvent restart --info {}</StartArguments>
|
||||
</PropertyGroup>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|AnyCPU'">
|
||||
<StartArguments>--port 23654 --pluginUUID com.michal-courson.cliptrim --registerEvent restart --info {}</StartArguments>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
@ -1,40 +1,40 @@
|
||||
{
|
||||
"id": "sampleDial",
|
||||
"items": [
|
||||
{
|
||||
"key": "title",
|
||||
"type": "text",
|
||||
"rect": [ 16, 10, 136, 24 ],
|
||||
"font": {
|
||||
"size": 16,
|
||||
"weight": 600
|
||||
},
|
||||
"alignment": "left"
|
||||
},
|
||||
{
|
||||
"key": "icon",
|
||||
"type": "pixmap",
|
||||
"rect": [ 16, 40, 48, 48 ]
|
||||
},
|
||||
{
|
||||
"key": "value",
|
||||
"type": "text",
|
||||
"rect": [ 76, 40, 108, 32 ],
|
||||
"font": {
|
||||
"size": 24,
|
||||
"weight": 600
|
||||
},
|
||||
"alignment": "right"
|
||||
},
|
||||
{
|
||||
"key": "indicator",
|
||||
"type": "gbar",
|
||||
"rect": [ 76, 74, 108, 20 ],
|
||||
"value": 0,
|
||||
"subtype": 4,
|
||||
"bar_h": 12,
|
||||
"border_w": 0,
|
||||
"bar_bg_c": "0:#427018,0.75:#705B1C,0.90:#702735,1:#702735"
|
||||
}
|
||||
]
|
||||
{
|
||||
"id": "sampleDial",
|
||||
"items": [
|
||||
{
|
||||
"key": "title",
|
||||
"type": "text",
|
||||
"rect": [ 16, 10, 136, 24 ],
|
||||
"font": {
|
||||
"size": 16,
|
||||
"weight": 600
|
||||
},
|
||||
"alignment": "left"
|
||||
},
|
||||
{
|
||||
"key": "icon",
|
||||
"type": "pixmap",
|
||||
"rect": [ 16, 40, 48, 48 ]
|
||||
},
|
||||
{
|
||||
"key": "value",
|
||||
"type": "text",
|
||||
"rect": [ 76, 40, 108, 32 ],
|
||||
"font": {
|
||||
"size": 24,
|
||||
"weight": 600
|
||||
},
|
||||
"alignment": "right"
|
||||
},
|
||||
{
|
||||
"key": "indicator",
|
||||
"type": "gbar",
|
||||
"rect": [ 76, 74, 108, 20 ],
|
||||
"value": 0,
|
||||
"subtype": 4,
|
||||
"bar_h": 12,
|
||||
"border_w": 0,
|
||||
"bar_bg_c": "0:#427018,0.75:#705B1C,0.90:#702735,1:#702735"
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -1,109 +1,49 @@
|
||||
using BarRaider.SdTools;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using BarRaider.SdTools.Wrappers;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using NAudio.MediaFoundation;
|
||||
|
||||
namespace ClipTrimDotNet
|
||||
{
|
||||
public class FileEntry
|
||||
{
|
||||
public FileEntry()
|
||||
{
|
||||
Volume = 1.0;
|
||||
Playtype = "Play/Overlap";
|
||||
}
|
||||
[JsonProperty(PropertyName = "Volume")]
|
||||
public double Volume { get; set; }
|
||||
[JsonProperty(PropertyName = "Playtype")]
|
||||
public string Playtype { get; set; }
|
||||
}
|
||||
public class CollectionEntry
|
||||
{
|
||||
public CollectionEntry()
|
||||
{
|
||||
Files = new Dictionary<string, FileEntry>();
|
||||
}
|
||||
[JsonProperty(PropertyName = "Files")]
|
||||
public Dictionary<string, FileEntry> Files { get; set; }
|
||||
}
|
||||
|
||||
public class GlobalSettings
|
||||
{
|
||||
public static GlobalSettings? _inst;
|
||||
public static GlobalSettings Instance
|
||||
{
|
||||
get
|
||||
{
|
||||
_inst ??= CreateDefaultSettings();
|
||||
return _inst;
|
||||
}
|
||||
set
|
||||
{
|
||||
_inst = value;
|
||||
}
|
||||
}
|
||||
public static GlobalSettings CreateDefaultSettings()
|
||||
{
|
||||
GlobalSettings instance = new GlobalSettings();
|
||||
instance.BasePath = null;
|
||||
instance.ProfileName = null;
|
||||
instance.Collections = new Dictionary<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;
|
||||
}
|
||||
}
|
||||
}
|
||||
using BarRaider.SdTools;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using BarRaider.SdTools.Wrappers;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace ClipTrimDotNet
|
||||
{
|
||||
public class GlobalSettings
|
||||
{
|
||||
public static GlobalSettings? _inst;
|
||||
public static GlobalSettings Instance
|
||||
{
|
||||
get
|
||||
{
|
||||
_inst ??= CreateDefaultSettings();
|
||||
return _inst;
|
||||
}
|
||||
set
|
||||
{
|
||||
_inst = value;
|
||||
}
|
||||
}
|
||||
public static GlobalSettings CreateDefaultSettings()
|
||||
{
|
||||
GlobalSettings instance = new GlobalSettings();
|
||||
instance.ProfileName = null;
|
||||
instance.PortNumber = 5010;
|
||||
return instance;
|
||||
}
|
||||
|
||||
|
||||
[JsonProperty(PropertyName = "profileName")]
|
||||
public string? ProfileName { get; set; }
|
||||
|
||||
[JsonProperty(PropertyName = "portNumber")]
|
||||
public int? PortNumber { get; set; }
|
||||
|
||||
|
||||
public void SetCurrentProfile(string profile)
|
||||
{
|
||||
ProfileName = profile;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BIN
stream_deck_plugin/ClipTrimDotNet/Images/app_icon.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
stream_deck_plugin/ClipTrimDotNet/Images/app_icon.psd
Normal file
BIN
stream_deck_plugin/ClipTrimDotNet/Images/back.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
stream_deck_plugin/ClipTrimDotNet/Images/back.psd
Normal file
|
Before Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 5.8 KiB |
BIN
stream_deck_plugin/ClipTrimDotNet/Images/category_icon.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
stream_deck_plugin/ClipTrimDotNet/Images/collection.png
Normal file
|
After Width: | Height: | Size: 464 B |
BIN
stream_deck_plugin/ClipTrimDotNet/Images/collection.psd
Normal file
BIN
stream_deck_plugin/ClipTrimDotNet/Images/collection_icon.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
stream_deck_plugin/ClipTrimDotNet/Images/collection_icon.psd
Normal file
|
Before Width: | Height: | Size: 4.5 KiB |
|
Before Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 7.5 KiB |
BIN
stream_deck_plugin/ClipTrimDotNet/Images/page_nav.png
Normal file
|
After Width: | Height: | Size: 484 B |
BIN
stream_deck_plugin/ClipTrimDotNet/Images/page_nav.psd
Normal file
BIN
stream_deck_plugin/ClipTrimDotNet/Images/page_nav_icon.png
Normal file
|
After Width: | Height: | Size: 2.4 KiB |
BIN
stream_deck_plugin/ClipTrimDotNet/Images/page_nav_icon.psd
Normal file
BIN
stream_deck_plugin/ClipTrimDotNet/Images/player.png
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
BIN
stream_deck_plugin/ClipTrimDotNet/Images/player.psd
Normal file
BIN
stream_deck_plugin/ClipTrimDotNet/Images/player_icon.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
stream_deck_plugin/ClipTrimDotNet/Images/player_icon.psd
Normal file
|
Before Width: | Height: | Size: 8.1 KiB |
|
Before Width: | Height: | Size: 6.8 KiB |
|
Before Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 5.9 KiB |
BIN
stream_deck_plugin/ClipTrimDotNet/Images/record.png
Normal file
|
After Width: | Height: | Size: 3.8 KiB |
BIN
stream_deck_plugin/ClipTrimDotNet/Images/record.psd
Normal file
BIN
stream_deck_plugin/ClipTrimDotNet/Images/record_icon.png
Normal file
|
After Width: | Height: | Size: 3.8 KiB |
BIN
stream_deck_plugin/ClipTrimDotNet/Images/record_icon.psd
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
@ -0,0 +1,107 @@
|
||||
using BarRaider.SdTools;
|
||||
using ClipTrimDotNet.Client;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace ClipTrimDotNet.Keys
|
||||
{
|
||||
[PluginActionId("com.michal-courson.cliptrim.page-navigator")]
|
||||
public class PageNavigator : KeypadBase
|
||||
{
|
||||
static List<PageNavigator> instances = new();
|
||||
private KeyCoordinates coordinates;
|
||||
public PageNavigator(SDConnection connection, InitialPayload payload) : base(connection, payload)
|
||||
{
|
||||
coordinates = payload.Coordinates;
|
||||
GlobalSettingsManager.Instance.RequestGlobalSettings();
|
||||
instances.Add(this);
|
||||
OnTick();
|
||||
}
|
||||
public void Instance_OnReceivedGlobalSettings(object sender, ReceivedGlobalSettingsPayload e)
|
||||
{
|
||||
Tools.AutoPopulateSettings(GlobalSettings.Instance, e.Settings);
|
||||
}
|
||||
public int GetIndex()
|
||||
{
|
||||
int index = Math.Min(Math.Max(coordinates.Column - 1, 0), 2);
|
||||
return index;
|
||||
}
|
||||
public override void Dispose()
|
||||
{
|
||||
}
|
||||
public override void KeyPressed(KeyPayload payload)
|
||||
{
|
||||
switch (GetIndex())
|
||||
{
|
||||
case 0:
|
||||
ClipTrimClient.Instance.PageDown();
|
||||
break;
|
||||
case 1:
|
||||
ClipTrimClient.Instance.PageMode = !ClipTrimClient.Instance.PageMode;
|
||||
break;
|
||||
case 2:
|
||||
ClipTrimClient.Instance.PageUp();
|
||||
break;
|
||||
}
|
||||
Player.TickAll();
|
||||
TickAll();
|
||||
}
|
||||
|
||||
public static void TickAll()
|
||||
{
|
||||
foreach (var instance in instances)
|
||||
{
|
||||
instance.OnTick();
|
||||
}
|
||||
instances.RemoveAll(i => i == null);
|
||||
}
|
||||
|
||||
private async void SetTitle()
|
||||
{
|
||||
switch (GetIndex())
|
||||
{
|
||||
case 0:
|
||||
await Connection.SetTitleAsync(ClipTrimClient.Instance.CanPageDown ? "<" : "");
|
||||
break;
|
||||
case 1:
|
||||
await Connection.SetTitleAsync(ClipTrimClient.Instance.GetCurrentCollectionName() + "\n" + (ClipTrimClient.Instance.PageIndex + 1).ToString());
|
||||
break;
|
||||
case 2:
|
||||
await Connection.SetTitleAsync(ClipTrimClient.Instance.CanPageUp ? ">" : "");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnTick()
|
||||
{
|
||||
SetTitle();
|
||||
}
|
||||
|
||||
public override void KeyReleased(KeyPayload payload)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public override void ReceivedSettings(ReceivedSettingsPayload payload)
|
||||
{
|
||||
|
||||
}
|
||||
public override void ReceivedGlobalSettings(ReceivedGlobalSettingsPayload payload)
|
||||
{
|
||||
Tools.AutoPopulateSettings(GlobalSettings.Instance, payload.Settings);
|
||||
//if (payload.Settings == null || payload.Settings.Count == 0)
|
||||
//{
|
||||
// var inst = GlobalSettings.Instance;
|
||||
// //GlobalSettingsManager.Instance.SetGlobalSettings(JObject.FromObject(inst));
|
||||
//}
|
||||
//else
|
||||
//{
|
||||
// GlobalSettings.Instance = payload.Settings.ToObject<GlobalSettings>();
|
||||
//}
|
||||
}
|
||||
}
|
||||
}
|
||||
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)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
140
stream_deck_plugin/ClipTrimDotNet/Keys/ProfileSwitcher.cs
Normal file
@ -0,0 +1,140 @@
|
||||
using BarRaider.SdTools;
|
||||
using BarRaider.SdTools.Wrappers;
|
||||
using ClipTrimDotNet.Client;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Dynamic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace ClipTrimDotNet.Keys
|
||||
{
|
||||
public class DataSourceItem
|
||||
{
|
||||
public string label { get; set; } = "";
|
||||
public string value { get; set; } = "";
|
||||
}
|
||||
[PluginActionId("com.michal-courson.cliptrim.profile-switcher")]
|
||||
public class ProfileSwitcher : KeypadBase
|
||||
{
|
||||
private class PluginSettings
|
||||
{
|
||||
public static PluginSettings CreateDefaultSettings()
|
||||
{
|
||||
PluginSettings instance = new PluginSettings();
|
||||
instance.ProfileName = null;
|
||||
return instance;
|
||||
}
|
||||
|
||||
[JsonProperty(PropertyName = "profileName")]
|
||||
public string? ProfileName { get; set; }
|
||||
}
|
||||
|
||||
#region Private Members
|
||||
|
||||
private PluginSettings settings;
|
||||
|
||||
#endregion
|
||||
public ProfileSwitcher(SDConnection connection, InitialPayload payload) : base(connection, payload)
|
||||
{
|
||||
if (payload.Settings == null || payload.Settings.Count == 0)
|
||||
{
|
||||
settings = PluginSettings.CreateDefaultSettings();
|
||||
SaveSettings();
|
||||
}
|
||||
else
|
||||
{
|
||||
settings = payload.Settings.ToObject<PluginSettings>()!;
|
||||
}
|
||||
Logger.Instance.LogMessage(TracingLevel.INFO, $"ProfileSwitcher initialized with payload {JsonConvert.SerializeObject(payload)}");
|
||||
GlobalSettingsManager.Instance.RequestGlobalSettings();
|
||||
Connection.OnSendToPlugin += Connection_OnSendToPlugin;
|
||||
SetTitle();
|
||||
}
|
||||
|
||||
private async void SetTitle()
|
||||
{
|
||||
|
||||
await Connection.SetTitleAsync(settings.ProfileName);
|
||||
}
|
||||
|
||||
private async void Connection_OnSendToPlugin(object? sender, SDEventReceivedEventArgs<BarRaider.SdTools.Events.SendToPlugin> e)
|
||||
{
|
||||
//Logger.Instance.LogMessage(TracingLevel.INFO, "get profiles");
|
||||
if (e.Event.Payload["event"] is null) return;
|
||||
|
||||
if (e.Event.Payload["event"]!.ToString() == "getProfiles")
|
||||
{
|
||||
var files = ClipTrimClient.Instance.GetCollectionNames();
|
||||
var items = files.Select(x => new DataSourceItem { label = x, value = x});
|
||||
var obj = new JObject
|
||||
{
|
||||
["event"] = "getProfiles",
|
||||
["items"] = JArray.FromObject(items)
|
||||
};
|
||||
await Connection.SendToPropertyInspectorAsync(obj);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
public override void Dispose()
|
||||
{
|
||||
}
|
||||
|
||||
public override async void KeyPressed(KeyPayload payload)
|
||||
{
|
||||
GlobalSettings.Instance.SetCurrentProfile(settings.ProfileName??"");
|
||||
ClipTrimClient.Instance.PageMode = false;
|
||||
PageNavigator.TickAll();
|
||||
|
||||
await Connection.SetGlobalSettingsAsync(JObject.FromObject(GlobalSettings.Instance));
|
||||
await Connection.SwitchProfileAsync("ClipTrim");
|
||||
}
|
||||
|
||||
public override void KeyReleased(KeyPayload payload)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public override void OnTick()
|
||||
{
|
||||
SetTitle();
|
||||
}
|
||||
|
||||
public override void ReceivedSettings(ReceivedSettingsPayload payload)
|
||||
{
|
||||
//Logger.Instance.LogMessage(TracingLevel.INFO, $"ProfileSwitcher received settings {JsonConvert.SerializeObject(payload.Settings)}");
|
||||
Tools.AutoPopulateSettings(settings, payload.Settings);
|
||||
SaveSettings();
|
||||
}
|
||||
|
||||
public override void ReceivedGlobalSettings(ReceivedGlobalSettingsPayload payload)
|
||||
{
|
||||
Tools.AutoPopulateSettings(GlobalSettings.Instance, payload.Settings);
|
||||
ClipTrimClient.Instance.CheckPort();
|
||||
//if (payload.Settings == null || payload.Settings.Count == 0)
|
||||
//{
|
||||
// var inst = GlobalSettings.Instance;
|
||||
//}
|
||||
//else
|
||||
//{
|
||||
// GlobalSettings.Instance = payload.Settings.ToObject<GlobalSettings>();
|
||||
//}
|
||||
}
|
||||
|
||||
#region Private Methods
|
||||
|
||||
private Task SaveSettings()
|
||||
{
|
||||
return Connection.SetSettingsAsync(JObject.FromObject(settings));
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@ -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 System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace ClipTrimDotNet
|
||||
{
|
||||
internal class Program
|
||||
{
|
||||
static void Main(string[] args)
|
||||
{
|
||||
// Uncomment this line of code to allow for debugging
|
||||
//while (!System.Diagnostics.Debugger.IsAttached) { System.Threading.Thread.Sleep(100); }
|
||||
SDWrapper.Run(args);
|
||||
}
|
||||
}
|
||||
}
|
||||
using BarRaider.SdTools;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace ClipTrimDotNet
|
||||
{
|
||||
internal class Program
|
||||
{
|
||||
static void Main(string[] args)
|
||||
{
|
||||
// Uncomment this line of code to allow for debugging
|
||||
//while (!System.Diagnostics.Debugger.IsAttached) { System.Threading.Thread.Sleep(100); }
|
||||
//Client.ClipTrimClient.Instance.PortNumber = 5010;
|
||||
SDWrapper.Run(args);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,36 +1,13 @@
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
// General Information about an assembly is controlled through the following
|
||||
// set of attributes. Change these attribute values to modify the information
|
||||
// associated with an assembly.
|
||||
[assembly: AssemblyTitle("ClipTrimDotNet")]
|
||||
[assembly: AssemblyDescription("")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("")]
|
||||
[assembly: AssemblyProduct("ClipTrimDotNet")]
|
||||
[assembly: AssemblyCopyright("Copyright © 2020")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
|
||||
// Setting ComVisible to false makes the types in this assembly not visible
|
||||
// to COM components. If you need to access a type in this assembly from
|
||||
// COM, set the ComVisible attribute to true on that type.
|
||||
[assembly: ComVisible(false)]
|
||||
|
||||
// The following GUID is for the ID of the typelib if this project is exposed to COM
|
||||
[assembly: Guid("1bb90885-9d98-46ef-b983-4a4ef3aea890")]
|
||||
|
||||
// Version information for an assembly consists of the following four values:
|
||||
//
|
||||
// Major Version
|
||||
// Minor Version
|
||||
// Build Number
|
||||
// Revision
|
||||
//
|
||||
// You can specify all the values or you can default the Build and Revision Numbers
|
||||
// by using the '*' as shown below:
|
||||
// [assembly: AssemblyVersion("1.0.*")]
|
||||
[assembly: AssemblyVersion("1.0.0.0")]
|
||||
[assembly: AssemblyFileVersion("1.0.0.0")]
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
|
||||
// Setting ComVisible to false makes the types in this assembly not visible
|
||||
// to COM components. If you need to access a type in this assembly from
|
||||
// COM, set the ComVisible attribute to true on that type.
|
||||
[assembly: ComVisible(false)]
|
||||
|
||||
// The following GUID is for the ID of the typelib if this project is exposed to COM
|
||||
[assembly: Guid("1bb90885-9d98-46ef-b983-4a4ef3aea890")]
|
||||
|
||||
@ -1,45 +1,45 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head lang="en">
|
||||
<title>Increment Counter Settings</title>
|
||||
<meta charset="utf-8" />
|
||||
<script src="https://sdpi-components.dev/releases/v3/sdpi-components.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<!--
|
||||
Learn more about property inspector components at https://sdpi-components.dev/docs/components
|
||||
-->
|
||||
<sdpi-item label="Index">
|
||||
<sdpi-select setting="index" placeholder="file index">
|
||||
<option value="0">0</option>
|
||||
<option value="1">1</option>
|
||||
<option value="2">2</option>
|
||||
<option value="3">3</option>
|
||||
<option value="4">4</option>
|
||||
<option value="5">5</option>
|
||||
<option value="6">6</option>
|
||||
<option value="7">7</option>
|
||||
<option value="8">8</option>
|
||||
<option value="9">9</option>
|
||||
<option value="10">10</option>
|
||||
<option value="11">11</option>
|
||||
<option value="12">12</option>
|
||||
<option value="13">13</option>
|
||||
<option value="14">14</option>
|
||||
<option value="15">15</option>
|
||||
</sdpi-select>
|
||||
</sdpi-item>
|
||||
<sdpi-item label="Play Mode">
|
||||
<sdpi-select setting="playtype" placeholder="Play Mode">
|
||||
<option value="Play/Overlap">Play/Overlap</option>
|
||||
<option value="Play/Stop">Play/Stop</option>
|
||||
</sdpi-select>
|
||||
</sdpi-item>
|
||||
<sdpi-item label="Volume">
|
||||
<sdpi-range setting="volume" min=".1" max="1" , step="0.05"></sdpi-range>
|
||||
</sdpi-item>
|
||||
</body>
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head lang="en">
|
||||
<title>Increment Counter Settings</title>
|
||||
<meta charset="utf-8" />
|
||||
<script src="https://sdpi-components.dev/releases/v3/sdpi-components.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<!--
|
||||
Learn more about property inspector components at https://sdpi-components.dev/docs/components
|
||||
-->
|
||||
<!--<sdpi-item label="Index">
|
||||
<sdpi-select setting="index" placeholder="file index">
|
||||
<option value="0">0</option>
|
||||
<option value="1">1</option>
|
||||
<option value="2">2</option>
|
||||
<option value="3">3</option>
|
||||
<option value="4">4</option>
|
||||
<option value="5">5</option>
|
||||
<option value="6">6</option>
|
||||
<option value="7">7</option>
|
||||
<option value="8">8</option>
|
||||
<option value="9">9</option>
|
||||
<option value="10">10</option>
|
||||
<option value="11">11</option>
|
||||
<option value="12">12</option>
|
||||
<option value="13">13</option>
|
||||
<option value="14">14</option>
|
||||
<option value="15">15</option>
|
||||
</sdpi-select>
|
||||
</sdpi-item>
|
||||
<sdpi-item label="Play Mode">
|
||||
<sdpi-select setting="playtype" placeholder="Play Mode">
|
||||
<option value="Play/Overlap">Play/Overlap</option>
|
||||
<option value="Play/Stop">Play/Stop</option>
|
||||
</sdpi-select>
|
||||
</sdpi-item>
|
||||
<sdpi-item label="Volume">
|
||||
<sdpi-range setting="volume" min=".1" max="1" , step="0.05"></sdpi-range>
|
||||
</sdpi-item>-->
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@ -1,34 +1,41 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head lang="en">
|
||||
<title>Increment Counter Settings</title>
|
||||
<meta charset="utf-8" />
|
||||
<script src="https://sdpi-components.dev/releases/v3/sdpi-components.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<!--
|
||||
Learn more about property inspector components at https://sdpi-components.dev/docs/components
|
||||
-->
|
||||
<sdpi-item label="Profile Name">
|
||||
<sdpi-select setting="profileName"
|
||||
datasource="getProfiles"
|
||||
show-refresh="true"
|
||||
placeholder="Select a ClipTrim page"></sdpi-select>
|
||||
</sdpi-item>
|
||||
<sdpi-item label="Base Path">
|
||||
<sdpi-file setting="basePath"
|
||||
global="true"
|
||||
webkitdirectory
|
||||
directory
|
||||
multiple></sdpi-file>
|
||||
</sdpi-item>
|
||||
<sdpi-item label="Output Device">
|
||||
<sdpi-select setting="outputDevice"
|
||||
global="true"
|
||||
datasource="getOutputDevices"
|
||||
show-refresh="true"
|
||||
placeholder="Select an Ouput Device"></sdpi-select>
|
||||
</sdpi-item>
|
||||
</body>
|
||||
</html>
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head lang="en">
|
||||
<title>Increment Counter Settings</title>
|
||||
<meta charset="utf-8" />
|
||||
<script src="https://sdpi-components.dev/releases/v3/sdpi-components.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<!--
|
||||
Learn more about property inspector components at https://sdpi-components.dev/docs/components
|
||||
-->
|
||||
<sdpi-item label="Profile Name">
|
||||
<sdpi-select setting="profileName"
|
||||
datasource="getProfiles"
|
||||
show-refresh="true"
|
||||
placeholder="Select a ClipTrim page"></sdpi-select>
|
||||
</sdpi-item>
|
||||
<sdpi-item>
|
||||
<sdpi-textfield
|
||||
global="true"
|
||||
setting="portNumber"
|
||||
placeholder="Port Number"
|
||||
pattern="[0-9]+"></sdpi-textfield>
|
||||
</sdpi-item>
|
||||
<!--<sdpi-item label="Base Path">
|
||||
<sdpi-file setting="basePath"
|
||||
global="true"
|
||||
webkitdirectory
|
||||
directory
|
||||
multiple></sdpi-file>
|
||||
</sdpi-item>
|
||||
<sdpi-item label="Output Device">
|
||||
<sdpi-select setting="outputDevice"
|
||||
global="true"
|
||||
datasource="getOutputDevices"
|
||||
show-refresh="true"
|
||||
placeholder="Select an Ouput Device"></sdpi-select>
|
||||
</sdpi-item>-->
|
||||
</body>
|
||||
</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": [
|
||||
{
|
||||
"Icon": "Images/icon",
|
||||
"Name": "Player",
|
||||
"States": [
|
||||
{
|
||||
"Image": "Images/pluginAction",
|
||||
"TitleAlignment": "middle",
|
||||
"FontSize": 11
|
||||
}
|
||||
],
|
||||
"SupportedInMultiActions": false,
|
||||
"Tooltip": "Plays a bound audio file",
|
||||
"UUID": "com.michal-courson.cliptrim.player",
|
||||
"PropertyInspectorPath": "PropertyInspector/file_player.html"
|
||||
},
|
||||
{
|
||||
"Icon": "Images/icon",
|
||||
"Name": "Profile Switcher",
|
||||
"States": [
|
||||
{
|
||||
"Image": "Images/pluginAction",
|
||||
"TitleAlignment": "middle",
|
||||
"FontSize": 11
|
||||
}
|
||||
],
|
||||
"SupportedInMultiActions": false,
|
||||
"Tooltip": "Selects which sub folder to use and opens effect profile",
|
||||
"UUID": "com.michal-courson.cliptrim.profile-switcher",
|
||||
"PropertyInspectorPath": "PropertyInspector/profile_swticher.html"
|
||||
}
|
||||
],
|
||||
"Author": "Michal Courson",
|
||||
"Name": "ClipTrimDotNet",
|
||||
"Description": "Pairs with cliptrim for easy voice recording and trimming",
|
||||
"Icon": "Images/pluginIcon",
|
||||
"Version": "0.1.0.0",
|
||||
"CodePath": "ClipTrimDotNet.exe",
|
||||
"Category": "ClipTrimDotNet",
|
||||
"CategoryIcon": "Images/categoryIcon",
|
||||
"UUID": "com.michal-courson.cliptrim",
|
||||
"OS": [
|
||||
{
|
||||
"Platform": "windows",
|
||||
"MinimumVersion": "10"
|
||||
}
|
||||
],
|
||||
"SDKVersion": 2,
|
||||
"Software": {
|
||||
"MinimumVersion": "6.4"
|
||||
},
|
||||
"Profiles": [
|
||||
{
|
||||
"Name": "ClipTrim",
|
||||
"DeviceType": 0,
|
||||
"Readonly": false,
|
||||
"DontAutoSwitchWhenInstalled": false
|
||||
}
|
||||
]
|
||||
{
|
||||
|
||||
"Actions": [
|
||||
{
|
||||
"Icon": "Images/player_icon",
|
||||
"Name": "Player",
|
||||
"States": [
|
||||
{
|
||||
"Image": "Images/player",
|
||||
"TitleAlignment": "middle",
|
||||
"FontSize": 11
|
||||
}
|
||||
],
|
||||
"SupportedInMultiActions": false,
|
||||
"Tooltip": "Plays a bound audio file",
|
||||
"UUID": "com.michal-courson.cliptrim.player",
|
||||
"PropertyInspectorPath": "PropertyInspector/file_player.html"
|
||||
},
|
||||
{
|
||||
"Icon": "Images/collection_icon",
|
||||
"Name": "Collection Selector",
|
||||
"States": [
|
||||
{
|
||||
"Image": "Images/collection",
|
||||
"TitleAlignment": "middle",
|
||||
"FontSize": 11
|
||||
}
|
||||
],
|
||||
"SupportedInMultiActions": false,
|
||||
"Tooltip": "Selects which collection to use",
|
||||
"UUID": "com.michal-courson.cliptrim.profile-switcher",
|
||||
"PropertyInspectorPath": "PropertyInspector/profile_swticher.html"
|
||||
},
|
||||
{
|
||||
"Icon": "Images/page_nav_icon",
|
||||
"Name": "Page Navigator",
|
||||
"States": [
|
||||
{
|
||||
"Image": "Images/page_nav",
|
||||
"TitleAlignment": "middle",
|
||||
"FontSize": 16
|
||||
}
|
||||
],
|
||||
"SupportedInMultiActions": false,
|
||||
"Tooltip": "Navigates pages",
|
||||
"UUID": "com.michal-courson.cliptrim.page-navigator",
|
||||
"PropertyInspectorPath": "PropertyInspector/file_player.html"
|
||||
},
|
||||
{
|
||||
"Icon": "Images/record_icon",
|
||||
"Name": "Save Clip",
|
||||
"States": [
|
||||
{
|
||||
"Image": "Images/record",
|
||||
"TitleAlignment": "middle",
|
||||
"FontSize": 16
|
||||
}
|
||||
],
|
||||
"SupportedInMultiActions": false,
|
||||
"Tooltip": "Saves the current clip",
|
||||
"UUID": "com.michal-courson.cliptrim.clip-save",
|
||||
"PropertyInspectorPath": "PropertyInspector/file_player.html"
|
||||
}
|
||||
],
|
||||
"Author": "Michal Courson",
|
||||
"Name": "ClipTrimDotNet",
|
||||
"Description": "Pairs with cliptrim for easy voice recording and trimming",
|
||||
"Icon": "Images/app_icon",
|
||||
"Version": "0.1.0.0",
|
||||
"CodePath": "ClipTrimDotNet.exe",
|
||||
"Category": "ClipTrim",
|
||||
"CategoryIcon": "Images/category_icon",
|
||||
"UUID": "com.michal-courson.cliptrim",
|
||||
"OS": [
|
||||
{
|
||||
"Platform": "windows",
|
||||
"MinimumVersion": "10"
|
||||
}
|
||||
],
|
||||
"SDKVersion": 2,
|
||||
"Software": {
|
||||
"MinimumVersion": "6.4"
|
||||
},
|
||||
"Profiles": [
|
||||
{
|
||||
"Name": "ClipTrim",
|
||||
"DeviceType": 0,
|
||||
"Readonly": false,
|
||||
"DontAutoSwitchWhenInstalled": false
|
||||
}
|
||||
]
|
||||
}
|
||||
544
stream_deck_plugin/ClipTrimDotNet/package-lock.json
generated
@ -1,272 +1,272 @@
|
||||
{
|
||||
"name": "ClipTrimDotNet",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"devDependencies": {
|
||||
"shx": "^0.3.4"
|
||||
}
|
||||
},
|
||||
"node_modules/balanced-match": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
||||
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/brace-expansion": {
|
||||
"version": "1.1.11",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
||||
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"balanced-match": "^1.0.0",
|
||||
"concat-map": "0.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/concat-map": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
||||
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/fs.realpath": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
|
||||
"integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/function-bind": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
||||
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/glob": {
|
||||
"version": "7.2.3",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
|
||||
"integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
|
||||
"deprecated": "Glob versions prior to v9 are no longer supported",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"fs.realpath": "^1.0.0",
|
||||
"inflight": "^1.0.4",
|
||||
"inherits": "2",
|
||||
"minimatch": "^3.1.1",
|
||||
"once": "^1.3.0",
|
||||
"path-is-absolute": "^1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "*"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/hasown": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
|
||||
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"function-bind": "^1.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/inflight": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
|
||||
"integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
|
||||
"deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"once": "^1.3.0",
|
||||
"wrappy": "1"
|
||||
}
|
||||
},
|
||||
"node_modules/inherits": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
|
||||
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/interpret": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz",
|
||||
"integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/is-core-module": {
|
||||
"version": "2.16.1",
|
||||
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz",
|
||||
"integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"hasown": "^2.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/minimatch": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
||||
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"brace-expansion": "^1.1.7"
|
||||
},
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/minimist": {
|
||||
"version": "1.2.8",
|
||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
|
||||
"integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/once": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
||||
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"wrappy": "1"
|
||||
}
|
||||
},
|
||||
"node_modules/path-is-absolute": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
|
||||
"integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/path-parse": {
|
||||
"version": "1.0.7",
|
||||
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
|
||||
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/rechoir": {
|
||||
"version": "0.6.2",
|
||||
"resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz",
|
||||
"integrity": "sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"resolve": "^1.1.6"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/resolve": {
|
||||
"version": "1.22.10",
|
||||
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz",
|
||||
"integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"is-core-module": "^2.16.0",
|
||||
"path-parse": "^1.0.7",
|
||||
"supports-preserve-symlinks-flag": "^1.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"resolve": "bin/resolve"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/shelljs": {
|
||||
"version": "0.8.5",
|
||||
"resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.5.tgz",
|
||||
"integrity": "sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow==",
|
||||
"dev": true,
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"glob": "^7.0.0",
|
||||
"interpret": "^1.0.0",
|
||||
"rechoir": "^0.6.2"
|
||||
},
|
||||
"bin": {
|
||||
"shjs": "bin/shjs"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/shx": {
|
||||
"version": "0.3.4",
|
||||
"resolved": "https://registry.npmjs.org/shx/-/shx-0.3.4.tgz",
|
||||
"integrity": "sha512-N6A9MLVqjxZYcVn8hLmtneQWIJtp8IKzMP4eMnx+nqkvXoqinUPCbUFLp2UcWTEIUONhlk0ewxr/jaVGlc+J+g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"minimist": "^1.2.3",
|
||||
"shelljs": "^0.8.5"
|
||||
},
|
||||
"bin": {
|
||||
"shx": "lib/cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/supports-preserve-symlinks-flag": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
|
||||
"integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/wrappy": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
||||
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
}
|
||||
}
|
||||
}
|
||||
{
|
||||
"name": "ClipTrimDotNet",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"devDependencies": {
|
||||
"shx": "^0.3.4"
|
||||
}
|
||||
},
|
||||
"node_modules/balanced-match": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
||||
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/brace-expansion": {
|
||||
"version": "1.1.11",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
||||
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"balanced-match": "^1.0.0",
|
||||
"concat-map": "0.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/concat-map": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
||||
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/fs.realpath": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
|
||||
"integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/function-bind": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
||||
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/glob": {
|
||||
"version": "7.2.3",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
|
||||
"integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
|
||||
"deprecated": "Glob versions prior to v9 are no longer supported",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"fs.realpath": "^1.0.0",
|
||||
"inflight": "^1.0.4",
|
||||
"inherits": "2",
|
||||
"minimatch": "^3.1.1",
|
||||
"once": "^1.3.0",
|
||||
"path-is-absolute": "^1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "*"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/hasown": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
|
||||
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"function-bind": "^1.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/inflight": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
|
||||
"integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
|
||||
"deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"once": "^1.3.0",
|
||||
"wrappy": "1"
|
||||
}
|
||||
},
|
||||
"node_modules/inherits": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
|
||||
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/interpret": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz",
|
||||
"integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/is-core-module": {
|
||||
"version": "2.16.1",
|
||||
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz",
|
||||
"integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"hasown": "^2.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/minimatch": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
||||
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"brace-expansion": "^1.1.7"
|
||||
},
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/minimist": {
|
||||
"version": "1.2.8",
|
||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
|
||||
"integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/once": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
||||
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"wrappy": "1"
|
||||
}
|
||||
},
|
||||
"node_modules/path-is-absolute": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
|
||||
"integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/path-parse": {
|
||||
"version": "1.0.7",
|
||||
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
|
||||
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/rechoir": {
|
||||
"version": "0.6.2",
|
||||
"resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz",
|
||||
"integrity": "sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"resolve": "^1.1.6"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/resolve": {
|
||||
"version": "1.22.10",
|
||||
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz",
|
||||
"integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"is-core-module": "^2.16.0",
|
||||
"path-parse": "^1.0.7",
|
||||
"supports-preserve-symlinks-flag": "^1.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"resolve": "bin/resolve"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/shelljs": {
|
||||
"version": "0.8.5",
|
||||
"resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.5.tgz",
|
||||
"integrity": "sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow==",
|
||||
"dev": true,
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"glob": "^7.0.0",
|
||||
"interpret": "^1.0.0",
|
||||
"rechoir": "^0.6.2"
|
||||
},
|
||||
"bin": {
|
||||
"shjs": "bin/shjs"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/shx": {
|
||||
"version": "0.3.4",
|
||||
"resolved": "https://registry.npmjs.org/shx/-/shx-0.3.4.tgz",
|
||||
"integrity": "sha512-N6A9MLVqjxZYcVn8hLmtneQWIJtp8IKzMP4eMnx+nqkvXoqinUPCbUFLp2UcWTEIUONhlk0ewxr/jaVGlc+J+g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"minimist": "^1.2.3",
|
||||
"shelljs": "^0.8.5"
|
||||
},
|
||||
"bin": {
|
||||
"shx": "lib/cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/supports-preserve-symlinks-flag": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
|
||||
"integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/wrappy": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
||||
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,14 +1,14 @@
|
||||
{
|
||||
"scripts": {
|
||||
"stop": "streamdeck stop com.michal-courson.cliptrim",
|
||||
"copy": "@powershell robocopy bin/Debug/ClipTrimDotNet.sdPlugin bin/Debug/com.michal-courson.cliptrim.sdPlugin",
|
||||
"link": "streamdeck link bin/Debug/com.michal-courson.cliptrim.sdPlugin",
|
||||
"restart": "streamdeck restart com.michal-courson.cliptrim",
|
||||
"start": "npm run link && npm run restart",
|
||||
"all": "npm run stop && npm run copy && npm run link && npm run restart",
|
||||
"pack": "streamdeck pack bin/Debug/com.michal-courson.cliptrim.sdPlugin/"
|
||||
},
|
||||
"devDependencies": {
|
||||
"shx": "^0.3.4"
|
||||
}
|
||||
}
|
||||
{
|
||||
"scripts": {
|
||||
"stop": "streamdeck stop com.michal-courson.cliptrim",
|
||||
"copy": "@powershell robocopy bin/Debug/ClipTrimDotNet.sdPlugin bin/Debug/com.michal-courson.cliptrim.sdPlugin/net8.0-windows",
|
||||
"link": "streamdeck link bin/Debug/com.michal-courson.cliptrim.sdPlugin",
|
||||
"restart": "streamdeck restart com.michal-courson.cliptrim",
|
||||
"start": "npm run link && npm run restart",
|
||||
"all": "npm run stop && npm run copy && npm run link && npm run restart",
|
||||
"pack": "streamdeck pack bin/Debug/com.michal-courson.cliptrim.sdPlugin/net8.0-windows/"
|
||||
},
|
||||
"devDependencies": {
|
||||
"shx": "^0.3.4"
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,18 +1,46 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="CommandLineParser" version="2.9.1" targetFramework="net472" />
|
||||
<package id="Microsoft.Win32.Registry" version="4.7.0" targetFramework="net48" />
|
||||
<package id="NAudio" version="2.2.1" targetFramework="net48" />
|
||||
<package id="NAudio.Asio" version="2.2.1" targetFramework="net48" />
|
||||
<package id="NAudio.Core" version="2.2.1" targetFramework="net48" />
|
||||
<package id="NAudio.Midi" version="2.2.1" targetFramework="net48" />
|
||||
<package id="NAudio.Wasapi" version="2.2.1" 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="StreamDeck-Tools" version="6.3.2" targetFramework="net48" />
|
||||
<package id="System.Drawing.Common" version="9.0.10" targetFramework="net48" />
|
||||
<package id="System.Security.AccessControl" version="4.7.0" targetFramework="net48" />
|
||||
<package id="System.Security.Principal.Windows" version="4.7.0" targetFramework="net48" />
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="CommandLineParser" version="2.9.1" targetFramework="net472" />
|
||||
<package id="Microsoft.Bcl.AsyncInterfaces" version="10.0.2" targetFramework="net48" />
|
||||
<package id="Microsoft.Extensions.DependencyInjection" version="10.0.2" targetFramework="net48" />
|
||||
<package id="Microsoft.Extensions.DependencyInjection.Abstractions" version="10.0.2" targetFramework="net48" />
|
||||
<package id="Microsoft.Extensions.Logging" version="10.0.2" targetFramework="net48" />
|
||||
<package id="Microsoft.Extensions.Logging.Abstractions" version="10.0.2" targetFramework="net48" />
|
||||
<package id="Microsoft.Extensions.Options" version="10.0.2" targetFramework="net48" />
|
||||
<package id="Microsoft.Extensions.Primitives" version="10.0.2" targetFramework="net48" />
|
||||
<package id="Microsoft.Win32.Registry" version="4.7.0" targetFramework="net48" />
|
||||
<package id="NAudio" version="2.2.1" targetFramework="net48" />
|
||||
<package id="NAudio.Asio" version="2.2.1" targetFramework="net48" />
|
||||
<package id="NAudio.Core" version="2.2.1" targetFramework="net48" />
|
||||
<package id="NAudio.Midi" version="2.2.1" targetFramework="net48" />
|
||||
<package id="NAudio.Wasapi" version="2.2.1" 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>
|
||||