personal memory agent
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Refactor screen describe utilities

Centralize segment/suffix parsing in describe and reuse video extension lists across observe and stats. Update category docs/comments to reflect current paths and legacy formatter support.

+40 -31
+1
observe/categories/README.md
··· 66 66 2. For categories with prompts here, a follow-up request extracts detailed content 67 67 3. Results are stored in JSONL under the category name (e.g., `"meeting": {...}`) 68 68 4. `observe/screen.py` formats JSONL to markdown, using custom formatters when available 69 + 5. The formatter also supports legacy keys (`meeting_analysis`, `extracted_text`) for older data
+24 -16
observe/describe.py
··· 38 38 CATEGORY = "category" # Category-specific follow-up 39 39 40 40 41 + def _segment_and_suffix(media_path: Path) -> tuple[str, str]: 42 + """Return segment key and descriptive suffix for a media path.""" 43 + from observe.utils import extract_descriptive_suffix 44 + from think.utils import segment_key 45 + 46 + segment = segment_key(media_path.stem) 47 + if segment is None: 48 + raise ValueError( 49 + f"Invalid video filename: {media_path.stem} (must be HHMMSS_LEN format)" 50 + ) 51 + try: 52 + suffix = extract_descriptive_suffix(media_path.stem) 53 + except ValueError as exc: 54 + raise ValueError( 55 + f"Invalid video filename: {media_path.stem} (must be HHMMSS_LEN format)" 56 + ) from exc 57 + return segment, suffix 58 + 59 + 41 60 def _discover_category_prompts() -> dict[str, dict]: 42 61 """ 43 62 Discover available category prompts from categories/ directory. ··· 264 283 265 284 def _move_to_segment(self, media_path: Path) -> Path: 266 285 """Move media file to its segment and return new path.""" 267 - from observe.utils import extract_descriptive_suffix 268 - from think.utils import segment_key 269 - 270 - segment = segment_key(media_path.stem) 271 - if segment is None: 272 - raise ValueError(f"Invalid media filename: {media_path.stem}") 273 - suffix = extract_descriptive_suffix(media_path.stem) 286 + segment, suffix = _segment_and_suffix(media_path) 274 287 segment_dir = media_path.parent / segment 275 288 try: 276 289 segment_dir.mkdir(exist_ok=True) ··· 696 709 suffix = None 697 710 if not args.frames_only: 698 711 # Extract segment and suffix for output naming 699 - from observe.utils import extract_descriptive_suffix 700 - from think.utils import segment_key 701 - 702 - segment = segment_key(video_path.stem) 703 - if segment is None: 704 - parser.error( 705 - f"Invalid video filename: {video_path.stem} (must be HHMMSS_LEN format)" 706 - ) 707 - suffix = extract_descriptive_suffix(video_path.stem) 712 + try: 713 + segment, suffix = _segment_and_suffix(video_path) 714 + except ValueError as exc: 715 + parser.error(str(exc)) 708 716 segment_dir = video_path.parent / segment 709 717 segment_dir.mkdir(exist_ok=True) 710 718 # Output JSONL matches input filename pattern (e.g., center_DP-3_screen.jsonl)
+1 -1
observe/screen.py
··· 24 24 25 25 26 26 def _discover_categories() -> list[str]: 27 - """Discover available categories from observe/describe/ directory. 27 + """Discover available categories from observe/categories/ directory. 28 28 29 29 Categories are defined by .json metadata files in the describe/ package. 30 30
+5 -6
observe/sense.py
··· 20 20 from watchdog.events import FileSystemEventHandler 21 21 from watchdog.observers import Observer 22 22 23 + from observe.utils import VIDEO_EXTENSIONS 23 24 from think.callosum import CallosumConnection 24 25 from think.runner import ManagedProcess as RunnerManagedProcess 25 26 from think.utils import day_path, setup_cli ··· 580 581 unprocessed = [] 581 582 unprocessed.extend(sorted(p.name for p in day_dir.glob("*.flac"))) 582 583 unprocessed.extend(sorted(p.name for p in day_dir.glob("*.m4a"))) 583 - unprocessed.extend(sorted(p.name for p in day_dir.glob("*.webm"))) 584 - unprocessed.extend(sorted(p.name for p in day_dir.glob("*.mp4"))) 585 - unprocessed.extend(sorted(p.name for p in day_dir.glob("*.mov"))) 584 + for ext in VIDEO_EXTENSIONS: 585 + unprocessed.extend(sorted(p.name for p in day_dir.glob(f"*{ext}"))) 586 586 587 587 return {"processed": processed, "unprocessed": unprocessed} 588 588 ··· 616 616 sensor.register("*.m4a", "transcribe", ["observe-transcribe", "{file}"]) 617 617 618 618 # Video files: any HHMMSS_*.webm, HHMMSS_*.mp4, HHMMSS_*.mov in day root 619 - sensor.register("*.webm", "describe", ["observe-describe", "{file}"]) 620 - sensor.register("*.mp4", "describe", ["observe-describe", "{file}"]) 621 - sensor.register("*.mov", "describe", ["observe-describe", "{file}"]) 619 + for ext in VIDEO_EXTENSIONS: 620 + sensor.register(f"*{ext}", "describe", ["observe-describe", "{file}"]) 622 621 623 622 if args.day: 624 623 # Batch mode: process specific day
+2
observe/utils.py
··· 7 7 8 8 logger = logging.getLogger(__name__) 9 9 10 + VIDEO_EXTENSIONS = (".webm", ".mp4", ".mov") 11 + 10 12 11 13 def extract_descriptive_suffix(filename: str) -> str: 12 14 """
+7 -8
think/journal_stats.py
··· 7 7 from pathlib import Path 8 8 from typing import Dict 9 9 10 - from observe.utils import load_analysis_frames 10 + from observe.utils import VIDEO_EXTENSIONS, load_analysis_frames 11 11 from think.insight import scan_day as insight_scan_day 12 12 from think.utils import day_dirs, setup_cli 13 13 ··· 43 43 files.extend(day_dir.glob("*/screen.jsonl")) 44 44 files.extend(day_dir.glob("*/*_screen.jsonl")) # Split screen files 45 45 files.extend(day_dir.glob("*/raw.flac")) 46 - files.extend(day_dir.glob("*/screen.webm")) 46 + for ext in VIDEO_EXTENSIONS: 47 + files.extend(day_dir.glob(f"*/screen{ext}")) 47 48 # Check day root for unprocessed files 48 49 files.extend(day_dir.glob("*_raw.flac")) 49 50 files.extend(day_dir.glob("*_raw.m4a")) 50 - files.extend(day_dir.glob("*_screen.webm")) 51 - files.extend(day_dir.glob("*_screen.mp4")) 52 - files.extend(day_dir.glob("*_screen.mov")) 51 + for ext in VIDEO_EXTENSIONS: 52 + files.extend(day_dir.glob(f"*_screen{ext}")) 53 53 54 54 insights = day_dir / "insights" 55 55 if insights.is_dir(): ··· 246 246 # --- Unprocessed files --- 247 247 unprocessed = list(day_dir.glob("*_raw.flac")) 248 248 unprocessed.extend(day_dir.glob("*_raw.m4a")) 249 - unprocessed.extend(day_dir.glob("*_screen.webm")) 250 - unprocessed.extend(day_dir.glob("*_screen.mp4")) 251 - unprocessed.extend(day_dir.glob("*_screen.mov")) 249 + for ext in VIDEO_EXTENSIONS: 250 + unprocessed.extend(day_dir.glob(f"*_screen{ext}")) 252 251 stats["unprocessed_files"] = len(unprocessed) 253 252 254 253 # --- Insight summaries ---