import os
import subprocess
import shutil
import time
import hashlib
import ctypes
from datetime import datetime, timedelta
from concurrent.futures import ThreadPoolExecutor
# --- CONFIGURATION ---
mobile_file_search_exe = r"D:\stockage\temp\nircmd\MobileFileSearch.exe"
final_folder = r"F:\android\photo\camera"
temp_folder = r"F:\android\temp_photos"
photo_extensions = ('.mov', '.jpg', '.jpeg', '.png', '.heic', '.dng', '.aae', '.mp4', '.m4v')
sync_history_file = os.path.join(final_folder, "sync_history.txt")
last_sync_file = os.path.join(final_folder, "last_sync.txt")
# --- ANTI-VEILLE WINDOWS ---
def prevent_sleep():
ctypes.windll.kernel32.SetThreadExecutionState(0x80000000 | 0x00000001 | 0x00000002)
def allow_sleep():
ctypes.windll.kernel32.SetThreadExecutionState(0x80000000)
# --- DATE DE DERNIÈRE SYNC ---
def get_last_sync_date():
if os.path.exists(last_sync_file):
try:
with open(last_sync_file, "r") as f:
return datetime.strptime(f.read().strip(), "%Y-%m-%d %H:%M:%S")
except:
pass
return datetime.now() - timedelta(days=30)
def save_last_sync_date():
with open(last_sync_file, "w") as f:
f.write(datetime.now().strftime("%Y-%m-%d %H:%M:%S"))
# --- FONCTIONS UTILITAIRES ---
def get_file_hash(filepath):
hasher = hashlib.md5()
try:
with open(filepath, 'rb') as f:
data = f.read(1024 * 1024)
if data:
hasher.update(data)
return hasher.hexdigest()
except:
return None
def load_sync_history():
history = set()
if os.path.exists(sync_history_file):
try:
with open(sync_history_file, "r", encoding="utf-8") as f:
for line in f:
history.add(line.strip())
except:
pass
return history
def save_sync_history(history):
try:
with open(sync_history_file, "w", encoding="utf-8") as f:
for entry in sorted(history):
f.write(entry + "\n")
except:
pass
def get_file_signature(filepath):
try:
name = os.path.basename(filepath)
size = os.path.getsize(filepath)
file_hash = get_file_hash(filepath)
return f"{name}|{size}|{file_hash}"
except:
return None
def get_all_temp_files():
if not os.path.exists(temp_folder):
return []
all_files = []
for root, dirs, files in os.walk(temp_folder):
for f in files:
if f.lower().endswith(photo_extensions):
all_files.append(os.path.join(root, f))
return all_files
def cleanup_temp():
if os.path.exists(temp_folder):
try:
shutil.rmtree(temp_folder)
except:
pass
os.makedirs(temp_folder, exist_ok=True)
def check_iphone_connected():
try:
result = subprocess.run(
['wmic', 'path', 'Win32_PnPEntity', 'where',
"Caption like '%Apple%' or Caption like '%iPhone%'",
'get', 'Caption'],
capture_output=True, text=True, timeout=5,
creationflags=subprocess.CREATE_NO_WINDOW
)
return 'iPhone' in result.stdout or 'Apple Mobile' in result.stdout
except:
return False
# --- TRANSFERT PARALLÈLE ---
def copy_file_worker(args):
source_file, final_dir, history = args
file_name = os.path.basename(source_file)
try:
signature = get_file_signature(source_file)
if signature and signature in history:
os.remove(source_file)
return ("skip", file_name)
dest_path = os.path.join(final_dir, file_name)
if os.path.exists(dest_path):
base, ext = os.path.splitext(file_name)
counter = 1
while os.path.exists(dest_path):
dest_path = os.path.join(final_dir, f"{base}_{counter}{ext}")
counter += 1
shutil.move(source_file, dest_path)
return ("ok", file_name, signature)
except Exception as e:
return ("error", file_name, str(e))
def sync_photos(temp_dir, final_dir):
os.makedirs(final_dir, exist_ok=True)
files = get_all_temp_files()
if not files:
return 0
history = load_sync_history()
copied = 0
skipped = 0
print(f"\n📁 Traitement de {len(files)} fichiers (8 threads)...")
tasks = [(f, final_dir, history) for f in files]
with ThreadPoolExecutor(max_workers=8) as executor:
results = list(executor.map(copy_file_worker, tasks))
for result in results:
if result[0] == "ok":
history.add(result[2])
copied += 1
print(f"✅ {result[1]}")
elif result[0] == "skip":
skipped += 1
save_sync_history(history)
if copied > 0 or skipped > 0:
print(f"\n📊 {copied} copiés, {skipped} ignorés")
return copied
# --- MODES DE RECHERCHE ---
def wait_for_files(timeout=60, stable_seconds=3):
last_count = 0
stable_time = 0
for _ in range(timeout):
files = get_all_temp_files()
count = len(files)
if count > 0:
if count == last_count:
stable_time += 1
if stable_time >= stable_seconds:
print(f"✅ {count} fichiers prêts")
return True
else:
stable_time = 0
print(f"\r 📥 {count} fichiers...", end="", flush=True)
last_count = count
time.sleep(1)
print()
return len(get_all_temp_files()) > 0
def launch_search_ultra():
"""
Mode ULTRA-RAPIDE :
- Uniquement *.JPG
- 30 jours max
"""
cleanup_temp()
try:
cmd = [
mobile_file_search_exe,
"/ScanSubfolders", "1",
"/ModifiedTime.FindMode", "6",
"/ModifiedTime.LastXX", "30",
"/CopyFiles.Folder", temp_folder,
"/CopyFiles.PreserveTime", "1",
"/CopyFiles",
"/Wildcard", "*.JPG"
]
print("⚡ Scan ULTRA-RAPIDE (30 jours, *.JPG uniquement)...")
subprocess.run(
cmd,
capture_output=True,
text=True,
timeout=90,
creationflags=subprocess.CREATE_NO_WINDOW
)
return wait_for_files(timeout=45, stable_seconds=2)
except subprocess.TimeoutExpired:
print("⏱️ Timeout")
return len(get_all_temp_files()) > 0
except Exception as e:
print(f"❌ Erreur: {e}")
return False
def launch_search_complet():
"""
Mode COMPLET :
- TOUS les fichiers
- TOUTE la période (365 jours)
"""
cleanup_temp()
try:
cmd = [
mobile_file_search_exe,
"/ScanSubfolders", "1",
"/ModifiedTime.FindMode", "6",
"/ModifiedTime.LastXX", "365",
"/CopyFiles.Folder", temp_folder,
"/CopyFiles.PreserveTime", "1",
"/CopyFiles"
# Pas de /Wildcard = tout copier
]
print("🔎 Scan COMPLET (365 jours, TOUS fichiers)...")
subprocess.run(
cmd,
capture_output=True,
text=True,
timeout=600,
creationflags=subprocess.CREATE_NO_WINDOW
)
return wait_for_files(timeout=120, stable_seconds=5)
except subprocess.TimeoutExpired:
print("⏱️ Timeout")
return len(get_all_temp_files()) > 0
except Exception as e:
print(f"❌ Erreur: {e}")
return False
# --- MAIN ---
def main():
os.makedirs(temp_folder, exist_ok=True)
os.makedirs(final_folder, exist_ok=True)
last_sync = get_last_sync_date()
print(f"""
╔═══════════════════════════════════════════════════════════════════════╗
║ 📱 SYNCHRONISATION iPHONE → PC ║
╠═══════════════════════════════════════════════════════════════════════╣
║ 📅 Dernière sync : {last_sync.strftime('%d/%m/%Y %H:%M'):<48} ║
╠═══════════════════════════════════════════════════════════════════════╣
║ ║
║ [1] ⚡ ULTRA-RAPIDE ║
║ • Fichiers *.JPG uniquement ║
║ • 30 derniers jours ║
║ • Très rapide pour sync quotidienne ║
║ ║
║ [2] 🔎 COMPLET ║
║ • TOUS les fichiers (JPG, HEIC, MOV, PNG, AAE...) ║
║ • 365 jours ║
║ • Plus lent, mais récupère tout ║
║ ║
╚═══════════════════════════════════════════════════════════════════════╝
""")
# Choix du mode
while True:
choice = input(" Votre choix [1/2] : ").strip()
if choice in ('1', '2'):
break
print(" ❌ Tapez 1 ou 2")
if choice == '1':
mode_name = "ULTRA-RAPIDE"
search_func = launch_search_ultra
else:
mode_name = "COMPLET"
search_func = launch_search_complet
print(f"\n ✅ Mode {mode_name} sélectionné\n")
# Vérifie iPhone
print("🔌 Vérification iPhone...")
if not check_iphone_connected():
print("""
╔═══════════════════════════════════════════════════════════════════════╗
║ ❌ iPHONE NON DÉTECTÉ ! ║
║ ║
║ • Connectez l'iPhone en USB ║
║ • Déverrouillez l'écran ║
║ • Acceptez "Faire confiance à cet ordinateur" ║
╚═══════════════════════════════════════════════════════════════════════╝
""")
input("Appuyez sur Entrée quand prêt...")
if not check_iphone_connected():
print("❌ Abandon.")
return
print("📱 iPhone détecté !\n")
prevent_sleep()
total_copied = 0
start_time = time.time()
try:
for attempt in range(1, 4):
print(f"{'─'*60}")
print(f"🔄 PASSE {attempt}/3")
print(f"{'─'*60}")
if not check_iphone_connected():
print("⚠️ iPhone déconnecté !")
input(" Reconnectez et appuyez Entrée...")
if not check_iphone_connected():
break
if search_func():
copied = sync_photos(temp_folder, final_folder)
total_copied += copied
if copied == 0:
print("\n✅ Synchronisation complète !")
break
else:
if attempt < 3:
print(" Nouvelle tentative dans 3s...")
time.sleep(3)
# Sauvegarde date
if total_copied > 0:
save_last_sync_date()
# Durée
elapsed = time.time() - start_time
minutes = int(elapsed // 60)
seconds = int(elapsed % 60)
# Résumé
print(f"""
╔═══════════════════════════════════════════════════════════════════════╗
║ 📊 RÉSUMÉ FINAL ║
╠═══════════════════════════════════════════════════════════════════════╣
║ Mode : {mode_name:<47} ║
║ Fichiers copiés : {total_copied:<47} ║
║ Durée : {minutes}m {seconds}s{' ' * (44 - len(f"{minutes}m {seconds}s"))} ║
║ Destination : {final_folder:<47} ║
╚═══════════════════════════════════════════════════════════════════════╝
""")
if total_copied > 0:
print("🎉 Vous pouvez déconnecter l'iPhone !")
else:
print("ℹ️ Aucun nouveau fichier.")
finally:
allow_sleep()
cleanup_temp()
if __name__ == "__main__":
main()