Skip to content

Commit 4fbfaf5

Browse files
When processing memories sort media by "year / month / day" folders
1 parent 9e8290f commit 4fbfaf5

1 file changed

Lines changed: 76 additions & 45 deletions

File tree

‎download_snapchat_memories_gui.py‎

Lines changed: 76 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -740,6 +740,21 @@ def get_file_extension(media_type):
740740
else:
741741
return ".bin"
742742

743+
def get_memory_output_dir(output_root, date_obj, create=True):
744+
"""Return the Year/Month/Day subfolder (under output_root) for a memory's local date.
745+
746+
e.g. output_root/2024/01 - January/2024-01-15
747+
"""
748+
day_dir = (
749+
Path(output_root)
750+
/ date_obj.strftime("%Y")
751+
/ date_obj.strftime("%m - %B")
752+
/ date_obj.strftime("%Y-%m-%d")
753+
)
754+
if create:
755+
day_dir.mkdir(parents=True, exist_ok=True)
756+
return day_dir
757+
743758
# ==================== GUI Application ====================
744759

745760
# --- Wrappers to refactored modules (override long original implementations) ---
@@ -1435,7 +1450,7 @@ def cleanup_temp_files(self, output_path):
14351450

14361451
cleaned_count = 0
14371452
for pattern in temp_patterns:
1438-
for temp_file in output_path.glob(pattern):
1453+
for temp_file in output_path.rglob(pattern):
14391454
try:
14401455
temp_file.unlink()
14411456
cleaned_count += 1
@@ -1478,46 +1493,51 @@ def should_skip_download(self, item, output_path, idx, date_obj, date_obj_local,
14781493
# Generate both local and UTC formatted dates for backward compatibility
14791494
date_formatted_local = date_obj_local.strftime("%Y%m%d_%H%M%S")
14801495
date_formatted_utc = date_obj.strftime("%Y%m%d_%H%M%S")
1481-
1482-
# We'll check patterns for both local (preferred) and UTC (legacy) timestamps
1483-
date_patterns = [date_formatted_local]
1496+
1497+
# We'll check patterns for both local (preferred) and UTC (legacy) timestamps,
1498+
# each paired with the date used to derive its Year/Month/Day folder
1499+
date_patterns = [(date_formatted_local, date_obj_local)]
14841500
if date_formatted_utc != date_formatted_local: # Only check UTC if different
1485-
date_patterns.append(date_formatted_utc)
1486-
1501+
date_patterns.append((date_formatted_utc, date_obj))
1502+
14871503
# Check 1: Normal download filename (YYYYMMDD_HHMMSS_idx.ext)
1488-
# Check both local and UTC patterns for backward compatibility
1489-
for date_formatted in date_patterns:
1490-
normal_filename = f"{date_formatted}_{idx}{extension}"
1491-
normal_path = output_path / normal_filename
1492-
if normal_path.exists():
1493-
if validate_downloaded_file(str(normal_path)):
1494-
return True, str(normal_path), "normal download"
1495-
else:
1496-
logging.warning(f"Found invalid existing file, will re-download: {normal_path}")
1497-
return False, None, "invalid file"
1498-
1499-
# Check 2: Merged overlay filename (YYYYMMDD_HHMMSS.ext) - no idx suffix
1500-
# This pattern is created when ZIP files contain -main/-overlay pairs
1501-
merged_base = f"{date_formatted}{extension}"
1502-
merged_path = output_path / merged_base
1503-
if merged_path.exists():
1504-
if validate_downloaded_file(str(merged_path)):
1505-
return True, str(merged_path), "merged overlay"
1506-
else:
1507-
logging.warning(f"Found invalid merged file, will re-download: {merged_path}")
1508-
return False, None, "invalid merged"
1509-
1510-
# Check 3: Collision-resolved merged files (YYYYMMDD_HHMMSS_1.ext, _2.ext, ...)
1511-
# When multiple overlays have the same timestamp, counter suffixes are added
1512-
for count in range(1, 11): # Reasonable upper bound
1513-
collision_name = f"{date_formatted}_{count}{extension}"
1514-
collision_path = output_path / collision_name
1515-
if collision_path.exists():
1516-
if validate_downloaded_file(str(collision_path)):
1517-
return True, str(collision_path), f"collision-resolved merge (_{count})"
1504+
# Check both local and UTC patterns for backward compatibility, in both the
1505+
# current Year/Month/Day folder and the flat root (files from before this
1506+
# folder structure was added)
1507+
for date_formatted, date_for_dir in date_patterns:
1508+
nested_dir = get_memory_output_dir(output_path, date_for_dir, create=False)
1509+
for search_dir in (nested_dir, output_path):
1510+
normal_filename = f"{date_formatted}_{idx}{extension}"
1511+
normal_path = search_dir / normal_filename
1512+
if normal_path.exists():
1513+
if validate_downloaded_file(str(normal_path)):
1514+
return True, str(normal_path), "normal download"
15181515
else:
1519-
logging.warning(f"Found invalid collision file, will re-download: {collision_path}")
1520-
return False, None, "invalid collision"
1516+
logging.warning(f"Found invalid existing file, will re-download: {normal_path}")
1517+
return False, None, "invalid file"
1518+
1519+
# Check 2: Merged overlay filename (YYYYMMDD_HHMMSS.ext) - no idx suffix
1520+
# This pattern is created when ZIP files contain -main/-overlay pairs
1521+
merged_base = f"{date_formatted}{extension}"
1522+
merged_path = search_dir / merged_base
1523+
if merged_path.exists():
1524+
if validate_downloaded_file(str(merged_path)):
1525+
return True, str(merged_path), "merged overlay"
1526+
else:
1527+
logging.warning(f"Found invalid merged file, will re-download: {merged_path}")
1528+
return False, None, "invalid merged"
1529+
1530+
# Check 3: Collision-resolved merged files (YYYYMMDD_HHMMSS_1.ext, _2.ext, ...)
1531+
# When multiple overlays have the same timestamp, counter suffixes are added
1532+
for count in range(1, 11): # Reasonable upper bound
1533+
collision_name = f"{date_formatted}_{count}{extension}"
1534+
collision_path = search_dir / collision_name
1535+
if collision_path.exists():
1536+
if validate_downloaded_file(str(collision_path)):
1537+
return True, str(collision_path), f"collision-resolved merge (_{count})"
1538+
else:
1539+
logging.warning(f"Found invalid collision file, will re-download: {collision_path}")
1540+
return False, None, "invalid collision"
15211541

15221542
# Check 4: Failed conversions directory (use local timezone pattern)
15231543
normal_filename_local = f"{date_formatted_local}_{idx}{extension}"
@@ -1621,11 +1641,12 @@ def progress_callback(message):
16211641
date_obj_local = date_obj
16221642
tz_offset = "+00:00"
16231643

1624-
# Generate filename
1644+
# Generate filename, sorted into a Year/Month/Day folder
16251645
date_formatted = date_obj_local.strftime("%Y%m%d_%H%M%S")
16261646
extension = get_file_extension(media_type)
16271647
filename = f"{date_formatted}_{idx}{extension}"
1628-
file_path = output_path / filename
1648+
media_output_dir = get_memory_output_dir(output_path, date_obj_local)
1649+
file_path = media_output_dir / filename
16291650

16301651
log_local(f" File: {filename}")
16311652
log_local(f" Type: {media_type}")
@@ -2529,6 +2550,7 @@ def process_local_files_thread(self, json_file, memories_root, output_dir):
25292550
)
25302551
date_str_preview = local_dt_skip.strftime("%Y%m%d_%H%M%S")
25312552
except Exception:
2553+
local_dt_skip = utc_mtime
25322554
date_str_preview = utc_mtime.strftime("%Y%m%d_%H%M%S")
25332555

