Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
146 changes: 146 additions & 0 deletions photos-metadata-restore/1_copy_files.py
Original file line number Diff line number Diff line change
@@ -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()
226 changes: 226 additions & 0 deletions photos-metadata-restore/2_filter_missing_metadata.py
Original file line number Diff line number Diff line change
@@ -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()
Loading