Skip to content

Instantly share code, notes, and snippets.

@samwho
Last active August 7, 2025 11:05
Show Gist options
  • Select an option

  • Save samwho/e1987a75fe69e9662d33be3114e85293 to your computer and use it in GitHub Desktop.

Select an option

Save samwho/e1987a75fe69e9662d33be3114e85293 to your computer and use it in GitHub Desktop.
#!/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