pep-8
This commit is contained in:
parent
54a83cead5
commit
af8c3edc96
@ -5,40 +5,39 @@ import pandas as pd
|
||||
|
||||
file_directory = Path("../downloads")
|
||||
files_to_check = [
|
||||
p for p in file_directory.iterdir()
|
||||
p
|
||||
for p in file_directory.iterdir()
|
||||
if p.is_file() and p.suffix.lower() in [".jpg", ".mp4"]
|
||||
]
|
||||
|
||||
file_df = pd.DataFrame(data=files_to_check, columns=["file"])
|
||||
|
||||
|
||||
def get_metadata(row: pd.Series) -> pd.Series:
|
||||
|
||||
|
||||
file_path = row["file"]
|
||||
command = [
|
||||
"exiftool",
|
||||
"-T",
|
||||
"-T",
|
||||
"-createdate",
|
||||
"-gpslatitude",
|
||||
"-gpslongitude",
|
||||
# *tags_to_check,
|
||||
str(file_path)
|
||||
# *tags_to_check,
|
||||
str(file_path),
|
||||
]
|
||||
result = subprocess.run(
|
||||
command,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=False,
|
||||
timeout=30
|
||||
command, capture_output=True, text=True, check=False, timeout=30
|
||||
)
|
||||
metadata = result.stdout.strip().split('\n')[0].split('\t')
|
||||
|
||||
metadata = result.stdout.strip().split("\n")[0].split("\t")
|
||||
|
||||
if len(metadata) < 3:
|
||||
metadata = metadata + ['-'] * (3 - len(metadata))
|
||||
metadata = metadata + ["-"] * (3 - len(metadata))
|
||||
row["createdate"] = metadata[0]
|
||||
row["gpslatitude"] = metadata[1]
|
||||
row["gpslongitude"] = metadata[2]
|
||||
return row
|
||||
|
||||
|
||||
df = file_df.apply(get_metadata, axis=1)
|
||||
|
||||
|
||||
|
||||
114
main.py
114
main.py
@ -70,7 +70,7 @@ def add_exif_data(image_path: Path, memory: Memory):
|
||||
|
||||
utc = pytz.utc
|
||||
# Use the 'America/Chicago' zone which handles both CST (-6) and CDT (-5) automatically
|
||||
central_timezone = pytz.timezone('America/Chicago')
|
||||
central_timezone = pytz.timezone("America/Chicago")
|
||||
utc_dt = memory.date.replace(tzinfo=utc)
|
||||
local_dt = utc_dt.astimezone(central_timezone)
|
||||
dt_str = local_dt.strftime("%Y:%m:%d %H:%M:%S")
|
||||
@ -87,10 +87,10 @@ def add_exif_data(image_path: Path, memory: Memory):
|
||||
minutes = int(minutes_decimal)
|
||||
seconds = (minutes_decimal - minutes) * 60
|
||||
return (degrees, minutes, seconds)
|
||||
|
||||
|
||||
lat_dms = decimal_to_dms(memory.latitude)
|
||||
lon_dms = decimal_to_dms(memory.longitude)
|
||||
|
||||
|
||||
img.gps_latitude = lat_dms
|
||||
img.gps_latitude_ref = "N" if memory.latitude >= 0 else "S"
|
||||
img.gps_longitude = lon_dms
|
||||
@ -103,108 +103,23 @@ def add_exif_data(image_path: Path, memory: Memory):
|
||||
|
||||
|
||||
def add_metadata_mp4(video_path: Path, memory: Memory):
|
||||
"""
|
||||
Adds metadata (Date and GPS) to MP4 files by calling ExifTool
|
||||
via the subprocess module.
|
||||
"""
|
||||
try:
|
||||
dt_str = memory.date.strftime("%Y:%m:%d %H:%M:%S")
|
||||
dt_str = memory.date.strftime("%Y:%m:%d %H:%M:%S")
|
||||
|
||||
command = [
|
||||
"exiftool",
|
||||
"-P", # Preserve file modification date
|
||||
"-P", # Preserve file modification date
|
||||
"-overwrite_original",
|
||||
"-m", # Allow minor warnings
|
||||
|
||||
"-m", # Allow minor warnings
|
||||
# QuickTime Date/Time tags
|
||||
f"-QuickTime:DateTimeOriginal={dt_str}",
|
||||
f"-QuickTime:CreateDate={dt_str}",
|
||||
f"-QuickTime:ModifyDate={dt_str}",
|
||||
|
||||
f"-QuickTime:CreateDate={dt_str}",
|
||||
f"-QuickTime:ModifyDate={dt_str}",
|
||||
# QuickTime TimeZone is often required for the date to be recognized
|
||||
f"-QuickTime:TimeZone=+00:00",
|
||||
|
||||
# # Final path argument
|
||||
# str(video_path)
|
||||
]
|
||||
# dt_str = memory.date.strftime("%Y:%m:%d %H:%M:%S")
|
||||
# dt_str_utc = memory.date.strftime("%Y:%m:%d %H:%M:%S-06:00") # ISO format with UTC offset
|
||||
# tz = "-06:00" # Central Time Zone offset
|
||||
|
||||
# # 1. Start with the base command, including date tags (ALWAYS written)
|
||||
# command = [
|
||||
# "exiftool",
|
||||
# "-overwrite_original",
|
||||
# ]
|
||||
|
||||
# # 2. Conditionally add GPS tags to the command list
|
||||
# if memory.latitude is not None and memory.longitude is not None:
|
||||
# lat = memory.latitude
|
||||
# lon = memory.longitude
|
||||
|
||||
# coord_str = f"{lat} {lon}"
|
||||
|
||||
# command.extend([
|
||||
# # Use the standard GPS tags instead of the specific QuickTime ones
|
||||
# f"-Keys:GPSCoordinates={coord_str}",
|
||||
# # Keep DateTimeOriginal and TimeZone, but ensure you also use the correct TimeZone tag
|
||||
# f"-QuickTime:DateTimeOriginal={dt_str}",
|
||||
# f"-QuickTime:TimeZone={tz}",
|
||||
# ])
|
||||
|
||||
# # command.extend([
|
||||
# # # Generic Composite Tag (Tries to create necessary atoms)
|
||||
# # f"-GPSCoordinates={coord_str}",
|
||||
|
||||
# # # EXIF
|
||||
# # f"-EXIF:GPSLatitude={lat}",
|
||||
# # f"-EXIF:GPSLongitude={lon}",
|
||||
# # f"-EXIF:GPSLatitudeRef={'N' if lat >= 0 else 'S'}",
|
||||
# # f"-EXIF:GPSLongitudeRef={'E' if lon >= 0 else 'W'}",
|
||||
|
||||
# # # Highly prioritized XMP Tag (Often read first by Adobe/Google)
|
||||
# # f"-XMP:GPSLatitude={lat}",
|
||||
# # f"-XMP:GPSLongitude={lon}",
|
||||
|
||||
# # # QuickTime Location tags (Apple/iOS compatibility)
|
||||
# # f"-QuickTime:LocationLatitude={lat}",
|
||||
# # f"-QuickTime:LocationLongitude={lon}",
|
||||
|
||||
# # # # Generic GPS tags (For broad compatibility)
|
||||
# # # f"-GPSLatitude={lat}",
|
||||
# # # f"-GPSLongitude={lon}",
|
||||
# # # f"-GPSLatitudeRef={'N' if lat >= 0 else 'S'}",
|
||||
# # # f"-GPSLongitudeRef={'E' if lon >= 0 else 'W'}",
|
||||
# # ])
|
||||
|
||||
# # command.extend([
|
||||
# # # Generic Composite Tag (Tries to create necessary atoms)
|
||||
# # # f"-GPSCoordinates={coord_str}",
|
||||
|
||||
# # # EXIF
|
||||
# # f"-exif:gpslatitude={lat}",
|
||||
# # f"-exif:gpslongitude={lon}",
|
||||
# # # f"-gpslatituderef={'N' if lat >= 0 else 'S'}",
|
||||
# # # f"-gpslongituderef={'E' if lon >= 0 else 'W'}",
|
||||
|
||||
# # # Highly prioritized XMP Tag (Often read first by Adobe/Google)
|
||||
# # f"-xmp:gpslatitude={lat}",
|
||||
# # f"-xmp:gpslongitude={lon}",
|
||||
|
||||
# # f"-gpslatituderef={'N' if lat >= 0 else 'S'}",
|
||||
# # f"-gpslongituderef={'E' if lon >= 0 else 'W'}",
|
||||
|
||||
# # # QuickTime Location tags (Apple/iOS compatibility)
|
||||
# # f"-QuickTime:LocationLatitude={lat}",
|
||||
# # f"-QuickTime:LocationLongitude={lon}",
|
||||
|
||||
# # # # Generic GPS tags (For broad compatibility)
|
||||
# # # f"-GPSLatitude={lat}",
|
||||
# # # f"-GPSLongitude={lon}",
|
||||
# # # f"-GPSLatitudeRef={'N' if lat >= 0 else 'S'}",
|
||||
# # # f"-GPSLongitudeRef={'E' if lon >= 0 else 'W'}",
|
||||
# # ])
|
||||
# # command.extend(["-overwrite_original"])
|
||||
|
||||
# # 3. Add the target file path at the end
|
||||
command.append(str(video_path))
|
||||
@ -212,25 +127,26 @@ def add_metadata_mp4(video_path: Path, memory: Memory):
|
||||
# 4. Execute the command
|
||||
# We use check=True to raise an error if ExifTool fails.
|
||||
subprocess.run(
|
||||
command,
|
||||
command,
|
||||
check=True,
|
||||
capture_output=True, # Captures stdout/stderr
|
||||
text=True
|
||||
capture_output=True, # Captures stdout/stderr
|
||||
text=True,
|
||||
)
|
||||
|
||||
|
||||
except subprocess.CalledProcessError as e:
|
||||
# Handle cases where ExifTool runs but returns an error (e.g., file corrupted)
|
||||
print(f"\nError: ExifTool failed for {video_path.name}: {e.stderr.strip()}")
|
||||
except FileNotFoundError:
|
||||
# Handle case where the 'exiftool' executable cannot be found
|
||||
print("\nError: ExifTool not found. Please install ExifTool and ensure it's in your system PATH.")
|
||||
print(
|
||||
"\nError: ExifTool not found. Please install ExifTool and ensure it's in your system PATH."
|
||||
)
|
||||
except Exception as e:
|
||||
# Catch all other exceptions
|
||||
print(f"\nWarning: Failed to add metadata to {video_path.name}: {e}")
|
||||
pass
|
||||
|
||||
|
||||
|
||||
async def download_memory(
|
||||
memory: Memory, output_dir: Path, add_exif: bool, semaphore: asyncio.Semaphore
|
||||
) -> tuple[bool, int]:
|
||||
|
||||
@ -23,7 +23,7 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"df: pd.DataFrame = pd.read_json(\"../temp/file_info.json\", orient='records')\n",
|
||||
"df: pd.DataFrame = pd.read_json(\"../temp/file_info.json\", orient=\"records\")\n",
|
||||
"df.head(5)"
|
||||
]
|
||||
},
|
||||
@ -35,9 +35,10 @@
|
||||
"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",
|
||||
" 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",
|
||||
@ -78,7 +79,9 @@
|
||||
"source": [
|
||||
"def convert_lat_long_to_decimal_degrees(row: pd.Series) -> pd.Series:\n",
|
||||
" def dms_to_decimal(dms_string):\n",
|
||||
" match = re.search(r\"(\\d+)\\s*deg\\s*(\\d+)'\\s*([\\d.]+)\\\"\\s*([NSEW])\", dms_string, re.IGNORECASE)\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",
|
||||
@ -87,7 +90,7 @@
|
||||
" 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",
|
||||
" if direction in [\"S\", \"W\"]:\n",
|
||||
" decimal_degrees *= -1\n",
|
||||
" decimal_degrees = round(decimal_degrees, 5)\n",
|
||||
" return decimal_degrees\n",
|
||||
@ -95,7 +98,7 @@
|
||||
" def convert(value: str) -> float:\n",
|
||||
" if pd.isna(value):\n",
|
||||
" return 0.0\n",
|
||||
" if value == '-':\n",
|
||||
" if value == \"-\":\n",
|
||||
" return 0.0\n",
|
||||
" try:\n",
|
||||
" return dms_to_decimal(value)\n",
|
||||
@ -115,11 +118,11 @@
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"def get_file_name(row: pd.Series) -> pd.Series:\n",
|
||||
" path_parts = row['file'].split('/')\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",
|
||||
" row[\"filename\"] = file\n",
|
||||
" row[\"extension\"] = file.split(\".\")[-1].lower()\n",
|
||||
" row[\"file_dt\"] = file.split(\".\")[0]\n",
|
||||
" return row"
|
||||
]
|
||||
},
|
||||
@ -144,29 +147,31 @@
|
||||
"source": [
|
||||
"def get_datetime_for_df(row: pd.Series) -> pd.Series:\n",
|
||||
" from datetime import datetime\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",
|
||||
" 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",
|
||||
" 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",
|
||||
" 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",
|
||||
" 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"
|
||||
]
|
||||
},
|
||||
@ -179,7 +184,14 @@
|
||||
"source": [
|
||||
"data = meta.apply(get_datetime_for_df, axis=1)\n",
|
||||
"data = data.apply(get_media_type, axis=1)\n",
|
||||
"joined = pd.merge(df, data, how='left', left_on='file_dt', right_on='file_dt', suffixes=('_df', '_meta'))"
|
||||
"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",
|
||||
")"
|
||||
]
|
||||
},
|
||||
{
|
||||
@ -190,16 +202,16 @@
|
||||
"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",
|
||||
" \"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)"
|
||||
]
|
||||
@ -213,20 +225,21 @@
|
||||
"source": [
|
||||
"def check_for_errors(row: pd.Series) -> pd.Series:\n",
|
||||
" errors = 0\n",
|
||||
" if row[\"correct_media_type\"] != row['actual_media_type']:\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",
|
||||
" 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)"
|
||||
]
|
||||
},
|
||||
@ -237,7 +250,7 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"errors = df1[df1['errors'] > 0]\n",
|
||||
"errors = df1[df1[\"errors\"] > 0]\n",
|
||||
"errors.head(5)"
|
||||
]
|
||||
},
|
||||
@ -258,7 +271,7 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"img_errors = errors[errors['correct_media_type'] == 'Image']\n",
|
||||
"img_errors = errors[errors[\"correct_media_type\"] == \"Image\"]\n",
|
||||
"print(f\"Image errors: {img_errors.shape[0]}\")"
|
||||
]
|
||||
},
|
||||
@ -269,7 +282,7 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"video_errors = errors[errors['correct_media_type'] == 'Video']\n",
|
||||
"video_errors = errors[errors[\"correct_media_type\"] == \"Video\"]\n",
|
||||
"print(f\"Video errors: {video_errors.shape[0]}\")"
|
||||
]
|
||||
},
|
||||
@ -295,22 +308,22 @@
|
||||
" 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",
|
||||
" if row[\"correct_media_type\"] == row[\"actual_media_type\"]:\n",
|
||||
" return row\n",
|
||||
"\n",
|
||||
" if row['path'] == '-':\n",
|
||||
" if row[\"path\"] == \"-\":\n",
|
||||
" return row\n",
|
||||
"\n",
|
||||
" if row['correct_media_type'] == 'Image':\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",
|
||||
" 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",
|
||||
" 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"
|
||||
@ -323,8 +336,8 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"wrong_filetype = df1[df1['correct_media_type'] != df1['actual_media_type']]\n",
|
||||
"wrong_filetype = wrong_filetype.fillna('-')\n",
|
||||
"wrong_filetype = df1[df1[\"correct_media_type\"] != df1[\"actual_media_type\"]]\n",
|
||||
"wrong_filetype = wrong_filetype.fillna(\"-\")\n",
|
||||
"wrong_filetype.head(5)"
|
||||
]
|
||||
},
|
||||
@ -347,15 +360,20 @@
|
||||
"source": [
|
||||
"tf = TimezoneFinder()\n",
|
||||
"\n",
|
||||
"def get_localized_dt_and_offset(utc_dt: datetime, latitude: float, longitude: float) -> tuple[str, str]:\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",
|
||||
"\n",
|
||||
" utc_timezone = pytz.utc\n",
|
||||
" central_timezone = pytz.timezone('America/Chicago')\n",
|
||||
" mountain_timezone = pytz.timezone('America/Denver')\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(f\"⚠️ Warning: No timezone found for {latitude}, {longitude}. Defaulting to MDT/MST.\")\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",
|
||||
@ -366,9 +384,9 @@
|
||||
" 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",
|
||||
" offset_str: str = dt_local.strftime(\"%z\")\n",
|
||||
" if len(offset_str) == 5:\n",
|
||||
" offset_str = offset_str[:3] + \":\" + offset_str[3:] \n",
|
||||
" offset_str = offset_str[:3] + \":\" + offset_str[3:]\n",
|
||||
"\n",
|
||||
" return dt_str, offset_str"
|
||||
]
|
||||
@ -381,27 +399,27 @@
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"def update_exif_with_exiftool(row: pd.Series) -> None:\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",
|
||||
" 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",
|
||||
" 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",
|
||||
" 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",
|
||||
" latitude=row[\"correct_latitude\"],\n",
|
||||
" longitude=row[\"correct_longitude\"],\n",
|
||||
" )\n",
|
||||
"\n",
|
||||
" if not os.path.exists(image_path):\n",
|
||||
@ -409,74 +427,66 @@
|
||||
" return\n",
|
||||
"\n",
|
||||
" jpg_command = [\n",
|
||||
" 'exiftool',\n",
|
||||
" \n",
|
||||
" \"exiftool\",\n",
|
||||
" # Local time\n",
|
||||
" f'-IFD0:DateTime={dt_str}',\n",
|
||||
" f'-ExifIFD:DateTimeOriginal={dt_str}',\n",
|
||||
" f'-ExifIFD:DateTimeDigitized={dt_str}',\n",
|
||||
" \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",
|
||||
" \n",
|
||||
" f\"-OffsetTimeOriginal={offset_str}\",\n",
|
||||
" # GPS tags\n",
|
||||
" f'-GPSLatitude={latitude}',\n",
|
||||
" f'-GPSLongitude={longitude}',\n",
|
||||
" f\"-GPSLatitude={latitude}\",\n",
|
||||
" f\"-GPSLongitude={longitude}\",\n",
|
||||
" f\"-GPSLatitudeRef='{lat_ref}'\",\n",
|
||||
" f\"-GPSLongitudeRef='{lon_ref}'\",\n",
|
||||
" \n",
|
||||
" # Keep overwrite and target file\n",
|
||||
" '-overwrite_original',\n",
|
||||
" image_path\n",
|
||||
" \"-overwrite_original\",\n",
|
||||
" image_path,\n",
|
||||
" ]\n",
|
||||
" mp4_command = [\n",
|
||||
" \"exiftool\",\n",
|
||||
" \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",
|
||||
" \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",
|
||||
" \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",
|
||||
"\n",
|
||||
" # Composite Tag (Needed for location on Google Photos)\n",
|
||||
" f\"-QuickTime:GPSCoordinates={abs_latitude} {lat_ref}, {abs_longitude} {lon_ref}\",\n",
|
||||
"\n",
|
||||
" # XMP Tags (Adobe/Google compatibility)\n",
|
||||
" f\"-xmp:gpslatitude={latitude}\",\n",
|
||||
" f\"-xmp:gpslongitude={longitude}\",\n",
|
||||
"\n",
|
||||
" # QuickTime Tags (Apple/iOS compatibility)\n",
|
||||
" f\"-QuickTime:LocationLatitude={latitude}\",\n",
|
||||
" f\"-QuickTime:LocationLongitude={longitude}\",\n",
|
||||
"\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",
|
||||
" \n",
|
||||
" '-overwrite_original',\n",
|
||||
" image_path\n",
|
||||
" \"-overwrite_original\",\n",
|
||||
" image_path,\n",
|
||||
" ]\n",
|
||||
"\n",
|
||||
" try:\n",
|
||||
" if file_type == 'Video':\n",
|
||||
" result = subprocess.run(mp4_command, check=True, capture_output=True, text=True)\n",
|
||||
" elif file_type == 'Image':\n",
|
||||
" result = subprocess.run(jpg_command, check=True, capture_output=True, text=True)\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",
|
||||
"\n",
|
||||
" if \"1 image files updated\" in result.stdout:\n",
|
||||
" print(f\"✅ Updated: {image_path}\")\n",
|
||||
" else:\n",
|
||||
@ -485,7 +495,9 @@
|
||||
" except subprocess.CalledProcessError as e:\n",
|
||||
" print(f\"🛑 Subprocess Error on {image_path}: {e.stderr.strip()}\")\n",
|
||||
" except FileNotFoundError:\n",
|
||||
" print(\"🛑 ERROR: ExifTool command not found. Is ExifTool installed and in your PATH?\")\n"
|
||||
" print(\n",
|
||||
" \"🛑 ERROR: ExifTool command not found. Is ExifTool installed and in your PATH?\"\n",
|
||||
" )"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
@ -26,7 +26,8 @@
|
||||
"file_directory = Path(\"../downloads\")\n",
|
||||
"\n",
|
||||
"files_to_check = [\n",
|
||||
" p for p in file_directory.iterdir() \n",
|
||||
" p\n",
|
||||
" for p in file_directory.iterdir()\n",
|
||||
" if p.is_file() and p.suffix.lower() in [\".jpg\", \".mp4\"]\n",
|
||||
"]"
|
||||
]
|
||||
@ -138,28 +139,24 @@
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"def get_metadata(row: pd.Series) -> pd.Series:\n",
|
||||
" \n",
|
||||
"\n",
|
||||
" file_path = row[\"file\"]\n",
|
||||
" command = [\n",
|
||||
" \"exiftool\",\n",
|
||||
" \"-T\", \n",
|
||||
" \"-T\",\n",
|
||||
" \"-createdate\",\n",
|
||||
" \"-gpslatitude\",\n",
|
||||
" \"-gpslongitude\",\n",
|
||||
" # *tags_to_check, \n",
|
||||
" str(file_path)\n",
|
||||
" # *tags_to_check,\n",
|
||||
" str(file_path),\n",
|
||||
" ]\n",
|
||||
" result = subprocess.run(\n",
|
||||
" command,\n",
|
||||
" capture_output=True,\n",
|
||||
" text=True,\n",
|
||||
" check=False,\n",
|
||||
" timeout=30\n",
|
||||
" command, capture_output=True, text=True, check=False, timeout=30\n",
|
||||
" )\n",
|
||||
" metadata = result.stdout.strip().split('\\n')[0].split('\\t')\n",
|
||||
" \n",
|
||||
" metadata = result.stdout.strip().split(\"\\n\")[0].split(\"\\t\")\n",
|
||||
"\n",
|
||||
" if len(metadata) < 3:\n",
|
||||
" metadata = metadata + ['-'] * (3 - len(metadata))\n",
|
||||
" metadata = metadata + [\"-\"] * (3 - len(metadata))\n",
|
||||
" row[\"createdate\"] = metadata[0]\n",
|
||||
" row[\"gpslatitude\"] = metadata[1]\n",
|
||||
" row[\"gpslongitude\"] = metadata[2]\n",
|
||||
|
||||
@ -8,4 +8,6 @@ timezonefinder
|
||||
|
||||
# For development
|
||||
jupyter
|
||||
pandas
|
||||
pandas
|
||||
black
|
||||
black[jupyter]
|
||||
|
||||
Loading…
Reference in New Issue
Block a user