fix python instance init, remove and edit clip meta, add meta on record

This commit is contained in:
michalcourson
2026-02-14 11:57:12 -05:00
parent f3b883602e
commit f9fdfb629b
14 changed files with 116 additions and 30 deletions

1
audio-service/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
recordings/

View File

@ -1,10 +1,17 @@
{ {
"Test": [ "Test": [],
"Uncategorized": [
{ {
"name": "test 2", "filename": "C:\\Users\\mickl\\Desktop\\cliptrim-ui\\ClipTrimApp\\audio-service\\recordings\\audio_capture_20260214_114317.wav",
"filePath": "C:\\Users\\mickl\\Music\\clips\\original\\audio_capture_20250105_131700.wav", "name": "Clip 20260214_114317",
"volume": 1, "playbackType": "playStop",
"playbackType": "playStop" "volume": 0.8
},
{
"filename": "C:\\Users\\mickl\\Desktop\\cliptrim-ui\\ClipTrimApp\\audio-service\\recordings\\audio_capture_20260214_114712.wav",
"name": "Clip 20260214_114712",
"playbackType": "playStop",
"volume": 1.0
} }
] ]
} }

View File

@ -3,6 +3,7 @@ import numpy as np
import os import os
from datetime import datetime from datetime import datetime
import scipy.io.wavfile as wavfile import scipy.io.wavfile as wavfile
from metadata_manager import MetaDataManager
class AudioRecorder: class AudioRecorder:
_instance = None _instance = None
@ -11,8 +12,9 @@ class AudioRecorder:
if cls._instance is None: if cls._instance is None:
print("Creating new AudioRecorder instance") print("Creating new AudioRecorder instance")
cls._instance = super().__new__(cls) cls._instance = super().__new__(cls)
cls._instance.init()
return cls._instance return cls._instance
def __init__(self, duration=30, sample_rate=44100, channels=2, recordings_dir='recordings'): def init(self):
""" """
Initialize audio recorder with configurable parameters. Initialize audio recorder with configurable parameters.
@ -20,12 +22,12 @@ class AudioRecorder:
:param sample_rate: Audio sample rate (if None, use default device sample rate) :param sample_rate: Audio sample rate (if None, use default device sample rate)
:param channels: Number of audio channels :param channels: Number of audio channels
""" """
print(f"Initializing AudioRecorder")
self.duration = duration self.duration = 30
self.sample_rate = sample_rate self.sample_rate = 44100
self.channels = channels self.channels = 2
self.buffer = np.zeros((int(duration * sample_rate), channels), dtype=np.float32) self.buffer = np.zeros((int(self.duration * self.sample_rate), self.channels), dtype=np.float32)
self.recordings_dir = recordings_dir self.recordings_dir = "recordings"
self.stream = sd.InputStream( self.stream = sd.InputStream(
samplerate=self.sample_rate, samplerate=self.sample_rate,
channels=self.channels, channels=self.channels,
@ -93,6 +95,16 @@ class AudioRecorder:
# Write buffer to file # Write buffer to file
wavfile.write(filename, int(self.sample_rate), audio_data_int16) wavfile.write(filename, int(self.sample_rate), audio_data_int16)
meta = MetaDataManager()
meta.add_clip_to_collection("Uncategorized",
{
"filename": filename,
"name": f"Clip {timestamp}",
"playbackType":"playStop",
"volume": 1.0,
})
return filename return filename
def set_buffer_duration(self, duration): def set_buffer_duration(self, duration):

View File

@ -18,7 +18,6 @@ import threading
app = Flask(__name__) app = Flask(__name__)
def main(): def main():
global recorder, audio_manager
# Create argument parser # Create argument parser
parser = argparse.ArgumentParser(description='Audio Recording Service') parser = argparse.ArgumentParser(description='Audio Recording Service')
@ -36,17 +35,6 @@ def main():
# Ensure save path exists # Ensure save path exists
os.makedirs(settings.get_settings('save_path'), exist_ok=True) os.makedirs(settings.get_settings('save_path'), exist_ok=True)
# Handle input device selection
# Create Singletons with correct parameters
recorder = AudioRecorder(
duration=settings.get_settings('recording_length'),
recordings_dir=settings.get_settings('save_path'),
# channels=min(2, devices[input_device]['max_input_channels']),
)
meta = MetaDataManager()
audio_manager = WindowsAudioManager()
# Register blueprints # Register blueprints
app.register_blueprint(recording_bp) app.register_blueprint(recording_bp)

View File

@ -7,8 +7,9 @@ class MetaDataManager:
def __new__(cls, *args, **kwargs): def __new__(cls, *args, **kwargs):
if cls._instance is None: if cls._instance is None:
cls._instance = super().__new__(cls) cls._instance = super().__new__(cls)
cls._instance.init()
return cls._instance return cls._instance
def __init__(self): def init(self):
# read metadata file from executing directory # read metadata file from executing directory
self.metadata_file = os.path.join(os.getcwd(), "metadata.json") self.metadata_file = os.path.join(os.getcwd(), "metadata.json")
if os.path.exists(self.metadata_file): if os.path.exists(self.metadata_file):
@ -16,6 +17,9 @@ class MetaDataManager:
self.collections = json.load(f) self.collections = json.load(f)
else: else:
self.collections = {} self.collections = {}
if(collections := self.collections.get("Uncategorized")) is None:
self.collections["Uncategorized"] = []
self.save_metadata()
def create_collection(self, name): def create_collection(self, name):
if name in self.collections: if name in self.collections:
@ -35,6 +39,31 @@ class MetaDataManager:
self.collections[collection_name].append(clip_metadata) self.collections[collection_name].append(clip_metadata)
self.save_metadata() self.save_metadata()
def remove_clip_from_collection(self, collection_name, clip_metadata):
if collection_name not in self.collections:
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 self.collections[collection_name])
if not in_list:
raise ValueError(f"Clip with filename '{clip_metadata.get('filename')}' not found in collection '{collection_name}'.")
self.collections[collection_name] = [
clip for clip in self.collections[collection_name]
if clip.get("filename") != clip_metadata.get("filename")
]
self.save_metadata()
def edit_clip_in_collection(self, collection_name, new_clip_metadata):
if collection_name not in self.collections:
raise ValueError(f"Collection '{collection_name}' does not exist.")
# Find the index of the clip with the same file name as old_clip_metadata["file_name"]
index = next((i for i, clip in enumerate(self.collections[collection_name]) 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}'.")
self.collections[collection_name][index] = new_clip_metadata
self.save_metadata()
def get_collections(self): def get_collections(self):
return list(self.collections.keys()) return list(self.collections.keys())
@ -43,6 +72,18 @@ class MetaDataManager:
raise ValueError(f"Collection '{collection_name}' does not exist.") raise ValueError(f"Collection '{collection_name}' does not exist.")
return self.collections[collection_name] return self.collections[collection_name]
def reorder_clips_in_collection(self, collection_name, new_order):
if collection_name not in self.collections:
raise ValueError(f"Collection '{collection_name}' does not exist.")
existing_filenames = {clip.get("filename") for clip in self.collections[collection_name]}
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.")
self.collections[collection_name] = new_order
self.save_metadata()
def save_metadata(self): def save_metadata(self):
with open(self.metadata_file, "w") as f: with open(self.metadata_file, "w") as f:
json.dump(self.collections, f, indent=4) json.dump(self.collections, f, indent=4)

View File

@ -20,16 +20,27 @@ def add_collection():
except ValueError as e: except ValueError as e:
return jsonify({'status': 'error', 'message': str(e)}), 400 return jsonify({'status': 'error', 'message': str(e)}), 400
@metadata_bp.route('/meta/collection/clips', methods=['GET']) @metadata_bp.route('/meta/collection/clips/<name>', methods=['GET'])
def get_clips_in_collection(): def get_clips_in_collection(name):
meta_manager = MetaDataManager() meta_manager = MetaDataManager()
collection_name = request.args.get('name') collection_name = name
try: try:
clips = meta_manager.get_clips_in_collection(collection_name) clips = meta_manager.get_clips_in_collection(collection_name)
return jsonify({'status': 'success', 'clips': clips}) return jsonify({'status': 'success', 'clips': clips})
except ValueError as e: except ValueError as e:
return jsonify({'status': 'error', 'message': str(e)}), 400 return jsonify({'status': 'error', 'message': str(e)}), 400
@metadata_bp.route('/meta/collection/clips/reorder', methods=['POST']):
def reorder_clips_in_collection():
meta_manager = MetaDataManager()
collection_name = request.json.get('name')
new_order = request.json.get('clips')
try:
meta_manager.reorder_clips_in_collection(collection_name, new_order)
return jsonify({'status': 'success', 'clips': new_order})
except ValueError as e:
return jsonify({'status': 'error', 'message': str(e)}), 400
@metadata_bp.route('/meta/collection/clips/add', methods=['POST']) @metadata_bp.route('/meta/collection/clips/add', methods=['POST'])
def add_clip_to_collection(): def add_clip_to_collection():
meta_manager = MetaDataManager() meta_manager = MetaDataManager()
@ -41,3 +52,27 @@ def add_clip_to_collection():
return jsonify({'status': 'success', 'clips': clips}) return jsonify({'status': 'success', 'clips': clips})
except ValueError as e: except ValueError as e:
return jsonify({'status': 'error', 'message': str(e)}), 400 return jsonify({'status': 'error', 'message': str(e)}), 400
@metadata_bp.route('/meta/collection/clips/remove', methods=['POST'])
def remove_clip_from_collection():
meta_manager = MetaDataManager()
collection_name = request.json.get('name')
clip_metadata = request.json.get('clip')
try:
meta_manager.remove_clip_from_collection(collection_name, clip_metadata)
clips = meta_manager.get_clips_in_collection(collection_name)
return jsonify({'status': 'success', 'clips': clips})
except ValueError as e:
return jsonify({'status': 'error', 'message': str(e)}), 400
@metadata_bp.route('/meta/collection/clips/edit', methods=['POST'])
def edit_clip_in_collection():
meta_manager = MetaDataManager()
collection_name = request.json.get('name')
clip_metadata = request.json.get('clip')
try:
meta_manager.edit_clip_in_collection(collection_name, clip_metadata)
clips = meta_manager.get_clips_in_collection(collection_name)
return jsonify({'status': 'success', 'clips': clips})
except ValueError as e:
return jsonify({'status': 'error', 'message': str(e)}), 400

View File

@ -9,8 +9,9 @@ class SettingsManager:
def __new__(cls, *args, **kwargs): def __new__(cls, *args, **kwargs):
if cls._instance is None: if cls._instance is None:
cls._instance = super().__new__(cls) cls._instance = super().__new__(cls)
cls._instance.init()
return cls._instance return cls._instance
def __init__(self): def init(self):
# read settings file from executing directory # read settings file from executing directory
self.settings_file = os.path.join(os.getcwd(), "settings.json") self.settings_file = os.path.join(os.getcwd(), "settings.json")
if os.path.exists(self.settings_file): if os.path.exists(self.settings_file):

View File

@ -12,8 +12,9 @@ class WindowsAudioManager:
def __new__(cls, *args, **kwargs): def __new__(cls, *args, **kwargs):
if cls._instance is None: if cls._instance is None:
cls._instance = super().__new__(cls) cls._instance = super().__new__(cls)
cls._instance.init()
return cls._instance return cls._instance
def __init__(self): def init(self):
""" """
Initialize Windows audio device and volume management. Initialize Windows audio device and volume management.
""" """