diff --git a/pagebot.py b/pagebot.py index 414386b..14ccf04 100644 --- a/pagebot.py +++ b/pagebot.py @@ -34,47 +34,53 @@ def wait_for_file_ready(filepath, stable_seconds=1, timeout=60): last_file_time = time.time() _processing_lock = threading.Lock() _processing_files = set() +_debounce_timers = {} +_debounce_lock = threading.Lock() -class MyHandler(FileSystemEventHandler): - def on_created(self, event): - global last_file_time - if event.is_directory: - return - filepath = event.src_path - filename, file_extension = os.path.splitext(filepath) - if filename.endswith('_stt'): - return - last_file_time = time.time() +DEBOUNCE_SECONDS = 0.5 + +def _process_file(filepath): + global last_file_time + with _debounce_lock: + _debounce_timers.pop(filepath, None) + filename, file_extension = os.path.splitext(filepath) + last_file_time = time.time() + try: if file_extension.lower() == '.amr': with _processing_lock: if filepath in _processing_files: + print(f"[SKIP] Already processing: {filepath}") return _processing_files.add(filepath) try: wait_for_file_ready(filepath) os.remove(filepath) - print("Removing AMR.") + print(f"[AMR] Removed: {filepath}") finally: with _processing_lock: _processing_files.discard(filepath) if file_extension.lower() == '.mp3': with _processing_lock: if filepath in _processing_files: + print(f"[SKIP] Already processing: {filepath}") return _processing_files.add(filepath) try: - print("New MP3!") + print(f"[MP3] Processing: {filepath}") wait_for_file_ready(filepath) if secrets_file.speech_to_text: mp3_copy = f"{filename}_stt.mp3" shutil.copy2(filepath, mp3_copy) - print("Converting to MP4") + print(f"[MP3] Converting to MP4: {filepath}") mp4_file = convert_to_mp4(filepath) - print("Sending to Discord") + if mp4_file is None: + print(f"[MP3] Conversion failed: {filepath}") + return + print(f"[MP3] Uploading to Discord: {mp4_file}") asyncio.run_coroutine_threadsafe(upload_to_discord(mp4_file), client.loop) if secrets_file.speech_to_text: - print("Transcribing in background") - mp4_basename = os.path.basename(mp4_file) if mp4_file else os.path.basename(filepath) + print(f"[STT] Transcribing in background: {filepath}") + mp4_basename = os.path.basename(mp4_file) def do_transcribe(): text = convert_to_text(mp3_copy, filename) try: @@ -89,6 +95,48 @@ class MyHandler(FileSystemEventHandler): finally: with _processing_lock: _processing_files.discard(filepath) + except Exception as e: + print(f"[ERROR] Processing {filepath}: {e}") + +class MyHandler(FileSystemEventHandler): + def on_created(self, event): + if event.is_directory: + return + filepath = event.src_path + filename, file_extension = os.path.splitext(filepath) + ext = file_extension.lower() + print(f"[EVENT] on_created: {filepath}") + if filename.endswith('_stt'): + print(f"[EVENT] Ignoring _stt file: {filepath}") + return + if ext not in ('.amr', '.mp3'): + print(f"[EVENT] Ignoring non-target file: {filepath}") + return + with _debounce_lock: + if filepath in _debounce_timers: + _debounce_timers[filepath].cancel() + print(f"[DEBOUNCE] Reset timer: {filepath}") + timer = threading.Timer(DEBOUNCE_SECONDS, _process_file, args=(filepath,)) + _debounce_timers[filepath] = timer + timer.start() + print(f"[DEBOUNCE] Started timer ({DEBOUNCE_SECONDS}s): {filepath}") + + def on_modified(self, event): + if event.is_directory: + return + filepath = event.src_path + filename, file_extension = os.path.splitext(filepath) + ext = file_extension.lower() + print(f"[EVENT] on_modified: {filepath}") + if filename.endswith('_stt') or ext not in ('.amr', '.mp3'): + return + with _debounce_lock: + if filepath in _debounce_timers: + _debounce_timers[filepath].cancel() + print(f"[DEBOUNCE] Reset timer (modified): {filepath}") + timer = threading.Timer(DEBOUNCE_SECONDS, _process_file, args=(filepath,)) + _debounce_timers[filepath] = timer + timer.start() def convert_to_mp4(mp3_file): try: @@ -229,7 +277,31 @@ def launch_and_watch(program_path): time.sleep(10) if __name__ == "__main__": - observer = Observer() + observer = None + observer_lock = threading.Lock() + event_handler = MyHandler() + + def start_observer(): + global observer + with observer_lock: + if observer is not None: + try: + observer.stop() + observer.join(timeout=5) + except Exception: + pass + observer = Observer() + observer.schedule(event_handler, secrets_file.watch_folder, recursive=False) + observer.start() + print("[OBSERVER] Started watching: " + secrets_file.watch_folder) + + def monitor_observer(): + while True: + time.sleep(30) + with observer_lock: + if observer is None or not observer.is_alive(): + print("[OBSERVER] Died! Restarting...") + start_observer() intents = discord.Intents.default() intents.message_content = True @@ -239,9 +311,8 @@ if __name__ == "__main__": async def on_ready(): print(f'We have logged in as {client.user}') - event_handler = MyHandler() - observer.schedule(event_handler, secrets_file.watch_folder, recursive=False) - observer.start() + start_observer() + threading.Thread(target=monitor_observer, daemon=True).start() if secrets_file.ttd_path != "": watchdog_thread = threading.Thread(target=launch_and_watch, args=(secrets_file.ttd_path,), daemon=True) @@ -250,6 +321,7 @@ if __name__ == "__main__": try: client.run(secrets_file.key) finally: - observer.stop() - observer.join() + if observer: + observer.stop() + observer.join()