Skip to content

Commit f648283

Browse files
committed
Limpieza de la aplicación
1 parent ee69c9e commit f648283

File tree

2 files changed

+56
-186
lines changed

2 files changed

+56
-186
lines changed

metadata_reader.py

Lines changed: 7 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,19 @@
11
# metadata_reader.py
2-
import piexif
32
import re
4-
from PIL import Image
5-
from PIL.ExifTags import TAGS
6-
from datetime import datetime
7-
from pathlib import Path
83
from datetime import datetime
94
from pathlib import Path
105

116
def get_photo_date(filepath: str) -> tuple[str, str]:
127
"""
13-
Determina la fecha de una foto con la siguiente prioridad:
8+
Determina la fecha de una foto de forma RÁPIDA (Sin leer EXIF).
9+
Prioridad:
1410
1. Patrón en el nombre del archivo (ej: IMG-20250402-...).
15-
2. Metadatos EXIF ('DateTimeOriginal' o 'DateTime').
16-
3. Fecha de modificación del archivo.
17-
Devuelve una tupla (año, mes) o ("Sin Fecha", "00").
11+
2. Fecha de modificación del archivo (sistema de archivos).
12+
Devuelve una tupla (año, mes).
1813
"""
1914
filename = Path(filepath).name
2015

21-
# 1. Buscar patrón en el nombre del archivo
16+
# 1. Buscar patrón en el nombre del archivo (Muy rápido)
2217
match = re.search(r'IMG-(\d{8})', filename)
2318
if match:
2419
date_str = match.group(1)
@@ -27,62 +22,21 @@ def get_photo_date(filepath: str) -> tuple[str, str]:
2722
dt = datetime.strptime(date_str, '%Y%m%d')
2823
return str(dt.year), f"{dt.month:02d}"
2924
except ValueError:
30-
# El patrón existe pero la fecha no es válida, pasamos al siguiente método
3125
pass
3226

33-
# 2. Si no hay patrón en el nombre, leer metadatos EXIF
27+
# 2. Usar fecha de modificación del archivo (stat)
3428
try:
35-
img = Image.open(filepath)
36-
exif_data = img.getexif()
37-
38-
if exif_data:
39-
for tag_id, value in exif_data.items():
40-
if TAGS.get(tag_id) in ['DateTimeOriginal', 'DateTime']:
41-
try:
42-
dt = datetime.strptime(value, '%Y:%m:%d %H:%M:%S')
43-
return str(dt.year), f"{dt.month:02d}"
44-
except (ValueError, TypeError):
45-
pass
46-
47-
# 3. Si no hay EXIF, usar fecha de modificación
4829
stat = Path(filepath).stat()
4930
dt_mod = datetime.fromtimestamp(stat.st_mtime)
5031
return str(dt_mod.year), f"{dt_mod.month:02d}"
51-
5232
except Exception:
5333
return "Sin Fecha", "00"
5434

55-
def get_exif_dict(filepath: str) -> dict:
56-
"""
57-
Lee todos los metadatos EXIF de una imagen usando piexif.
58-
Devuelve un diccionario (puede estar vacío).
59-
"""
60-
try:
61-
exif_dict = piexif.load(filepath)
62-
return exif_dict
63-
except Exception:
64-
# Si no hay EXIF o el formato no es compatible (ej. PNG)
65-
return {}
66-
67-
def save_exif_dict(filepath: str, exif_dict: dict):
68-
"""
69-
Guarda el diccionario de metadatos EXIF en el archivo de imagen.
70-
"""
71-
try:
72-
exif_bytes = piexif.dump(exif_dict)
73-
piexif.insert(exif_bytes, filepath)
74-
print(f"Metadatos guardados en: {filepath}")
75-
except Exception as e:
76-
print(f"Error al guardar metadatos en {filepath}: {e}")
77-
7835
def get_video_date(filepath: str) -> tuple[str, str]:
7936
"""
80-
Determina la fecha de un vídeo.
81-
Por ahora, solo usa la fecha de modificación del archivo.
82-
Devuelve una tupla (año, mes) o ("Sin Fecha", "00").
37+
Determina la fecha de un vídeo usando la fecha de modificación.
8338
"""
8439
try:
85-
# Usar fecha de modificación del archivo
8640
stat = Path(filepath).stat()
8741
dt_mod = datetime.fromtimestamp(stat.st_mtime)
8842
return str(dt_mod.year), f"{dt_mod.month:02d}"

visagevault.py

Lines changed: 49 additions & 133 deletions
Original file line numberDiff line numberDiff line change
@@ -868,9 +868,6 @@ def resizeEvent(self, event):
868868
# =================================================================
869869
# CLASE: PreviewListWidget
870870
# =================================================================
871-
# =================================================================
872-
# CLASE: PreviewListWidget (LIMPIA)
873-
# =================================================================
874871
class PreviewListWidget(QListWidget):
875872
"""
876873
QListWidget personalizado:
@@ -1121,23 +1118,31 @@ def _open_preview(self):
11211118
# -----------------------------------------------------------------
11221119
class PhotoDetailDialog(QDialog):
11231120
metadata_changed = Signal(str, str, str)
1121+
11241122
def _setup_ui(self):
11251123
main_layout = QVBoxLayout(self)
11261124
self.main_splitter = QSplitter(Qt.Horizontal)
11271125
main_layout.addWidget(self.main_splitter)
1126+
1127+
# Panel Izquierdo: Imagen
11281128
self.image_label = ZoomableClickableLabel(self.original_path, self)
11291129
self.image_label.is_thumbnail_view = False
11301130
self.main_splitter.addWidget(self.image_label)
1131+
1132+
# Panel Derecho: Solo fechas (SIN EXIF)
11311133
right_panel_widget = QWidget()
11321134
right_panel_layout = QVBoxLayout(right_panel_widget)
1133-
right_panel_widget.setMinimumWidth(300)
1134-
right_panel_widget.setMaximumWidth(450)
1135-
date_group = QGroupBox("Fecha (Base de Datos)")
1135+
right_panel_widget.setMinimumWidth(250)
1136+
right_panel_widget.setMaximumWidth(350)
1137+
1138+
date_group = QGroupBox("Editar Fecha")
11361139
date_layout = QGridLayout(date_group)
1140+
11371141
date_layout.addWidget(QLabel("Año:"), 0, 0)
11381142
self.year_edit = QLineEdit()
1139-
self.year_edit.setPlaceholderText("Ej: 2024 o 'Sin Fecha'")
1143+
self.year_edit.setPlaceholderText("Ej: 2024")
11401144
date_layout.addWidget(self.year_edit, 0, 1)
1145+
11411146
date_layout.addWidget(QLabel("Mes:"), 1, 0)
11421147
self.month_combo = QComboBox()
11431148
self.month_combo.addItem("Mes Desconocido", "00")
@@ -1146,121 +1151,91 @@ def _setup_ui(self):
11461151
try:
11471152
month_name = datetime.datetime.strptime(month_str, "%m").strftime("%B").capitalize()
11481153
except ValueError:
1149-
month_name = datetime.date(2000, i, 1).strftime("%B").capitalize()
1154+
month_name = str(i)
11501155
self.month_combo.addItem(month_name, month_str)
11511156
date_layout.addWidget(self.month_combo, 1, 1)
1152-
exif_group = QGroupBox("Datos EXIF (Solo Lectura)")
1153-
exif_layout = QVBoxLayout(exif_group)
1154-
self.metadata_table = QTableWidget()
1155-
self.metadata_table.setColumnCount(2)
1156-
self.metadata_table.setHorizontalHeaderLabels(["Campo", "Valor"])
1157-
self.metadata_table.horizontalHeader().setSectionResizeMode(0, QHeaderView.ResizeMode.ResizeToContents)
1158-
self.metadata_table.horizontalHeader().setSectionResizeMode(1, QHeaderView.ResizeMode.Stretch)
1159-
self.metadata_table.setEditTriggers(QAbstractItemView.EditTrigger.NoEditTriggers)
1160-
exif_layout.addWidget(self.metadata_table)
1157+
11611158
right_panel_layout.addWidget(date_group)
1162-
right_panel_layout.addWidget(exif_group, 1)
1159+
right_panel_layout.addStretch(1) # Relleno para empujar hacia arriba
1160+
11631161
self.main_splitter.addWidget(right_panel_widget)
1162+
1163+
# Botones
11641164
self.button_box = QDialogButtonBox(QDialogButtonBox.StandardButton.Save | QDialogButtonBox.StandardButton.Cancel)
11651165
self.button_box.accepted.connect(self._save_metadata)
11661166
self.button_box.rejected.connect(self.reject)
11671167
main_layout.addWidget(self.button_box)
1168-
self.main_splitter.setSizes([int(self.width() * 0.7), int(self.width() * 0.3)])
1168+
1169+
# Ajuste de tamaños (más espacio para la foto)
1170+
self.main_splitter.setSizes([800, 250])
1171+
11691172
def __init__(self, original_path, db_manager: VisageVaultDB, parent=None):
11701173
super().__init__(parent)
11711174
self.original_path = original_path
11721175
self.db = db_manager
1173-
self.exif_dict = {}
1174-
self.date_time_tag_info = None
11751176
self.setWindowTitle(Path(original_path).name)
11761177
self.resize(1000, 800)
1178+
11771179
self._setup_ui()
11781180
self._load_photo()
1179-
self._load_metadata()
1181+
self._load_current_date() # Renombrado de _load_metadata
1182+
11801183
def _load_photo(self):
1184+
# ... (MANTENER EL CÓDIGO DE CARGA DE IMAGEN/RAW IGUAL QUE ANTES) ...
1185+
# (No copies esto, solo deja el método _load_photo tal cual lo tenías)
11811186
try:
1182-
# Definir extensiones RAW (deben coincidir con las de los otros archivos)
1187+
# Definir extensiones RAW
11831188
RAW_EXTENSIONS = ('.nef', '.cr2', '.cr3', '.crw', '.arw', '.srf', '.orf', '.rw2', '.raf', '.pef', '.dng', '.raw')
11841189
file_suffix = Path(self.original_path).suffix.lower()
1185-
pixmap = QPixmap() # Empezar con un pixmap vacío
1190+
pixmap = QPixmap()
11861191

11871192
if file_suffix in RAW_EXTENSIONS:
1188-
# 1. Usar rawpy para leer el archivo RAW
11891193
with rawpy.imread(self.original_path) as raw:
1190-
# postprocess() aplica correcciones de color y devuelve un array numpy (H, W, 3)
11911194
rgb_array = raw.postprocess()
1192-
1193-
# 2. Convertir el array de numpy a QImage
11941195
height, width, channel = rgb_array.shape
11951196
bytes_per_line = 3 * width
1196-
# .copy() es crucial para que QImage tome propiedad de los datos
11971197
q_image = QImage(rgb_array.data, width, height, bytes_per_line, QImage.Format.Format_RGB888).copy()
1198-
1199-
# 3. Convertir QImage a QPixmap
12001198
pixmap = QPixmap.fromImage(q_image)
1201-
12021199
else:
1203-
# 4. Lógica original para JPG, PNG, etc.
12041200
pixmap = QPixmap(self.original_path)
12051201

1206-
if pixmap.isNull():
1207-
raise Exception("QPixmap está vacío después de cargar (formato no soportado o archivo corrupto).")
1208-
12091202
self.image_label.setOriginalPixmap(pixmap)
1210-
12111203
except Exception as e:
1212-
print(f"Error detallado al cargar {self.original_path}: {e}")
1213-
self.image_label.setText(f"Error al cargar imagen: {e}")
1204+
self.image_label.setText(f"Error: {e}")
12141205

1215-
def _load_metadata(self):
1216-
self.exif_dict = metadata_reader.get_exif_dict(self.original_path)
1217-
self.metadata_table.setRowCount(0)
1206+
def _load_current_date(self):
1207+
"""Carga solo la fecha actual de la BD o del archivo."""
12181208
current_year, current_month = self.db.get_photo_date(self.original_path)
1209+
1210+
# Si no está en BD, usar la función simple de metadata_reader
12191211
if current_year is None or current_month is None:
1220-
exif_year, exif_month = metadata_reader.get_photo_date(self.original_path)
1221-
if current_year is None:
1222-
current_year = exif_year
1223-
if current_month is None:
1224-
current_month = exif_month
1212+
current_year, current_month = metadata_reader.get_photo_date(self.original_path)
1213+
12251214
self.year_edit.setText(current_year or "Sin Fecha")
12261215
month_index = self.month_combo.findData(current_month or "00")
12271216
self.month_combo.setCurrentIndex(month_index if month_index != -1 else 0)
1228-
if not self.exif_dict:
1229-
self.metadata_table.insertRow(0)
1230-
self.metadata_table.setItem(0, 0, QTableWidgetItem("Info"))
1231-
self.metadata_table.setItem(0, 1, QTableWidgetItem("No se encontraron metadatos EXIF."))
1232-
return
1233-
row = 0
1234-
for ifd_name, tags in self.exif_dict.items():
1235-
if not isinstance(tags, dict): continue
1236-
for tag_id, value in tags.items():
1237-
self.metadata_table.insertRow(row)
1238-
tag_name = piexif.TAGS[ifd_name].get(tag_id, {"name": f"UnknownTag_{tag_id}"})["name"]
1239-
if isinstance(value, bytes):
1240-
try: value_str = piexif.helper.decode_bytes(value)
1241-
except: value_str = str(value)
1242-
else:
1243-
value_str = str(value)
1244-
self.metadata_table.setItem(row, 0, QTableWidgetItem(tag_name))
1245-
self.metadata_table.setItem(row, 1, QTableWidgetItem(value_str))
1246-
row += 1
1217+
12471218
def _save_metadata(self):
1219+
# ... (MANTENER IGUAL QUE ANTES) ...
1220+
# Solo cambia la BD, lo cual es correcto según tus instrucciones.
12481221
try:
12491222
new_year_str = self.year_edit.text()
12501223
new_month_str = self.month_combo.currentData()
1224+
1225+
# Validación simple
12511226
if not (new_year_str == "Sin Fecha" or (len(new_year_str) == 4 and new_year_str.isdigit())):
12521227
from PySide6.QtWidgets import QMessageBox
1253-
QMessageBox.warning(self,
1254-
"Datos Inválidos",
1255-
"El Año debe ser 'Sin Fecha' o un número de 4 dígitos (ej: 2024).")
1228+
QMessageBox.warning(self, "Datos Inválidos", "Año inválido.")
12561229
return
1257-
old_year, old_month = self.db.get_photo_date(self.original_path)
1258-
if old_year != new_year_str or old_month != new_month_str:
1259-
self.db.update_photo_date(self.original_path, new_year_str, new_month_str)
1260-
self.metadata_changed.emit(self.original_path, new_year_str, new_month_str)
1230+
1231+
# Guardar en BD
1232+
self.db.update_photo_date(self.original_path, new_year_str, new_month_str)
1233+
1234+
# Notificar cambio
1235+
self.metadata_changed.emit(self.original_path, new_year_str, new_month_str)
12611236
self.accept()
12621237
except Exception as e:
1263-
print(f"Error al guardar la fecha en la BD: {e}")
1238+
print(f"Error al guardar: {e}")
12641239

12651240
# =================================================================
12661241
# CLASE: DIÁLOGO DE ETIQUETADO DE GRUPOS (CLUSTERS) (Sin cambios)
@@ -3571,19 +3546,6 @@ def _finish_cloud_preview(self, local_path):
35713546
self._set_status("Imagen descargada. Abriendo visor...")
35723547
self._open_preview_dialog(local_path)
35733548

3574-
def _download_for_preview(self, file_id, local_path):
3575-
"""Descarga y abre el diálogo de vista previa rápida."""
3576-
try:
3577-
local_manager = DriveManager()
3578-
local_manager.download_file(file_id, local_path)
3579-
3580-
# Abrir la vista previa (ImagePreviewDialog) en el hilo principal
3581-
QTimer.singleShot(0, lambda: self._open_preview_dialog(local_path))
3582-
QTimer.singleShot(0, lambda: self._set_status("Vista previa lista."))
3583-
except Exception as e:
3584-
print(f"Error descarga preview: {e}")
3585-
QTimer.singleShot(0, lambda: self._set_status("Error al descargar para vista previa."))
3586-
35873549
@Slot(QListWidgetItem)
35883550
def _on_drive_item_double_clicked(self, item):
35893551
file_data = item.data(Qt.UserRole)
@@ -4882,17 +4844,6 @@ def _on_drive_scan_finished(self, total_count):
48824844

48834845
self._set_status(f"Listo. {self.cloud_photo_count} fotos disponibles.")
48844846

4885-
@Slot(list)
4886-
def _add_drive_items(self, items):
4887-
"""
4888-
Solo actualiza la estructura en memoria (RAM).
4889-
La inserción en BD ya la hizo el worker.
4890-
"""
4891-
# ELIMINADO: self.db.bulk_upsert_drive_photos(...) <- Esto congelaba la UI
4892-
4893-
self._classify_drive_items_in_memory(items)
4894-
self._set_status(f"Sincronizando... {self.cloud_photo_count} fotos procesadas.")
4895-
48964847
def _display_cloud_photos(self):
48974848
"""Dibuja la interfaz de Nube EXACTAMENTE igual que Fotos (Estilo nativo)."""
48984849

@@ -5003,41 +4954,6 @@ def _display_cloud_photos(self):
50034954
self.cloud_container_layout.addStretch(1)
50044955
QTimer.singleShot(100, self._load_visible_cloud_thumbnails)
50054956

5006-
@Slot(QTreeWidgetItem, QTreeWidgetItem)
5007-
def _scroll_to_cloud_item(self, current, previous):
5008-
"""Navegación rápida al hacer clic en el árbol de fechas."""
5009-
if not current: return
5010-
5011-
key = ""
5012-
if current.parent(): # Es un mes
5013-
key = current.data(0, Qt.UserRole) # "2023-05"
5014-
else: # Es un año
5015-
key = current.text(0) # "2023"
5016-
5017-
target_widget = self.cloud_group_widgets.get(key)
5018-
if target_widget:
5019-
self.cloud_scroll_area.ensureWidgetVisible(target_widget, 0, 0)
5020-
# Forzar carga de miniaturas en la nueva posición
5021-
QTimer.singleShot(200, self._load_visible_cloud_thumbnails)
5022-
5023-
# ----------------------------------------------------------------------
5024-
# FUNCIONES AUXILIARES QUE FALTAN (Pegar dentro de VisageVaultApp)
5025-
# ----------------------------------------------------------------------
5026-
5027-
def _calculate_list_height(self, num_items, icon_size, viewport_width):
5028-
"""Calcula la altura necesaria para una lista sin scrollbar."""
5029-
# Ajuste de márgenes (icono + padding)
5030-
item_w = icon_size + 20
5031-
item_h = icon_size + 40
5032-
5033-
# Columnas que caben en el ancho actual
5034-
cols = max(1, (viewport_width - 30) // item_w)
5035-
5036-
# Filas necesarias
5037-
rows = (num_items + cols - 1) // cols
5038-
5039-
return rows * item_h
5040-
50414957
@Slot(QTreeWidgetItem, QTreeWidgetItem)
50424958
def _scroll_to_cloud_item(self, current, previous):
50434959
"""Navegación rápida al hacer clic en el árbol de fechas."""

0 commit comments

Comments
 (0)