Session.py 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128
  1. from datetime import datetime
  2. import pickle
  3. import subprocess
  4. from warnings import warn
  5. import os
  6. from tqdm import tqdm
  7. from py.FileUtils import list_folders, list_jpegs_recursive, verify_expected_subfolders
  8. from py.ImageUtils import get_image_date
  9. class Session:
  10. def __init__(self, folder: str):
  11. self.folder = folder
  12. # session name = folder name[33:], the first 33 characters are always the same
  13. self.name = os.path.basename(folder)[33:]
  14. print(f"Session '{self.name}' at folder: {self.folder}")
  15. assert self.name != ""
  16. verify_expected_subfolders(self.folder)
  17. self.scanned = False
  18. # maps lapse files to their exif dates (for statistic and prediction purposes)
  19. self.lapse_dates = {}
  20. # maps motion files to their exif dates (for statistic purposes)
  21. self.motion_dates = {}
  22. # maps exif dates to lapse files (for prediction purposes)
  23. self.lapse_map = {}
  24. self.load_scans()
  25. if not self.scanned:
  26. print("Session not scanned. Run session.scan() to create scan files")
  27. def load_scans(self):
  28. lapse_dates_file = os.path.join("session_scans", self.name, "lapse_dates.pickle")
  29. motion_dates_file = os.path.join("session_scans", self.name, "motion_dates.pickle")
  30. lapse_map_file = os.path.join("session_scans", self.name, "lapse_map.pickle")
  31. lapse_dates_exists = os.path.isfile(lapse_dates_file)
  32. motion_dates_exists = os.path.isfile(motion_dates_file)
  33. lapse_map_exists = os.path.isfile(lapse_map_file)
  34. if lapse_dates_exists and motion_dates_exists and lapse_map_exists:
  35. with open(lapse_dates_file, "rb") as handle:
  36. self.lapse_dates = pickle.load(handle)
  37. with open(motion_dates_file, "rb") as handle:
  38. self.motion_dates = pickle.load(handle)
  39. with open(lapse_map_file, "rb") as handle:
  40. self.lapse_map = pickle.load(handle)
  41. self.scanned = True
  42. print("Loaded scans.")
  43. else:
  44. if not (not lapse_dates_exists and not motion_dates_exists and not lapse_map_exists):
  45. warn(f"Warning: Only partial scan data available. Not loading.")
  46. self.scanned = False
  47. def save_scans(self):
  48. os.makedirs(os.path.join("session_scans", self.name), exist_ok=True)
  49. lapse_dates_file = os.path.join("session_scans", self.name, "lapse_dates.pickle")
  50. motion_dates_file = os.path.join("session_scans", self.name, "motion_dates.pickle")
  51. lapse_map_file = os.path.join("session_scans", self.name, "lapse_map.pickle")
  52. with open(lapse_dates_file, "wb") as handle:
  53. pickle.dump(self.lapse_dates, handle, protocol=pickle.HIGHEST_PROTOCOL)
  54. print(f"Saved {lapse_dates_file}")
  55. with open(motion_dates_file, "wb") as handle:
  56. pickle.dump(self.motion_dates, handle, protocol=pickle.HIGHEST_PROTOCOL)
  57. print(f"Saved {motion_dates_file}")
  58. with open(lapse_map_file, "wb") as handle:
  59. pickle.dump(self.lapse_map, handle, protocol=pickle.HIGHEST_PROTOCOL)
  60. print(f"Saved {lapse_map_file}")
  61. def scan(self, force=False, auto_save=True):
  62. if self.scanned and not force:
  63. raise ValueError("Session is already scanned. Use force=True to scan anyway and override scan progress.")
  64. # Scan motion dates
  65. print("Scanning motion dates...")
  66. self.motion_dates = {}
  67. motion_folder = os.path.join(self.folder, "Motion")
  68. for file in tqdm(list_jpegs_recursive(motion_folder)):
  69. self.motion_dates[os.path.relpath(file, motion_folder)] = get_image_date(file)
  70. # Scan lapse dates
  71. print("Scanning lapse dates...")
  72. self.lapse_dates = {}
  73. lapse_folder = os.path.join(self.folder, "Lapse")
  74. for file in tqdm(list_jpegs_recursive(lapse_folder)):
  75. self.lapse_dates[os.path.relpath(file, lapse_folder)] = get_image_date(file)
  76. # Create lapse map
  77. print("Creating lapse map...")
  78. self.lapse_map = {}
  79. for file, date in self.lapse_dates.items():
  80. if date in self.lapse_map:
  81. self.lapse_map[date].append(file)
  82. else:
  83. self.lapse_map[date] = [file]
  84. self.scanned = True
  85. # Auto save
  86. if auto_save:
  87. print("Saving...")
  88. self.save_scans()
  89. def check_lapse_duplicates(self) -> bool:
  90. total = 0
  91. total_duplicates = 0
  92. total_multiples = 0
  93. deviant_duplicates = []
  94. for date, files in tqdm(self.lapse_map.items()):
  95. total += 1
  96. if len(files) > 1:
  97. total_duplicates += 1
  98. file_size = -1
  99. for f in files:
  100. f_size = os.path.getsize(os.path.join(self.folder, "Lapse", f))
  101. if file_size == -1:
  102. file_size = f_size
  103. elif f_size != file_size:
  104. deviant_duplicates.append(date)
  105. break
  106. if len(files) > 2:
  107. total_multiples += 1
  108. deviant_duplicates.sort()
  109. print(f"* {total} lapse dates")
  110. print(f"* {total_duplicates} duplicates")
  111. print(f"* {total_multiples} multiples (more than two files per date)")
  112. print(f"* {len(deviant_duplicates)} deviant duplicates: {deviant_duplicates}")
  113. return total, total_duplicates, total_multiples, deviant_duplicates
  114. def open_images_for_date(self, date: datetime):
  115. img_names = self.lapse_map.get(date, [])
  116. if len(img_names) == 0:
  117. warn("No images for this date!")
  118. for i, img_name in enumerate(img_names):
  119. full_path = os.path.join(self.folder, "Lapse", img_name)
  120. print(f"#{i+1} {full_path}")
  121. subprocess.call(("xdg-open", full_path))