Last active
July 7, 2023 16:31
-
-
Save C0ntroller/d716837c440f4807c83e6970d461a4a4 to your computer and use it in GitHub Desktop.
Download and EXIF-tag Snpachat memories
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
| # 1. Request Snapchat data archive here: https://accounts.snapchat.com/accounts/downloadmydata | |
| # - DO NOT enable "Include your Memories and other saved Media"! | |
| # 2. Wait for your archive to be ready and download it | |
| # 3. Search for "memories_history.json" in the "json" directory in the archive | |
| # 4. Put it right next to this script | |
| # 5. Run the script | |
| # - Be sure to have all requirements installed. See below for the pip command you need to run. | |
| # 6. Output is stored in "memories" directory next to the script | |
| # Because of how exif works, we can't just save a timezone. | |
| # So we have to use the local timezone of the machine this script is running on. | |
| # This is not the best solution, but it works for me and usually img are at most 1 hour of because of sommer time. | |
| # Images from vacations etc. can be wrong multiple hours... | |
| # pip install requests exif python-dateutil | |
| import requests | |
| from exif import Image, DATETIME_STR_FORMAT | |
| from dateutil.parser import parse as parse_datetime | |
| from dateutil.tz import tzlocal | |
| from json import load as json_load | |
| from typing import Tuple, Union | |
| from os import mkdir, path | |
| from re import search as regex_search | |
| def setup_paths(base: str): | |
| """ | |
| Creates the memories folder if it doesnt exist | |
| """ | |
| memories_path = path.join(base, "memories") | |
| if not path.exists(memories_path): | |
| mkdir(memories_path) | |
| def get_memories(path: str) -> dict: | |
| """ | |
| Loads the JSON file into a dict | |
| """ | |
| with open(path) as f: | |
| return json_load(f) | |
| def grad_dec_to_minute_second(grad_dec: float) -> Tuple[int, int, float]: | |
| """ | |
| Converts a decimal grad value to a tuple of (grad, minute, second) | |
| """ | |
| grad = int(grad_dec) | |
| minute = int((grad_dec - grad) * 60) | |
| second = ((grad_dec - grad) * 60 - minute) * 60 | |
| return (grad, minute, second) | |
| def get_location_triples(location: Union[None, str]) -> Union[None, Tuple[Tuple[int, int, float], Tuple[int, int, float]]]: | |
| """ | |
| Converts a location string to a tuple of (grad, minute, second) for long- and latitude | |
| """ | |
| if location is None: | |
| return None | |
| # format: "Latitude, Longitude: %grad_decimal%, %grad_decimal%" | |
| splitted = location.split(": ")[1].split(", ") | |
| return [tuple(grad_dec_to_minute_second(float(x))) for x in splitted] | |
| def main(): | |
| # Get script path | |
| this_path = path.dirname(path.realpath(__file__)) | |
| # Create the memories folder | |
| setup_paths(this_path) | |
| # Load JSON | |
| memories = get_memories(f"{this_path}/memories_history.json") | |
| for x in memories["Saved Media"]: | |
| # UTC date of the memory | |
| date = parse_datetime(x["Date"]).astimezone(tzlocal()) | |
| # Location of the memory as (Grad, Minute, Second) for long- and latitude | |
| location = get_location_triples(x.get("Location", None)) | |
| # Download URL | |
| url = x["Download Link"] | |
| # Splitting the URL because we are gonna POST the data, not GET it | |
| split_url = url.split("?") | |
| r = requests.post( | |
| split_url[0], | |
| headers={"Content-type": "application/x-www-form-urlencoded"}, | |
| data=split_url[1], | |
| ) | |
| if r.status_code != 200: | |
| print("Snapchat ", r.status_code) | |
| continue | |
| # We get an download URL from AWS back | |
| aws_url = r.text | |
| # filename is set to yyyy-mm-dd hh-mm-ss.ext | |
| # original uuid filename: re.search("[a-f0-9\-]*\.(jpe?g|mp4)", aws_url).group(0) | |
| filename = date.strftime("%Y-%m-%d %H-%M-%S") | |
| filename += "." + regex_search("[a-f0-9\-]*\.(jpe?g|mp4)", aws_url).group(1) | |
| # Finally doanload the file | |
| aws_resp = requests.get(aws_url) | |
| if aws_resp.status_code != 200: | |
| print("AWS ", r.status_code) | |
| continue | |
| # For images we add exif data | |
| if x["Media Type"].lower() == "image": | |
| img = Image(aws_resp.content) | |
| img.datetime_original = date.strftime(DATETIME_STR_FORMAT) | |
| img.datetime_scanned = date.strftime(DATETIME_STR_FORMAT) | |
| img.datetime_digitized = date.strftime(DATETIME_STR_FORMAT) | |
| if location != None: | |
| img.gps_latitude = location[0] | |
| img.gps_latitude_ref = "N" | |
| img.gps_longitude = location[1] | |
| img.gps_longitude_ref = "E" | |
| # Save the image | |
| with open(f"{this_path}/memories/{filename}", "wb") as f: | |
| f.write(img.get_file()) | |
| # For videos we cant just embed exif data, so we just save the file | |
| else: | |
| with open(f"{this_path}/memories/{filename}", "wb") as f: | |
| f.write(aws_resp.content) | |
| if __name__ == "__main__": | |
| main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment