From d49ac95fa22b719a6cba4c227b3bf0bd36555125 Mon Sep 17 00:00:00 2001 From: michalcourson Date: Sun, 22 Feb 2026 14:57:04 -0500 Subject: [PATCH] settings work --- audio-service/settings.json | 16 +- .../audio_recorder.cpython-313.pyc | Bin 7319 -> 7245 bytes .../src/__pycache__/settings.cpython-313.pyc | Bin 4426 -> 5027 bytes .../__pycache__/windows_audio.cpython-313.pyc | Bin 5093 -> 5764 bytes audio-service/src/audio_recorder.py | 2 - .../routes/__pycache__/device.cpython-313.pyc | Bin 1768 -> 1211 bytes audio-service/src/routes/device.py | 14 +- audio-service/src/settings.py | 33 +++- audio-service/src/windows_audio.py | 18 +- electron-ui/src/main/service.ts | 0 electron-ui/src/renderer/App.tsx | 3 +- electron-ui/src/renderer/Settings.tsx | 162 ++++++++++++------ electron-ui/src/renderer/api.ts | 13 ++ .../src/renderer/components/ClipList.tsx | 47 +++-- ...5ba82e2c-dcf4-4055-a830-639383c8c842.vsidx | Bin 22678 -> 0 bytes ...f41a6896-d9b5-4af7-8dec-d0296008b1ac.vsidx | Bin 58082 -> 0 bytes 16 files changed, 205 insertions(+), 103 deletions(-) create mode 100644 electron-ui/src/main/service.ts create mode 100644 electron-ui/src/renderer/api.ts delete mode 100644 stream_deck_plugin/.vs/ClipTrimDotNet/FileContentIndex/5ba82e2c-dcf4-4055-a830-639383c8c842.vsidx delete mode 100644 stream_deck_plugin/.vs/ClipTrimDotNet/FileContentIndex/f41a6896-d9b5-4af7-8dec-d0296008b1ac.vsidx diff --git a/audio-service/settings.json b/audio-service/settings.json index 693356b..3e9bf0b 100644 --- a/audio-service/settings.json +++ b/audio-service/settings.json @@ -1,10 +1,16 @@ { "input_device": { - "default_samplerate": 44100.0, - "index": 1, - "max_input_channels": 8, - "name": "VM Mic mix (VB-Audio Voicemeete" + "index": 49, + "name": "Microphone (Logi C615 HD WebCam)", + "max_input_channels": 1, + "default_samplerate": 48000.0 }, "save_path": "C:\\Users\\mickl\\Desktop\\cliptrim-ui\\ClipTrimApp\\audio-service\\recordings", - "recording_length": 30 + "recording_length": 30, + "output_device": { + "index": 40, + "name": "Speakers (Realtek(R) Audio)", + "max_output_channels": 2, + "default_samplerate": 48000.0 + } } \ No newline at end of file diff --git a/audio-service/src/__pycache__/audio_recorder.cpython-313.pyc b/audio-service/src/__pycache__/audio_recorder.cpython-313.pyc index 61d41c879209cff7be8632298c04d97804889bfb..e036ec8402ccf7cbe0a28a3a6c3443b0bc59bc9e 100644 GIT binary patch delta 669 zcmbPkdDepWGcPX}0}yxy%+5^S$m_(+=&(6}S&ngX3yT;dkIYQsTpcXE+(jZl-N}p~GoXM2NHYWRXAY*xeiC|{i`k1A#bsc! z2s)V6l5w&y=fg-%px7<8#N?99vea8F#U=R#MG8QnB1I6P1R`WXgdB*F2NB93LIp&C z++L&xBGf^I29UVLT3nJ?RHDgLq&?Y}%aaYHuK3dAIb5rR(H!5y{f%RD68B0*VFi#r zmZJQe9I&yPtU$eTyoQV#lLL5_7%esz^FCu{RGl0pr~)>wNMUk|pe`HOX>yad3n~k0 zgACv-$;?d!TAo`_HVCIhhRK~^C72#aSxE(R%*oqRyVoiTSZhvSyKtvpf zNCXinAR>LTpL8sjAS>4=HGWowPa2cAN;?UGV(1oYUTJPYB_wKHCv(ckGx|@~lrdv0 Uotz+}!5_%PsPu^eM1oTk058RglmGw# delta 689 zcmZvZ%WD%+6vlJs)jH|aPLk;)Ga;|0O(w0eMUW82(rdpJ05sf-vFk`}iy!xK_|A8}`*ra}P5G@Ta)hmG zXJ58Hy;qiaa?xMrGXy>=ap6Mbd=k!$$N7;O=RXqaMBxJ2Z93hyO`Dz#U&M9t5c*O( zp_`j}X+tml&{CV4wV_!%A`iP#p3891PgVWXBQDX$vC;pFbx30es0tE%AN_Ty!rlX^ zdBbb<>_Bk6PM7A{6)hm95E(=kkwX*_(+He{mJnrx#Sn>`W$G(9;JMDI;Z;4Fg+t>oJKvD{%?pw+>PiA03Erz-@4|VmB z6ne~pqfV)FC~&}QIW}|Qbm<9rr9L1HKOdXs=EE`ejJD5IXr0N>4D=GGNC!SAwnk^z zML5zM?J~LdVX{FCjEx>fgel~3w}6<1oBDCm_h0KZqV1`g9~SH+kC7)(O>Ia2z0X3{ zSR~J2)hHTaxS?X`8M_bjufTJoQ9OcY44bYZ)(}?_*AX`276|4QIVQ@3@`RWhRN%I` p6vLki#5;GL?)~8xpM-TYOO|2FJW6^nnJyn(;YeW+LDB@P{tIcjmu~<7 diff --git a/audio-service/src/__pycache__/settings.cpython-313.pyc b/audio-service/src/__pycache__/settings.cpython-313.pyc index a5a10d69efd3990990566d3403ced96abb519923..ace0d6d013a61e54dfbee51b0c40d8c0e0f151eb 100644 GIT binary patch delta 1433 zcma)6O-vg{6rQnnm$kiXgJT-o!DQ`Vz!Y%!hZ1Z|2qaM)By5Y87LbE&>NYlLY@?<< zKrWRlRcfM95~3bLlpIP=Mmf~S)&*B(stq^F=2&9$y*^;i6uOnVR_a7dP@Q7yc z1s`Crhdu3n&!+WrTF|B_bt-`oOMt0A5(^TE0ONbF#QC|Bl1fFD$jX9mYOC=})+`Le zt5ToxG>AZl4*$d24U8;A<8!IxR46;<@tx7Wb9dPwQ0-lUb7s$ z!NX-voZ?0okVIK+1RP3qhT1h0stF{N1SDw@(Qkp?YCUVN14JAC+8Rhi<1vG4Vj%o(Rfl8 zY4B7coJ^9;griJm65;C+8AfC=mB!s7$j30^F{~wB&y zv7B}6H=$zP(4GlohS&AyHif>u`i@)PW$T8aCfiO38(Y$YKX||ME;Z)_2PM(xG?vF_ zUf9Q_!sUs;NeXwJh+Pkz=oVVjdOG*O^aEHj~0Yz`f= z9IZA+X&rwOMAgaE0TE>o}NXaFhV^vyExQG z=~r-{&JIWMwC)B+bx;%jU3aCYNEzO zQxg+EH%5#bjhcWPQ8yZ0=)#38OdCMRgoTNTiHRo0PeeER9!T^qPVSsJ_uM--cW$p3 z_uIe9vWRHByL&0sVS8ZDo2)Jlp*{Rn2=Tk1m3xnyA;=%Z99-u&*Y9*QF^e8+9Bo$? z^8e9}>P&0!0^>tW14r!6<${?~1AH&vry{g->0(8kug5*MGIT(Qa}c(8<$wNSFAuNG z6^;T{xefQsS1~G`-13+>jLFC928b(}@ACdladhVG6A_aFTf7wY9aXf;QAW6y`pl(xm(4udZb>sdT@t zn&GL-t-^<(j|j-1jwC9Qw&S1)x8w_NEQ$z38C<`XQB5R5bFvKD zr6{h3Gg6d|zC4*`T2A@1SFi7T2 z{+icOS~pSf`3O5bQVV^Be!TY4*}@FAu%jYn@Vlr6H$si0&Cdoh1fuObOp+QQ_4HAv zx6QV{P~HyUsKilt;y7kueaK2^P&$`1vb34OCI)O=(hk>@PS7%Q6rR~1du_*dVmmmp+OVXK*RE|vA!>?U+ceZ_8&s#2jR{gHmJ@Fhx0`j? zb&`@w1W*s*=TK=RR0L8vaA+?`$R{KYs89hm{k`M58hyxXd_!2o|i^rB8t`GA=O1oPr%+1Vv`i z0lTG(k}g68 z|9cHvcMUo+56NADIzD2upJfoSqd(Z>&Ya1q)ZYgA!eyB&Z-sphO8$=lgA12jcr?3m=rZUPVhUKQ^a91dc}0Tz9DOGlvZncy}Vx4 zOSNu zzAcII=v^IZcB-le`!xje9C_9|MFEMx7bWC z-esFoqUm4SDiMKXQPP3e?H{dSD;O^4x=f z1!LnM4nlN`_l~UCyW?b*@lfetu@!NksO6n$Z3O3OPhl`U6?wV5q0~!yd2L;hwQ^mK za7W_8(rQ^%WkuKG=x2)CKReAZeRn`qKH3&+t->jgnPVFaIRiU|}K zM$vE9G$j&EG1-UQ7qGV#JMOI9j;R&P5nnQ5z$=N%W!Z5j#k0?!bjvZt{X!7ZYBhYs_NZye9U30i{HsGP*95-(2np^+b4Czn(dHS#}b2^4dt z;bP;O^DAhf5;M1& delta 743 zcmZ9KOK1~O6o%){Bai7!nx;w9#I~fhF`XuDG^Q?E)2h@KOB;k@H$FlzHz7KSdZ&rH z5Tq!$a3P$n8wD5H2r@er+_)=(kA*O}5Tpw?F0{Dzo=Gd>U3~Y<|Nor(ntM|_slYE? zR|P(PJbP5X6MPZSB_hH~@R)v1Fhw+QoQDN{7@h{h{p&>G-`vB=9e=%nb#c%0?0YrI zkYQe#H5j(~PB@B&*AcCXqu*#zcDq!e?fPYEIo`bjRL>EOII=~}s)c#D>6wh3;>BHI z*C!0dKSxs^qp43(KsLbuo#~^5*y)@BVXn19f_X}-uTau82Hs?c&YWlTQvf0wctxnzt1jyNT0S} zsRfc5#XkQphuR^Iq9MAy*$^rQ%xJ45Z*OU7LXz-Y&kRLTk0TBs+}I$m489roty^Tt zz7oihY_hAlarE6JZ;m@=c@7I7E0JI!6mqcLTOb>@Klqw>+(-mLYXATM diff --git a/audio-service/src/audio_recorder.py b/audio-service/src/audio_recorder.py index b4db2ef..243271b 100644 --- a/audio-service/src/audio_recorder.py +++ b/audio-service/src/audio_recorder.py @@ -30,7 +30,6 @@ class AudioRecorder: self.recordings_dir = "recordings" self.stream = sd.InputStream( - samplerate=self.sample_rate, callback=self.record_callback ) @@ -45,7 +44,6 @@ class AudioRecorder: self.buffer = np.zeros((int(self.duration * self.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.stream = sd.InputStream( - samplerate=self.sample_rate, callback=self.record_callback ) diff --git a/audio-service/src/routes/__pycache__/device.cpython-313.pyc b/audio-service/src/routes/__pycache__/device.cpython-313.pyc index c20c29996d04c563fe3b247bcf2bf9c658e77e68..7a8e875f24502028369d5b3bf32b7f8ec71f369e 100644 GIT binary patch delta 289 zcmaFCyPK2uGcPX}0}yNqot^o6BCjN4&qVcJmUK={u8Ge=d3f|wQp+-vQ}uH)izmA= zs!!g^{BN=xlXN{hM7;PGYidzZevu|q5kJt7TkN^1#l?x~shJZP85lG{q>7i; zg6Ks^SUh-kxVYmRq8Ro~iSXnH;uIQWAg(7n$-ZTvJyYrTW z;~=_)5s@XqxbPx|NiETI!<=IQ)hy-3lo<-^tyJvRQt;ivs{Y@0Sj9U^S QSDJ0NIp8>QRF3hgKV+MzlK=n! diff --git a/audio-service/src/routes/device.py b/audio-service/src/routes/device.py index 1f9bdd2..b9e7974 100644 --- a/audio-service/src/routes/device.py +++ b/audio-service/src/routes/device.py @@ -19,13 +19,13 @@ recorder = AudioRecorder() # except Exception as e: # return jsonify({'status': 'error', 'message': str(e)}), 400 -@device_bp.route('/device/get', methods=['GET']) -def get_audio_device(): - try: - device_info = audio_manager.get_default_device('input') - return jsonify({'status': 'success', 'device_info': device_info}) - except Exception as e: - return jsonify({'status': 'error', 'message': str(e)}), 400 +# @device_bp.route('/device/get', methods=['GET']) +# def get_audio_device(): +# try: +# device_info = audio_manager.get_default_device('input') +# return jsonify({'status': 'success', 'device_info': device_info}) +# except Exception as e: +# return jsonify({'status': 'error', 'message': str(e)}), 400 @device_bp.route('/device/list', methods=['GET']) def list_audio_devices(): diff --git a/audio-service/src/settings.py b/audio-service/src/settings.py index 1092d80..56672e4 100644 --- a/audio-service/src/settings.py +++ b/audio-service/src/settings.py @@ -20,21 +20,44 @@ class SettingsManager: else: self.settings = { "input_device": None, + "output_device": None, "save_path": os.path.join(os.getcwd(), "recordings"), "recording_length": 15 } audio_manager = WindowsAudioManager() - devices = audio_manager.list_audio_devices('input') - print(f"Available input devices: {self.settings}") - input = self.settings["input_device"] + input_devices = audio_manager.list_audio_devices('input') + output_devices = audio_manager.list_audio_devices('output') + # print("Available input devices:") + # for i, dev in enumerate(input_devices): + # print(i, dev['name']) + # print("Available output devices:") + # for i, dev in enumerate(output_devices): + # print(i, dev['name']) + # print(f"Available input devices: {input_devices}") + # print(f"Available output devices: {output_devices}") + input = None + output = None + + if("input_device" in self.settings): + input = self.settings["input_device"] + if("output_device" in self.settings): + output = self.settings["output_device"] #see if input device is in "devices", if not set to the first index - if input is not None and any(d['name'] == input["name"] for d in devices): + if input is not None and any(d['name'] == input["name"] for d in input_devices): print(f"Using saved input device index: {input}") else: - input = devices[0] if devices else None + input = input_devices[0] if input_devices else None self.settings["input_device"] = input + + #see if output device is in "devices", if not set to the first index + if output is not None and any(d['name'] == output["name"] for d in output_devices): + print(f"Using saved output device index: {output}") + else: + output = output_devices[0] if output_devices else None + self.settings["output_device"] = output + self.save_settings() diff --git a/audio-service/src/windows_audio.py b/audio-service/src/windows_audio.py index 1099d04..e9a574a 100644 --- a/audio-service/src/windows_audio.py +++ b/audio-service/src/windows_audio.py @@ -18,7 +18,19 @@ class WindowsAudioManager: """ Initialize Windows audio device and volume management. """ - self.devices = sd.query_devices() + 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] @@ -34,7 +46,7 @@ class WindowsAudioManager: { 'index': dev['index'], 'name': dev['name'], - 'max_input_channels': dev['max_input_channels'], + 'channels': dev['max_input_channels'], 'default_samplerate': dev['default_samplerate'] } for dev in self.devices if dev['max_input_channels'] > 0 @@ -44,7 +56,7 @@ class WindowsAudioManager: { 'index': dev['index'], 'name': dev['name'], - 'max_output_channels': dev['max_output_channels'], + 'channels': dev['max_output_channels'], 'default_samplerate': dev['default_samplerate'] } for dev in self.devices if dev['max_output_channels'] > 0 diff --git a/electron-ui/src/main/service.ts b/electron-ui/src/main/service.ts new file mode 100644 index 0000000..e69de29 diff --git a/electron-ui/src/renderer/App.tsx b/electron-ui/src/renderer/App.tsx index 545b7f0..e3d8501 100644 --- a/electron-ui/src/renderer/App.tsx +++ b/electron-ui/src/renderer/App.tsx @@ -14,6 +14,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'; function MainPage() { const dispatch = useAppDispatch(); @@ -30,7 +31,7 @@ function MainPage() { useEffect(() => { const fetchMetadata = async () => { try { - const response = await fetch('http://localhost:5010/meta'); + const response = await apiFetch('meta'); const data = await response.json(); dispatch({ type: 'metadata/setAllData', payload: data }); } catch (error) { diff --git a/electron-ui/src/renderer/Settings.tsx b/electron-ui/src/renderer/Settings.tsx index c1576ec..4f2cfec 100644 --- a/electron-ui/src/renderer/Settings.tsx +++ b/electron-ui/src/renderer/Settings.tsx @@ -5,38 +5,57 @@ import './App.css'; import TextField from '@mui/material/TextField'; import Select from '@mui/material/Select'; import MenuItem from '@mui/material/MenuItem'; +import { apiFetch } from './api'; type AudioDevice = { - id: string; - label: string; + index: number; + name: string; + default_sample_rate: number; + channels: number; }; type Settings = { - httpPort: string; - inputDevice: AudioDevice; - outputDevice: AudioDevice; - recordingLength: number; - outputFolder: string; + http_port: number; + input_device: AudioDevice; + output_device: AudioDevice; + recording_length: number; + save_path: string; }; const defaultSettings: Settings = { - httpPort: '', - inputDevice: { id: '', label: '' }, - outputDevice: { id: '', label: '' }, - recordingLength: 0, - outputFolder: '', + http_port: 0, + input_device: { index: 0, name: '', default_sample_rate: 0, channels: 0 }, + output_device: { index: 0, name: '', default_sample_rate: 0, channels: 0 }, + recording_length: 0, + save_path: '', }; -const fetchAudioDevices = async (): Promise => { +async function fetchAudioDevices( + type: 'input' | 'output', +): Promise { // Replace with actual backend call // Example: return window.api.getAudioDevices(); - return [ - { id: 'default-in', label: 'Default Input' }, - { id: 'mic-1', label: 'Microphone 1' }, - { id: 'default-out', label: 'Default Output' }, - { id: 'spk-1', label: 'Speakers 1' }, - ]; -}; + return apiFetch(`device/list?device_type=${type}`) + .then((res) => res.json()) + .then((data) => data.devices as AudioDevice[]) + .catch((error) => { + console.error('Error fetching audio devices:', error); + return []; + }); +} + +async function fetchSettings(): Promise { + // Replace with actual backend call + // Example: return window.api.getAudioDevices(); + console.log('Fetching settings from backend...'); + return apiFetch('settings') + .then((res) => res.json()) + .then((data) => data.settings as Settings) + .catch((error) => { + console.error('Error fetching settings:', error); + return defaultSettings; + }); +} const sendSettingsToBackend = (settings: Settings) => { // Replace with actual backend call @@ -51,11 +70,24 @@ export default function SettingsPage() { const navigate = useNavigate(); useEffect(() => { - fetchAudioDevices() + fetchSettings() + .then((fetchedSettings) => { + console.log('Fetched settings:', fetchedSettings); + setSettings(fetchedSettings); + return null; + }) + .then(() => { + return fetchAudioDevices('input'); + }) .then((devices) => { - // For demo, split devices by id - setInputDevices(devices.filter((d) => d.id.includes('in'))); - setOutputDevices(devices.filter((d) => d.id.includes('out'))); + setInputDevices(devices); + // console.log('Input devices:', devices); + return fetchAudioDevices('output'); + }) + .then((devices) => { + setOutputDevices(devices); + + // console.log('Output devices:', devices); return devices; }) .catch((error) => { @@ -78,16 +110,16 @@ export default function SettingsPage() { const handleFolderChange = async () => { // Replace with actual folder picker // Example: const folder = await window.api.selectFolder(); - const folder = window.prompt( - 'Enter output folder path:', - settings.outputFolder, - ); - if (folder !== null) { - setSettings((prev) => ({ - ...prev, - outputFolder: folder, - })); - } + // const folder = window.prompt( + // 'Enter output folder path:', + // settings.outputFolder, + // ); + // if (folder !== null) { + // setSettings((prev) => ({ + // ...prev, + // outputFolder: folder, + // })); + // } }; return ( @@ -109,9 +141,15 @@ export default function SettingsPage() { variant="standard" type="text" name="httpPort" - value={settings.httpPort} + value={settings.http_port} + onBlur={() => console.log('port blur')} onChange={(e) => { - setSettings((prev) => ({ ...prev, httpPort: e.target.value })); + if (!Number.isNaN(Number(e.target.value))) { + setSettings((prev) => ({ + ...prev, + http_port: Number(e.target.value), + })); + } }} className="ml-2 text-white w-[150px]" /> @@ -121,15 +159,24 @@ export default function SettingsPage() { @@ -139,18 +186,23 @@ export default function SettingsPage() { @@ -161,12 +213,14 @@ export default function SettingsPage() { variant="standard" type="text" name="recordingLength" - value={settings.recordingLength} + value={settings.recording_length} onChange={(e) => { - setSettings((prev) => ({ - ...prev, - recordingLength: e.target.value, - })); + if (!Number.isNaN(Number(e.target.value))) { + setSettings((prev) => ({ + ...prev, + recording_length: Number(e.target.value), + })); + } }} className="ml-2 w-[150px]" /> @@ -177,8 +231,8 @@ export default function SettingsPage() {