Created
March 28, 2025 10:17
-
-
Save mitcdh/fb90d26556c65c2ffae02d68e8eed746 to your computer and use it in GitHub Desktop.
Script to delete photos from a specific camera before a specific date (reprocessed raws)
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
import flickrapi | |
import json | |
import time | |
import os | |
from datetime import datetime | |
# Get API credentials from environment variables | |
API_KEY = os.environ.get('FLICKR_API_KEY') | |
API_SECRET = os.environ.get('FLICKR_API_SECRET') | |
# The camera model to search for | |
TARGET_CAMERA_MODEL = 'Olympus TG-4' # Replace with your target camera | |
# Date threshold - photos uploaded before this date will be considered for deletion | |
# Format: 'YYYY-MM-DD' | |
DATE_THRESHOLD = '2024-03-01' | |
# Convert the date threshold to a timestamp | |
date_threshold_timestamp = int(datetime.strptime(DATE_THRESHOLD, '%Y-%m-%d').timestamp()) | |
# List of album IDs to search through | |
ALBUM_IDS = [ | |
'72177720313836905', | |
'72177720313869418' | |
] | |
def authenticate(): | |
"""Authenticate with Flickr API and return the API object""" | |
flickr = flickrapi.FlickrAPI(API_KEY, API_SECRET, format='parsed-json') | |
# Only do this if we need to authenticate | |
if not flickr.token_valid(perms='delete'): | |
# Get a request token | |
flickr.get_request_token(oauth_callback='oob') | |
# Open a browser at the authentication URL | |
authorize_url = flickr.auth_url(perms='delete') | |
print(f"Open this URL in your browser: {authorize_url}") | |
# Get the verification code from the user | |
verifier = input('Verification code: ') | |
# Trade the request token for an access token | |
flickr.get_access_token(verifier) | |
return flickr | |
def get_photos_in_album(flickr, album_id): | |
"""Get all photos in a specific album""" | |
try: | |
photos = [] | |
page = 1 | |
total_pages = 1 | |
while page <= total_pages: | |
response = flickr.photosets.getPhotos( | |
photoset_id=album_id, | |
page=page, | |
per_page=500 # Maximum allowed by Flickr | |
) | |
if page == 1: | |
total_pages = response['photoset']['pages'] | |
photos.extend(response['photoset']['photo']) | |
page += 1 | |
return photos | |
except Exception as e: | |
print(f"Error getting photos from album {album_id}: {e}") | |
return [] | |
def get_photo_info(flickr, photo_id): | |
"""Get detailed information about a specific photo""" | |
try: | |
return flickr.photos.getInfo(photo_id=photo_id) | |
except Exception as e: | |
print(f"Error getting info for photo {photo_id}: {e}") | |
return None | |
def get_photo_exif(flickr, photo_id): | |
"""Get EXIF data for a specific photo""" | |
try: | |
return flickr.photos.getExif(photo_id=photo_id) | |
except Exception as e: | |
# Some photos might not have EXIF data | |
print(f"Error getting EXIF data for photo {photo_id}: {e}") | |
return None | |
def get_camera_model(flickr, photo_id): | |
"""Get the camera model from EXIF data""" | |
exif_data = get_photo_exif(flickr, photo_id) | |
if not exif_data or 'photo' not in exif_data: | |
return None | |
# First check if camera is directly in the response | |
if 'camera' in exif_data['photo'] and exif_data['photo']['camera']: | |
return exif_data['photo']['camera'] | |
# If not, look in the EXIF tags | |
if 'exif' in exif_data['photo']: | |
for exif_tag in exif_data['photo']['exif']: | |
if exif_tag.get('tag') == 'Model': | |
make = None | |
# Try to find the Make tag as well | |
for make_tag in exif_data['photo']['exif']: | |
if make_tag.get('tag') == 'Make': | |
make = make_tag.get('raw', {}).get('_content') | |
break | |
model = exif_tag.get('raw', {}).get('_content') | |
# If we have both make and model, combine them | |
if make and model and not model.startswith(make): | |
return f"{make} {model}" | |
return model | |
return None | |
def delete_photo(flickr, photo_id): | |
"""Delete a specific photo""" | |
try: | |
result = flickr.photos.delete(photo_id=photo_id) | |
print(f"Successfully deleted photo {photo_id}") | |
return True | |
except Exception as e: | |
print(f"Error deleting photo {photo_id}: {e}") | |
return False | |
def format_date(timestamp): | |
"""Convert a Unix timestamp to a human-readable date""" | |
return datetime.fromtimestamp(int(timestamp)).strftime('%Y-%m-%d %H:%M:%S') | |
def main(): | |
# Authenticate with Flickr | |
flickr = authenticate() | |
# Keep track of photos to delete | |
photos_to_delete = [] | |
# Process each album | |
for album_id in ALBUM_IDS: | |
print(f"Processing album {album_id}...") | |
# Get all photos in the album | |
photos = get_photos_in_album(flickr, album_id) | |
print(f"Found {len(photos)} photos in album {album_id}") | |
# Check each photo for the target camera model and upload date | |
for photo in photos: | |
photo_id = photo['id'] | |
photo_info = get_photo_info(flickr, photo_id) | |
if photo_info and 'photo' in photo_info: | |
# Get photo title | |
title = photo_info['photo'].get('title', {}).get('_content', 'Untitled') | |
# Check upload date | |
date_match = False | |
if 'dateuploaded' in photo_info['photo']: | |
upload_timestamp = int(photo_info['photo']['dateuploaded']) | |
upload_date = format_date(upload_timestamp) | |
if upload_timestamp < date_threshold_timestamp: | |
date_match = True | |
# Check camera model | |
camera_model = get_camera_model(flickr, photo_id) | |
camera_match = False | |
if camera_model: | |
# For logging purposes | |
print(f"Photo '{title}' (ID: {photo_id}) - Camera: {camera_model}") | |
# Check if it matches our target camera | |
if camera_model == TARGET_CAMERA_MODEL: | |
camera_match = True | |
# If both conditions match, add to deletion list | |
if camera_match and date_match: | |
print(f"MATCH: Photo '{title}' (ID: {photo_id}) taken with {camera_model}, uploaded on {upload_date}") | |
photos_to_delete.append((photo_id, title, upload_date)) | |
# Be nice to Flickr API - don't make too many requests too quickly | |
time.sleep(0.5) | |
# Confirm deletion | |
if photos_to_delete: | |
print(f"\nFound {len(photos_to_delete)} photos taken with {TARGET_CAMERA_MODEL} uploaded before {DATE_THRESHOLD}") | |
print("\nPhotos to be deleted:") | |
for idx, (photo_id, title, upload_date) in enumerate(photos_to_delete, 1): | |
print(f"{idx}. '{title}' (ID: {photo_id}) - Uploaded: {upload_date}") | |
confirm = input("\nDo you want to delete these photos? (yes/no): ") | |
if confirm.lower() == 'yes': | |
deleted_count = 0 | |
for photo_id, _, _ in photos_to_delete: | |
if delete_photo(flickr, photo_id): | |
deleted_count += 1 | |
# Be nice to Flickr API | |
time.sleep(1) | |
print(f"Deleted {deleted_count} photos") | |
else: | |
print("Deletion cancelled") | |
else: | |
print(f"No photos found taken with {TARGET_CAMERA_MODEL} uploaded before {DATE_THRESHOLD}") | |
if __name__ == "__main__": | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment