luna-clasificador 1.2.2 → 1.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/BD/luna_db.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
- "version": "1.0.0",
3
- "fechaActualizacion": "2026-10-19T00:00:00Z",
2
+ "version": "3.0.0",
3
+ "fechaActualizacion": "2026-11-01T00:00:00Z",
4
4
  "fuente": "curación manual",
5
5
  "categorias": {
6
6
  "serie": {
@@ -0,0 +1,119 @@
1
+ // EtiquetadorVariado.js
2
+ // v1.3.0 — Determina la etiqueta específica para los archivos esVARIADO.
3
+ // Es un sistema de expansión modular.
4
+
5
+ const path = require('path');
6
+ const fs = require('fs');
7
+ const Clasificador = require('./Clasificacion.js'); // Para reutilizar limpiadores si hace falta
8
+
9
+ /**
10
+ * Determina la Etiqueta_V para un archivo variado.
11
+ * @param {Object} archivo - El objeto archivo del pipeline (con ruta, nombreOriginal, duracion, etc.)
12
+ * @param {Object} info - El objeto info actual (con nombreLimpio, etc.)
13
+ * @returns {string} - La etiqueta determinada.
14
+ */
15
+ function determinarEtiquetaVariado(archivo, info) {
16
+ const nombreOrig = archivo.nombreOriginal || '';
17
+ const ruta = archivo.ruta || '';
18
+ const duracion = archivo.duracion || 0;
19
+
20
+ // ------------------------------------------------------------------------
21
+ // 1. Grabación de WhatsApp (se detecta por nombre y por ruta típica)
22
+ // ------------------------------------------------------------------------
23
+ // Patrones: PTT-20240719-WA0002.opus, VID-20231012-WA0001.mp4, AUD-...
24
+ if (/\b(?:PTT|VID|AUD|IMG)-\d{8}-WA\d{4}\b/i.test(nombreOrig)) {
25
+ return 'Grabación de WhatsApp';
26
+ }
27
+ // Ruta típica de WhatsApp en Android: .../WhatsApp/Media/WhatsApp Audio/ ...
28
+ if (/WhatsApp[\\\/]Media/i.test(ruta)) {
29
+ return 'Grabación de WhatsApp';
30
+ }
31
+
32
+ // ------------------------------------------------------------------------
33
+ // 2. Grabación de pantalla (Screen Record)
34
+ // ------------------------------------------------------------------------
35
+ // Patrones: ScreenRecord_20261101_143022.mp4, ScreenCapture_..., OBS (2026-11-01-14-30-22)
36
+ if (/Screen(Record|Capture)_\d{8}_\d{6}/i.test(nombreOrig)) {
37
+ return 'Grabación de Pantalla';
38
+ }
39
+
40
+ if (/(?:Screen Recording|Grabaci[oó]n de pantalla)\s+\d{8}\s+\d{6}/i.test(nombreOrig)) {
41
+ return 'Grabación de Pantalla';
42
+ }
43
+
44
+ if (/^\d{4}-\d{2}-\d{2}-\d{2}-\d{2}-\d{2}\.(mp4|mkv|avi)$/i.test(nombreOrig)) {
45
+ return 'Grabación de Pantalla'; // Formato OBS
46
+ }
47
+ // Ruta típica de Android para Screen Record: .../DCIM/ScreenRecorder/ ...
48
+ if (/(ScreenRecorder|ScreenRecord|Grabaciones de pantalla)/i.test(ruta)) {
49
+ return 'Grabación de Pantalla';
50
+ }
51
+
52
+ // ------------------------------------------------------------------------
53
+ // 3. Grabación de Cámara / DCIM (Teléfono o Cámara profesional)
54
+ // ------------------------------------------------------------------------
55
+ // Patrones: VID_20250115_143022.mp4, IMG_4821.MP4, _MG_4821.MOV
56
+ if (/\b(?:VID|IMG|_MG|DSC)_\d{4,8}/i.test(nombreOrig)) {
57
+ return 'Grabación de Cámara / DCIM';
58
+ }
59
+ // Ruta típica: .../DCIM/Camera/... o .../DCIM/100APPLE/...
60
+ if (/DCIM/i.test(ruta)) {
61
+ return 'Grabación de Cámara / DCIM';
62
+ }
63
+
64
+ // ------------------------------------------------------------------------
65
+ // 4. Animados o Videos Cortos (Usa la DB de categoría 'variado')
66
+ // ------------------------------------------------------------------------
67
+ try {
68
+ // Para no ensuciar este archivo, importamos la DB de LUNA (simulada aquí, o pasada como parámetro)
69
+ // La mejor forma es que LUNA le pase la DB ya cargada, pero para mantenerlo desacoplado,
70
+ // haremos que la función reciba la DB o la cargue.
71
+ // (Esto lo ajustaremos en LUNA_clasificador.js)
72
+ const DB_PATH = path.join(__dirname, 'BD', 'luna_db.json');
73
+ if (fs.existsSync(DB_PATH)) {
74
+ const db = JSON.parse(fs.readFileSync(DB_PATH, 'utf8'));
75
+ // Usamos la misma lógica de normalización que usa LUNA para la DB
76
+ const normalizarClaveDB = (nombre) => {
77
+ if (!nombre) return '';
78
+ return String(nombre).normalize('NFD').replace(/[\u0300-\u036f]/g, '').toLowerCase().replace(/[\(\)\[\]\{\}]+/g, ' ').replace(/[_\.\-=\+]+/g, ' ').replace(/\s+/g, ' ').trim();
79
+ };
80
+ const clave = normalizarClaveDB(info.nombreLimpio || '');
81
+ const bucket = db.categorias && db.categorias['variado'] && db.categorias['variado'].nombres;
82
+ if (bucket) {
83
+ for (const key of Object.keys(bucket)) {
84
+ if (normalizarClaveDB(key) === clave) {
85
+ return 'Animado / Video Corto';
86
+ }
87
+ }
88
+ }
89
+ }
90
+ } catch (e) {
91
+ // Si falla la DB, no rompemos, seguimos
92
+ }
93
+
94
+ // ------------------------------------------------------------------------
95
+ // 5. VideoClick (Duración 1-6 min y patrón de corchetes [470k], (720P_HD))
96
+ // ------------------------------------------------------------------------
97
+ if (duracion >= 60 && duracion <= 360) { // 1 min a 6 min
98
+ // Busca corchetes con contenido que parezca métrica o calidad
99
+ if (/\[[^\]]+\]|\([^\)]+\)/.test(nombreOrig)) {
100
+ // Nos aseguramos de que no sea un tag técnico típico de peli (1080p, etc.)
101
+ if (!/\[\s*(1080p|2160p|720p|4k|bd|bluray)\s*\]/i.test(nombreOrig)) {
102
+ return 'VideoClick';
103
+ }
104
+ }
105
+ // Patrón de música: "Artista - Título [Official Video]"
106
+ if (/\s[-–—]\s.*\[.*(?:Official|Video|Lyric|Audio).*\]/i.test(nombreOrig)) {
107
+ return 'VideoClick';
108
+ }
109
+ }
110
+
111
+ // ------------------------------------------------------------------------
112
+ // 6. Variado (Fallback o desconocido)
113
+ // ------------------------------------------------------------------------
114
+ return 'Variado Desconocido';
115
+ }
116
+
117
+ module.exports = {
118
+ determinarEtiquetaVariado
119
+ };
@@ -14,7 +14,7 @@ const path = require('path');
14
14
  const { setBinariosFromFolder, procesarDuracionMediainfo } = require('./Servicios/ProcesosCore.js');
15
15
  const Clasificador = require('./Clasificacion.js');
16
16
  const { guardarLogDebug } = require('./Debug/LUNA_Logger.js');
17
- const VERSION_LUNA = '1.2.1';
17
+ const VERSION_LUNA = '1.2.4';
18
18
  var seEjecuto = false;
19
19
  var seEjecutoIPC = false;
20
20
 
@@ -916,6 +916,22 @@ function tomarDecision(paquete) {
916
916
  }
917
917
  }
918
918
 
919
+ // === NUEVO: Etiquetado específico para VARIADO ===
920
+ if (elegido === 'esVARIADO') {
921
+ try {
922
+ const Etiquetador = require('./EtiquetadorVariado.js');
923
+ const etiqueta = Etiquetador.determinarEtiquetaVariado(archivo, archivo.info);
924
+ archivo.info.Etiqueta_V = etiqueta;
925
+ } catch (e) {
926
+ paquete.errores.push({
927
+ etapa: 'tomarDecision.EtiquetaVariado',
928
+ ruta: archivo.ruta,
929
+ mensaje: e.message
930
+ });
931
+ archivo.info.Etiqueta_V = 'Variado Desconocido'; // Fallback seguro
932
+ }
933
+ }
934
+
919
935
  // NUEVO: Si la DB lo marcó como película, forzamos.
