diff --git a/audio-service/.gitignore b/audio-service/.gitignore new file mode 100644 index 0000000..f5fe691 --- /dev/null +++ b/audio-service/.gitignore @@ -0,0 +1 @@ +recordings/ \ No newline at end of file diff --git a/audio-service/metadata.json b/audio-service/metadata.json index 59bd4ac..110d2c4 100644 --- a/audio-service/metadata.json +++ b/audio-service/metadata.json @@ -1,10 +1,17 @@ { - "Test": [ + "Test": [], + "Uncategorized": [ { - "name": "test 2", - "filePath": "C:\\Users\\mickl\\Music\\clips\\original\\audio_capture_20250105_131700.wav", - "volume": 1, - "playbackType": "playStop" + "filename": "C:\\Users\\mickl\\Desktop\\cliptrim-ui\\ClipTrimApp\\audio-service\\recordings\\audio_capture_20260214_114317.wav", + "name": "Clip 20260214_114317", + "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 } ] } \ No newline at end of file diff --git a/audio-service/recordings/audio_capture_20260214_101634.wav b/audio-service/recordings/audio_capture_20260214_101634.wav deleted file mode 100644 index fd04805..0000000 Binary files a/audio-service/recordings/audio_capture_20260214_101634.wav and /dev/null differ diff --git a/audio-service/src/__pycache__/audio_recorder.cpython-313.pyc b/audio-service/src/__pycache__/audio_recorder.cpython-313.pyc index 97d7bc7..ebe64eb 100644 Binary files a/audio-service/src/__pycache__/audio_recorder.cpython-313.pyc and b/audio-service/src/__pycache__/audio_recorder.cpython-313.pyc differ diff --git a/audio-service/src/__pycache__/metadata_manager.cpython-313.pyc b/audio-service/src/__pycache__/metadata_manager.cpython-313.pyc index c182d0b..95cded5 100644 Binary files a/audio-service/src/__pycache__/metadata_manager.cpython-313.pyc and b/audio-service/src/__pycache__/metadata_manager.cpython-313.pyc differ diff --git a/audio-service/src/__pycache__/settings.cpython-313.pyc b/audio-service/src/__pycache__/settings.cpython-313.pyc index 5dc05a9..a5a10d6 100644 Binary files a/audio-service/src/__pycache__/settings.cpython-313.pyc and b/audio-service/src/__pycache__/settings.cpython-313.pyc differ diff --git a/audio-service/src/__pycache__/windows_audio.cpython-313.pyc b/audio-service/src/__pycache__/windows_audio.cpython-313.pyc index 3d135f3..7aa364e 100644 Binary files a/audio-service/src/__pycache__/windows_audio.cpython-313.pyc and b/audio-service/src/__pycache__/windows_audio.cpython-313.pyc differ diff --git a/audio-service/src/audio_recorder.py b/audio-service/src/audio_recorder.py index 11d6bc2..0817647 100644 --- a/audio-service/src/audio_recorder.py +++ b/audio-service/src/audio_recorder.py @@ -3,6 +3,7 @@ import numpy as np import os from datetime import datetime import scipy.io.wavfile as wavfile +from metadata_manager import MetaDataManager class AudioRecorder: _instance = None @@ -11,8 +12,9 @@ class AudioRecorder: if cls._instance is None: print("Creating new AudioRecorder instance") cls._instance = super().__new__(cls) + cls._instance.init() 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. @@ -20,12 +22,12 @@ class AudioRecorder: :param sample_rate: Audio sample rate (if None, use default device sample rate) :param channels: Number of audio channels """ - - self.duration = duration - self.sample_rate = sample_rate - self.channels = channels - self.buffer = np.zeros((int(duration * sample_rate), channels), dtype=np.float32) - self.recordings_dir = recordings_dir + print(f"Initializing AudioRecorder") + self.duration = 30 + self.sample_rate = 44100 + self.channels = 2 + self.buffer = np.zeros((int(self.duration * self.sample_rate), self.channels), dtype=np.float32) + self.recordings_dir = "recordings" self.stream = sd.InputStream( samplerate=self.sample_rate, channels=self.channels, @@ -92,6 +94,16 @@ class AudioRecorder: # Write buffer to file 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 diff --git a/audio-service/src/main.py b/audio-service/src/main.py index 2074bac..6bbab76 100644 --- a/audio-service/src/main.py +++ b/audio-service/src/main.py @@ -18,7 +18,6 @@ import threading app = Flask(__name__) def main(): - global recorder, audio_manager # Create argument parser parser = argparse.ArgumentParser(description='Audio Recording Service') @@ -35,17 +34,6 @@ def main(): # Ensure save path exists 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 diff --git a/audio-service/src/metadata_manager.py b/audio-service/src/metadata_manager.py index cf95027..613dc32 100644 --- a/audio-service/src/metadata_manager.py +++ b/audio-service/src/metadata_manager.py @@ -7,8 +7,9 @@ class MetaDataManager: def __new__(cls, *args, **kwargs): if cls._instance is None: cls._instance = super().__new__(cls) + cls._instance.init() return cls._instance - def __init__(self): + 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): @@ -16,6 +17,9 @@ class MetaDataManager: self.collections = json.load(f) else: self.collections = {} + if(collections := self.collections.get("Uncategorized")) is None: + self.collections["Uncategorized"] = [] + self.save_metadata() def create_collection(self, name): if name in self.collections: @@ -34,6 +38,31 @@ class MetaDataManager: raise ValueError(f"Collection '{collection_name}' does not exist.") self.collections[collection_name].append(clip_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): return list(self.collections.keys()) @@ -43,6 +72,18 @@ class MetaDataManager: raise ValueError(f"Collection '{collection_name}' does not exist.") 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): with open(self.metadata_file, "w") as f: json.dump(self.collections, f, indent=4) diff --git a/audio-service/src/routes/__pycache__/metadata.cpython-313.pyc b/audio-service/src/routes/__pycache__/metadata.cpython-313.pyc index 3639687..0db3b45 100644 Binary files a/audio-service/src/routes/__pycache__/metadata.cpython-313.pyc and b/audio-service/src/routes/__pycache__/metadata.cpython-313.pyc differ diff --git a/audio-service/src/routes/metadata.py b/audio-service/src/routes/metadata.py index 10268fc..0f02bf0 100644 --- a/audio-service/src/routes/metadata.py +++ b/audio-service/src/routes/metadata.py @@ -20,15 +20,26 @@ def add_collection(): except ValueError as e: return jsonify({'status': 'error', 'message': str(e)}), 400 -@metadata_bp.route('/meta/collection/clips', methods=['GET']) -def get_clips_in_collection(): +@metadata_bp.route('/meta/collection/clips/', methods=['GET']) +def get_clips_in_collection(name): meta_manager = MetaDataManager() - collection_name = request.args.get('name') + collection_name = name try: 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/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']) def add_clip_to_collection(): @@ -41,3 +52,27 @@ def add_clip_to_collection(): return jsonify({'status': 'success', 'clips': clips}) except ValueError as e: 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 diff --git a/audio-service/src/settings.py b/audio-service/src/settings.py index 3d10198..1092d80 100644 --- a/audio-service/src/settings.py +++ b/audio-service/src/settings.py @@ -9,8 +9,9 @@ class SettingsManager: def __new__(cls, *args, **kwargs): if cls._instance is None: cls._instance = super().__new__(cls) + cls._instance.init() return cls._instance - def __init__(self): + def init(self): # read settings file from executing directory self.settings_file = os.path.join(os.getcwd(), "settings.json") if os.path.exists(self.settings_file): diff --git a/audio-service/src/windows_audio.py b/audio-service/src/windows_audio.py index 99e6cd4..1099d04 100644 --- a/audio-service/src/windows_audio.py +++ b/audio-service/src/windows_audio.py @@ -12,8 +12,9 @@ class WindowsAudioManager: def __new__(cls, *args, **kwargs): if cls._instance is None: cls._instance = super().__new__(cls) + cls._instance.init() return cls._instance - def __init__(self): + def init(self): """ Initialize Windows audio device and volume management. """