Last active
August 7, 2025 11:05
-
-
Save samwho/e1987a75fe69e9662d33be3114e85293 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/bin/bash | |
| # Configuration | |
| WATCH_DIR="$HOME/Library/Group Containers/group.com.apple.VoiceMemos.shared/Recordings" | |
| ENDPOINT="http://127.0.0.1:8080" # Replace with your actual endpoint | |
| API_KEY="your-api-key" # Optional: Add if your endpoint requires authentication | |
| # State file to track processed files | |
| STATE_FILE="$HOME/.voice_memos_processed" | |
| touch "$STATE_FILE" | |
| # Temp directory for copying files (to avoid Full Disk Access issues with curl) | |
| TEMP_DIR="$HOME/.voice_memos_temp" | |
| mkdir -p "$TEMP_DIR" | |
| # File to track what we're currently processing | |
| PROCESSING_FILE="$HOME/.voice_memos_processing" | |
| # Colors for output | |
| RED='\033[0;31m' | |
| GREEN='\033[0;32m' | |
| YELLOW='\033[1;33m' | |
| BLUE='\033[0;34m' | |
| NC='\033[0m' # No Color | |
| echo -e "${GREEN}Starting Voice Memos watcher...${NC}" | |
| echo -e "${YELLOW}Watching: $WATCH_DIR${NC}" | |
| echo -e "${YELLOW}Endpoint: $ENDPOINT${NC}" | |
| echo -e "${BLUE}Temp directory: $TEMP_DIR${NC}" | |
| echo "" | |
| # Function to check if file was already processed | |
| is_processed() { | |
| grep -Fxq "$1" "$STATE_FILE" | |
| } | |
| # Function to mark file as processed | |
| mark_processed() { | |
| echo "$1" >> "$STATE_FILE" | |
| } | |
| # Function to check if file is being processed | |
| is_being_processed() { | |
| [[ -f "$PROCESSING_FILE" ]] && grep -Fxq "$1" "$PROCESSING_FILE" | |
| } | |
| # Function to mark file as being processed | |
| mark_processing() { | |
| echo "$1" >> "$PROCESSING_FILE" | |
| } | |
| # Function to unmark file as being processed | |
| unmark_processing() { | |
| if [[ -f "$PROCESSING_FILE" ]]; then | |
| grep -vFx "$1" "$PROCESSING_FILE" > "$PROCESSING_FILE.tmp" || true | |
| mv "$PROCESSING_FILE.tmp" "$PROCESSING_FILE" | |
| fi | |
| } | |
| # Function to get file size | |
| get_file_size() { | |
| stat -f%z "$1" 2>/dev/null || echo 0 | |
| } | |
| # Function to upload file | |
| upload_file() { | |
| local filepath="$1" | |
| local filename=$(basename "$filepath") | |
| echo -e "${YELLOW}[$(date '+%Y-%m-%d %H:%M:%S')] Uploading: $filename${NC}" | |
| # Check if file still exists and is readable | |
| if [[ ! -f "$filepath" ]]; then | |
| echo -e "${RED} β File no longer exists${NC}" | |
| return 1 | |
| fi | |
| # Get file size for info | |
| local filesize=$(get_file_size "$filepath") | |
| local filesize_mb=$(echo "scale=2; $filesize / 1048576" | bc) | |
| echo -e " π File size: ${filesize_mb}MB" | |
| # Copy file to temp directory to avoid Full Disk Access issues with curl | |
| local temp_file="$TEMP_DIR/$filename" | |
| echo -e " π Copying to temp directory..." | |
| if ! cp "$filepath" "$temp_file" 2>/dev/null; then | |
| echo -e "${RED} β Failed to copy file (check Full Disk Access permissions)${NC}" | |
| return 1 | |
| fi | |
| echo -e " β Uploading to endpoint: $ENDPOINT" | |
| # Upload from temp directory (no Full Disk Access needed) | |
| response=$(curl -s -w "\n%{http_code}" \ | |
| --connect-timeout 10 \ | |
| --max-time 30 \ | |
| -X POST \ | |
| -H "Authorization: Bearer $API_KEY" \ | |
| -F "file=@$temp_file" \ | |
| -F "filename=$filename" \ | |
| -F "timestamp=$(date -u +%Y-%m-%dT%H:%M:%SZ)" \ | |
| -F "filesize=$filesize" \ | |
| "$ENDPOINT" 2>&1) | |
| curl_exit_code=$? | |
| # Clean up temp file | |
| rm -f "$temp_file" | |
| # Check if curl command itself failed | |
| if [[ $curl_exit_code -ne 0 ]]; then | |
| echo -e "${RED} β Upload failed (curl exit code: $curl_exit_code)${NC}" | |
| case $curl_exit_code in | |
| 6) echo -e "${RED} Could not resolve host${NC}" ;; | |
| 7) echo -e "${RED} Failed to connect to host${NC}" ;; | |
| 26) echo -e "${RED} Could not read file${NC}" ;; | |
| 28) echo -e "${RED} Connection timeout${NC}" ;; | |
| *) echo -e "${RED} Error uploading file${NC}" ;; | |
| esac | |
| return 1 | |
| fi | |
| # Extract HTTP status code (last line) | |
| http_code=$(echo "$response" | tail -n1) | |
| # Extract response body (all but last line) | |
| body=$(echo "$response" | sed '$d') | |
| if [[ "$http_code" -ge 200 && "$http_code" -lt 300 ]]; then | |
| echo -e "${GREEN} β Upload successful (HTTP $http_code)${NC}" | |
| mark_processed "$filepath" | |
| # Optional: Print response body if needed | |
| if [[ -n "$body" ]]; then | |
| echo -e " Response: $body" | |
| fi | |
| else | |
| echo -e "${RED} β Upload failed (HTTP $http_code)${NC}" | |
| if [[ -n "$body" ]]; then | |
| echo -e "${RED} Error: $body${NC}" | |
| fi | |
| return 1 | |
| fi | |
| echo "" | |
| return 0 | |
| } | |
| # Function to wait for file to stabilize | |
| wait_for_file_stable() { | |
| local filepath="$1" | |
| local filename=$(basename "$filepath") | |
| echo -e "${BLUE}[$(date '+%Y-%m-%d %H:%M:%S')] Detected: $filename${NC}" | |
| local stable_count=0 | |
| local last_size=$(get_file_size "$filepath") | |
| local wait_count=0 | |
| while [[ $stable_count -lt 3 ]]; do | |
| sleep 1 | |
| ((wait_count++)) | |
| # Check if file still exists | |
| if [[ ! -f "$filepath" ]]; then | |
| echo -e "${RED} β File disappeared${NC}" | |
| return 1 | |
| fi | |
| current_size=$(get_file_size "$filepath") | |
| if [[ $current_size -eq $last_size ]]; then | |
| ((stable_count++)) | |
| if [[ $stable_count -eq 1 ]]; then | |
| echo -e " β³ File appears stable, verifying..." | |
| fi | |
| else | |
| if [[ $stable_count -gt 0 ]]; then | |
| echo -e " β³ File size changed, continuing to monitor..." | |
| else | |
| size_mb=$(echo "scale=2; $current_size / 1048576" | bc) | |
| printf "\r β³ Recording in progress... %ds (%.2fMB)" $wait_count "$size_mb" | |
| fi | |
| stable_count=0 | |
| fi | |
| last_size=$current_size | |
| # Timeout after 5 minutes | |
| if [[ $wait_count -gt 300 ]]; then | |
| echo -e "\n${YELLOW} β Timeout waiting for recording${NC}" | |
| return 1 | |
| fi | |
| done | |
| printf "\r\033[K" # Clear the progress line | |
| echo -e "${GREEN} β Recording completed (waited ${wait_count}s)${NC}" | |
| return 0 | |
| } | |
| # Clean up old temp files and processing markers on startup | |
| rm -f "$TEMP_DIR"/*.m4a 2>/dev/null | |
| rm -f "$PROCESSING_FILE" 2>/dev/null | |
| # Process any existing files that haven't been processed yet | |
| echo -e "${GREEN}Checking for existing unprocessed files...${NC}" | |
| find "$WATCH_DIR" -name "*.m4a" -type f -print0 2>/dev/null | while IFS= read -r -d '' file; do | |
| if ! is_processed "$file"; then | |
| upload_file "$file" | |
| fi | |
| done | |
| echo -e "${GREEN}Now watching for new files...${NC}" | |
| echo -e "${BLUE}βΉοΈ Files will be uploaded after 3 seconds of no size changes${NC}" | |
| echo "" | |
| # Watch for file changes using fswatch | |
| # Using --batch-marker to handle multiple events better | |
| fswatch -0 \ | |
| --event Created \ | |
| --event Updated \ | |
| --event Renamed \ | |
| --event MovedTo \ | |
| -e ".*" \ | |
| -i "\\.m4a$" \ | |
| "$WATCH_DIR" | while IFS= read -r -d '' event; do | |
| # Skip if already processed | |
| if is_processed "$event"; then | |
| continue | |
| fi | |
| # Skip if currently being processed (handles duplicate events) | |
| if is_being_processed "$event"; then | |
| continue | |
| fi | |
| # Skip if file doesn't exist | |
| if [[ ! -f "$event" ]]; then | |
| continue | |
| fi | |
| # Mark as being processed | |
| mark_processing "$event" | |
| # Wait for file to stabilize and upload | |
| if wait_for_file_stable "$event"; then | |
| if ! is_processed "$event"; then | |
| upload_file "$event" | |
| fi | |
| fi | |
| # Unmark as being processed | |
| unmark_processing "$event" | |
| done |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment