diff --git a/download_stats.py b/download_stats.py index d1d24a4..8f5ef94 100644 --- a/download_stats.py +++ b/download_stats.py @@ -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) diff --git a/main.py b/main.py index d0c931a..b4acf5c 100644 --- a/main.py +++ b/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]: diff --git a/notebooks/make_df.ipynb b/notebooks/make_df.ipynb index 061757d..e9a01ed 100644 --- a/notebooks/make_df.ipynb +++ b/notebooks/make_df.ipynb @@ -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", + " )" ] }, { diff --git a/notebooks/verify_metadata.ipynb b/notebooks/verify_metadata.ipynb index 6a765b0..e708d9e 100644 --- a/notebooks/verify_metadata.ipynb +++ b/notebooks/verify_metadata.ipynb @@ -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", diff --git a/requirements.txt b/requirements.txt index f494b84..6c92599 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,4 +8,6 @@ timezonefinder # For development jupyter -pandas \ No newline at end of file +pandas +black +black[jupyter]