diff --git a/photos-metadata-restore/1_copy_files.py b/photos-metadata-restore/1_copy_files.py new file mode 100644 index 0000000..fd91e0b --- /dev/null +++ b/photos-metadata-restore/1_copy_files.py @@ -0,0 +1,146 @@ +#!/usr/bin/env python3 +""" +画像ファイルをハッシュ値に変換してコピーするスクリプト +共通規格に従って実装 +""" + +import os +import json +import hashlib +import shutil +from pathlib import Path +import logging +import sys + +# ログ設定 +logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') +logger = logging.getLogger(__name__) + +# 画像・動画の拡張子 +IMAGE_EXTENSIONS = { + '.jpg', '.jpeg', '.png', '.gif', '.bmp', '.tiff', '.tif', '.webp', + '.svg', '.ico', '.heic', '.heif', '.raw', '.cr2', '.nef', '.arw', + '.dng', '.orf', '.rw2', '.pef', '.srw', '.x3f' +} + +VIDEO_EXTENSIONS = { + '.mp4', '.avi', '.mov', '.wmv', '.flv', '.webm', '.mkv', '.m4v', + '.3gp', '.ogv', '.mts', '.m2ts', '.ts' +} + +def get_all_extensions(directory): + """ディレクトリ内のすべてのファイルの拡張子を取得""" + extensions = set() + for root, dirs, files in os.walk(directory): + for file in files: + ext = Path(file).suffix.lower() + if ext: + extensions.add(ext) + return extensions + +def is_media_file(file_path): + """ファイルが画像または動画かどうかを判定""" + ext = Path(file_path).suffix.lower() + return ext in IMAGE_EXTENSIONS or ext in VIDEO_EXTENSIONS + +def calculate_file_hash(file_path): + """ファイルのMD5ハッシュを計算""" + hash_md5 = hashlib.md5() + try: + with open(file_path, "rb") as f: + for chunk in iter(lambda: f.read(4096), b""): + hash_md5.update(chunk) + return hash_md5.hexdigest() + except Exception as e: + logger.error(f"ハッシュ計算エラー {file_path}: {e}") + return None + +def main(): + input_dir = Path("/workspace/photos-metadata-restore/input") + output_dir = Path("/workspace/photos-metadata-restore/output") + images_dir = output_dir / "images" + tmp_dir = Path("/workspace/photos-metadata-restore/tmp") + + # 入力ディレクトリの存在確認 + if not input_dir.exists(): + logger.error(f"入力ディレクトリが存在しません: {input_dir}") + return + + # 出力ディレクトリの準備 + if output_dir.exists(): + shutil.rmtree(output_dir) + output_dir.mkdir(parents=True, exist_ok=True) + images_dir.mkdir(parents=True, exist_ok=True) + + # tmpディレクトリのリセット + if tmp_dir.exists(): + shutil.rmtree(tmp_dir) + tmp_dir.mkdir(parents=True, exist_ok=True) + + # すべての拡張子を取得 + all_extensions = get_all_extensions(input_dir) + logger.info(f"発見された拡張子: {sorted(all_extensions)}") + + # 画像・動画以外の拡張子を表示 + media_extensions = IMAGE_EXTENSIONS | VIDEO_EXTENSIONS + non_media_extensions = all_extensions - media_extensions + if non_media_extensions: + logger.info(f"画像・動画以外の拡張子: {sorted(non_media_extensions)}") + + # メディアファイルを収集 + media_files = [] + for root, dirs, files in os.walk(input_dir): + for file in files: + file_path = Path(root) / file + if is_media_file(file_path): + media_files.append(file_path) + + logger.info(f"処理対象のメディアファイル数: {len(media_files)}") + + # ファイルをコピー + pair_data = [] + success_count = 0 + error_count = 0 + + for i, file_path in enumerate(media_files): + try: + print(f"処理中: {i+1}/{len(media_files)} - {file_path.name}") + + # ハッシュ計算 + file_hash = calculate_file_hash(file_path) + if not file_hash: + error_count += 1 + continue + + # 新しいファイル名 + ext = file_path.suffix.lower() + new_filename = f"{file_hash}{ext}" + dest_path = images_dir / new_filename + + # ファイルコピー + shutil.copy2(file_path, dest_path) + + # pair.json用のデータ(共通規格に従う) + pair_data.append({ + "source": str(file_path), + "destination": str(dest_path), + "filename": new_filename, + "hash": file_hash + }) + + success_count += 1 + + except Exception as e: + logger.error(f"ファイルコピーエラー {file_path}: {e}") + error_count += 1 + + # pair.jsonを保存(共通規格に従う) + pair_file = output_dir / "pair.json" + with open(pair_file, 'w', encoding='utf-8') as f: + json.dump(pair_data, f, ensure_ascii=False, indent=2) + + logger.info(f"処理完了: 成功 {success_count}件, エラー {error_count}件") + logger.info(f"pair.jsonを保存しました: {pair_file}") + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/photos-metadata-restore/2_filter_missing_metadata.py b/photos-metadata-restore/2_filter_missing_metadata.py new file mode 100644 index 0000000..488836d --- /dev/null +++ b/photos-metadata-restore/2_filter_missing_metadata.py @@ -0,0 +1,226 @@ +#!/usr/bin/env python3 +""" +画像ファイルのメタデータを調査するスクリプト +共通規格に従って実装 +""" + +import json +import os +from pathlib import Path +from datetime import datetime +import logging + +# ログ設定 +logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') +logger = logging.getLogger(__name__) + +# PILの代替として、基本的な画像処理のみ実装 +try: + from PIL import Image + from PIL.ExifTags import TAGS, GPSTAGS + PIL_AVAILABLE = True +except ImportError: + PIL_AVAILABLE = False + logger.warning("PILが利用できません。基本的なファイル情報のみ取得します。") + +def get_exif_data(image_path): + """EXIFデータを取得""" + if not PIL_AVAILABLE: + return None + + try: + image = Image.open(image_path) + exif_data = image._getexif() + + if exif_data is None: + return None + + exif_dict = {} + for tag_id, value in exif_data.items(): + tag = TAGS.get(tag_id, tag_id) + exif_dict[tag] = value + + return exif_dict + except Exception as e: + logger.error(f"EXIFデータ取得エラー {image_path}: {e}") + return None + +def get_datetime_from_exif(exif_data): + """EXIFから日時情報を取得""" + datetime_info = {} + + if not exif_data: + return datetime_info + + # 日時関連のタグ + datetime_tags = { + 'DateTime': 'exif_datetime', + 'DateTimeOriginal': 'exif_datetime_original', + 'DateTimeDigitized': 'exif_datetime_digitized' + } + + for tag, key in datetime_tags.items(): + if tag in exif_data: + try: + # EXIFの日時フォーマットをパース + dt_str = exif_data[tag] + if isinstance(dt_str, str): + # EXIF日時フォーマット: "YYYY:MM:DD HH:MM:SS" + dt = datetime.strptime(dt_str, "%Y:%m:%d %H:%M:%S") + datetime_info[key] = dt.isoformat() + except Exception as e: + logger.warning(f"日時パースエラー {tag}: {e}") + + return datetime_info + +def get_location_from_exif(exif_data): + """EXIFから位置情報を取得""" + location_info = {} + + if not exif_data: + return location_info + + # GPS情報の取得 + if 'GPSInfo' in exif_data: + gps_info = exif_data['GPSInfo'] + gps_data = {} + + for tag_id, value in gps_info.items(): + tag = GPSTAGS.get(tag_id, tag_id) + gps_data[tag] = value + + # 緯度・経度の計算 + if 'GPSLatitude' in gps_data and 'GPSLongitude' in gps_data: + try: + lat = convert_to_degrees(gps_data['GPSLatitude']) + lon = convert_to_degrees(gps_data['GPSLongitude']) + + # 南緯・西経の場合は負の値にする + if gps_data.get('GPSLatitudeRef') == 'S': + lat = -lat + if gps_data.get('GPSLongitudeRef') == 'W': + lon = -lon + + location_info['latitude'] = lat + location_info['longitude'] = lon + location_info['exif_gps'] = True + + # 高度情報 + if 'GPSAltitude' in gps_data: + altitude = gps_data['GPSAltitude'] + if isinstance(altitude, tuple): + altitude = altitude[0] / altitude[1] + location_info['altitude'] = altitude + + except Exception as e: + logger.warning(f"GPS座標計算エラー: {e}") + + return location_info + +def convert_to_degrees(value): + """GPS座標を度に変換""" + if isinstance(value, tuple) and len(value) == 3: + d, m, s = value + return d + (m / 60.0) + (s / 3600.0) + return value + +def get_file_creation_time(file_path): + """ファイルの作成日時を取得""" + try: + stat = os.stat(file_path) + return datetime.fromtimestamp(stat.st_ctime).isoformat() + except Exception as e: + logger.warning(f"ファイル作成日時取得エラー {file_path}: {e}") + return None + +def analyze_image_metadata(image_path): + """画像のメタデータを分析(共通規格に従う)""" + result = { + "datetime": {}, + "location": {}, + "has_datetime": False, + "has_location": False, + "metadata_sources": [] + } + + # EXIFデータの取得 + exif_data = get_exif_data(image_path) + if exif_data: + result["metadata_sources"].append("exif") + + # 日時情報 + datetime_info = get_datetime_from_exif(exif_data) + result["datetime"].update(datetime_info) + + # 位置情報 + location_info = get_location_from_exif(exif_data) + result["location"].update(location_info) + + # ファイル作成日時 + file_creation = get_file_creation_time(image_path) + if file_creation: + result["datetime"]["file_creation_time"] = file_creation + + # 日時情報の有無を判定 + result["has_datetime"] = bool(result["datetime"]) + + # 位置情報の有無を判定 + result["has_location"] = bool(result["location"]) + + return result + +def main(): + input_dir = Path("/workspace/photos-metadata-restore/input") + output_dir = Path("/workspace/photos-metadata-restore/output") + images_dir = output_dir / "images" + + # 入力ディレクトリの存在確認 + if not input_dir.exists(): + logger.error(f"入力ディレクトリが存在しません: {input_dir}") + return + + # 出力ディレクトリの存在確認 + if not images_dir.exists(): + logger.error(f"画像ディレクトリが存在しません: {images_dir}") + return + + # 画像ファイルのリストを取得 + image_files = [] + for file_path in images_dir.iterdir(): + if file_path.is_file() and file_path.suffix.lower() in {'.jpg', '.jpeg', '.png', '.tiff', '.tif', '.heic', '.heif'}: + image_files.append(file_path) + + logger.info(f"分析対象の画像ファイル数: {len(image_files)}") + + # メタデータを分析 + metadata = {} + success_count = 0 + error_count = 0 + + for i, image_path in enumerate(image_files): + try: + print(f"分析中: {i+1}/{len(image_files)} - {image_path.name}") + filename = image_path.name + metadata[filename] = analyze_image_metadata(image_path) + success_count += 1 + + except Exception as e: + logger.error(f"メタデータ分析エラー {image_path}: {e}") + error_count += 1 + + # metadata.jsonを保存(共通規格に従う) + metadata_file = output_dir / "metadata.json" + with open(metadata_file, 'w', encoding='utf-8') as f: + json.dump(metadata, f, ensure_ascii=False, indent=2) + + # 統計情報を表示 + datetime_count = sum(1 for data in metadata.values() if data.get("has_datetime")) + location_count = sum(1 for data in metadata.values() if data.get("has_location")) + + logger.info(f"処理完了: 成功 {success_count}件, エラー {error_count}件") + logger.info(f"日時情報あり: {datetime_count}件") + logger.info(f"位置情報あり: {location_count}件") + logger.info(f"metadata.jsonを保存しました: {metadata_file}") + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/photos-metadata-restore/3_find_metadata_file.py b/photos-metadata-restore/3_find_metadata_file.py new file mode 100644 index 0000000..9064363 --- /dev/null +++ b/photos-metadata-restore/3_find_metadata_file.py @@ -0,0 +1,167 @@ +#!/usr/bin/env python3 +""" +メタデータファイルを検索するスクリプト +共通規格に従って実装 +""" + +import json +import os +from pathlib import Path +import logging + +# ログ設定 +logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') +logger = logging.getLogger(__name__) + +def find_metadata_files(original_path): + """元のファイルパスからメタデータファイルを検索""" + original_path = Path(original_path) + parent_dir = original_path.parent + base_name = original_path.stem + + # 検索するメタデータファイルのパターン + metadata_patterns = [ + f"{base_name}.json", + f"{base_name}.supplemental-metadata.json", + f"{base_name}.metadata.json", + f"{base_name}.exif.json", + f"{base_name}.sidecar.json" + ] + + found_files = [] + + # 同じディレクトリで検索 + for pattern in metadata_patterns: + metadata_path = parent_dir / pattern + if metadata_path.exists(): + found_files.append({ + "path": str(metadata_path), + "type": "same_directory", + "pattern": pattern + }) + + # 親ディレクトリで検索 + for pattern in metadata_patterns: + metadata_path = parent_dir.parent / pattern + if metadata_path.exists(): + found_files.append({ + "path": str(metadata_path), + "type": "parent_directory", + "pattern": pattern + }) + + # 兄弟ディレクトリで検索(.jsonディレクトリなど) + for sibling_dir in parent_dir.iterdir(): + if sibling_dir.is_dir() and sibling_dir.name.endswith('.json'): + for pattern in metadata_patterns: + metadata_path = sibling_dir / pattern + if metadata_path.exists(): + found_files.append({ + "path": str(metadata_path), + "type": "sibling_directory", + "pattern": pattern + }) + + return found_files + +def analyze_metadata_file(metadata_path): + """メタデータファイルの内容を分析""" + try: + with open(metadata_path, 'r', encoding='utf-8') as f: + content = f.read() + + # JSONとして解析を試行 + try: + data = json.loads(content) + return { + "format": "json", + "size": len(content), + "keys": list(data.keys()) if isinstance(data, dict) else None + } + except json.JSONDecodeError: + return { + "format": "text", + "size": len(content), + "preview": content[:200] + "..." if len(content) > 200 else content + } + except Exception as e: + return { + "format": "error", + "error": str(e) + } + +def main(): + output_dir = Path("/workspace/photos-metadata-restore/output") + pair_file = output_dir / "pair.json" + + # pair.jsonの存在確認 + if not pair_file.exists(): + logger.error(f"pair.jsonが存在しません: {pair_file}") + return + + # pair.jsonを読み込み + with open(pair_file, 'r', encoding='utf-8') as f: + pair_data = json.load(f) + + logger.info(f"処理対象のファイル数: {len(pair_data)}") + + # メタデータファイルを検索 + metadata_location = {} + success_count = 0 + error_count = 0 + + for i, item in enumerate(pair_data): + try: + print(f"検索中: {i+1}/{len(pair_data)} - {item.get('filename', 'unknown')}") + filename = item["filename"] + original_source = item["source"] + + # メタデータファイルを検索 + found_files = find_metadata_files(original_source) + + if found_files: + # 最初に見つかったファイルを使用 + metadata_file = found_files[0] + metadata_path = Path(metadata_file["path"]) + + # メタデータファイルの分析 + analysis = analyze_metadata_file(metadata_path) + + # 共通規格に従う + metadata_location[filename] = { + "original_source": original_source, + "metadata_file": str(metadata_path), + "metadata_type": metadata_file["pattern"], + "found": True, + "file_exists": True + } + else: + # 共通規格に従う + metadata_location[filename] = { + "original_source": original_source, + "metadata_file": None, + "metadata_type": None, + "found": False, + "file_exists": False + } + + success_count += 1 + + except Exception as e: + logger.error(f"メタデータファイル検索エラー {item.get('filename', 'unknown')}: {e}") + error_count += 1 + + # metadata_location.jsonを保存(共通規格に従う) + location_file = output_dir / "metadata_location.json" + with open(location_file, 'w', encoding='utf-8') as f: + json.dump(metadata_location, f, ensure_ascii=False, indent=2) + + # 統計情報を表示 + found_count = sum(1 for data in metadata_location.values() if data.get("found")) + + logger.info(f"処理完了: 成功 {success_count}件, エラー {error_count}件") + logger.info(f"メタデータファイル発見: {found_count}件") + logger.info(f"metadata_location.jsonを保存しました: {location_file}") + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/photos-metadata-restore/4_restore_metadata.py b/photos-metadata-restore/4_restore_metadata.py new file mode 100644 index 0000000..c65184b --- /dev/null +++ b/photos-metadata-restore/4_restore_metadata.py @@ -0,0 +1,298 @@ +#!/usr/bin/env python3 +""" +メタデータを復元するスクリプト +""" + +import json +import os +from pathlib import Path +from datetime import datetime +import logging + +# ログ設定 +logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') +logger = logging.getLogger(__name__) + +# PILの代替として、基本的な画像処理のみ実装 +try: + from PIL import Image + from PIL.ExifTags import TAGS, GPSTAGS + import piexif + PIL_AVAILABLE = True +except ImportError: + PIL_AVAILABLE = False + logger.warning("PIL/piexifが利用できません。メタデータの復元は制限されます。") + +def load_json_file(file_path): + """JSONファイルを読み込み""" + try: + with open(file_path, 'r', encoding='utf-8') as f: + return json.load(f) + except Exception as e: + logger.error(f"JSONファイル読み込みエラー {file_path}: {e}") + return None + +def parse_datetime_string(dt_str): + """日時文字列をパース""" + if not dt_str: + return None + + # ISO形式の日時をパース + try: + return datetime.fromisoformat(dt_str.replace('Z', '+00:00')) + except ValueError: + # EXIF形式の日時をパース + try: + return datetime.strptime(dt_str, "%Y:%m:%d %H:%M:%S") + except ValueError: + logger.warning(f"日時パースエラー: {dt_str}") + return None + +def create_gps_ifd(latitude, longitude, altitude=None): + """GPS情報のIFDを作成""" + def deg_to_dms(deg): + d = int(deg) + m = int((deg - d) * 60) + s = (deg - d - m/60) * 3600 + return (d, 1), (m, 1), (int(s * 100), 100) + + lat_dms = deg_to_dms(abs(latitude)) + lon_dms = deg_to_dms(abs(longitude)) + + gps_ifd = { + piexif.GPSIFD.GPSLatitudeRef: 'S' if latitude < 0 else 'N', + piexif.GPSIFD.GPSLatitude: lat_dms, + piexif.GPSIFD.GPSLongitudeRef: 'W' if longitude < 0 else 'E', + piexif.GPSIFD.GPSLongitude: lon_dms, + } + + if altitude is not None: + gps_ifd[piexif.GPSIFD.GPSAltitude] = (int(altitude * 100), 100) + gps_ifd[piexif.GPSIFD.GPSAltitudeRef] = 0 # 0 = above sea level + + return gps_ifd + +def restore_metadata_to_image(image_path, metadata_info, pair_info, location_info): + """画像にメタデータを復元""" + if not PIL_AVAILABLE: + return { + "datetime_restored": False, + "location_restored": False, + "success": False, + "reason": "PIL/piexifが利用できません" + } + + try: + # 画像を開く + image = Image.open(image_path) + + # 既存のEXIFデータを取得 + exif_dict = piexif.load(image.info.get('exif', b'')) if image.info.get('exif') else {'0th': {}, 'Exif': {}, 'GPS': {}, '1st': {}, 'thumbnail': None} + + # 日時情報の復元 + datetime_restored = False + if metadata_info.get("datetime"): + datetime_data = metadata_info["datetime"] + + # 最適な日時を選択 + best_datetime = None + for key in ['exif_datetime_original', 'exif_datetime', 'json_datetime', 'file_creation_time']: + if key in datetime_data and datetime_data[key]: + best_datetime = parse_datetime_string(datetime_data[key]) + if best_datetime: + break + + if best_datetime: + # EXIF日時フォーマットに変換 + exif_datetime = best_datetime.strftime("%Y:%m:%d %H:%M:%S") + exif_dict['0th'][piexif.ImageIFD.DateTime] = exif_datetime + exif_dict['Exif'][piexif.ExifIFD.DateTimeOriginal] = exif_datetime + exif_dict['Exif'][piexif.ExifIFD.DateTimeDigitized] = exif_datetime + datetime_restored = True + + # 位置情報の復元 + location_restored = False + if metadata_info.get("location") and metadata_info["location"].get("latitude") and metadata_info["location"].get("longitude"): + lat = metadata_info["location"]["latitude"] + lon = metadata_info["location"]["longitude"] + alt = metadata_info["location"].get("altitude") + + gps_ifd = create_gps_ifd(lat, lon, alt) + exif_dict['GPS'] = gps_ifd + location_restored = True + + # メタデータファイルからの追加情報 + if location_info.get("found") and location_info.get("metadata_file"): + metadata_file_path = Path(location_info["metadata_file"]) + if metadata_file_path.exists(): + try: + with open(metadata_file_path, 'r', encoding='utf-8') as f: + external_metadata = json.load(f) + + # 外部メタデータから追加の日時情報を取得 + if not datetime_restored and isinstance(external_metadata, dict): + for key, value in external_metadata.items(): + if 'datetime' in key.lower() and isinstance(value, str): + dt = parse_datetime_string(value) + if dt: + exif_datetime = dt.strftime("%Y:%m:%d %H:%M:%S") + exif_dict['0th'][piexif.ImageIFD.DateTime] = exif_datetime + exif_dict['Exif'][piexif.ExifIFD.DateTimeOriginal] = exif_datetime + datetime_restored = True + break + + # 外部メタデータから追加の位置情報を取得 + if not location_restored and isinstance(external_metadata, dict): + if 'latitude' in external_metadata and 'longitude' in external_metadata: + lat = external_metadata['latitude'] + lon = external_metadata['longitude'] + alt = external_metadata.get('altitude') + + gps_ifd = create_gps_ifd(lat, lon, alt) + exif_dict['GPS'] = gps_ifd + location_restored = True + + except Exception as e: + logger.warning(f"外部メタデータ読み込みエラー {metadata_file_path}: {e}") + + # EXIFデータを画像に埋め込み + if datetime_restored or location_restored: + exif_bytes = piexif.dump(exif_dict) + + # 画像を保存(EXIFデータ付き) + if image.mode in ('RGBA', 'LA'): + image = image.convert('RGB') + + image.save(image_path, exif=exif_bytes, quality=95) + + return { + "datetime_restored": datetime_restored, + "location_restored": location_restored, + "success": True + } + else: + return { + "datetime_restored": False, + "location_restored": False, + "success": False, + "reason": "復元可能なメタデータなし" + } + + except Exception as e: + logger.error(f"メタデータ復元エラー {image_path}: {e}") + return { + "datetime_restored": False, + "location_restored": False, + "success": False, + "reason": f"エラー: {str(e)}" + } + +def main(): + output_dir = Path("/workspace/photos-metadata-restore/output") + images_dir = output_dir / "images" + + # 必要なファイルの存在確認 + pair_file = output_dir / "pair.json" + metadata_file = output_dir / "metadata.json" + location_file = output_dir / "metadata_location.json" + + for file_path in [pair_file, metadata_file, location_file]: + if not file_path.exists(): + logger.error(f"必要なファイルが存在しません: {file_path}") + return + + # データを読み込み + pair_data = load_json_file(pair_file) + metadata_data = load_json_file(metadata_file) + location_data = load_json_file(location_file) + + if not all([pair_data, metadata_data, location_data]): + logger.error("データの読み込みに失敗しました") + return + + logger.info(f"処理対象の画像ファイル数: {len(pair_data)}") + + # メタデータを復元 + results = [] + success_count = 0 + error_count = 0 + datetime_restored_count = 0 + location_restored_count = 0 + + for i, item in enumerate(pair_data): + try: + print(f"復元中: {i+1}/{len(pair_data)} - {item.get('filename', 'unknown')}") + filename = item["filename"] + image_path = images_dir / filename + + if not image_path.exists(): + logger.warning(f"画像ファイルが存在しません: {image_path}") + error_count += 1 + continue + + # メタデータ情報を取得 + metadata_info = metadata_data.get(filename, {}) + location_info = location_data.get(filename, {}) + + # メタデータを復元 + result = restore_metadata_to_image(image_path, metadata_info, item, location_info) + result["filename"] = filename + result["image_path"] = str(image_path) + results.append(result) + + if result["success"]: + success_count += 1 + if result["datetime_restored"]: + datetime_restored_count += 1 + if result["location_restored"]: + location_restored_count += 1 + else: + error_count += 1 + + except Exception as e: + logger.error(f"処理エラー {item.get('filename', 'unknown')}: {e}") + error_count += 1 + + # 結果を保存 + result_file = output_dir / "result.txt" + with open(result_file, 'w', encoding='utf-8') as f: + f.write("メタデータ復元結果\n") + f.write("=" * 50 + "\n\n") + f.write(f"総処理数: {len(pair_data)}\n") + f.write(f"成功: {success_count}\n") + f.write(f"エラー: {error_count}\n") + f.write(f"日時復元: {datetime_restored_count}\n") + f.write(f"位置復元: {location_restored_count}\n\n") + + # 失敗したファイルの詳細 + failed_files = [r for r in results if not r["success"]] + if failed_files: + f.write("失敗したファイル:\n") + f.write("-" * 30 + "\n") + for result in failed_files: + f.write(f"ファイル: {result['filename']}\n") + f.write(f"理由: {result.get('reason', '不明')}\n") + f.write(f"パス: {result['image_path']}\n\n") + + # 部分的な復元 + partial_restored = [r for r in results if r["success"] and (not r["datetime_restored"] or not r["location_restored"])] + if partial_restored: + f.write("部分的な復元(情報が不足):\n") + f.write("-" * 30 + "\n") + for result in partial_restored: + f.write(f"ファイル: {result['filename']}\n") + f.write(f"日時復元: {result['datetime_restored']}\n") + f.write(f"位置復元: {result['location_restored']}\n") + f.write(f"パス: {result['image_path']}\n\n") + + # 統計情報を表示 + logger.info(f"処理完了:") + logger.info(f" 総処理数: {len(pair_data)}") + logger.info(f" 成功: {success_count}") + logger.info(f" エラー: {error_count}") + logger.info(f" 日時復元: {datetime_restored_count}") + logger.info(f" 位置復元: {location_restored_count}") + logger.info(f"結果ファイルを保存しました: {result_file}") + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/photos-metadata-restore/input/test.jpg b/photos-metadata-restore/input/test.jpg new file mode 100644 index 0000000..5f33bc5 --- /dev/null +++ b/photos-metadata-restore/input/test.jpg @@ -0,0 +1 @@ +test image diff --git a/photos-metadata-restore/input/test.txt b/photos-metadata-restore/input/test.txt new file mode 100644 index 0000000..9daeafb --- /dev/null +++ b/photos-metadata-restore/input/test.txt @@ -0,0 +1 @@ +test diff --git a/photos-metadata-restore/input/test2.png b/photos-metadata-restore/input/test2.png new file mode 100644 index 0000000..138a9a1 --- /dev/null +++ b/photos-metadata-restore/input/test2.png @@ -0,0 +1 @@ +test image2 diff --git a/photos-metadata-restore/output/images/065ee0b246f2a96f175035e9111acdab.png b/photos-metadata-restore/output/images/065ee0b246f2a96f175035e9111acdab.png new file mode 100644 index 0000000..138a9a1 --- /dev/null +++ b/photos-metadata-restore/output/images/065ee0b246f2a96f175035e9111acdab.png @@ -0,0 +1 @@ +test image2 diff --git a/photos-metadata-restore/output/images/ac603c30f7903706da12ecf99e57f4f3.jpg b/photos-metadata-restore/output/images/ac603c30f7903706da12ecf99e57f4f3.jpg new file mode 100644 index 0000000..5f33bc5 --- /dev/null +++ b/photos-metadata-restore/output/images/ac603c30f7903706da12ecf99e57f4f3.jpg @@ -0,0 +1 @@ +test image diff --git a/photos-metadata-restore/output/metadata.json b/photos-metadata-restore/output/metadata.json new file mode 100644 index 0000000..58f09b5 --- /dev/null +++ b/photos-metadata-restore/output/metadata.json @@ -0,0 +1,20 @@ +{ + "ac603c30f7903706da12ecf99e57f4f3.jpg": { + "datetime": { + "file_creation_time": "2025-09-25T01:52:08.202816" + }, + "location": {}, + "has_datetime": true, + "has_location": false, + "metadata_sources": [] + }, + "065ee0b246f2a96f175035e9111acdab.png": { + "datetime": { + "file_creation_time": "2025-09-25T01:52:08.202816" + }, + "location": {}, + "has_datetime": true, + "has_location": false, + "metadata_sources": [] + } +} \ No newline at end of file diff --git a/photos-metadata-restore/output/metadata_location.json b/photos-metadata-restore/output/metadata_location.json new file mode 100644 index 0000000..cc23cc9 --- /dev/null +++ b/photos-metadata-restore/output/metadata_location.json @@ -0,0 +1,16 @@ +{ + "ac603c30f7903706da12ecf99e57f4f3.jpg": { + "original_source": "/workspace/photos-metadata-restore/input/test.jpg", + "metadata_file": null, + "metadata_type": null, + "found": false, + "file_exists": false + }, + "065ee0b246f2a96f175035e9111acdab.png": { + "original_source": "/workspace/photos-metadata-restore/input/test2.png", + "metadata_file": null, + "metadata_type": null, + "found": false, + "file_exists": false + } +} \ No newline at end of file diff --git a/photos-metadata-restore/output/pair.json b/photos-metadata-restore/output/pair.json new file mode 100644 index 0000000..618d490 --- /dev/null +++ b/photos-metadata-restore/output/pair.json @@ -0,0 +1,14 @@ +[ + { + "source": "/workspace/photos-metadata-restore/input/test.jpg", + "destination": "/workspace/photos-metadata-restore/output/images/ac603c30f7903706da12ecf99e57f4f3.jpg", + "filename": "ac603c30f7903706da12ecf99e57f4f3.jpg", + "hash": "ac603c30f7903706da12ecf99e57f4f3" + }, + { + "source": "/workspace/photos-metadata-restore/input/test2.png", + "destination": "/workspace/photos-metadata-restore/output/images/065ee0b246f2a96f175035e9111acdab.png", + "filename": "065ee0b246f2a96f175035e9111acdab.png", + "hash": "065ee0b246f2a96f175035e9111acdab" + } +] \ No newline at end of file diff --git a/photos-metadata-restore/output/result.txt b/photos-metadata-restore/output/result.txt new file mode 100644 index 0000000..7d1ca23 --- /dev/null +++ b/photos-metadata-restore/output/result.txt @@ -0,0 +1,19 @@ +メタデータ復元結果 +================================================== + +総処理数: 2 +成功: 0 +エラー: 2 +日時復元: 0 +位置復元: 0 + +失敗したファイル: +------------------------------ +ファイル: ac603c30f7903706da12ecf99e57f4f3.jpg +理由: PIL/piexifが利用できません +パス: /workspace/photos-metadata-restore/output/images/ac603c30f7903706da12ecf99e57f4f3.jpg + +ファイル: 065ee0b246f2a96f175035e9111acdab.png +理由: PIL/piexifが利用できません +パス: /workspace/photos-metadata-restore/output/images/065ee0b246f2a96f175035e9111acdab.png +