25342556
# Build the full set of outputs this file would produce
@@ -2541,7 +2563,13 @@ def process_local_files_thread(self, json_file, memories_root, output_dir):
25412563
if has_overlay_skip and overlay_mode_skip == "both":
25422564
expected.append(f"{date_str_preview}_{global_idx}_original{ext}")
25432565

2544-
if all((output_path / n).exists() for n in expected):
2566+
# Check the Year/Month/Day folder (current behavior) as well as
2567+
# the flat root (files placed before this folder structure existed)
2568+
media_output_dir_skip = get_memory_output_dir(output_path, local_dt_skip, create=False)
2569+
if all(
2570+
(media_output_dir_skip / n).exists() or (output_path / n).exists()
2571+
for n in expected
2572+
):
25452573
self.log(
25462574
f"[{global_idx}/{grand_total}] ↷ Skipped "
25472575
f"(already exists: {expected[0]})",
@@ -2996,9 +3024,12 @@ def log_local(msg):
29963024
has_overlay = overlay_path and os.path.exists(overlay_path)
29973025
primary_output = None
29983026

3027+
# Sort into a Year/Month/Day folder under output_path
3028+
media_output_dir = get_memory_output_dir(output_path, local_dt)
3029+
29993030
if has_overlay and overlay_mode in ("merge", "both"):
30003031
out_name = f"{date_str}_{idx}{ext}"
3001-
out_file = str(output_path / out_name)
3032+
out_file = str(media_output_dir / out_name)
30023033

30033034
if is_video:
30043035
ok, result = merge_video_overlay(file_path, overlay_path, out_file)
@@ -3012,21 +3043,21 @@ def log_local(msg):
30123043
primary_output = out_file
30133044
else:
30143045
log_local(f" ⚠ Merge failed ({result}), copying original instead")
3015-
out_file = str(output_path / out_name)
3046+
out_file = str(media_output_dir / out_name)
30163047
_copy_file_with_metadata(file_path, out_file, is_video, local_dt,
30173048
latitude, longitude, tz_off_str, log_local)
30183049
primary_output = out_file
30193050

30203051
if overlay_mode == "both":
30213052
orig_name = f"{date_str}_{idx}_original{ext}"
3022-
orig_out = str(output_path / orig_name)
3053+
orig_out = str(media_output_dir / orig_name)
30233054
_copy_file_with_metadata(file_path, orig_out, is_video, local_dt,
30243055
latitude, longitude, tz_off_str, log_local)
30253056
log_local(f" ✓ Saved original → {orig_name}")
30263057

30273058
else:
30283059
out_name = f"{date_str}_{idx}{ext}"
3029-
out_file = str(output_path / out_name)
3060+
out_file = str(media_output_dir / out_name)
30303061
_copy_file_with_metadata(file_path, out_file, is_video, local_dt,
30313062
latitude, longitude, tz_off_str, log_local)
30323063
log_local(f" ✓ → {out_name}")

0 commit comments

Comments
 (0)