├── .gitignore ├── requirements.txt ├── dailyPhoto.plist ├── readme.md └── main.py /.gitignore: -------------------------------------------------------------------------------- 1 | path/ 2 | .DS_Store 3 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | opencv-python>=4.10.0 2 | dlib>=19.24.6 -------------------------------------------------------------------------------- /dailyPhoto.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Label 6 | skyonedot.dailycapture 7 | 8 | ProgramArguments 9 | 10 | /bin/bash 11 | -c 12 | source ${PATH}/.virtualenvs/DailyPhoto/bin/activate && python3 ${HOME}/Applications/DailyPhoto/main.py 13 | 14 | 15 | EnvironmentVariables 16 | 17 | PATH 18 | /usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin:/opt/homebrew/bin 19 | 20 | 21 | StartCalendarInterval 22 | 23 | Hour10Minute0 24 | Hour14Minute0 25 | Hour16Minute0 26 | Hour20Minute0 27 | Hour23Minute0 28 | 29 | 30 | RunAtLoad 31 | 32 | 33 | StandardOutPath 34 | ${PATH}/Pictures/DailyPhoto/dailyPhoto.log 35 | StandardErrorPath 36 | ${PATH}/Pictures/DailyPhoto/dailyPhoto.err 37 | 38 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Daily Photo Capture 2 | 3 | ![License](https://img.shields.io/badge/license-MIT-blue.svg) 4 | ![Python](https://img.shields.io/badge/python-3.13%2B-blue) 5 | ![Platform](https://img.shields.io/badge/platform-macOS-lightgrey) 6 | 7 | Daily Photo Capture is an automated tool that captures both webcam photos and screenshots at specified times throughout the day. It uses face detection to ensure that only photos containing people are saved, making it perfect for tracking your daily work patterns or creating time-lapse records of your workday. 8 | 9 | ## Features 10 | 11 | - 🎯 Automated photo capture at scheduled times 12 | - 👤 Face detection to ensure meaningful captures 13 | - 🗑️ Automatic cleanup of photos without faces 14 | - 📅 Organized storage with date-based naming 15 | - 🔒 Privacy-focused (all data stored locally) 16 | 17 | ## Prerequisites 18 | 19 | - macOS 20 | - Python 3.13 or higher 21 | - ffmpeg (for photo capture), and it's path contains in `dailyPhoto.plist` --> `EnvironmentVariables-->PATH` 22 | - Webcam access permissions for Terminal and ffmpeg 23 | - change ${PATH} in `dailyPhoto.plist` as your reality. 🚩🚩🚩🚩 24 | 25 | 26 | 27 | 28 | ## Installation 29 | 30 | 1. Clone the repository: 31 | ```bash 32 | cd ~/Applications 33 | git clone https://github.com/skyonedot/DailyPhoto.git 34 | cd DailyPhoto 35 | ``` 36 | 37 | 2. Create and activate virtual environment: 38 | ```bash 39 | python3 -m venv ~/.virtualenvs/DailyPhoto 40 | source ~/.virtualenvs/DailyPhoto/bin/activate 41 | ``` 42 | 43 | 3. Install dependencies: 44 | ```bash 45 | pip3 install -r requirements.txt 46 | ``` 47 | 48 | 49 | 4. Install and load the LaunchAgent: 50 | ```bash 51 | cp dailyPhoto.plist ~/Library/LaunchAgents/ 52 | launchctl load ~/Library/LaunchAgents/dailyPhoto.plist 53 | ``` 54 | 55 | 56 | 57 | ## Key Points 58 | 59 | - The program runs at scheduled times (10:00, 14:00, 16:00, 20:00, and 23:00 by default). You can adjust these times in the `StartCalendarInterval` field of the `dailyPhoto.plist` file. 60 | - If a face is detected in any photo during these time slots, the program will skip the remaining captures for that day. 61 | - For detailed implementation logic, please refer to the `main.py` file. 62 | - All successfully captured photos are saved in the `~/Pictures/DailyPhoto/Photos` directory, organized by date. 63 | - All successfully screen shot are saved in the `~/Pictures/DailyPhoto/Photos` directory, organized by date. 64 | - Both output logs and error logs are located in the `~/Pictures/DailyPhoto/Screenshots` directory. 65 | - Currently, this program only runs on Macos. For Windows users, you're welcome to develop and customize your own version. 66 | - For any issues or questions, please open an issue on GitHub. Pull requests are always welcome! -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import cv2 4 | import os 5 | from datetime import datetime 6 | import dlib 7 | from pathlib import Path 8 | import subprocess 9 | import re 10 | 11 | class DailyPhotoCapture: 12 | """ 13 | A class to automatically capture daily photos with face detection. 14 | This tool uses ffmpeg to capture photos and dlib for face detection. 15 | """ 16 | 17 | def __init__(self): 18 | # Get user's home directory 19 | home_dir = str(Path.home()) 20 | 21 | # Set up directory structure 22 | self.base_dir = os.path.join(home_dir, "Pictures", "DailyPhoto") 23 | self.photos_dir = os.path.join(self.base_dir, "Photos") 24 | 25 | # Create directories if they don't exist 26 | os.makedirs(self.photos_dir, exist_ok=True) 27 | 28 | # Set up file paths 29 | self.current_date = datetime.now().strftime("%Y-%m-%d") 30 | self.photo_path = os.path.join(self.photos_dir, f"{self.current_date}.png") 31 | self.log_path = os.path.join(self.base_dir, "daily_capture.log") 32 | 33 | # Add screenshot directory 34 | self.screenshots_dir = os.path.join(self.base_dir, "Screenshots") 35 | os.makedirs(self.screenshots_dir, exist_ok=True) 36 | 37 | 38 | def capture_photo(self): 39 | """ 40 | Capture a photo using ffmpeg through the system's default camera. 41 | Uses high quality settings for better face detection. 42 | """ 43 | command = ( 44 | "ffmpeg -f avfoundation -pixel_format uyvy422 -framerate 30 " 45 | "-video_size 1920x1080 -i '0' -vframes 1 -loglevel error {}" 46 | ).format(self.photo_path) 47 | os.system(command) 48 | 49 | def detect_face(self): 50 | """ 51 | Detect faces in the captured photo using dlib's face detector. 52 | 53 | Returns: 54 | bool: True if at least one face is detected, False otherwise 55 | """ 56 | try: 57 | image = cv2.imread(self.photo_path) 58 | if image is None: 59 | print(f"Error: Unable to load image from {self.photo_path}") 60 | return False 61 | 62 | gray_image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) 63 | face_detector = dlib.get_frontal_face_detector() 64 | faces = face_detector(gray_image, 2) # Upsample 2 times for better detection 65 | 66 | return len(faces) > 0 67 | 68 | except Exception as e: 69 | print(f"Face detection error: {e}") 70 | return False 71 | 72 | def get_display_count(self): 73 | """ 74 | Get the number of connected displays 75 | """ 76 | command = ["system_profiler", "SPDisplaysDataType"] 77 | output = subprocess.check_output(command, text=True) 78 | return len(re.findall(r"Resolution", output)) 79 | 80 | def capture_screenshots(self): 81 | """ 82 | Capture screenshots from all connected displays and save as PDF 83 | Returns: 84 | list: Paths to captured screenshot files 85 | """ 86 | timestamp = datetime.now().strftime("%Y%m%d%H%M%S") 87 | display_count = self.get_display_count() 88 | capture_files = [] 89 | 90 | for i in range(1, display_count + 1): 91 | file_path = os.path.join(self.screenshots_dir, f"screen_{self.current_date}_{i}.pdf") 92 | capture_files.append(file_path) 93 | 94 | capture_command = ["screencapture", "-x", "-t", "pdf"] + capture_files 95 | subprocess.run(capture_command, check=True) 96 | return capture_files 97 | 98 | def run(self): 99 | """ 100 | Main execution flow: 101 | 1. Check if photo already exists for today 102 | 2. Capture new photo and screenshots if needed 103 | 3. Verify face presence 104 | 4. Keep or delete photo based on face detection 105 | """ 106 | if os.path.exists(self.photo_path): 107 | print(f"Today's photo already exists: {self.photo_path}") 108 | return 109 | 110 | print(f"Capturing new photo: {self.photo_path}") 111 | self.capture_photo() 112 | 113 | print("Capturing screenshots...") 114 | try: 115 | screenshot_files = self.capture_screenshots() 116 | print(f"Screenshots saved: {len(screenshot_files)} displays captured") 117 | except Exception as e: 118 | print(f"Error capturing screenshots: {e}") 119 | 120 | if self.detect_face(): 121 | print("Face detected - photo saved successfully") 122 | else: 123 | print("No face detected - deleting photo") 124 | os.remove(self.photo_path) 125 | for screenshot in screenshot_files: 126 | if os.path.exists(screenshot): 127 | os.remove(screenshot) 128 | 129 | 130 | if __name__ == "__main__": 131 | DailyPhotoCapture().run() --------------------------------------------------------------------------------