{"id":849,"date":"2025-11-11T16:11:57","date_gmt":"2025-11-11T20:11:57","guid":{"rendered":"https:\/\/scootercam.net\/?p=849"},"modified":"2025-11-11T16:12:03","modified_gmt":"2025-11-11T20:12:03","slug":"scootercams-sunset-system","status":"publish","type":"post","link":"https:\/\/scootercam.net\/blog\/scootercams-sunset-system\/","title":{"rendered":"Scootercam&#8217;s Sunset System"},"content":{"rendered":"\n<p>There have been several iterations of this system but the idea hasn&#8217;t much changed &#8211; a camera swings towards the setting sun and captures images, then produces a timelapse video of the sunset. In this version an image is captures every five seconds from 20 minutes before until 20 minutes after sunset. The issue came when I added a second camera to the mix and expected a tiny Raspberry Pi to handle taking two images from two cameras at the exact same time, and doing that 600 times over. Things needed to stagger.ScooterCam Sunset System &#8211; Project Summary<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Overview<\/h2>\n\n\n\n<p>A complete automated sunset timelapse capture, compilation, and upload system for dual IP cameras (Amcrest and Reolink) on Lake Michigan&#8217;s eastern shore. Built for Raspberry Pi 5 with SSD storage.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Workflow Timeline<\/h2>\n\n\n\n<pre class=\"wp-block-code\"><code>15:00 - Sunset at 17:30\n  |\n16:00 - Cron job starts sunset_master.py\n  |\n17:10 - Capture window begins (sunset - 20 min)\n  |     \u251c\u2500 capture_amcrest.py starts\n  |     \u2514\u2500 capture_reolink.py starts 2.5 seconds later\n  |\n  |     &#91;Both cameras capture 1 image every 5 seconds]\n  |     &#91;~480 images per camera over 40 minutes]\n  |\n17:50 - Capture window ends (sunset + 20 min)\n  |     \u2514\u2500 ~80 images per camera captured\n  |\n17:50 - Amcrest processing begins\n  |     \u251c\u2500 compile_video.py creates MP4\n  |     \u251c\u2500 Identifies poster image (closest to 17:30)\n  |     \u2514\u2500 upload_ftp.py sends to server\n  |\n17:55 - 5-minute delay (stagger compilation)\n  |\n17:55 - Reolink processing begins\n  |     \u251c\u2500 compile_video.py creates MP4\n  |     \u251c\u2500 Identifies poster image\n  |     \u2514\u2500 upload_ftp.py sends to server\n  |\n18:00 - Process complete<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Component Details<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">1. sunset_calculator.py<\/h3>\n\n\n\n<p><strong>Purpose<\/strong>: Calculate daily sunset times and capture windows<\/p>\n\n\n\n<p><strong>Features<\/strong>:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Uses Astral library for astronomical calculations<\/li>\n\n\n\n<li>America\/Detroit timezone aware<\/li>\n\n\n\n<li>Configurable location coordinates<\/li>\n\n\n\n<li>Provides 40-minute capture window (\u00b120 min from civil sunset)<\/li>\n<\/ul>\n\n\n\n<p><strong>Key Functions<\/strong>:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code>get_sunset_times()<\/code> &#8211; Returns today&#8217;s sunset and capture window<\/li>\n\n\n\n<li><code>should_capture_now()<\/code> &#8211; Checks if current time is in capture window<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">2. capture_amcrest.py &amp; capture_reolink.py<\/h3>\n\n\n\n<p><strong>Purpose<\/strong>: Download images from cameras during sunset window<\/p>\n\n\n\n<p><strong>Features<\/strong>:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Waits automatically until capture start time<\/li>\n\n\n\n<li>Downloads snapshot every 30 seconds<\/li>\n\n\n\n<li>Error handling with retries<\/li>\n\n\n\n<li>Creates organized storage structure<\/li>\n\n\n\n<li>Validates downloaded files<\/li>\n\n\n\n<li>Logs success\/failure of each capture<\/li>\n<\/ul>\n\n\n\n<p><strong>Output<\/strong>:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Images: <code>\/mnt\/ssd\/sunset_captures\/YYYY-MM-DD\/[camera]\/camerane_YYYYMMDD_HHMMSS.jpg<\/code><\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">3. compile_video.py<\/h3>\n\n\n\n<p><strong>Purpose<\/strong>: Convert captured images into MP4 timelapse videos<\/p>\n\n\n\n<p><strong>Features<\/strong>:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Uses ffmpeg for video compilation<\/li>\n\n\n\n<li>30 fps output, H.264 codec<\/li>\n\n\n\n<li>CRF 23 quality (configurable)<\/li>\n\n\n\n<li>Identifies poster image (closest to actual sunset)<\/li>\n\n\n\n<li>Creates both video and poster image<\/li>\n\n\n\n<li>Error handling and validation<\/li>\n<\/ul>\n\n\n\n<p><strong>Output<\/strong>:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Video: <code>sunset_[camera]_YYYY-MM-DD.mp4<\/code><\/li>\n\n\n\n<li>Poster: <code>sunset_[camera]_YYYY-MM-DD_poster.jpg<\/code><\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">4. upload_ftp.py<\/h3>\n\n\n\n<p><strong>Purpose<\/strong>: Upload compiled videos and posters to Glowhost<\/p>\n\n\n\n<p><strong>Features<\/strong>:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Secure FTP connection<\/li>\n\n\n\n<li>Separate directories for videos (\/sw) and images (\/sr)<\/li>\n\n\n\n<li>Connection testing mode (&#8211;test flag)<\/li>\n\n\n\n<li>Retry logic for failed uploads<\/li>\n\n\n\n<li>Upload progress reporting<\/li>\n\n\n\n<li>Validates successful transfer<\/li>\n<\/ul>\n\n\n\n<p><strong>Configuration<\/strong>:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Host: scootercam.net<\/li>\n\n\n\n<li>User: <a href=\"mailto:sunsets@scootercam.net\">sunsets@scootercam.net<\/a><\/li>\n\n\n\n<li>Videos \u2192 \/sw directory<\/li>\n\n\n\n<li>Images \u2192 \/sr directory<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">5. sunset_master.py<\/h3>\n\n\n\n<p><strong>Purpose<\/strong>: Orchestrate the complete workflow<\/p>\n\n\n\n<p><strong>Features<\/strong>:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Coordinates all components<\/li>\n\n\n\n<li>Runs both camera captures simultaneously<\/li>\n\n\n\n<li>Staggers video compilation (5-min delay)<\/li>\n\n\n\n<li>Comprehensive logging<\/li>\n\n\n\n<li>Error tracking and reporting<\/li>\n\n\n\n<li>Final summary of all operations<\/li>\n<\/ul>\n\n\n\n<p><strong>Workflow<\/strong>:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Calculate sunset time<\/li>\n\n\n\n<li>Launch both capture scripts in parallel<\/li>\n\n\n\n<li>Wait for captures to complete<\/li>\n\n\n\n<li>Compile and upload Amcrest<\/li>\n\n\n\n<li>Wait 5 minutes<\/li>\n\n\n\n<li>Compile and upload Reolink<\/li>\n\n\n\n<li>Generate summary report<\/li>\n<\/ol>\n\n\n\n<h3 class=\"wp-block-heading\">6. system_check.py<\/h3>\n\n\n\n<p><strong>Purpose<\/strong>: Verify system configuration before first run<\/p>\n\n\n\n<p><strong>Checks<\/strong>:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>System commands (ffmpeg, wget, python3)<\/li>\n\n\n\n<li>Python packages (astral, pytz)<\/li>\n\n\n\n<li>Required scripts present<\/li>\n\n\n\n<li>Storage directories exist and writable<\/li>\n\n\n\n<li>Camera connectivity<\/li>\n\n\n\n<li>FTP connection<\/li>\n\n\n\n<li>Sunset calculator functionality<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">7. setup.sh<\/h3>\n\n\n\n<p><strong>Purpose<\/strong>: Automated installation script<\/p>\n\n\n\n<p><strong>Actions<\/strong>:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Installs system dependencies<\/li>\n\n\n\n<li>Installs Python packages<\/li>\n\n\n\n<li>Creates project directory<\/li>\n\n\n\n<li>Sets up storage directories<\/li>\n\n\n\n<li>Makes scripts executable<\/li>\n\n\n\n<li>Optionally configures cron job<\/li>\n\n\n\n<li>Runs system check<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">File Organization<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">Local Storage Structure<\/h3>\n\n\n\n<pre class=\"wp-block-code\"><code>\/mnt\/ssd\/\n\u251c\u2500\u2500 sunset_captures\/\n\u2502   \u2514\u2500\u2500 2025-11-11\/\n\u2502       \u251c\u2500\u2500 amcrest\/\n\u2502       \u2502   \u251c\u2500\u2500 amcrest_20251111_171000.jpg\n\u2502       \u2502   \u251c\u2500\u2500 amcrest_20251111_171005.jpg\n\u2502       \u2502   \u251c\u2500\u2500 amcrest_20251111_171010.jpg\n\u2502       \u2502   \u2514\u2500\u2500 ... (~480 images)\n\u2502       \u2514\u2500\u2500 reolink\/\n\u2502           \u251c\u2500\u2500 reolink_20251111_171002.jpg (staggered 2.5s)\n\u2502           \u251c\u2500\u2500 reolink_20251111_171007.jpg\n\u2502           \u2514\u2500\u2500 ... (~480 images)\n\u2514\u2500\u2500 sunset_videos\/\n    \u2514\u2500\u2500 2025-11-11\/\n        \u251c\u2500\u2500 sunset_amcrest_2025-11-11.mp4\n        \u251c\u2500\u2500 sunset_amcrest_2025-11-11_poster.jpg\n        \u251c\u2500\u2500 sunset_reolink_2025-11-11.mp4\n        \u2514\u2500\u2500 sunset_reolink_2025-11-11_poster.jpg<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Remote Storage Structure<\/h3>\n\n\n\n<pre class=\"wp-block-code\"><code>scootercam.net\/\n\u251c\u2500\u2500 sw\/ (videos)\n\u2502   \u251c\u2500\u2500 sunset_amcrest_2025-11-11.mp4\n\u2502   \u2514\u2500\u2500 sunset_reolink_2025-11-11.mp4\n\u2514\u2500\u2500 sr\/ (images)\n    \u251c\u2500\u2500 sunset_amcrest_2025-11-11_poster.jpg\n    \u2514\u2500\u2500 sunset_reolink_2025-11-11_poster.jpg<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Technical Specifications<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">Video Settings<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Codec<\/strong>: H.264 (libx264)<\/li>\n\n\n\n<li><strong>Frame Rate<\/strong>: 30 fps<\/li>\n\n\n\n<li><strong>Quality<\/strong>: CRF 23<\/li>\n\n\n\n<li><strong>Preset<\/strong>: medium<\/li>\n\n\n\n<li><strong>Pixel Format<\/strong>: yuv420p (universal compatibility)<\/li>\n\n\n\n<li><strong>Typical Size<\/strong>: 10-50 MB per video<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">Image Capture<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Interval<\/strong>: 5 seconds<\/li>\n\n\n\n<li><strong>Duration<\/strong>: 40 minutes<\/li>\n\n\n\n<li><strong>Images per camera<\/strong>: ~480 (8 images per minute \u00d7 40 minutes)<\/li>\n\n\n\n<li><strong>Format<\/strong>: JPEG<\/li>\n\n\n\n<li><strong>Source Resolution<\/strong>: Native camera resolution<\/li>\n\n\n\n<li><strong>Stagger<\/strong>: Reolink starts 2.5 seconds after Amcrest to avoid simultaneous network load<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">Network Requirements<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Local Network<\/strong>: Access to 192.168.1.x cameras<\/li>\n\n\n\n<li><strong>Internet<\/strong>: FTP upload to scootercam.net<\/li>\n\n\n\n<li><strong>Bandwidth<\/strong>: ~50-100 MB upload per day<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">Storage Requirements<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Daily Capture<\/strong>: ~800 MB &#8211; 1.2 GB (images + videos)\n<ul class=\"wp-block-list\">\n<li>~480 images per camera at ~500 KB each = ~240 MB per camera<\/li>\n\n\n\n<li>Videos after compression: ~50-100 MB per camera<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li><strong>Monthly<\/strong>: ~25-35 GB<\/li>\n\n\n\n<li><strong>Recommended<\/strong>: 128+ GB SSD<\/li>\n\n\n\n<li><strong>Retention<\/strong>: Configure cleanup as needed<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">Dependencies<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">System Packages<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li>python3 (3.9+)<\/li>\n\n\n\n<li>ffmpeg<\/li>\n\n\n\n<li>wget<\/li>\n\n\n\n<li>ftp (optional, for manual testing)<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">Python Packages<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li>astral==3.2 (astronomical calculations)<\/li>\n\n\n\n<li>pytz==2024.1 (timezone support)<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">Automation<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">Cron Configuration<\/h3>\n\n\n\n<p>cron<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># Daily execution at 4:00 PM\n0 16 * * * cd \/home\/pi\/scootercam-sunset &amp;&amp; \/usr\/bin\/python3 sunset_master.py &gt;&gt; \/var\/log\/scootercam-sunset.log 2&gt;&amp;1<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Log Management<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Location<\/strong>: <code>\/var\/log\/scootercam-sunset.log<\/code><\/li>\n\n\n\n<li><strong>Rotation<\/strong>: Implement logrotate if desired<\/li>\n\n\n\n<li><strong>Monitoring<\/strong>: Use <code>tail -f<\/code> for real-time viewing<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">Maintenance<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">Daily<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li>System runs automatically via cron<\/li>\n\n\n\n<li>Check logs for errors if needed<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">Weekly<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Review log files<\/li>\n\n\n\n<li>Verify uploads to server<\/li>\n\n\n\n<li>Check storage space<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">Monthly<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Clean up old captures<\/li>\n\n\n\n<li>Verify camera connectivity<\/li>\n\n\n\n<li>Review system performance<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">Seasonal<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Update coordinates if needed<\/li>\n\n\n\n<li>Adjust for significant camera repositioning<\/li>\n\n\n\n<li>Test system before DST changes (auto-adjusts)<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">Error Handling<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">Capture Failures<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Individual image failures logged but don&#8217;t stop process<\/li>\n\n\n\n<li>Retries on timeout (3 attempts)<\/li>\n\n\n\n<li>Continues even if some images fail<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">Compilation Failures<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Logged with detailed error messages<\/li>\n\n\n\n<li>Doesn&#8217;t stop other camera&#8217;s processing<\/li>\n\n\n\n<li>Can be re-run manually for specific dates<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">Upload Failures<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Logged with connection details<\/li>\n\n\n\n<li>Can be re-uploaded manually<\/li>\n\n\n\n<li>FTP test mode available for diagnostics<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">Recovery<\/h3>\n\n\n\n<p>All components support manual execution:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Capture: Can be re-run for current day<\/li>\n\n\n\n<li>Compile: Can be run for any past date with images<\/li>\n\n\n\n<li>Upload: Can be run for any past date with videos<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">Performance Considerations<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">Resource Usage<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>CPU<\/strong>: Moderate during compilation<\/li>\n\n\n\n<li><strong>RAM<\/strong>: Low (&lt;500 MB)<\/li>\n\n\n\n<li><strong>Disk I\/O<\/strong>: Moderate during capture<\/li>\n\n\n\n<li><strong>Network<\/strong>: Low (periodic small uploads)<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">Optimization<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li>5-minute stagger prevents resource contention<\/li>\n\n\n\n<li>Parallel captures maximize efficiency<\/li>\n\n\n\n<li>H.264 preset &#8216;medium&#8217; balances speed\/quality<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">Scalability<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Can add more cameras by duplicating capture scripts<\/li>\n\n\n\n<li>Stagger compilation by additional 5-minute intervals<\/li>\n\n\n\n<li>Storage scales linearly with retention period<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">Security Considerations<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">Credentials<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Camera passwords in scripts (local system only)<\/li>\n\n\n\n<li>FTP credentials in scripts (consider encryption for production)<\/li>\n\n\n\n<li>File permissions set appropriately<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">Network<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Cameras on local network only<\/li>\n\n\n\n<li>FTP upload over internet (consider FTPS)<\/li>\n\n\n\n<li>No incoming connections required<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">Recommendations<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Keep Pi system updated<\/li>\n\n\n\n<li>Use strong camera passwords<\/li>\n\n\n\n<li>Consider VPN for remote access<\/li>\n\n\n\n<li>Regular backup of scripts<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">Future Enhancements<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">Potential Features<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Web interface for monitoring<\/li>\n\n\n\n<li>SMS\/email notifications on failures<\/li>\n\n\n\n<li>Cloud storage integration<\/li>\n\n\n\n<li>Machine learning sunset quality prediction<\/li>\n\n\n\n<li>Multi-day compilation<\/li>\n\n\n\n<li>Weather data overlay<\/li>\n\n\n\n<li>Social media auto-posting<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">System Improvements<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Database logging<\/li>\n\n\n\n<li>Grafana dashboard<\/li>\n\n\n\n<li>Automated cleanup policies<\/li>\n\n\n\n<li>Redundant storage<\/li>\n\n\n\n<li>FTPS support<\/li>\n\n\n\n<li>Configuration file (vs. hardcoded)<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">Documentation Files<\/h2>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>README.md<\/strong> &#8211; Complete system documentation<\/li>\n\n\n\n<li><strong>INSTALL.md<\/strong> &#8211; Installation instructions<\/li>\n\n\n\n<li><strong>QUICK_REFERENCE.md<\/strong> &#8211; Command reference<\/li>\n\n\n\n<li><strong>PROJECT_SUMMARY.md<\/strong> &#8211; This file<\/li>\n\n\n\n<li><strong>requirements.txt<\/strong> &#8211; Python dependencies<\/li>\n<\/ol>\n\n\n\n<h2 class=\"wp-block-heading\">Support and Troubleshooting<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">System Check<\/h3>\n\n\n\n<p>bash<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>python3 system_check.py<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Manual Testing<\/h3>\n\n\n\n<p>bash<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code><em># Test camera<\/em>\npython3 capture_amcrest.py\n\n<em># Test compilation<\/em>\npython3 compile_video.py amcrest\n\n<em># Test upload<\/em>\npython3 upload_ftp.py --test<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Logs<\/h3>\n\n\n\n<p>bash<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>tail -f \/var\/log\/scootercam-sunset.log<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Project Stats<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Scripts<\/strong>: 7 Python files<\/li>\n\n\n\n<li><strong>Total Lines<\/strong>: ~1,500<\/li>\n\n\n\n<li><strong>Documentation<\/strong>: 5 markdown files<\/li>\n\n\n\n<li><strong>Setup Time<\/strong>: ~15 minutes<\/li>\n\n\n\n<li><strong>Daily Runtime<\/strong>: ~60 minutes<\/li>\n\n\n\n<li><strong>Storage per Day<\/strong>: ~200 MB<\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p><strong>Built for Scootercam.net<\/strong> &#8211; Capturing Lake Michigan sunsets since 2016 \ud83c\udf05<\/p>\n\n\n\n<p><\/p>\n","protected":false},"excerpt":{"rendered":"<p>There have been several iterations of this system but the idea hasn&#8217;t much changed &#8211; a camera swings towards the setting sun and captures images, then produces a timelapse video of the sunset. In this version an image is captures every five seconds from 20 minutes before until 20 minutes after sunset. The issue came &#8230; <a title=\"Scootercam&#8217;s Sunset System\" class=\"read-more\" href=\"https:\/\/scootercam.net\/blog\/scootercams-sunset-system\/\" aria-label=\"Read more about Scootercam&#8217;s Sunset System\">Read more<\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_monsterinsights_skip_tracking":false,"_monsterinsights_sitenote_active":false,"_monsterinsights_sitenote_note":"","_monsterinsights_sitenote_category":0,"footnotes":""},"categories":[37],"tags":[],"class_list":["post-849","post","type-post","status-publish","format-standard","hentry","category-dev-journal"],"_links":{"self":[{"href":"https:\/\/scootercam.net\/blog\/wp-json\/wp\/v2\/posts\/849","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/scootercam.net\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/scootercam.net\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/scootercam.net\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/scootercam.net\/blog\/wp-json\/wp\/v2\/comments?post=849"}],"version-history":[{"count":1,"href":"https:\/\/scootercam.net\/blog\/wp-json\/wp\/v2\/posts\/849\/revisions"}],"predecessor-version":[{"id":850,"href":"https:\/\/scootercam.net\/blog\/wp-json\/wp\/v2\/posts\/849\/revisions\/850"}],"wp:attachment":[{"href":"https:\/\/scootercam.net\/blog\/wp-json\/wp\/v2\/media?parent=849"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/scootercam.net\/blog\/wp-json\/wp\/v2\/categories?post=849"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/scootercam.net\/blog\/wp-json\/wp\/v2\/tags?post=849"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}