Files
sky-cam/sunrise2mm.py
T
Claude e56663209e Prepend <cam> to all output filenames consistently
Audio:
  AUDIO_DIR/<cam>/YYYY-MM-DD/<cam>-HH-MM-SS.m4a

Movies — cam name now always leads the filename:
  daily_sunrise_video.sh:
    east-2026-04-22-daily-sunrise.mp4
    east-2026-04-22-daily-sunrise-sped.mp4  (intermediate)
    east-2026-04-22-daily-sunrise-test.mp4  (demo)
  4-seasons.sh:
    east-2026-04-21_Mvt2-Day2of31-final.mp4  (was: 2026-04-21_east_Mvt2-...)
  montage-mvt.sh:
    east-2026-03-20_Spring_Mvt1-Sped.mp4
    east-2026-03-20_Spring_Mvt1-Montage.mp4
  year-end-join.sh:
    east-2026.mp4  (already had cam at front)

sunrise2mm.py updated to look for the new east-YYYY-MM-DD-daily-sunrise.mp4
filename. Retention find patterns (wildcards) already match both formats.

https://claude.ai/code/session_01C4jbd3waXG3eKZYbGUjLUQ
2026-04-22 14:45:36 +00:00

167 lines
5.0 KiB
Python
Executable File

#!/usr/bin/env python3
import os
import pathlib
import re
import subprocess
from datetime import datetime
import requests
_here = pathlib.Path(__file__).resolve().parent
def read_config(path):
conf = {}
try:
with open(path) as f:
for line in f:
line = line.strip()
if not line or line.startswith('#') or '=' not in line:
continue
k, v = line.split('=', 1)
k = k.strip()
v = v.strip().strip('"').strip("'")
m = re.match(r'^\$\{[^}]+:-([^}]*)\}$', v)
if m:
v = m.group(1).strip('"').strip("'")
conf[k] = v
except FileNotFoundError:
pass
return conf
config = read_config(_here / 'sky-cam.conf')
config.update(read_config(_here / '.env'))
mattermost_url = config.get('mattermost_url', '').rstrip('/')
access_token = config.get('access_token', '')
channel_id = config.get('channel_id', '')
if not all([mattermost_url, access_token, channel_id]):
print("Error: mattermost_url / access_token / channel_id not set in .env")
raise SystemExit(1)
headers = {'Authorization': f'Bearer {access_token}'}
today_date_str = datetime.now().strftime('%Y-%m-%d')
today_month_str = datetime.now().strftime('%Y-%m')
movies_dir = config.get('MOVIES_DIR', '')
sunrise_cam = config.get('SUNRISE_CAM', 'east')
output_dir = os.path.join(movies_dir, sunrise_cam, today_month_str, 'sunrise-only')
video_file = os.path.join(output_dir, f'{sunrise_cam}-{today_date_str}-daily-sunrise.mp4')
print(f"Looking for video file: {video_file}")
if not os.path.isfile(video_file):
print(f"No video file found at {video_file}.")
subprocess.run(
[str(_here / 'notify.sh'),
f'FAILED: sunrise upload {today_date_str}',
f'Video file not found: {video_file}'],
check=False
)
raise SystemExit(1)
# ── Upload ────────────────────────────────────────────────────────────────────
with open(video_file, 'rb') as f:
resp = requests.post(
f'{mattermost_url}/api/v4/files',
headers=headers,
files={'files': f},
data={'channel_id': channel_id}
)
if resp.status_code != 201:
print(f'Failed to upload: {resp.text}')
subprocess.run(
[str(_here / 'notify.sh'),
f'FAILED: sunrise upload {today_date_str}',
f'Mattermost file upload failed: {resp.status_code}'],
check=False
)
raise SystemExit(1)
print(f'Uploaded {video_file}')
file_id = resp.json()['file_infos'][0]['id']
post_resp = requests.post(
f'{mattermost_url}/api/v4/posts',
headers=headers,
json={
'channel_id': channel_id,
'message': f'Sunrise {today_date_str}',
'file_ids': [file_id],
}
)
if post_resp.status_code == 201:
print('Message posted successfully.')
subprocess.run(
[str(_here / 'notify.sh'),
f'Sunrise uploaded: {today_date_str}',
'Video posted to Mattermost successfully'],
check=False
)
else:
print(f'Failed to post message: {post_resp.text}')
subprocess.run(
[str(_here / 'notify.sh'),
f'FAILED: sunrise Mattermost post {today_date_str}',
f'File uploaded but post failed: {post_resp.status_code}'],
check=False
)
raise SystemExit(1)
# ── Cleanup old unpinned posts ─────────────────────────────────────────────────
retention_days = int(config.get('MM_SUNRISE_RETENTION_DAYS', 8))
if retention_days <= 0:
raise SystemExit(0)
cutoff_ms = (datetime.now().timestamp() - retention_days * 86400) * 1000
deleted = 0
page = 0
while True:
r = requests.get(
f'{mattermost_url}/api/v4/channels/{channel_id}/posts',
headers=headers,
params={'page': page, 'per_page': 200}
)
if r.status_code != 200:
print(f'Cleanup: could not fetch posts ({r.status_code}) — skipping')
break
data = r.json()
posts = data.get('posts', {})
order = data.get('order', [])
if not order:
break
for post_id in order:
post = posts[post_id]
if post.get('is_pinned'):
continue
if not post.get('file_ids'):
continue
# Only delete posts this script created — message starts with "Sunrise YYYY-MM-DD"
if not re.match(r'^Sunrise \d{4}-\d{2}-\d{2}', post.get('message', '')):
continue
if post['create_at'] >= cutoff_ms:
continue
del_r = requests.delete(
f'{mattermost_url}/api/v4/posts/{post_id}',
headers=headers
)
if del_r.status_code == 200:
deleted += 1
if len(order) < 200:
break
page += 1
if deleted:
print(f'Cleanup: removed {deleted} unpinned post(s) older than {retention_days} days')