snapchat-memories-downloader/notebooks/make_df.ipynb
2025-12-10 16:25:46 -06:00

1604 lines
68 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

{
"cells": [
{
"cell_type": "code",
"execution_count": 1,
"id": "ffcab11d",
"metadata": {},
"outputs": [],
"source": [
"import pandas as pd\n",
"import re\n",
"import pytz\n",
"import os\n",
"from timezonefinder import TimezoneFinder\n",
"from datetime import datetime\n",
"import subprocess"
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "a4cda272",
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"<div>\n",
"<style scoped>\n",
" .dataframe tbody tr th:only-of-type {\n",
" vertical-align: middle;\n",
" }\n",
"\n",
" .dataframe tbody tr th {\n",
" vertical-align: top;\n",
" }\n",
"\n",
" .dataframe thead th {\n",
" text-align: right;\n",
" }\n",
"</style>\n",
"<table border=\"1\" class=\"dataframe\">\n",
" <thead>\n",
" <tr style=\"text-align: right;\">\n",
" <th></th>\n",
" <th>Date</th>\n",
" <th>Media Type</th>\n",
" <th>Location</th>\n",
" <th>Download Link</th>\n",
" <th>Media Download Url</th>\n",
" </tr>\n",
" </thead>\n",
" <tbody>\n",
" <tr>\n",
" <th>0</th>\n",
" <td>2025-11-29 04:50:47+00:00</td>\n",
" <td>Video</td>\n",
" <td>Latitude, Longitude: 44.110836, -94.217804</td>\n",
" <td>https://app.snapchat.com/dmd/memories?uid=098c...</td>\n",
" <td>https://us-east1-aws.api.snapchat.com/dmd/mm?u...</td>\n",
" </tr>\n",
" <tr>\n",
" <th>1</th>\n",
" <td>2025-11-29 04:49:56+00:00</td>\n",
" <td>Video</td>\n",
" <td>Latitude, Longitude: 44.110767, -94.217705</td>\n",
" <td>https://app.snapchat.com/dmd/memories?uid=098c...</td>\n",
" <td>https://us-east1-aws.api.snapchat.com/dmd/mm?u...</td>\n",
" </tr>\n",
" <tr>\n",
" <th>2</th>\n",
" <td>2025-11-26 14:23:27+00:00</td>\n",
" <td>Video</td>\n",
" <td>Latitude, Longitude: 44.354908, -93.96698</td>\n",
" <td>https://app.snapchat.com/dmd/memories?uid=098c...</td>\n",
" <td>https://us-east1-aws.api.snapchat.com/dmd/mm?u...</td>\n",
" </tr>\n",
" <tr>\n",
" <th>3</th>\n",
" <td>2025-11-26 14:18:45+00:00</td>\n",
" <td>Video</td>\n",
" <td>Latitude, Longitude: 44.355003, -93.96696</td>\n",
" <td>https://app.snapchat.com/dmd/memories?uid=098c...</td>\n",
" <td>https://us-east1-aws.api.snapchat.com/dmd/mm?u...</td>\n",
" </tr>\n",
" <tr>\n",
" <th>4</th>\n",
" <td>2025-11-16 17:58:59+00:00</td>\n",
" <td>Image</td>\n",
" <td>Latitude, Longitude: 46.184643, -94.363495</td>\n",
" <td>https://app.snapchat.com/dmd/memories?uid=098c...</td>\n",
" <td>https://us-east1-aws.api.snapchat.com/dmd/mm?u...</td>\n",
" </tr>\n",
" </tbody>\n",
"</table>\n",
"</div>"
],
"text/plain": [
" Date Media Type \\\n",
"0 2025-11-29 04:50:47+00:00 Video \n",
"1 2025-11-29 04:49:56+00:00 Video \n",
"2 2025-11-26 14:23:27+00:00 Video \n",
"3 2025-11-26 14:18:45+00:00 Video \n",
"4 2025-11-16 17:58:59+00:00 Image \n",
"\n",
" Location \\\n",
"0 Latitude, Longitude: 44.110836, -94.217804 \n",
"1 Latitude, Longitude: 44.110767, -94.217705 \n",
"2 Latitude, Longitude: 44.354908, -93.96698 \n",
"3 Latitude, Longitude: 44.355003, -93.96696 \n",
"4 Latitude, Longitude: 46.184643, -94.363495 \n",
"\n",
" Download Link \\\n",
"0 https://app.snapchat.com/dmd/memories?uid=098c... \n",
"1 https://app.snapchat.com/dmd/memories?uid=098c... \n",
"2 https://app.snapchat.com/dmd/memories?uid=098c... \n",
"3 https://app.snapchat.com/dmd/memories?uid=098c... \n",
"4 https://app.snapchat.com/dmd/memories?uid=098c... \n",
"\n",
" Media Download Url \n",
"0 https://us-east1-aws.api.snapchat.com/dmd/mm?u... \n",
"1 https://us-east1-aws.api.snapchat.com/dmd/mm?u... \n",
"2 https://us-east1-aws.api.snapchat.com/dmd/mm?u... \n",
"3 https://us-east1-aws.api.snapchat.com/dmd/mm?u... \n",
"4 https://us-east1-aws.api.snapchat.com/dmd/mm?u... "
]
},
"execution_count": 2,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"df: pd.DataFrame = pd.read_json(\"../temp/file_info.json\", orient=\"records\")\n",
"df.head(5)"
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "71007845",
"metadata": {},
"outputs": [],
"source": [
"def get_join_col(row: pd.Series) -> pd.Series:\n",
" row[\"file_dt\"] = f\"{row['Date'].strftime('%Y-%m-%d_%H-%M-%S')}\"\n",
" return row\n",
"\n",
"\n",
"def extract_lat_long(row: pd.Series) -> pd.Series:\n",
" coordinates = row[\"Location\"].split(\": \")[1]\n",
" lat_str, long_str = coordinates.split(\", \")\n",
" row[\"location_latitude\"] = round(float(lat_str), 5)\n",
" row[\"location_longitude\"] = round(float(long_str), 5)\n",
" return row"
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "68ed283f",
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"<div>\n",
"<style scoped>\n",
" .dataframe tbody tr th:only-of-type {\n",
" vertical-align: middle;\n",
" }\n",
"\n",
" .dataframe tbody tr th {\n",
" vertical-align: top;\n",
" }\n",
"\n",
" .dataframe thead th {\n",
" text-align: right;\n",
" }\n",
"</style>\n",
"<table border=\"1\" class=\"dataframe\">\n",
" <thead>\n",
" <tr style=\"text-align: right;\">\n",
" <th></th>\n",
" <th>Date</th>\n",
" <th>Media Type</th>\n",
" <th>Location</th>\n",
" <th>Download Link</th>\n",
" <th>Media Download Url</th>\n",
" <th>file_dt</th>\n",
" <th>location_latitude</th>\n",
" <th>location_longitude</th>\n",
" </tr>\n",
" </thead>\n",
" <tbody>\n",
" <tr>\n",
" <th>0</th>\n",
" <td>2025-11-29 04:50:47+00:00</td>\n",
" <td>Video</td>\n",
" <td>Latitude, Longitude: 44.110836, -94.217804</td>\n",
" <td>https://app.snapchat.com/dmd/memories?uid=098c...</td>\n",
" <td>https://us-east1-aws.api.snapchat.com/dmd/mm?u...</td>\n",
" <td>2025-11-29_04-50-47</td>\n",
" <td>44.11084</td>\n",
" <td>-94.21780</td>\n",
" </tr>\n",
" <tr>\n",
" <th>1</th>\n",
" <td>2025-11-29 04:49:56+00:00</td>\n",
" <td>Video</td>\n",
" <td>Latitude, Longitude: 44.110767, -94.217705</td>\n",
" <td>https://app.snapchat.com/dmd/memories?uid=098c...</td>\n",
" <td>https://us-east1-aws.api.snapchat.com/dmd/mm?u...</td>\n",
" <td>2025-11-29_04-49-56</td>\n",
" <td>44.11077</td>\n",
" <td>-94.21770</td>\n",
" </tr>\n",
" <tr>\n",
" <th>2</th>\n",
" <td>2025-11-26 14:23:27+00:00</td>\n",
" <td>Video</td>\n",
" <td>Latitude, Longitude: 44.354908, -93.96698</td>\n",
" <td>https://app.snapchat.com/dmd/memories?uid=098c...</td>\n",
" <td>https://us-east1-aws.api.snapchat.com/dmd/mm?u...</td>\n",
" <td>2025-11-26_14-23-27</td>\n",
" <td>44.35491</td>\n",
" <td>-93.96698</td>\n",
" </tr>\n",
" <tr>\n",
" <th>3</th>\n",
" <td>2025-11-26 14:18:45+00:00</td>\n",
" <td>Video</td>\n",
" <td>Latitude, Longitude: 44.355003, -93.96696</td>\n",
" <td>https://app.snapchat.com/dmd/memories?uid=098c...</td>\n",
" <td>https://us-east1-aws.api.snapchat.com/dmd/mm?u...</td>\n",
" <td>2025-11-26_14-18-45</td>\n",
" <td>44.35500</td>\n",
" <td>-93.96696</td>\n",
" </tr>\n",
" <tr>\n",
" <th>4</th>\n",
" <td>2025-11-16 17:58:59+00:00</td>\n",
" <td>Image</td>\n",
" <td>Latitude, Longitude: 46.184643, -94.363495</td>\n",
" <td>https://app.snapchat.com/dmd/memories?uid=098c...</td>\n",
" <td>https://us-east1-aws.api.snapchat.com/dmd/mm?u...</td>\n",
" <td>2025-11-16_17-58-59</td>\n",
" <td>46.18464</td>\n",
" <td>-94.36350</td>\n",
" </tr>\n",
" </tbody>\n",
"</table>\n",
"</div>"
],
"text/plain": [
" Date Media Type \\\n",
"0 2025-11-29 04:50:47+00:00 Video \n",
"1 2025-11-29 04:49:56+00:00 Video \n",
"2 2025-11-26 14:23:27+00:00 Video \n",
"3 2025-11-26 14:18:45+00:00 Video \n",
"4 2025-11-16 17:58:59+00:00 Image \n",
"\n",
" Location \\\n",
"0 Latitude, Longitude: 44.110836, -94.217804 \n",
"1 Latitude, Longitude: 44.110767, -94.217705 \n",
"2 Latitude, Longitude: 44.354908, -93.96698 \n",
"3 Latitude, Longitude: 44.355003, -93.96696 \n",
"4 Latitude, Longitude: 46.184643, -94.363495 \n",
"\n",
" Download Link \\\n",
"0 https://app.snapchat.com/dmd/memories?uid=098c... \n",
"1 https://app.snapchat.com/dmd/memories?uid=098c... \n",
"2 https://app.snapchat.com/dmd/memories?uid=098c... \n",
"3 https://app.snapchat.com/dmd/memories?uid=098c... \n",
"4 https://app.snapchat.com/dmd/memories?uid=098c... \n",
"\n",
" Media Download Url file_dt \\\n",
"0 https://us-east1-aws.api.snapchat.com/dmd/mm?u... 2025-11-29_04-50-47 \n",
"1 https://us-east1-aws.api.snapchat.com/dmd/mm?u... 2025-11-29_04-49-56 \n",
"2 https://us-east1-aws.api.snapchat.com/dmd/mm?u... 2025-11-26_14-23-27 \n",
"3 https://us-east1-aws.api.snapchat.com/dmd/mm?u... 2025-11-26_14-18-45 \n",
"4 https://us-east1-aws.api.snapchat.com/dmd/mm?u... 2025-11-16_17-58-59 \n",
"\n",
" location_latitude location_longitude \n",
"0 44.11084 -94.21780 \n",
"1 44.11077 -94.21770 \n",
"2 44.35491 -93.96698 \n",
"3 44.35500 -93.96696 \n",
"4 46.18464 -94.36350 "
]
},
"execution_count": 4,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"df = df.apply(get_join_col, axis=1)\n",
"df = df.apply(extract_lat_long, axis=1)\n",
"df.head(5)"
]
},
{
"cell_type": "code",
"execution_count": 5,
"id": "713d3202",
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"<div>\n",
"<style scoped>\n",
" .dataframe tbody tr th:only-of-type {\n",
" vertical-align: middle;\n",
" }\n",
"\n",
" .dataframe tbody tr th {\n",
" vertical-align: top;\n",
" }\n",
"\n",
" .dataframe thead th {\n",
" text-align: right;\n",
" }\n",
"</style>\n",
"<table border=\"1\" class=\"dataframe\">\n",
" <thead>\n",
" <tr style=\"text-align: right;\">\n",
" <th></th>\n",
" <th>file</th>\n",
" <th>createdate</th>\n",
" <th>gpslatitude</th>\n",
" <th>gpslongitude</th>\n",
" </tr>\n",
" </thead>\n",
" <tbody>\n",
" <tr>\n",
" <th>0</th>\n",
" <td>../downloads/2025-03-15_01-17-10.mp4</td>\n",
" <td>2025:03:15 01:17:29</td>\n",
" <td>-</td>\n",
" <td>-</td>\n",
" </tr>\n",
" <tr>\n",
" <th>1</th>\n",
" <td>../downloads/2025-02-28_18-25-46.mp4</td>\n",
" <td>2025:02:28 18:26:16</td>\n",
" <td>-</td>\n",
" <td>-</td>\n",
" </tr>\n",
" <tr>\n",
" <th>2</th>\n",
" <td>../downloads/2025-03-08_06-20-39.mp4</td>\n",
" <td>2025:03:08 06:21:10</td>\n",
" <td>-</td>\n",
" <td>-</td>\n",
" </tr>\n",
" <tr>\n",
" <th>3</th>\n",
" <td>../downloads/2025-06-28_02-54-42.jpg</td>\n",
" <td>-</td>\n",
" <td>-</td>\n",
" <td>-</td>\n",
" </tr>\n",
" <tr>\n",
" <th>4</th>\n",
" <td>../downloads/2025-04-13_22-54-57.jpg</td>\n",
" <td>-</td>\n",
" <td>-</td>\n",
" <td>-</td>\n",
" </tr>\n",
" </tbody>\n",
"</table>\n",
"</div>"
],
"text/plain": [
" file createdate gpslatitude \\\n",
"0 ../downloads/2025-03-15_01-17-10.mp4 2025:03:15 01:17:29 - \n",
"1 ../downloads/2025-02-28_18-25-46.mp4 2025:02:28 18:26:16 - \n",
"2 ../downloads/2025-03-08_06-20-39.mp4 2025:03:08 06:21:10 - \n",
"3 ../downloads/2025-06-28_02-54-42.jpg - - \n",
"4 ../downloads/2025-04-13_22-54-57.jpg - - \n",
"\n",
" gpslongitude \n",
"0 - \n",
"1 - \n",
"2 - \n",
"3 - \n",
"4 - "
]
},
"execution_count": 5,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"metadata = pd.read_csv(\"../temp/filemetadata.csv\", sep=\"#\")\n",
"metadata[\"file\"] = f\"../\" + metadata[\"file\"]\n",
"metadata.head(5)"
]
},
{
"cell_type": "code",
"execution_count": 6,
"id": "97ee9100",
"metadata": {},
"outputs": [],
"source": [
"def convert_lat_long_to_decimal_degrees(row: pd.Series) -> pd.Series:\n",
" def dms_to_decimal(dms_string):\n",
" match = re.search(\n",
" r\"(\\d+)\\s*deg\\s*(\\d+)'\\s*([\\d.]+)\\\"\\s*([NSEW])\", dms_string, re.IGNORECASE\n",
" )\n",
" if not match:\n",
" raise ValueError(f\"Coordinate format not recognized: {dms_string}\")\n",
"\n",
" degrees = float(match.group(1))\n",
" minutes = float(match.group(2))\n",
" seconds = float(match.group(3))\n",
" direction = match.group(4).upper()\n",
" decimal_degrees = degrees + (minutes / 60) + (seconds / 3600)\n",
" if direction in [\"S\", \"W\"]:\n",
" decimal_degrees *= -1\n",
" decimal_degrees = round(decimal_degrees, 5)\n",
" return decimal_degrees\n",
"\n",
" def convert(value: str) -> float:\n",
" if pd.isna(value):\n",
" return 0.0\n",
" if value == \"-\":\n",
" return 0.0\n",
" try:\n",
" return dms_to_decimal(value)\n",
" except ValueError:\n",
" return 0.0\n",
"\n",
" row[\"Latitude\"] = convert(row[\"gpslatitude\"])\n",
" row[\"Longitude\"] = convert(row[\"gpslongitude\"])\n",
" return row"
]
},
{
"cell_type": "code",
"execution_count": 7,
"id": "8684b468",
"metadata": {},
"outputs": [],
"source": [
"def get_file_name(row: pd.Series) -> pd.Series:\n",
" path_parts = row[\"file\"].split(\"/\")\n",
" file = path_parts[-1]\n",
" row[\"filename\"] = file\n",
" row[\"extension\"] = file.split(\".\")[-1].lower()\n",
" row[\"file_dt\"] = file.split(\".\")[0]\n",
" return row"
]
},
{
"cell_type": "code",
"execution_count": 8,
"id": "6a1e641e",
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"<div>\n",
"<style scoped>\n",
" .dataframe tbody tr th:only-of-type {\n",
" vertical-align: middle;\n",
" }\n",
"\n",
" .dataframe tbody tr th {\n",
" vertical-align: top;\n",
" }\n",
"\n",
" .dataframe thead th {\n",
" text-align: right;\n",
" }\n",
"</style>\n",
"<table border=\"1\" class=\"dataframe\">\n",
" <thead>\n",
" <tr style=\"text-align: right;\">\n",
" <th></th>\n",
" <th>file</th>\n",
" <th>createdate</th>\n",
" <th>gpslatitude</th>\n",
" <th>gpslongitude</th>\n",
" <th>Latitude</th>\n",
" <th>Longitude</th>\n",
" <th>filename</th>\n",
" <th>extension</th>\n",
" <th>file_dt</th>\n",
" </tr>\n",
" </thead>\n",
" <tbody>\n",
" <tr>\n",
" <th>0</th>\n",
" <td>../downloads/2025-03-15_01-17-10.mp4</td>\n",
" <td>2025:03:15 01:17:29</td>\n",
" <td>-</td>\n",
" <td>-</td>\n",
" <td>0.0</td>\n",
" <td>0.0</td>\n",
" <td>2025-03-15_01-17-10.mp4</td>\n",
" <td>mp4</td>\n",
" <td>2025-03-15_01-17-10</td>\n",
" </tr>\n",
" <tr>\n",
" <th>1</th>\n",
" <td>../downloads/2025-02-28_18-25-46.mp4</td>\n",
" <td>2025:02:28 18:26:16</td>\n",
" <td>-</td>\n",
" <td>-</td>\n",
" <td>0.0</td>\n",
" <td>0.0</td>\n",
" <td>2025-02-28_18-25-46.mp4</td>\n",
" <td>mp4</td>\n",
" <td>2025-02-28_18-25-46</td>\n",
" </tr>\n",
" <tr>\n",
" <th>2</th>\n",
" <td>../downloads/2025-03-08_06-20-39.mp4</td>\n",
" <td>2025:03:08 06:21:10</td>\n",
" <td>-</td>\n",
" <td>-</td>\n",
" <td>0.0</td>\n",
" <td>0.0</td>\n",
" <td>2025-03-08_06-20-39.mp4</td>\n",
" <td>mp4</td>\n",
" <td>2025-03-08_06-20-39</td>\n",
" </tr>\n",
" <tr>\n",
" <th>3</th>\n",
" <td>../downloads/2025-06-28_02-54-42.jpg</td>\n",
" <td>-</td>\n",
" <td>-</td>\n",
" <td>-</td>\n",
" <td>0.0</td>\n",
" <td>0.0</td>\n",
" <td>2025-06-28_02-54-42.jpg</td>\n",
" <td>jpg</td>\n",
" <td>2025-06-28_02-54-42</td>\n",
" </tr>\n",
" <tr>\n",
" <th>4</th>\n",
" <td>../downloads/2025-04-13_22-54-57.jpg</td>\n",
" <td>-</td>\n",
" <td>-</td>\n",
" <td>-</td>\n",
" <td>0.0</td>\n",
" <td>0.0</td>\n",
" <td>2025-04-13_22-54-57.jpg</td>\n",
" <td>jpg</td>\n",
" <td>2025-04-13_22-54-57</td>\n",
" </tr>\n",
" </tbody>\n",
"</table>\n",
"</div>"
],
"text/plain": [
" file createdate gpslatitude \\\n",
"0 ../downloads/2025-03-15_01-17-10.mp4 2025:03:15 01:17:29 - \n",
"1 ../downloads/2025-02-28_18-25-46.mp4 2025:02:28 18:26:16 - \n",
"2 ../downloads/2025-03-08_06-20-39.mp4 2025:03:08 06:21:10 - \n",
"3 ../downloads/2025-06-28_02-54-42.jpg - - \n",
"4 ../downloads/2025-04-13_22-54-57.jpg - - \n",
"\n",
" gpslongitude Latitude Longitude filename extension \\\n",
"0 - 0.0 0.0 2025-03-15_01-17-10.mp4 mp4 \n",
"1 - 0.0 0.0 2025-02-28_18-25-46.mp4 mp4 \n",
"2 - 0.0 0.0 2025-03-08_06-20-39.mp4 mp4 \n",
"3 - 0.0 0.0 2025-06-28_02-54-42.jpg jpg \n",
"4 - 0.0 0.0 2025-04-13_22-54-57.jpg jpg \n",
"\n",
" file_dt \n",
"0 2025-03-15_01-17-10 \n",
"1 2025-02-28_18-25-46 \n",
"2 2025-03-08_06-20-39 \n",
"3 2025-06-28_02-54-42 \n",
"4 2025-04-13_22-54-57 "
]
},
"execution_count": 8,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"meta = metadata.apply(convert_lat_long_to_decimal_degrees, axis=1)\n",
"meta = meta.apply(get_file_name, axis=1)\n",
"meta.head(5)"
]
},
{
"cell_type": "code",
"execution_count": 9,
"id": "543eecf8",
"metadata": {},
"outputs": [],
"source": [
"def get_datetime_for_df(row: pd.Series) -> pd.Series:\n",
" from datetime import datetime\n",
"\n",
" try:\n",
" row[\"createdate_mod\"] = None\n",
" if row[\"createdate\"] != \"-\":\n",
" createdate = datetime.strptime(row[\"createdate\"], \"%Y:%m:%d %H:%M:%S\")\n",
" createdate_wtz = createdate.astimezone(pytz.timezone(\"America/Chicago\"))\n",
" row[\"createdate_mod\"] = createdate_wtz\n",
"\n",
" date = datetime.strptime(row[\"file_dt\"], \"%Y-%m-%d_%H-%M-%S\")\n",
" dt_w_tz = date.astimezone(pytz.UTC)\n",
" row[\"datetime\"] = dt_w_tz\n",
" row[\"dt_join\"] = f\"{dt_w_tz}\"\n",
" except ValueError:\n",
" row[\"createdate_mod\"] = None\n",
" row[\"datetime\"] = None\n",
" row[\"dt_join\"] = \"\"\n",
" return row\n",
"\n",
"\n",
"def get_media_type(row: pd.Series) -> pd.Series:\n",
" row[\"file_type\"] = \"Unknown\"\n",
" if row[\"extension\"] == \"jpg\":\n",
" row[\"file_type\"] = \"Image\"\n",
" if row[\"extension\"] == \"mp4\":\n",
" row[\"file_type\"] = \"Video\"\n",
" return row"
]
},
{
"cell_type": "code",
"execution_count": 10,
"id": "b57f9726",
"metadata": {},
"outputs": [],
"source": [
"data = meta.apply(get_datetime_for_df, axis=1)\n",
"data = data.apply(get_media_type, axis=1)\n",
"joined = pd.merge(\n",
" df,\n",
" data,\n",
" how=\"left\",\n",
" left_on=\"file_dt\",\n",
" right_on=\"file_dt\",\n",
" suffixes=(\"_df\", \"_meta\"),\n",
")"
]
},
{
"cell_type": "code",
"execution_count": 11,
"id": "3f350445",
"metadata": {},
"outputs": [],
"source": [
"col_mapper = {\n",
" \"Date\": \"correct_date_utc\",\n",
" \"Media Type\": \"correct_media_type\",\n",
" \"location_latitude\": \"correct_latitude\",\n",
" \"location_longitude\": \"correct_longitude\",\n",
" \"file\": \"path\",\n",
" \"filename\": \"filename\",\n",
" \"createdate\": \"actual_date_cst\",\n",
" \"Latitude\": \"actual_latitude\",\n",
" \"Longitude\": \"actual_longitude\",\n",
" \"file_type\": \"actual_media_type\",\n",
"}\n",
"df0 = joined[col_mapper.keys()].rename(columns=col_mapper)"
]
},
{
"cell_type": "code",
"execution_count": 12,
"id": "192561b3",
"metadata": {},
"outputs": [],
"source": [
"def check_for_errors(row: pd.Series) -> pd.Series:\n",
" errors = 0\n",
" if row[\"correct_media_type\"] != row[\"actual_media_type\"]:\n",
" errors += 1\n",
" if row[\"actual_latitude\"] == 0.00 and row[\"correct_media_type\"] == \"Image\":\n",
" if row[\"correct_latitude\"] != 0.00:\n",
" errors += 1\n",
" if abs(row[\"actual_longitude\"] - row[\"correct_longitude\"]) > 2.00:\n",
" errors += 1\n",
" if row[\"actual_date_cst\"] == \"-\":\n",
" errors += 1\n",
" if pd.isna(row[\"path\"]):\n",
" errors += 1\n",
" row[\"errors\"] = errors\n",
" return row\n",
"\n",
"\n",
"df1 = df0.apply(check_for_errors, axis=1)"
]
},
{
"cell_type": "code",
"execution_count": 13,
"id": "445c1b9b",
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"<div>\n",
"<style scoped>\n",
" .dataframe tbody tr th:only-of-type {\n",
" vertical-align: middle;\n",
" }\n",
"\n",
" .dataframe tbody tr th {\n",
" vertical-align: top;\n",
" }\n",
"\n",
" .dataframe thead th {\n",
" text-align: right;\n",
" }\n",
"</style>\n",
"<table border=\"1\" class=\"dataframe\">\n",
" <thead>\n",
" <tr style=\"text-align: right;\">\n",
" <th></th>\n",
" <th>correct_date_utc</th>\n",
" <th>correct_media_type</th>\n",
" <th>correct_latitude</th>\n",
" <th>correct_longitude</th>\n",
" <th>path</th>\n",
" <th>filename</th>\n",
" <th>actual_date_cst</th>\n",
" <th>actual_latitude</th>\n",
" <th>actual_longitude</th>\n",
" <th>actual_media_type</th>\n",
" <th>errors</th>\n",
" </tr>\n",
" </thead>\n",
" <tbody>\n",
" <tr>\n",
" <th>0</th>\n",
" <td>2025-11-29 04:50:47+00:00</td>\n",
" <td>Video</td>\n",
" <td>44.11084</td>\n",
" <td>-94.21780</td>\n",
" <td>../downloads/2025-11-29_04-50-47.mp4</td>\n",
" <td>2025-11-29_04-50-47.mp4</td>\n",
" <td>2025:11:29 04:50:55</td>\n",
" <td>0.0</td>\n",
" <td>0.0</td>\n",
" <td>Video</td>\n",
" <td>1</td>\n",
" </tr>\n",
" <tr>\n",
" <th>1</th>\n",
" <td>2025-11-29 04:49:56+00:00</td>\n",
" <td>Video</td>\n",
" <td>44.11077</td>\n",
" <td>-94.21770</td>\n",
" <td>../downloads/2025-11-29_04-49-56.mp4</td>\n",
" <td>2025-11-29_04-49-56.mp4</td>\n",
" <td>2025:11:29 04:50:31</td>\n",
" <td>0.0</td>\n",
" <td>0.0</td>\n",
" <td>Video</td>\n",
" <td>1</td>\n",
" </tr>\n",
" <tr>\n",
" <th>2</th>\n",
" <td>2025-11-26 14:23:27+00:00</td>\n",
" <td>Video</td>\n",
" <td>44.35491</td>\n",
" <td>-93.96698</td>\n",
" <td>../downloads/2025-11-26_14-23-27.mp4</td>\n",
" <td>2025-11-26_14-23-27.mp4</td>\n",
" <td>2025:11:26 14:23:44</td>\n",
" <td>0.0</td>\n",
" <td>0.0</td>\n",
" <td>Video</td>\n",
" <td>1</td>\n",
" </tr>\n",
" <tr>\n",
" <th>3</th>\n",
" <td>2025-11-26 14:18:45+00:00</td>\n",
" <td>Video</td>\n",
" <td>44.35500</td>\n",
" <td>-93.96696</td>\n",
" <td>../downloads/2025-11-26_14-18-45.mp4</td>\n",
" <td>2025-11-26_14-18-45.mp4</td>\n",
" <td>2025:11:26 14:19:00</td>\n",
" <td>0.0</td>\n",
" <td>0.0</td>\n",
" <td>Video</td>\n",
" <td>1</td>\n",
" </tr>\n",
" <tr>\n",
" <th>4</th>\n",
" <td>2025-11-16 17:58:59+00:00</td>\n",
" <td>Image</td>\n",
" <td>46.18464</td>\n",
" <td>-94.36350</td>\n",
" <td>../downloads/2025-11-16_17-58-59.jpg</td>\n",
" <td>2025-11-16_17-58-59.jpg</td>\n",
" <td>-</td>\n",
" <td>0.0</td>\n",
" <td>0.0</td>\n",
" <td>Image</td>\n",
" <td>3</td>\n",
" </tr>\n",
" </tbody>\n",
"</table>\n",
"</div>"
],
"text/plain": [
" correct_date_utc correct_media_type correct_latitude \\\n",
"0 2025-11-29 04:50:47+00:00 Video 44.11084 \n",
"1 2025-11-29 04:49:56+00:00 Video 44.11077 \n",
"2 2025-11-26 14:23:27+00:00 Video 44.35491 \n",
"3 2025-11-26 14:18:45+00:00 Video 44.35500 \n",
"4 2025-11-16 17:58:59+00:00 Image 46.18464 \n",
"\n",
" correct_longitude path \\\n",
"0 -94.21780 ../downloads/2025-11-29_04-50-47.mp4 \n",
"1 -94.21770 ../downloads/2025-11-29_04-49-56.mp4 \n",
"2 -93.96698 ../downloads/2025-11-26_14-23-27.mp4 \n",
"3 -93.96696 ../downloads/2025-11-26_14-18-45.mp4 \n",
"4 -94.36350 ../downloads/2025-11-16_17-58-59.jpg \n",
"\n",
" filename actual_date_cst actual_latitude \\\n",
"0 2025-11-29_04-50-47.mp4 2025:11:29 04:50:55 0.0 \n",
"1 2025-11-29_04-49-56.mp4 2025:11:29 04:50:31 0.0 \n",
"2 2025-11-26_14-23-27.mp4 2025:11:26 14:23:44 0.0 \n",
"3 2025-11-26_14-18-45.mp4 2025:11:26 14:19:00 0.0 \n",
"4 2025-11-16_17-58-59.jpg - 0.0 \n",
"\n",
" actual_longitude actual_media_type errors \n",
"0 0.0 Video 1 \n",
"1 0.0 Video 1 \n",
"2 0.0 Video 1 \n",
"3 0.0 Video 1 \n",
"4 0.0 Image 3 "
]
},
"execution_count": 13,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"errors = df1[df1[\"errors\"] > 0]\n",
"errors.head(5)"
]
},
{
"cell_type": "code",
"execution_count": 14,
"id": "de9ed1f5",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Errors: 3973\n"
]
}
],
"source": [
"print(f\"Errors: {errors.shape[0]}\")"
]
},
{
"cell_type": "code",
"execution_count": 15,
"id": "0b325144",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Image errors: 1433\n"
]
}
],
"source": [
"img_errors = errors[errors[\"correct_media_type\"] == \"Image\"]\n",
"print(f\"Image errors: {img_errors.shape[0]}\")"
]
},
{
"cell_type": "code",
"execution_count": 16,
"id": "3559f965",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Video errors: 2540\n"
]
}
],
"source": [
"video_errors = errors[errors[\"correct_media_type\"] == \"Video\"]\n",
"print(f\"Video errors: {video_errors.shape[0]}\")"
]
},
{
"cell_type": "code",
"execution_count": 17,
"id": "49e183fd",
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"<div>\n",
"<style scoped>\n",
" .dataframe tbody tr th:only-of-type {\n",
" vertical-align: middle;\n",
" }\n",
"\n",
" .dataframe tbody tr th {\n",
" vertical-align: top;\n",
" }\n",
"\n",
" .dataframe thead th {\n",
" text-align: right;\n",
" }\n",
"</style>\n",
"<table border=\"1\" class=\"dataframe\">\n",
" <thead>\n",
" <tr style=\"text-align: right;\">\n",
" <th></th>\n",
" <th>correct_date_utc</th>\n",
" <th>correct_media_type</th>\n",
" <th>correct_latitude</th>\n",
" <th>correct_longitude</th>\n",
" <th>path</th>\n",
" <th>filename</th>\n",
" <th>actual_date_cst</th>\n",
" <th>actual_latitude</th>\n",
" <th>actual_longitude</th>\n",
" <th>actual_media_type</th>\n",
" <th>errors</th>\n",
" </tr>\n",
" </thead>\n",
" <tbody>\n",
" <tr>\n",
" <th>0</th>\n",
" <td>2025-11-29 04:50:47+00:00</td>\n",
" <td>Video</td>\n",
" <td>44.11084</td>\n",
" <td>-94.21780</td>\n",
" <td>../downloads/2025-11-29_04-50-47.mp4</td>\n",
" <td>2025-11-29_04-50-47.mp4</td>\n",
" <td>2025:11:29 04:50:55</td>\n",
" <td>0.0</td>\n",
" <td>0.0</td>\n",
" <td>Video</td>\n",
" <td>1</td>\n",
" </tr>\n",
" <tr>\n",
" <th>1</th>\n",
" <td>2025-11-29 04:49:56+00:00</td>\n",
" <td>Video</td>\n",
" <td>44.11077</td>\n",
" <td>-94.21770</td>\n",
" <td>../downloads/2025-11-29_04-49-56.mp4</td>\n",
" <td>2025-11-29_04-49-56.mp4</td>\n",
" <td>2025:11:29 04:50:31</td>\n",
" <td>0.0</td>\n",
" <td>0.0</td>\n",
" <td>Video</td>\n",
" <td>1</td>\n",
" </tr>\n",
" <tr>\n",
" <th>2</th>\n",
" <td>2025-11-26 14:23:27+00:00</td>\n",
" <td>Video</td>\n",
" <td>44.35491</td>\n",
" <td>-93.96698</td>\n",
" <td>../downloads/2025-11-26_14-23-27.mp4</td>\n",
" <td>2025-11-26_14-23-27.mp4</td>\n",
" <td>2025:11:26 14:23:44</td>\n",
" <td>0.0</td>\n",
" <td>0.0</td>\n",
" <td>Video</td>\n",
" <td>1</td>\n",
" </tr>\n",
" <tr>\n",
" <th>3</th>\n",
" <td>2025-11-26 14:18:45+00:00</td>\n",
" <td>Video</td>\n",
" <td>44.35500</td>\n",
" <td>-93.96696</td>\n",
" <td>../downloads/2025-11-26_14-18-45.mp4</td>\n",
" <td>2025-11-26_14-18-45.mp4</td>\n",
" <td>2025:11:26 14:19:00</td>\n",
" <td>0.0</td>\n",
" <td>0.0</td>\n",
" <td>Video</td>\n",
" <td>1</td>\n",
" </tr>\n",
" <tr>\n",
" <th>5</th>\n",
" <td>2025-11-12 02:10:34+00:00</td>\n",
" <td>Video</td>\n",
" <td>44.35490</td>\n",
" <td>-93.96707</td>\n",
" <td>../downloads/2025-11-12_02-10-34.mp4</td>\n",
" <td>2025-11-12_02-10-34.mp4</td>\n",
" <td>2025:11:12 03:47:55</td>\n",
" <td>0.0</td>\n",
" <td>0.0</td>\n",
" <td>Video</td>\n",
" <td>1</td>\n",
" </tr>\n",
" </tbody>\n",
"</table>\n",
"</div>"
],
"text/plain": [
" correct_date_utc correct_media_type correct_latitude \\\n",
"0 2025-11-29 04:50:47+00:00 Video 44.11084 \n",
"1 2025-11-29 04:49:56+00:00 Video 44.11077 \n",
"2 2025-11-26 14:23:27+00:00 Video 44.35491 \n",
"3 2025-11-26 14:18:45+00:00 Video 44.35500 \n",
"5 2025-11-12 02:10:34+00:00 Video 44.35490 \n",
"\n",
" correct_longitude path \\\n",
"0 -94.21780 ../downloads/2025-11-29_04-50-47.mp4 \n",
"1 -94.21770 ../downloads/2025-11-29_04-49-56.mp4 \n",
"2 -93.96698 ../downloads/2025-11-26_14-23-27.mp4 \n",
"3 -93.96696 ../downloads/2025-11-26_14-18-45.mp4 \n",
"5 -93.96707 ../downloads/2025-11-12_02-10-34.mp4 \n",
"\n",
" filename actual_date_cst actual_latitude \\\n",
"0 2025-11-29_04-50-47.mp4 2025:11:29 04:50:55 0.0 \n",
"1 2025-11-29_04-49-56.mp4 2025:11:29 04:50:31 0.0 \n",
"2 2025-11-26_14-23-27.mp4 2025:11:26 14:23:44 0.0 \n",
"3 2025-11-26_14-18-45.mp4 2025:11:26 14:19:00 0.0 \n",
"5 2025-11-12_02-10-34.mp4 2025:11:12 03:47:55 0.0 \n",
"\n",
" actual_longitude actual_media_type errors \n",
"0 0.0 Video 1 \n",
"1 0.0 Video 1 \n",
"2 0.0 Video 1 \n",
"3 0.0 Video 1 \n",
"5 0.0 Video 1 "
]
},
"execution_count": 17,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"video_errors.head(5)"
]
},
{
"cell_type": "code",
"execution_count": 21,
"id": "f7891aa1",
"metadata": {},
"outputs": [],
"source": [
"def fix_filetype(row: pd.Series) -> pd.Series:\n",
" try:\n",
" if pd.isna(row[\"path\"]) or row[\"path\"] == \"-\":\n",
" return row\n",
" \n",
" mp4_file_path = f\"../downloads/{row['filename'][:-4]}.mp4\"\n",
" jpg_file_path = f\"../downloads/{row['filename'][:-4]}.jpg\"\n",
"\n",
" if row[\"correct_media_type\"] == row[\"actual_media_type\"]:\n",
" return row\n",
"\n",
" if row[\"path\"] == \"-\":\n",
" return row\n",
"\n",
" if row[\"correct_media_type\"] == \"Image\":\n",
" os.rename(src=mp4_file_path, dst=jpg_file_path)\n",
" if os.path.exists(jpg_file_path):\n",
" row[\"filename\"] = f\"{row['filename'][:-4]}.jpg\"\n",
" row[\"actual_media_type\"] = \"Image\"\n",
" elif row[\"correct_media_type\"] == \"Video\":\n",
" os.rename(src=jpg_file_path, dst=mp4_file_path)\n",
" if os.path.exists(mp4_file_path):\n",
" row[\"filename\"] = f\"{row['filename'][:-4]}.mp4\"\n",
" row[\"actual_media_type\"] = \"Video\"\n",
" except FileNotFoundError:\n",
" print(f\"File not found: {row['filename']}\")\n",
" return row"
]
},
{
"cell_type": "code",
"execution_count": 19,
"id": "5450fd2c",
"metadata": {},
"outputs": [],
"source": [
"# wrong_filetype = df1[df1[\"correct_media_type\"] != df1[\"actual_media_type\"]]\n",
"# wrong_filetype = wrong_filetype.fillna(\"-\")\n",
"# wrong_filetype.head(5)"
]
},
{
"cell_type": "code",
"execution_count": 22,
"id": "df5eedbd",
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"<div>\n",
"<style scoped>\n",
" .dataframe tbody tr th:only-of-type {\n",
" vertical-align: middle;\n",
" }\n",
"\n",
" .dataframe tbody tr th {\n",
" vertical-align: top;\n",
" }\n",
"\n",
" .dataframe thead th {\n",
" text-align: right;\n",
" }\n",
"</style>\n",
"<table border=\"1\" class=\"dataframe\">\n",
" <thead>\n",
" <tr style=\"text-align: right;\">\n",
" <th></th>\n",
" <th>correct_date_utc</th>\n",
" <th>correct_media_type</th>\n",
" <th>correct_latitude</th>\n",
" <th>correct_longitude</th>\n",
" <th>path</th>\n",
" <th>filename</th>\n",
" <th>actual_date_cst</th>\n",
" <th>actual_latitude</th>\n",
" <th>actual_longitude</th>\n",
" <th>actual_media_type</th>\n",
" <th>errors</th>\n",
" </tr>\n",
" </thead>\n",
" <tbody>\n",
" <tr>\n",
" <th>0</th>\n",
" <td>2025-11-29 04:50:47+00:00</td>\n",
" <td>Video</td>\n",
" <td>44.11084</td>\n",
" <td>-94.21780</td>\n",
" <td>../downloads/2025-11-29_04-50-47.mp4</td>\n",
" <td>2025-11-29_04-50-47.mp4</td>\n",
" <td>2025:11:29 04:50:55</td>\n",
" <td>0.0</td>\n",
" <td>0.0</td>\n",
" <td>Video</td>\n",
" <td>1</td>\n",
" </tr>\n",
" <tr>\n",
" <th>1</th>\n",
" <td>2025-11-29 04:49:56+00:00</td>\n",
" <td>Video</td>\n",
" <td>44.11077</td>\n",
" <td>-94.21770</td>\n",
" <td>../downloads/2025-11-29_04-49-56.mp4</td>\n",
" <td>2025-11-29_04-49-56.mp4</td>\n",
" <td>2025:11:29 04:50:31</td>\n",
" <td>0.0</td>\n",
" <td>0.0</td>\n",
" <td>Video</td>\n",
" <td>1</td>\n",
" </tr>\n",
" <tr>\n",
" <th>2</th>\n",
" <td>2025-11-26 14:23:27+00:00</td>\n",
" <td>Video</td>\n",
" <td>44.35491</td>\n",
" <td>-93.96698</td>\n",
" <td>../downloads/2025-11-26_14-23-27.mp4</td>\n",
" <td>2025-11-26_14-23-27.mp4</td>\n",
" <td>2025:11:26 14:23:44</td>\n",
" <td>0.0</td>\n",
" <td>0.0</td>\n",
" <td>Video</td>\n",
" <td>1</td>\n",
" </tr>\n",
" <tr>\n",
" <th>3</th>\n",
" <td>2025-11-26 14:18:45+00:00</td>\n",
" <td>Video</td>\n",
" <td>44.35500</td>\n",
" <td>-93.96696</td>\n",
" <td>../downloads/2025-11-26_14-18-45.mp4</td>\n",
" <td>2025-11-26_14-18-45.mp4</td>\n",
" <td>2025:11:26 14:19:00</td>\n",
" <td>0.0</td>\n",
" <td>0.0</td>\n",
" <td>Video</td>\n",
" <td>1</td>\n",
" </tr>\n",
" <tr>\n",
" <th>4</th>\n",
" <td>2025-11-16 17:58:59+00:00</td>\n",
" <td>Image</td>\n",
" <td>46.18464</td>\n",
" <td>-94.36350</td>\n",
" <td>../downloads/2025-11-16_17-58-59.jpg</td>\n",
" <td>2025-11-16_17-58-59.jpg</td>\n",
" <td>-</td>\n",
" <td>0.0</td>\n",
" <td>0.0</td>\n",
" <td>Image</td>\n",
" <td>3</td>\n",
" </tr>\n",
" <tr>\n",
" <th>...</th>\n",
" <td>...</td>\n",
" <td>...</td>\n",
" <td>...</td>\n",
" <td>...</td>\n",
" <td>...</td>\n",
" <td>...</td>\n",
" <td>...</td>\n",
" <td>...</td>\n",
" <td>...</td>\n",
" <td>...</td>\n",
" <td>...</td>\n",
" </tr>\n",
" <tr>\n",
" <th>3968</th>\n",
" <td>2017-05-27 01:00:16+00:00</td>\n",
" <td>Image</td>\n",
" <td>40.43235</td>\n",
" <td>-104.76955</td>\n",
" <td>NaN</td>\n",
" <td>NaN</td>\n",
" <td>NaN</td>\n",
" <td>NaN</td>\n",
" <td>NaN</td>\n",
" <td>NaN</td>\n",
" <td>2</td>\n",
" </tr>\n",
" <tr>\n",
" <th>3969</th>\n",
" <td>2017-05-27 00:59:40+00:00</td>\n",
" <td>Image</td>\n",
" <td>40.43227</td>\n",
" <td>-104.76962</td>\n",
" <td>NaN</td>\n",
" <td>NaN</td>\n",
" <td>NaN</td>\n",
" <td>NaN</td>\n",
" <td>NaN</td>\n",
" <td>NaN</td>\n",
" <td>2</td>\n",
" </tr>\n",
" <tr>\n",
" <th>3970</th>\n",
" <td>2017-05-27 00:58:00+00:00</td>\n",
" <td>Video</td>\n",
" <td>40.43234</td>\n",
" <td>-104.76959</td>\n",
" <td>NaN</td>\n",
" <td>NaN</td>\n",
" <td>NaN</td>\n",
" <td>NaN</td>\n",
" <td>NaN</td>\n",
" <td>NaN</td>\n",
" <td>2</td>\n",
" </tr>\n",
" <tr>\n",
" <th>3971</th>\n",
" <td>2017-05-27 00:57:35+00:00</td>\n",
" <td>Video</td>\n",
" <td>40.43234</td>\n",
" <td>-104.76960</td>\n",
" <td>NaN</td>\n",
" <td>NaN</td>\n",
" <td>NaN</td>\n",
" <td>NaN</td>\n",
" <td>NaN</td>\n",
" <td>NaN</td>\n",
" <td>2</td>\n",
" </tr>\n",
" <tr>\n",
" <th>3972</th>\n",
" <td>2017-05-25 14:50:13+00:00</td>\n",
" <td>Image</td>\n",
" <td>40.42855</td>\n",
" <td>-104.77030</td>\n",
" <td>NaN</td>\n",
" <td>NaN</td>\n",
" <td>NaN</td>\n",
" <td>NaN</td>\n",
" <td>NaN</td>\n",
" <td>NaN</td>\n",
" <td>2</td>\n",
" </tr>\n",
" </tbody>\n",
"</table>\n",
"<p>3973 rows × 11 columns</p>\n",
"</div>"
],
"text/plain": [
" correct_date_utc correct_media_type correct_latitude \\\n",
"0 2025-11-29 04:50:47+00:00 Video 44.11084 \n",
"1 2025-11-29 04:49:56+00:00 Video 44.11077 \n",
"2 2025-11-26 14:23:27+00:00 Video 44.35491 \n",
"3 2025-11-26 14:18:45+00:00 Video 44.35500 \n",
"4 2025-11-16 17:58:59+00:00 Image 46.18464 \n",
"... ... ... ... \n",
"3968 2017-05-27 01:00:16+00:00 Image 40.43235 \n",
"3969 2017-05-27 00:59:40+00:00 Image 40.43227 \n",
"3970 2017-05-27 00:58:00+00:00 Video 40.43234 \n",
"3971 2017-05-27 00:57:35+00:00 Video 40.43234 \n",
"3972 2017-05-25 14:50:13+00:00 Image 40.42855 \n",
"\n",
" correct_longitude path \\\n",
"0 -94.21780 ../downloads/2025-11-29_04-50-47.mp4 \n",
"1 -94.21770 ../downloads/2025-11-29_04-49-56.mp4 \n",
"2 -93.96698 ../downloads/2025-11-26_14-23-27.mp4 \n",
"3 -93.96696 ../downloads/2025-11-26_14-18-45.mp4 \n",
"4 -94.36350 ../downloads/2025-11-16_17-58-59.jpg \n",
"... ... ... \n",
"3968 -104.76955 NaN \n",
"3969 -104.76962 NaN \n",
"3970 -104.76959 NaN \n",
"3971 -104.76960 NaN \n",
"3972 -104.77030 NaN \n",
"\n",
" filename actual_date_cst actual_latitude \\\n",
"0 2025-11-29_04-50-47.mp4 2025:11:29 04:50:55 0.0 \n",
"1 2025-11-29_04-49-56.mp4 2025:11:29 04:50:31 0.0 \n",
"2 2025-11-26_14-23-27.mp4 2025:11:26 14:23:44 0.0 \n",
"3 2025-11-26_14-18-45.mp4 2025:11:26 14:19:00 0.0 \n",
"4 2025-11-16_17-58-59.jpg - 0.0 \n",
"... ... ... ... \n",
"3968 NaN NaN NaN \n",
"3969 NaN NaN NaN \n",
"3970 NaN NaN NaN \n",
"3971 NaN NaN NaN \n",
"3972 NaN NaN NaN \n",
"\n",
" actual_longitude actual_media_type errors \n",
"0 0.0 Video 1 \n",
"1 0.0 Video 1 \n",
"2 0.0 Video 1 \n",
"3 0.0 Video 1 \n",
"4 0.0 Image 3 \n",
"... ... ... ... \n",
"3968 NaN NaN 2 \n",
"3969 NaN NaN 2 \n",
"3970 NaN NaN 2 \n",
"3971 NaN NaN 2 \n",
"3972 NaN NaN 2 \n",
"\n",
"[3973 rows x 11 columns]"
]
},
"execution_count": 22,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# wrong_filetype.apply(fix_filetype, axis=1)\n",
"df1.apply(fix_filetype, axis=1)"
]
},
{
"cell_type": "code",
"execution_count": 23,
"id": "07514caa",
"metadata": {},
"outputs": [],
"source": [
"tf = TimezoneFinder()\n",
"\n",
"\n",
"def get_localized_dt_and_offset(\n",
" utc_dt: datetime, latitude: float, longitude: float\n",
") -> tuple[str, str]:\n",
" tz_name = tf.timezone_at(lng=longitude, lat=latitude)\n",
"\n",
" utc_timezone = pytz.utc\n",
" central_timezone = pytz.timezone(\"America/Chicago\")\n",
" mountain_timezone = pytz.timezone(\"America/Denver\")\n",
"\n",
" if not tz_name or latitude == 0 or longitude == 0:\n",
" print(\n",
" f\"⚠️ Warning: No timezone found for {latitude}, {longitude}. Defaulting to MDT/MST.\"\n",
" )\n",
" local_tz = mountain_timezone\n",
" else:\n",
" local_tz = pytz.timezone(tz_name)\n",
"\n",
" dt_local = utc_dt.astimezone(local_tz)\n",
"\n",
" # 'YYYY:MM:DD HH:MM:SS'\n",
" dt_str = dt_local.strftime(\"%Y:%m:%d %H:%M:%S\")\n",
"\n",
" # '+/-HHMM' -> '+/-HH:MM' (needed format for tags)\n",
" offset_str: str = dt_local.strftime(\"%z\")\n",
" if len(offset_str) == 5:\n",
" offset_str = offset_str[:3] + \":\" + offset_str[3:]\n",
"\n",
" return dt_str, offset_str"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "e7b8202f",
"metadata": {},
"outputs": [],
"source": [
"from tqdm.asyncio import tqdm\n",
"\n",
"\n",
"\n",
"def update_exif_with_exiftool(row: pd.Series) -> None:\n",
" progress_bar.update(1)\n",
" image_path = row[\"path\"]\n",
" dt_utc = row[\"correct_date_utc\"] # timezone-aware, UTC datetime\n",
" latitude = row[\"correct_latitude\"]\n",
" longitude = row[\"correct_longitude\"]\n",
" file_type = row[\"correct_media_type\"]\n",
" abs_latitude = abs(latitude)\n",
" abs_longitude = abs(longitude)\n",
"\n",
" dt_utc_str = dt_utc.strftime(\"%Y:%m:%d %H:%M:%S\")\n",
"\n",
" if image_path is None or pd.isna(image_path) or image_path == \"\":\n",
" print(f\"❌ File not found, skipping: {image_path}\")\n",
" return\n",
"\n",
" lat_ref = \"N\" if latitude >= 0 else \"S\"\n",
" lon_ref = \"E\" if longitude >= 0 else \"W\"\n",
"\n",
" dt_str, offset_str = get_localized_dt_and_offset(\n",
" utc_dt=dt_utc,\n",
" latitude=row[\"correct_latitude\"],\n",
" longitude=row[\"correct_longitude\"],\n",
" )\n",
"\n",
" if not os.path.exists(image_path):\n",
" print(f\"❌ File not found, skipping: {image_path}\")\n",
" return\n",
"\n",
" jpg_command = [\n",
" \"exiftool\",\n",
" # Local time\n",
" f\"-IFD0:DateTime={dt_str}\",\n",
" f\"-ExifIFD:DateTimeOriginal={dt_str}\",\n",
" f\"-ExifIFD:DateTimeDigitized={dt_str}\",\n",
" # Dynamic offset\n",
" f\"-OffsetTimeOriginal={offset_str}\",\n",
" # GPS tags\n",
" f\"-GPSLatitude={latitude}\",\n",
" f\"-GPSLongitude={longitude}\",\n",
" f\"-GPSLatitudeRef='{lat_ref}'\",\n",
" f\"-GPSLongitudeRef='{lon_ref}'\",\n",
" # Keep overwrite and target file\n",
" \"-overwrite_original\",\n",
" image_path,\n",
" ]\n",
" mp4_command = [\n",
" \"exiftool\",\n",
" # EXIF Tags\n",
" f\"-IFD0:DateTime={dt_str}\",\n",
" f\"-ExifIFD:DateTimeOriginal={dt_str}\",\n",
" f\"-ExifIFD:DateTimeDigitized={dt_str}\",\n",
" f\"-OffsetTimeOriginal={offset_str}\",\n",
" # QuickTime tags (must be set to UTC time)\n",
" f\"-QuickTime:CreationDate={dt_utc_str}\",\n",
" f\"-QuickTime:TrackCreateDate={dt_utc_str}\",\n",
" f\"-QuickTime:MediaCreateDate={dt_utc_str}\",\n",
" # This is for the display TimeZone of the above tags\n",
" # Not 100% sure needed, doesn't seem to hurt\n",
" f\"-QuickTime:TimeZone={offset_str}\",\n",
" # Composite Tag (Needed for location on Google Photos)\n",
" f\"-QuickTime:GPSCoordinates={abs_latitude} {lat_ref}, {abs_longitude} {lon_ref}\",\n",
" # XMP Tags (Adobe/Google compatibility)\n",
" f\"-xmp:gpslatitude={latitude}\",\n",
" f\"-xmp:gpslongitude={longitude}\",\n",
" # QuickTime Tags (Apple/iOS compatibility)\n",
" f\"-QuickTime:LocationLatitude={latitude}\",\n",
" f\"-QuickTime:LocationLongitude={longitude}\",\n",
" # # Generic GPS tags (For broad compatibility)\n",
" f\"-GPSLatitude={latitude}\",\n",
" f\"-GPSLongitude={longitude}\",\n",
" f\"-GPSLatitudeRef={lat_ref}\",\n",
" f\"-GPSLongitudeRef={lon_ref}\",\n",
" \"-overwrite_original\",\n",
" image_path,\n",
" ]\n",
"\n",
" try:\n",
" if file_type == \"Video\":\n",
" result = subprocess.run(\n",
" mp4_command, check=True, capture_output=True, text=True\n",
" )\n",
" elif file_type == \"Image\":\n",
" result = subprocess.run(\n",
" jpg_command, check=True, capture_output=True, text=True\n",
" )\n",
" else:\n",
" print(f\"⚠️ Warning: Unknown media type for {image_path}, skipping.\")\n",
" return\n",
"\n",
" if \"1 image files updated\" in result.stdout:\n",
" pass\n",
" print(f\"✅ Updated: {image_path}\")\n",
" else:\n",
" print(f\"⚠️ Warning updating {image_path}: {result.stderr.strip()}\")\n",
"\n",
" except subprocess.CalledProcessError as e:\n",
" print(f\"🛑 Subprocess Error on {image_path}: {e.stderr.strip()}\")\n",
" except FileNotFoundError:\n",
" print(\n",
" \"🛑 ERROR: ExifTool command not found. Is ExifTool installed and in your PATH?\"\n",
" )"
]
},
{
"cell_type": "code",
"execution_count": 33,
"id": "e2736799",
"metadata": {},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"Updating EXIF: 1%| | 21/3973 [00:26<1:22:35, 1.25s/file]\n"
]
},
{
"ename": "KeyboardInterrupt",
"evalue": "",
"output_type": "error",
"traceback": [
"\u001b[31m---------------------------------------------------------------------------\u001b[39m",
"\u001b[31mKeyboardInterrupt\u001b[39m Traceback (most recent call last)",
"\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[33]\u001b[39m\u001b[32m, line 9\u001b[39m\n\u001b[32m 1\u001b[39m progress_bar = tqdm(\n\u001b[32m 2\u001b[39m total=df1.shape[\u001b[32m0\u001b[39m],\n\u001b[32m 3\u001b[39m desc=\u001b[33m\"\u001b[39m\u001b[33mUpdating EXIF\u001b[39m\u001b[33m\"\u001b[39m,\n\u001b[32m 4\u001b[39m unit=\u001b[33m\"\u001b[39m\u001b[33mfile\u001b[39m\u001b[33m\"\u001b[39m,\n\u001b[32m 5\u001b[39m disable=\u001b[38;5;28;01mFalse\u001b[39;00m,\n\u001b[32m 6\u001b[39m )\n\u001b[32m 8\u001b[39m \u001b[38;5;66;03m# img_errors.apply(update_exif_with_exiftool, axis=1) # type: ignore\u001b[39;00m\n\u001b[32m----> \u001b[39m\u001b[32m9\u001b[39m \u001b[43mdf1\u001b[49m\u001b[43m.\u001b[49m\u001b[43mapply\u001b[49m\u001b[43m(\u001b[49m\u001b[43mupdate_exif_with_exiftool\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43maxis\u001b[49m\u001b[43m=\u001b[49m\u001b[32;43m1\u001b[39;49m\u001b[43m)\u001b[49m \u001b[38;5;66;03m# type: ignore\u001b[39;00m\n",
"\u001b[36mFile \u001b[39m\u001b[32m~/repositories/personal-projects/snapchat-memories-downloader/.venv/lib/python3.12/site-packages/pandas/core/frame.py:10401\u001b[39m, in \u001b[36mDataFrame.apply\u001b[39m\u001b[34m(self, func, axis, raw, result_type, args, by_row, engine, engine_kwargs, **kwargs)\u001b[39m\n\u001b[32m 10387\u001b[39m \u001b[38;5;28;01mfrom\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[34;01mpandas\u001b[39;00m\u001b[34;01m.\u001b[39;00m\u001b[34;01mcore\u001b[39;00m\u001b[34;01m.\u001b[39;00m\u001b[34;01mapply\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[38;5;28;01mimport\u001b[39;00m frame_apply\n\u001b[32m 10389\u001b[39m op = frame_apply(\n\u001b[32m 10390\u001b[39m \u001b[38;5;28mself\u001b[39m,\n\u001b[32m 10391\u001b[39m func=func,\n\u001b[32m (...)\u001b[39m\u001b[32m 10399\u001b[39m kwargs=kwargs,\n\u001b[32m 10400\u001b[39m )\n\u001b[32m> \u001b[39m\u001b[32m10401\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[43mop\u001b[49m\u001b[43m.\u001b[49m\u001b[43mapply\u001b[49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m.__finalize__(\u001b[38;5;28mself\u001b[39m, method=\u001b[33m\"\u001b[39m\u001b[33mapply\u001b[39m\u001b[33m\"\u001b[39m)\n",
"\u001b[36mFile \u001b[39m\u001b[32m~/repositories/personal-projects/snapchat-memories-downloader/.venv/lib/python3.12/site-packages/pandas/core/apply.py:916\u001b[39m, in \u001b[36mFrameApply.apply\u001b[39m\u001b[34m(self)\u001b[39m\n\u001b[32m 913\u001b[39m \u001b[38;5;28;01melif\u001b[39;00m \u001b[38;5;28mself\u001b[39m.raw:\n\u001b[32m 914\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28mself\u001b[39m.apply_raw(engine=\u001b[38;5;28mself\u001b[39m.engine, engine_kwargs=\u001b[38;5;28mself\u001b[39m.engine_kwargs)\n\u001b[32m--> \u001b[39m\u001b[32m916\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43mapply_standard\u001b[49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\n",
"\u001b[36mFile \u001b[39m\u001b[32m~/repositories/personal-projects/snapchat-memories-downloader/.venv/lib/python3.12/site-packages/pandas/core/apply.py:1063\u001b[39m, in \u001b[36mFrameApply.apply_standard\u001b[39m\u001b[34m(self)\u001b[39m\n\u001b[32m 1061\u001b[39m \u001b[38;5;28;01mdef\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[34mapply_standard\u001b[39m(\u001b[38;5;28mself\u001b[39m):\n\u001b[32m 1062\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28mself\u001b[39m.engine == \u001b[33m\"\u001b[39m\u001b[33mpython\u001b[39m\u001b[33m\"\u001b[39m:\n\u001b[32m-> \u001b[39m\u001b[32m1063\u001b[39m results, res_index = \u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43mapply_series_generator\u001b[49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m 1064\u001b[39m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[32m 1065\u001b[39m results, res_index = \u001b[38;5;28mself\u001b[39m.apply_series_numba()\n",
"\u001b[36mFile \u001b[39m\u001b[32m~/repositories/personal-projects/snapchat-memories-downloader/.venv/lib/python3.12/site-packages/pandas/core/apply.py:1081\u001b[39m, in \u001b[36mFrameApply.apply_series_generator\u001b[39m\u001b[34m(self)\u001b[39m\n\u001b[32m 1078\u001b[39m \u001b[38;5;28;01mwith\u001b[39;00m option_context(\u001b[33m\"\u001b[39m\u001b[33mmode.chained_assignment\u001b[39m\u001b[33m\"\u001b[39m, \u001b[38;5;28;01mNone\u001b[39;00m):\n\u001b[32m 1079\u001b[39m \u001b[38;5;28;01mfor\u001b[39;00m i, v \u001b[38;5;129;01min\u001b[39;00m \u001b[38;5;28menumerate\u001b[39m(series_gen):\n\u001b[32m 1080\u001b[39m \u001b[38;5;66;03m# ignore SettingWithCopy here in case the user mutates\u001b[39;00m\n\u001b[32m-> \u001b[39m\u001b[32m1081\u001b[39m results[i] = \u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43mfunc\u001b[49m\u001b[43m(\u001b[49m\u001b[43mv\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43m*\u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43margs\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43m*\u001b[49m\u001b[43m*\u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43mkwargs\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m 1082\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28misinstance\u001b[39m(results[i], ABCSeries):\n\u001b[32m 1083\u001b[39m \u001b[38;5;66;03m# If we have a view on v, we need to make a copy because\u001b[39;00m\n\u001b[32m 1084\u001b[39m \u001b[38;5;66;03m# series_generator will swap out the underlying data\u001b[39;00m\n\u001b[32m 1085\u001b[39m results[i] = results[i].copy(deep=\u001b[38;5;28;01mFalse\u001b[39;00m)\n",
"\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[32]\u001b[39m\u001b[32m, line 84\u001b[39m, in \u001b[36mupdate_exif_with_exiftool\u001b[39m\u001b[34m(row)\u001b[39m\n\u001b[32m 82\u001b[39m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[32m 83\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m file_type == \u001b[33m\"\u001b[39m\u001b[33mVideo\u001b[39m\u001b[33m\"\u001b[39m:\n\u001b[32m---> \u001b[39m\u001b[32m84\u001b[39m result = \u001b[43msubprocess\u001b[49m\u001b[43m.\u001b[49m\u001b[43mrun\u001b[49m\u001b[43m(\u001b[49m\n\u001b[32m 85\u001b[39m \u001b[43m \u001b[49m\u001b[43mmp4_command\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mcheck\u001b[49m\u001b[43m=\u001b[49m\u001b[38;5;28;43;01mTrue\u001b[39;49;00m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mcapture_output\u001b[49m\u001b[43m=\u001b[49m\u001b[38;5;28;43;01mTrue\u001b[39;49;00m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mtext\u001b[49m\u001b[43m=\u001b[49m\u001b[38;5;28;43;01mTrue\u001b[39;49;00m\n\u001b[32m 86\u001b[39m \u001b[43m \u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m 87\u001b[39m \u001b[38;5;28;01melif\u001b[39;00m file_type == \u001b[33m\"\u001b[39m\u001b[33mImage\u001b[39m\u001b[33m\"\u001b[39m:\n\u001b[32m 88\u001b[39m result = subprocess.run(\n\u001b[32m 89\u001b[39m jpg_command, check=\u001b[38;5;28;01mTrue\u001b[39;00m, capture_output=\u001b[38;5;28;01mTrue\u001b[39;00m, text=\u001b[38;5;28;01mTrue\u001b[39;00m\n\u001b[32m 90\u001b[39m )\n",
"\u001b[36mFile \u001b[39m\u001b[32m~/.pyenv/versions/3.12.6/lib/python3.12/subprocess.py:550\u001b[39m, in \u001b[36mrun\u001b[39m\u001b[34m(input, capture_output, timeout, check, *popenargs, **kwargs)\u001b[39m\n\u001b[32m 548\u001b[39m \u001b[38;5;28;01mwith\u001b[39;00m Popen(*popenargs, **kwargs) \u001b[38;5;28;01mas\u001b[39;00m process:\n\u001b[32m 549\u001b[39m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[32m--> \u001b[39m\u001b[32m550\u001b[39m stdout, stderr = \u001b[43mprocess\u001b[49m\u001b[43m.\u001b[49m\u001b[43mcommunicate\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;28;43minput\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mtimeout\u001b[49m\u001b[43m=\u001b[49m\u001b[43mtimeout\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m 551\u001b[39m \u001b[38;5;28;01mexcept\u001b[39;00m TimeoutExpired \u001b[38;5;28;01mas\u001b[39;00m exc:\n\u001b[32m 552\u001b[39m process.kill()\n",
"\u001b[36mFile \u001b[39m\u001b[32m~/.pyenv/versions/3.12.6/lib/python3.12/subprocess.py:1209\u001b[39m, in \u001b[36mPopen.communicate\u001b[39m\u001b[34m(self, input, timeout)\u001b[39m\n\u001b[32m 1206\u001b[39m endtime = \u001b[38;5;28;01mNone\u001b[39;00m\n\u001b[32m 1208\u001b[39m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[32m-> \u001b[39m\u001b[32m1209\u001b[39m stdout, stderr = \u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43m_communicate\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;28;43minput\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mendtime\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mtimeout\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m 1210\u001b[39m \u001b[38;5;28;01mexcept\u001b[39;00m \u001b[38;5;167;01mKeyboardInterrupt\u001b[39;00m:\n\u001b[32m 1211\u001b[39m \u001b[38;5;66;03m# https://bugs.python.org/issue25942\u001b[39;00m\n\u001b[32m 1212\u001b[39m \u001b[38;5;66;03m# See the detailed comment in .wait().\u001b[39;00m\n\u001b[32m 1213\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m timeout \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m:\n",
"\u001b[36mFile \u001b[39m\u001b[32m~/.pyenv/versions/3.12.6/lib/python3.12/subprocess.py:2115\u001b[39m, in \u001b[36mPopen._communicate\u001b[39m\u001b[34m(self, input, endtime, orig_timeout)\u001b[39m\n\u001b[32m 2108\u001b[39m \u001b[38;5;28mself\u001b[39m._check_timeout(endtime, orig_timeout,\n\u001b[32m 2109\u001b[39m stdout, stderr,\n\u001b[32m 2110\u001b[39m skip_check_and_raise=\u001b[38;5;28;01mTrue\u001b[39;00m)\n\u001b[32m 2111\u001b[39m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mRuntimeError\u001b[39;00m( \u001b[38;5;66;03m# Impossible :)\u001b[39;00m\n\u001b[32m 2112\u001b[39m \u001b[33m'\u001b[39m\u001b[33m_check_timeout(..., skip_check_and_raise=True) \u001b[39m\u001b[33m'\u001b[39m\n\u001b[32m 2113\u001b[39m \u001b[33m'\u001b[39m\u001b[33mfailed to raise TimeoutExpired.\u001b[39m\u001b[33m'\u001b[39m)\n\u001b[32m-> \u001b[39m\u001b[32m2115\u001b[39m ready = \u001b[43mselector\u001b[49m\u001b[43m.\u001b[49m\u001b[43mselect\u001b[49m\u001b[43m(\u001b[49m\u001b[43mtimeout\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m 2116\u001b[39m \u001b[38;5;28mself\u001b[39m._check_timeout(endtime, orig_timeout, stdout, stderr)\n\u001b[32m 2118\u001b[39m \u001b[38;5;66;03m# XXX Rewrite these to use non-blocking I/O on the file\u001b[39;00m\n\u001b[32m 2119\u001b[39m \u001b[38;5;66;03m# objects; they are no longer using C stdio!\u001b[39;00m\n",
"\u001b[36mFile \u001b[39m\u001b[32m~/.pyenv/versions/3.12.6/lib/python3.12/selectors.py:415\u001b[39m, in \u001b[36m_PollLikeSelector.select\u001b[39m\u001b[34m(self, timeout)\u001b[39m\n\u001b[32m 413\u001b[39m ready = []\n\u001b[32m 414\u001b[39m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[32m--> \u001b[39m\u001b[32m415\u001b[39m fd_event_list = \u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43m_selector\u001b[49m\u001b[43m.\u001b[49m\u001b[43mpoll\u001b[49m\u001b[43m(\u001b[49m\u001b[43mtimeout\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m 416\u001b[39m \u001b[38;5;28;01mexcept\u001b[39;00m \u001b[38;5;167;01mInterruptedError\u001b[39;00m:\n\u001b[32m 417\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m ready\n",
"\u001b[31mKeyboardInterrupt\u001b[39m: "
]
}
],
"source": [
"progress_bar = tqdm(\n",
" total=df1.shape[0],\n",
" desc=\"Updating EXIF\",\n",
" unit=\"file\",\n",
" disable=False,\n",
")\n",
"\n",
"# img_errors.apply(update_exif_with_exiftool, axis=1) # type: ignore\n",
"df1.apply(update_exif_with_exiftool, axis=1) # type: ignore"
]
}
],
"metadata": {
"kernelspec": {
"display_name": ".venv",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.12.6"
}
},
"nbformat": 4,
"nbformat_minor": 5
}