920
936
  if (esPeliDB) {
921
937
  elegido = 'esPELI';
@@ -1206,6 +1222,7 @@ function transformarSalida(paquete) {
1206
1222
  nombreLimpio: archivo.nombreLimpio,
1207
1223
  tituloLimpio: archivo.info.tituloLimpio || null,
1208
1224
  fechaExtra: archivo.info.fechaExtra || null,
1225
+ Etiqueta_V: archivo.info.Etiqueta_V || null,
1209
1226
  ruta: archivo.ruta,
1210
1227
  info: archivo.info,
1211
1228
  extension: archivo.extension || archivo.info?.extension || archivo.ext || path.extname(archivo.nombreOriginal).replace('.', '').toLowerCase(),
package/assets/1.png ADDED
Binary file
package/assets/2.png ADDED
Binary file
package/assets/3.png ADDED
Binary file
package/assets/4.png ADDED
Binary file
package/assets/5.png ADDED
Binary file
Binary file
package/package.json CHANGED
@@ -1,17 +1,24 @@
1
1
  {
2
2
  "name": "luna-clasificador",
3
- "version": "1.2.2",
3
+ "version": "1.2.4",
4
4
  "icon": "assets/icons.png",
5
5
  "description": "Clasificador local de archivos multimedia (película, serie, variado, programa, novela)",
6
6
  "main": "LUNA.js",
7
7
  "files": [
8
8
  "LUNA_clasificador.js",
9
9
  "Clasificacion.js",
10
+ "EtiquetadorVariado.js",
10
11
  "Debug/LUNA_Logger.js",
11
12
  "tests/probe_LUNA.js",
12
13
  "tests/luna_dashboard_server.js",
13
14
  "tests/luna_dashboard.html",
14
15
  "assets/icons.png",
16
+ "assets/banner.png",
17
+ "assets/1.png",
18
+ "assets/2.png",
19
+ "assets/3.png",
20
+ "assets/4.png",
21
+ "assets/5.png",
15
22
  "LUNA.js",
16
23
  "Servicios/ProcesosCore.js",
17
24
  "Servicios/ResolverPathffmpeg.js",
package/readme.md CHANGED
@@ -60,6 +60,73 @@ Fue diseñado para ordenar discos duros llenos de series, películas, novelas y
60
60
  > Versión actual del proyecto: **1.2.1**
61
61
  > Base de datos actual: **1.0.0**
62
62
 
63
+
64
+
65
+ ***
66
+
67
+ ## 🆕 Novedades en v1.2.4: Etiquetado Inteligente de Variados
68
+
69
+ <p align="center">
70
+ <img src="./assets/4.png" width="100%">
71
+ </p>
72
+
73
+ En esta versión, LUNA no solo clasifica los archivos como `esVARIADO`, sino que ahora es capaz de **sub-clasificarlos** con una etiqueta semántica (`Etiqueta_V`).
74
+
75
+ Sabemos que en la categoría "Variado" cae de todo: desde un clip de YouTube, hasta una nota de voz de WhatsApp o un video de la cámara del celular. Para que no tengas que adivinar qué es cada archivo, agregamos un sistema modular de etiquetas.
76
+
77
+ ### 🏷️ ¿Qué etiquetas existen?
78
+ Si el archivo es `esVARIADO`, LUNA intentará asignar una de las siguientes etiquetas:
79
+
80
+ 1. **VideoClick**: Videos de 1 a 6 minutos que tienen pinta de ser clips musicales o de redes (detecta patrones como `[Official Video]`, `[470k]`, etc.).
81
+ 2. **Grabación de WhatsApp**: Detecta el famoso formato `PTT-202311...-WA0001` o videos en rutas de WhatsApp.
82
+ 3. **Grabación de Pantalla**: Reconoce desde el formato de Android (`ScreenRecord_2023...`), el de OBS (`2026-11-01-...`), hasta el de Windows 11 (`Screen Recording 20250713 012858`).
83
+ 4. **Grabación de Cámara / DCIM**: Identifica videos crudos del teléfono (`VID_202...`, `IMG_...`) o archivos en la carpeta `/DCIM/`.
84
+ 5. **Animado / Video Corto**: Si el nombre limpio del archivo coincide con la base de datos de animados (`Pocoyo`, `Peppa Pig`, etc.).
85
+ 6. **Variado Desconocido**: El cajón de sastre. Si no coincide con ninguno, se le asigna esta etiqueta para que el usuario decida.
86
+
87
+ ### 📍 ¿Cómo se ve en la salida?
88
+ La etiqueta se agrega directamente en el `info` y en la raíz de la salida.
89
+
90
+ ```json
91
+ {
92
+ "esCC": "esVARIADO",
93
+ "nombreLimpio": "Screen Recording 20250713 012858 Chrome",
94
+ "Etiqueta_V": "Grabación de Pantalla",
95
+ "info": {
96
+ "tipoArchivo": "video",
97
+ "extension": "mp4",
98
+ "Etiqueta_V": "Grabación de Pantalla"
99
+ }
100
+ }
101
+ ```
102
+
103
+ ### 🛠️ ¿Cómo se usa? (Para desarrolladores)
104
+ No tenés que hacer nada especial. El sistema se ejecuta automáticamente justo después de que LUNA decide que un archivo es `esVARIADO`.
105
+
106
+ Simplemente, cuando leas la salida, podés filtrar por la etiqueta:
107
+ ```javascript
108
+ const resultado = await LUNA('C:\\DCIM\\VID_20231101_143022.mp4');
109
+ if (resultado.salida.esCC === 'esVARIADO') {
110
+ console.log(resultado.salida.Etiqueta_V); // "Grabación de Cámara / DCIM"
111
+ }
112
+ ```
113
+
114
+ ### ⚙️ ¿Cómo funciona por dentro? (Arquitectura)
115
+ La lógica vive en el nuevo archivo **`EtiquetadorVariado.js`**. Fue diseñado bajo el principio de **"solo agregar, nunca modificar"**. Es un sistema de reglas en cascada:
116
+ 1. Se prueba el nombre del archivo (Regex).
117
+ 2. Se prueba la ruta del archivo (por si viene de `/WhatsApp/` o `/DCIM/`).
118
+ 3. Se prueba la duración (para los VideoClicks).
119
+ 4. Se consulta la base de datos (para los Animados).
120
+ Si alguna regla hace "match", devuelve la etiqueta y se detiene. Si querés agregar una nueva etiqueta en el futuro (ej: "Grabación de Discord"), solo tenés que agregar un `if` nuevo en ese archivo.
121
+
122
+ ***
123
+
124
+ **LOG de README:**
125
+ <p align="center">
126
+ <img src="./assets/5.png" width="100%">
127
+ </p>
128
+
129
+
63
130
  ---
64
131
 
65
132
  ## 🆕 Novedades en v1.2.1
@@ -89,6 +89,9 @@
89
89
  const badge = item.estado === 'ok' ? 'ok' : item.estado === 'warning' ? 'warn' : 'err';
90
90
  const badgeText = item.estado === 'ok' ? 'OK' : item.estado === 'warning' ? 'Aviso' : 'Error';
91
91
 
92
+ const etiquetaVariado = item.etiquetaVariado || null;
93
+ const subtituloFinal = item.subtitulo || (item.categoriaHumana === 'Variado' && etiquetaVariado ? etiquetaVariado : '');
94
+
92
95
  // Construir HTML de detalles (solo si hay datos)
93
96
  let detallesHtml = '';
94
97
  if (item.detalles && item.detalles.length > 0) {
@@ -101,7 +104,7 @@
101
104
  }
102
105
 
103
106
  // Subtítulo opcional
104
- const subtituloHtml = item.subtitulo ? `<div class="card-subtitle">${item.subtitulo}</div>` : '';
107
+ const subtituloHtml = subtituloFinal ? `<div class="card-subtitle">${subtituloFinal}</div>` : '';
105
108
 
106
109
  // Título (si es null, usamos el nombre del archivo)
107
110
  const titulo = item.titulo || 'Archivo sin nombre claro';
@@ -56,6 +56,7 @@ function serializeResult(item, data) {
56
56
  titulo: null,
57
57
  subtitulo: null,
58
58
  categoriaHumana: null,
59
+ etiquetaVariado: null,
59
60
  detalles: [], // Solo datos reales
60
61
  error: null
61
62
  };
@@ -93,6 +94,15 @@ function serializeResult(item, data) {
93
94
  base.subtitulo = subParts.join(' · ');
94
95
  }
95
96
 
97
+ const etiquetaVariado = info.Etiqueta_V || s.Etiqueta_V || null;
98
+ if (etiquetaVariado) {
99
+ base.etiquetaVariado = etiquetaVariado;
100
+ if (s.esCC === 'esVARIADO') {
101
+ base.subtitulo = etiquetaVariado;
102
+ }
103
+ base.detalles.push({ etiqueta: 'Etiqueta', valor: etiquetaVariado });
104
+ }
105
+
96
106
  // Detalles: Solo lo que existe y es útil
97
107
  if (info.serie) base.detalles.push({ etiqueta: 'Serie', valor: info.serie });
98
108
  if (info.anio) base.detalles.push({ etiqueta: 'Año', valor: info.anio });
@@ -29,6 +29,13 @@ const RUTAS_PRUEBA = [
29
29
  'D:\\ANIME\\Peliculas\\Harry Potter La Piedra Filosofal.mkv',
30
30
  'D:\\ANIME\\Peliculas\\John Wick 4.mkv',
31
31
  'D:\\ANIME\\Thunder 3 [Temp 1] [2026] [Caps 12]\\#Zombie Sagashitemasu 3 Sub Español Online gratis — JkAnime.mp4',
32
+ 'D:\\MÚSICA\\NEW\\Bebe_Rexha_-_Im_A_Mess_[Official_Music_Video](360p).mp4',
33
+ 'D:\\MÚSICA\\NEW\\Evanescence - Bring Me To Life (Official Music Video)(720P_HD).mp4',
34
+ 'D:\\MÚSICA\\NEW\\VID_20261101_143022.mp4',
35
+ 'D:\\MÚSICA\\NEW\\Screen_Recording_20250713-012858_Chrome.mp4',
36
+ 'D:\\MÚSICA\\NEW\\ScreenCapture_20261101-143022.mp4',
37
+ 'D:\\MÚSICA\\NEW\\ScreenRecord_20261101-143022.mp4',
38
+ 'D:\\MÚSICA\\NEW\\Yomil y el Dany - SANTA 2 (video oficial) _drill(480P)_1.mp4'
32
39
  ];
33
40
 
34
41
  // ============================================================================
@@ -266,6 +273,7 @@ async function ejecutarProbeLUNA2() {
266
273
  console.log(' salida.tituloLimpio:', JSON.stringify(resultado && resultado.salida && resultado.salida.tituloLimpio));
267
274
  console.log(' salida.extension:', JSON.stringify(resultado && resultado.salida && resultado.salida.extension));
268
275
  console.log(' salida.tipoArchivo:', JSON.stringify(resultado && resultado.salida && resultado.salida.tipoArchivo));
276
+ console.log(' salida.Etiqueta_V:', JSON.stringify(resultado && resultado.salida && resultado.salida.Etiqueta_V));
269
277
  if (resultado && resultado.salida && resultado.salida.info) {
270
278
  console.log(' salida.info.serie:', JSON.stringify(resultado.salida.info.serie));
271
279
  console.log(' salida.info.temporada:', resultado.salida.info.temporada);