// Audio Tag // (c) 2025 DASOTA // Script para Directory Opus // INICIALIZACIÓN Y CONFIGURACIONES ============================================================================================================ function OnInit(initData) { // Información del script initData.name = 'Audio Tag'; initData.version = '1.0'; initData.copyright = '(c) 2025 DASOTA'; initData.desc = 'Audio Tag'; initData.default_enable = true; initData.min_version = '13.0'; // Descripciones de la configuración initData.config_desc = DOpus.Create.Map(); initData.config_desc('genre') = 'Añadir/eliminar géneros musicales'; initData.config_desc('idioma') = '1= Español, 2= English'; // Idiomas initData.config['idioma'] = 1; // Idioma por defecto (1 = Español) // Géneros musicales (personalizables) var generos = [ 'Romántico', 'Balada', 'Bolero', 'Trova', 'Patrio', 'Son', 'Salsa', 'Guaracha', 'Montuno', 'Cumbia', 'Merengue', 'Tropical', 'Ranchera', 'Reggae', 'Reggaeton', 'Hip Hop', 'Pop', 'Rock', 'Tecno', 'Electrónico', 'Jazz', 'Infantil', 'Religioso', 'Instrumental', 'Vocal', 'Efecto sonoro', 'Inglés', 'Bossa Nova', 'Tropicália', 'Samba', 'Pagode', 'Lambada', 'MPB', 'Forró', 'Sertanejo', 'Sertanejo universitário', 'Axé', 'Funk', 'Gospel', 'Brega', 'Choro', 'Frevo', 'Baião', 'Piseiro', 'Pisadinha' ]; var gen_perso = DOpus.Create.Vector(); for (var i = 0; i < generos.length; i++) { gen_perso.push_back(generos[i]); } initData.config['genre'] = gen_perso; } // COMANDO ===================================================================================================================================== function OnAddCommands(addCmdData) { var cmd = addCmdData.AddCommand(); cmd.name = 'AudioTag'; cmd.method = 'OnAudioTag'; cmd.desc = ''; // Descripción cmd.label = 'Audio Tag'; cmd.template = 'dialog1'; cmd.hide = false; cmd.icon = 'script'; } // ============================================================================================================================================= // FUNCIÓN PRINCIPAL DEL SCRIPT // ============================================================================================================================================= function OnAudioTag(scriptCmdData) { // INICIO Y VARIABLES GLOBALES ============================================================================================================= var dlg = scriptCmdData.func.Dlg(); dlg.title = 'Audio Tag - Directory Opus'; dlg.template = 'dialog1'; dlg.detach = true; dlg.Create(); var tab = scriptCmdData.func.sourcetab; var cmd = scriptCmdData.func.command; cmd.ClearFiles(); var item; var str_tools = DOpus.Create().StringTools(); // Herramienta de cadenas, en este caso para eliminar diacríticos en el campo géneros var generos_map = DOpus.NewMap(); var enum_gen; var genres = Script.config['genre']; var cover_archivo = ''; // Ruta en la que se extrae la carátula del archivo para ser visualizada en caso de existir. La carátula en el archivo se encuentra en forma binaria, por lo que para poder mostrarse tiene que convertirse a imagen física. var cover_usuario = ''; // Ruta de la carátula seleccionada por el usuario, o "eliminar" si se quitó manualmente var cover_identificador = {}; // Identificador simplificado de la carátula de cada archivo, para comparaciones futuras var archivosListado = {}; // Ruta completa de todos los archivos existentes en el listado var datosExtraidos = {}; // Valores (pista, título, artista y álbum) extraidos desde nombre de archivos seleccionados al presionar el botón 'desde_archivo' var estadoInicial = {}; // DICCIONARIO DE IDIOMAS ================================================================================================================== var idiomas = { 2: { // Inglés // Botones del listado anadir_archivo: "Add...", limpiar_lista: "Delete", seleccionar_todo: "Select all", deseleccionar: "Deselect", // Columnas del listado col_archivo: "File", col_pista: "Track", col_titulo: "Title", col_artista: "Artist", col_album: "Album", col_anio: "Year", col_genero: "Genre", col_comentario: "Comment", col_artista_album: "Album artist", col_compositor: "Composer", col_derechos: "Copyright", col_disco: "Disc", col_ruta: "Path", // Panel metadatos archivo: "File:", titulo: "Title:", artista: "Artist:", album: "Album:", anio: "Year:", pista: "Track:", genero: "Genre:", comentario: "Comment:", artista_album: "Album artist:", compositor: "Composer:", derechos: "Copyright:", numero_disco: "Disc:", // Desde archivo desde_archivo: "Track, title, artist and album from filename", // Carátulas caratula: "Cover art", anadir: "Add...", limpiar: "Delete", extraer: "Extract...", // Limpiar todos los metadatos limpiar_todo: "Clear all metadata", // Botones del diálogo aplicar: "Save", cancelar: "Cancel" } }; // APLICAR TRADUCCIÓN A CONTROLES ========================================================================================================== // Obtener idioma activo desde configuración del script (si no es válido, usar 1= Español) var idioma = Script.config.idioma || 1; // Si idioma dintinto de 1 (Español), entonces aplicar traducción correspondiente if (idioma !== 1 && idiomas.hasOwnProperty(idioma)) { var tra = idiomas[idioma]; dlg.Control("btn_anadir_archivo").label = tra.anadir_archivo; dlg.Control("btn_eliminar_archivo").label = tra.limpiar_lista; dlg.Control("btn_seleccionar_todo").label = tra.seleccionar_todo; dlg.Control("btn_deseleccionar").label = tra.deseleccionar; var list = dlg.Control("listview1"); list.columns.GetColumnAt(0).name = tra.col_archivo; list.columns.GetColumnAt(1).name = tra.col_pista; list.columns.GetColumnAt(2).name = tra.col_titulo; list.columns.GetColumnAt(3).name = tra.col_artista; list.columns.GetColumnAt(4).name = tra.col_album; list.columns.GetColumnAt(5).name = tra.col_anio; list.columns.GetColumnAt(6).name = tra.col_genero; list.columns.GetColumnAt(7).name = tra.col_comentario; list.columns.GetColumnAt(8).name = tra.col_artista_album; list.columns.GetColumnAt(9).name = tra.col_compositor; list.columns.GetColumnAt(10).name = tra.col_derechos; list.columns.GetColumnAt(11).name = tra.col_disco; list.columns.GetColumnAt(12).name = tra.col_ruta; dlg.Control("total_archivos").label = tra.total_archivos; dlg.Control("archivo_t").label = tra.archivo; dlg.Control("titulo_t").label = tra.titulo; dlg.Control("artista_t").label = tra.artista; dlg.Control("album_t").label = tra.album; dlg.Control("anio_t").label = tra.anio; dlg.Control("pista_t").label = tra.pista; dlg.Control("genero_t").label = tra.genero; dlg.Control("comentario_t").label = tra.comentario; dlg.Control("artista_album_t").label = tra.artista_album; dlg.Control("compositor_t").label = tra.compositor; dlg.Control("derechos_t").label = tra.derechos; dlg.Control("numero_disco_t").label = tra.numero_disco; dlg.Control("btn_desde_archivo").label = tra.desde_archivo; dlg.Control("caratula_t").label = tra.caratula; dlg.Control("btn_anadir_caratula").label = tra.anadir; dlg.Control("btn_eliminar_caratula").label = tra.limpiar; dlg.Control("btn_extraer_caratula").label = tra.extraer; dlg.Control("btn_eliminar_todo").label = tra.limpiar_todo; dlg.Control("btn_guardar").label = tra.aplicar; dlg.Control("btn_cancelar").label = tra.cancelar; } // ANCHO DE COLUMAS ======================================================================================================================== var list = dlg.Control("listview1"); // Ancho fijo list.columns.GetColumnAt(0).width = 210; // Archivo list.columns.GetColumnAt(1).width = 45; // Pista list.columns.GetColumnAt(2).width = 180; // Título list.columns.GetColumnAt(3).width = 130; // Artista list.columns.GetColumnAt(4).width = 130; // Álbum list.columns.GetColumnAt(5).width = 60; // Año list.columns.GetColumnAt(6).width = 130; // Género list.columns.GetColumnAt(7).width = 100; // Comentario list.columns.GetColumnAt(8).width = 100; // Artista álbum list.columns.GetColumnAt(9).width = 100; // Compositor list.columns.GetColumnAt(10).width = 100; // Copuright list.columns.GetColumnAt(11).width = 60; // Disco list.columns.GetColumnAt(12).width = 300; // Ruta // Ancho automático (según contenido) de todas las columnas // list.columns.autosize(); // CARGAR ARCHIVOS EN LA LISTVIEW ========================================================================================================== // Se llama a la función 'anadirArchivos' var archivosSeleccionados = tab.selected_files; if (archivosSeleccionados && archivosSeleccionados.count > 0) { anadirArchivos(dlg, archivosListado, archivosSeleccionados); } // ASIGNAR VALORES A CONTROLES ============================================================================================================= function cargarMetadatos(audio_item) { if (!audio_item || audio_item.ext.toLowerCase() != '.mp3' || audio_item.metadata != 'audio') { DOpus.Output('Archivo inválido o sin metadatos'); return false; } try { dlg.Control('archivo').value = audio_item.name_stem; var titulo = audio_item.metadata.audio.mp3title; dlg.Control('titulo').RemoveItem(-1); // Elimina cualquier valor que pudiera existir dlg.Control('titulo').AddItem(titulo); // Añade un nuevo valor, en este caso titulo dlg.Control('titulo').value = 0; // Selecciona el primer valor de la lista (caso varios valores) var artista = audio_item.metadata.audio.mp3artist; dlg.Control('artista').RemoveItem(-1); dlg.Control('artista').AddItem(artista); dlg.Control('artista').value = 0; var album = audio_item.metadata.audio.mp3album; dlg.Control('album').RemoveItem(-1); dlg.Control('album').AddItem(album); dlg.Control('album').value = 0; var anio = audio_item.metadata.audio.mp3year; dlg.Control('anio').RemoveItem(-1); dlg.Control('anio').AddItem(anio); dlg.Control('anio').value = 0; var pista = audio_item.metadata.audio.mp3track; dlg.Control('pista').RemoveItem(-1); dlg.Control('pista').AddItem(pista); dlg.Control('pista').value = 0; var comentario = audio_item.metadata.audio.mp3comment; dlg.Control('comentario').RemoveItem(-1); dlg.Control('comentario').AddItem(comentario); dlg.Control('comentario').value = 0; var artista_album = audio_item.metadata.audio.mp3albumartist; dlg.Control('artista_album').RemoveItem(-1); dlg.Control('artista_album').AddItem(artista_album); dlg.Control('artista_album').value = 0; var compositor = audio_item.metadata.audio.composers; dlg.Control('compositor').RemoveItem(-1); dlg.Control('compositor').AddItem(compositor); dlg.Control('compositor').value = 0; var derechos = audio_item.metadata.audio.copyright; dlg.Control('derechos').RemoveItem(-1); dlg.Control('derechos').AddItem(derechos); dlg.Control('derechos').value = 0; var numero_disco = audio_item.metadata.audio.mp3disc; dlg.Control('numero_disco').RemoveItem(-1); dlg.Control('numero_disco').AddItem(numero_disco); dlg.Control('numero_disco').value = 0; // Carátula var coverEncontrada = false; if (audio_item.metadata.audio.coverart > 0) { for (var i = 0; i < audio_item.metadata.audio.coverart.count; i++) { var reso = audio_item.metadata.audio.coverart(i); if (reso + '' === 'front') { var tmp = DOpus.FSUtil().GetTempFile(); tmp.Write(reso.data); tmp.Close(); cover_archivo = tmp.path + ''; cover_usuario = ''; dlg.Control('caratula').label = Script.LoadImage(tmp); dlg.Control('info_caratula').label = reso.width + ' x ' + reso.height + ' x ' + reso.depth + ' (' + reso.size.fmt + ')'; coverEncontrada = true; break; } } } if (!coverEncontrada) { dlg.Control('caratula').label = ''; dlg.Control('info_caratula').label = ''; cover_archivo = ''; } // Género setupGeneroMap(); updateGenero(); dlg.Control('genero').InsertItemAt(0, audio_item.metadata.audio.mp3genre); dlg.Control('genero').value = 0; // Valores iniciales de los controles estadoInicial = { titulo: titulo, artista: artista, album: album, anio: anio, pista: pista, comentario: comentario, artista_album: artista_album, compositor: compositor, derechos: derechos, numero_disco: numero_disco, genero: audio_item.metadata.audio.mp3genre }; item = audio_item; return true; } catch (err) { DOpus.Output('Error leyendo el archivo: ' + err); return false; } } setupGeneroMap(); // CONFIGURACIÓN DE LA TECLA DELETE ======================================================================================================== var LISTVIEW = "listview1"; // Control en el que queremos que la tecla actue (listview1) var hotkeyOn = false; // Bandera booleana para evitar que la tecla se active más de una vez; o se elimine, si ya está eliminada var focoActual = ""; // ========================================================================================================================================= // MOSTRAR EL DIÁLOGO Y ESPERAR INTERACCIÓN DEL USUARIO // ========================================================================================================================================= dlg.Show(); // AÑADIR ARCHIVOS ======================================================================================================================== // Se llama al seleccionar archivos y ejecutar el script, desde botón btn_anadir, y al arrastrar y soltar archivos sobre el listado function anadirArchivos(dlg, archivosListado, archivos) { var lista = dlg.Control("listview1"); // Verificar si el listado se encuentra vacío var listadoVacio = (lista.count === 0); // Si no se pasan archivos, abrir la ventana de selección de archivos if (!archivos) { archivos = dlg.Multi("file"); // Abrir ventana if (!archivos || archivos.count == 0) return; } var nuevosArchivos = []; for (var i = 0; i < archivos.count; i++) { var file = archivos(i); if (!file || file.ext.toLowerCase() !== ".mp3" || !file.metadata || !file.metadata.audio) continue; var filePath = file.realpath; // Evitar duplicados usando ruta de archivo como clave de identificación if (archivosListado.hasOwnProperty(filePath)) { DOpus.Output('Este archivo ya se encuentra en la lista: ' + file.name + ' (' + filePath + ')'); continue; } var meta = file.metadata.audio; var idx = lista.AddItem(file.name_stem || ""); // Valor de la 1ª columna del listado (nombre de archivo) archivosListado[filePath] = file; // archivosListado = Ruta de todos los archivos en el listado // Identificador de carátulas // Asignarle a cada carátula principal (front) un identificador (Hash MD5) para futuramente poder comparar las carátulas entre sí y saber si existen algunas iguales cover_identificador[filePath] = (meta.coverart && meta.coverart.count > 0) ? DOpus.FSUtil.Hash(meta.coverart(0).data, "md5") : ""; // Valores de las demás columnas var fila = lista.GetItemAt(idx); fila.subitems(0) = meta.mp3track || ""; // 2ª columna (1º subitem de la columna 1) fila.subitems(1) = meta.mp3title || ""; fila.subitems(2) = meta.mp3artist || ""; fila.subitems(3) = meta.mp3album || ""; fila.subitems(4) = meta.mp3year || ""; fila.subitems(5) = meta.mp3genre || ""; fila.subitems(6) = meta.mp3comment || ""; fila.subitems(7) = meta.mp3albumartist || ""; fila.subitems(8) = meta.composers || ""; fila.subitems(9) = meta.copyright || ""; fila.subitems(10) = meta.mp3disc || ""; fila.subitems(11) = filePath; // Columna que se utiliza como clave de identificación (ruta de archivo) //DOpus.Output("Añadido fila " + idx + ": " + filePath); nuevosArchivos.push(file); } // Si el listado estaba vacio, seleccionar automáticamente, el primer archivo añadido if (listadoVacio && lista.count > 0) { lista.value = 0; } // Actualizar valores totalArchivos(dlg); // Total de archivos (barra del listado) if (nuevosArchivos.length > 0) { caratulaDuo(dlg, nuevosArchivos); // Infomación de carátula (panel derecho) } lista.focus = true; // Hacer que la ventana principal retome el foco despúes haber abierto la ventana de selección de archivos } // LIMPIAR LISTA =========================================================================================================================== // Se llama desde btn_eliminar_archivo, y al presionar la tecla Delete function limpiarLista(dlg) { var lista = dlg.Control("listview1"); var seleccionados = lista.value; var indices = []; if (seleccionados && seleccionados.count && seleccionados.count > 0) { // Obtener índices de los elementos seleccionados for (var e = new Enumerator(seleccionados); !e.atEnd(); e.moveNext()) { var item = e.item(); indices.push(item.index); } // Ordenar de mayor a menor para eliminar correctamente indices.sort(function(a, b) { return b - a; }); // Si archivos seleccionados, eliminarlos del listado for (var i = 0; i < indices.length; i++) { var fila = lista.GetItemAt(indices[i]); var ruta = fila.subitems(11); if (archivosListado[ruta]) { delete archivosListado[ruta]; } lista.RemoveItem(indices[i]); } } else { // Si no hay archivos seleccionados, eliminar todos los archivos del listado for (var i = lista.count - 1; i >= 0; i--) { var fila = lista.GetItemAt(i); var ruta = fila.subitems(11); if (archivosListado[ruta]) { delete archivosListado[ruta]; } lista.RemoveItem(i); } // Eliminar los archivos también en el objeto 'archivosListado' for (var ruta in archivosListado) { delete archivosListado[ruta]; } } } // LIMPIAR PANEL DERECHO =================================================================================================================== function limpiarPanelDerecho(omitirCampoArchivo) { if (!omitirCampoArchivo) { dlg.Control('archivo').value = ''; } dlg.Control('titulo').RemoveItem(-1); dlg.Control('artista').RemoveItem(-1); dlg.Control('album').RemoveItem(-1); dlg.Control('anio').RemoveItem(-1); dlg.Control('pista').RemoveItem(-1); //dlg.Control('genero').label = ''; // Dejar el campo 'genero' vacío, y sin lista de opciones dlg.Control('genero').RemoveItem(-1); // Dejar el campo 'genero' vacío, pero con lista de opciones dlg.Control('comentario').RemoveItem(-1); dlg.Control('artista_album').RemoveItem(-1); dlg.Control('compositor').RemoveItem(-1); dlg.Control('derechos').RemoveItem(-1); dlg.Control('numero_disco').RemoveItem(-1); dlg.Control('caratula').label = ''; dlg.Control('info_caratula').label = ''; // Limpiar variables internas item = null; cover_archivo = ''; cover_interna = ''; cover_usuario = 'eliminar'; } // COMPARAR CONTROLES Y EN LOS DIFERENTES, MOSTRAR < ignorar > ============================================================================= function compararControles(archivos, dlg) { // Función auxiliar: Compara un campo específico entre todos los archivos function comparar(campoRuta) { var conjunto = DOpus.Create.Vector(); // Vector con los distintos valores encontrados for (var i = 0; i < archivos.length; i++) { var valor = eval("archivos[i]." + campoRuta) || ""; var yaExiste = false; for (var j = 0; j < conjunto.count; j++) { if (conjunto(j) == valor) { yaExiste = true; break; } } if (!yaExiste) conjunto.push_back(valor); // Solo se agregan valores diferentes } return conjunto; } // Lista de campos a comparar, con su ID de control y la ruta de metadatos correspondiente var meta = [ ['titulo', 'metadata.audio.mp3title'], ['artista', 'metadata.audio.mp3artist'], ['album', 'metadata.audio.mp3album'], ['anio', 'metadata.audio.mp3year'], ['pista', 'metadata.audio.mp3track'], ['comentario', 'metadata.audio.mp3comment'], ['artista_album', 'metadata.audio.mp3albumartist'], ['compositor', 'metadata.audio.composers'], ['derechos', 'metadata.audio.copyright'], ['numero_disco', 'metadata.audio.mp3disc'], ['genero', 'metadata.audio.mp3genre'] ]; // Comparar todos los campos definidos arriba y reflejar sus valores en los controles for (var j = 0; j < meta.length; j++) { var idCampo = meta[j][0]; // ID del campo var ruta = meta[j][1]; // Ruta del campo dentro de los metadatos var valores = comparar(ruta); // Obtener los valores únicos encontrados var ctrl = dlg.Control(idCampo); if (!ctrl) continue; ctrl.RemoveItem(-1); if (valores.count == 1) { ctrl.AddItem(valores(0)); // Si todos los archivos tienen el mismo valor, mostrarlo en el campo ctrl.label = valores(0); } else if (valores.count > 1) { for (var k = 0; k < valores.count; k++) { ctrl.AddItem(valores(k)); // Si varios valores en el campo, agregarlos como opciones } ctrl.label = idioma === 2 ? "< ignore >" : "< ignorar >"; // Si varios valores en el campo, mostrar < ignorar > en el campo } else { ctrl.label = ""; } } dlg.Control('archivo').value = idioma === 2 ? "< several >" : "< varios >"; // Si varios valores en el campo Archivo, mostrar < varios > en el campo caratulaDuo(dlg, archivos); // Llamar la función 'caratulaDuo' } // CARÁTULA_DUO (CARÁTULA + INFORMACIÓN) =================================================================================================== function caratulaDuo(dlg, archivos) { var ctrlCara = dlg.Control("caratula"); var ctrlInfo = dlg.Control("info_caratula"); // Si no hay archivos seleccionados, limpiar los campos 'caratula' e 'infoCaratula' if (archivos.length === 0) { ctrlCara.label = ""; ctrlInfo.label = ""; cover_archivo = ""; cover_usuario = ""; return; // Interrumpir el análisis (no continuar) } // PROCESAMIENTO DE 'caratula' // Recorrer todos los archivos en busca de alguna carátula, si se encuentra, posicionarla y calcular su resolución y tamaño var total = archivos.length; var conCaratula = 0; var primeraCaratula = null; var primeraResolucion = ""; for (var i = 0; i < total; i++) { var f = archivos[i]; if (!f.metadata || !f.metadata.audio) continue; var covers = f.metadata.audio.coverart; if (covers && covers.count > 0) { conCaratula++; if (!primeraCaratula) { var cara = covers(0); primeraCaratula = cara; primeraResolucion = cara.width + " x " + cara.height + " x " + cara.depth + " (" + cara.size.fmt + ")"; } } } // Mostrar la carátula en el campo 'caratula' if (ctrlCara) { if (primeraCaratula) { // Si se encontra alguna carátula... var tmp = DOpus.FSUtil().GetTempFile(); tmp.Write(primeraCaratula.data); tmp.Close(); cover_archivo = tmp.path + ""; cover_usuario = ""; ctrlCara.label = Script.LoadImage(tmp); // Mostrar la carátula en el campo 'caratula' } else { // Si no... ctrlCara.label = ""; // Mostrar el campo 'caratula' vacío cover_archivo = ""; cover_usuario = ""; } } // PROCESAMIENTO DE 'infoCaratula' // Traducciones de etiquetas txt_sin_caratula = idioma === 2 ? "No cover arts" : "Sin carátulas"; txt_una_caratula = idioma === 2 ? "1 cover art" : "1 carátula"; txt_sin_caratulas = idioma === 2 ? "No cover arts" : "Sin carátulas"; txt_varias_caratulas = idioma === 2 ? "cover arts < ignore >" : "carátulas < ignorar >"; // Determinar el valor de la etiqueta var etiqueta = ""; if (total === 1) { // Si un archivo seleccionado... etiqueta = (conCaratula === 0) ? txt_sin_caratula : primeraResolucion; // Y no tiene carátula: "Sin carátula"; si tiene carátula, [resolución] } else { // Si no (si varios archivos seleccionados)... if (conCaratula === 0) { // Si no tienen carátula... etiqueta = txt_sin_caratulas; // "Sin carátulas" } else if (conCaratula === 1) { // Si 1 carátula... etiqueta = txt_una_caratula; // "1 carátula" } else { // Si no... etiqueta = conCaratula + " " + txt_varias_caratulas; // "# carátulas < ignorar >" } } // Mostrar la etiqueta en el control 'info_caratula' ctrlInfo.label = etiqueta; } // TOTAL DE ARCHIVOS (BARRA LISTADO) ======================================================================================================= function totalArchivos(dlg) { var lista = dlg.Control("listview1"); // Obtener datos del listado var total = lista.count; // Total de archivos var seleccionados = (lista.value && lista.value.count) ? lista.value.count : 0; // Total de archivos seleccionados (con prevención de error) // Traducciones directas (sin diccionario) var totalSin = idioma === 2 ? "file" : "archivo"; var totalPlu = idioma === 2 ? "files" : "archivos"; var selecSin = idioma === 2 ? "selected" : "seleccionado"; var selecPlu = idioma === 2 ? "selected" : "seleccionados"; var etiqueta = ""; if (total === 0) { // SI NO HAY ARCHIVOS EN EL LISTADO etiqueta = ""; // No mostrar nada } else if (seleccionados === 0) { // SI NINGÚN ARCHIVO SELECCIONADO... if (total === 1) { etiqueta = "1 " + totalSin; // 1 archivo en el listado (ninguno seleccionado) } else { etiqueta = total + " " + totalPlu; // Varios archivos en el listado (ninguno seleccionado) } } else { // SI ARCHIVOS SELECCIONADOS... var parteSeleccionados = ""; // a) Parte seleccionados if (seleccionados === 1) { parteSeleccionados = "1 " + selecSin; // 1 archivo seleccionado } else { parteSeleccionados = seleccionados + " " + selecPlu; // Varios archivos seleccionados } var parteTotal = ""; // b) Parte total de archivos if (total === 1) { parteTotal = "1 " + totalSin; // 1 archivo en el listado } else { parteTotal = total + " " + totalPlu; // Varios archivos en el listado } etiqueta = parteSeleccionados + " / " + parteTotal; // Resultado: Seleccionados / Total de archivos } dlg.Control("total_archivos").label = etiqueta; } // MAPA DE GÉNEROS ========================================================================================================================= function setupGeneroMap() { generos_map.Clear(); genres.unique(); for (var i = genres.length - 1; i >= 0; i--) { generos_map.Set(genres(i), str_tools.RemoveDiacritics(genres(i).toLowerCase())); } enum_gen = new Enumerator(generos_map); } // FILTRO DINÁMICO DE GÉNEROS ============================================================================================================== function updateGenero(combo_str) { if (combo_str) var str = str_tools.RemoveDiacritics(combo_str.toLowerCase()); dlg.Control('genero').redraw = false; dlg.Control('genero').RemoveItem(-1); enum_gen.moveFirst(); for (; !enum_gen.atEnd(); enum_gen.moveNext()) { var g = enum_gen.item(); if (!combo_str || generos_map(g).indexOf(str) !== -1) dlg.Control('genero').AddItem(g); } dlg.Control('genero').redraw = true; if (combo_str) { dlg.Control('genero').label = combo_str; dlg.Control('genero').SelectRange(combo_str.length); } } // ACTIVAR/DESACTIVAR BOTONES SEGÚN SELECCIÓN DE ARCHIVOS ================================================================================== function activarBotones(dlg) { var lista = dlg.Control("listview1"); var tieneSeleccion = lista.value && lista.value.count > 0; // Referencias a los botones var btn = { desdeArchivo: dlg.Control('btn_desde_archivo'), anadirCaratula: dlg.Control('btn_anadir_caratula'), eliminarCaratula: dlg.Control('btn_eliminar_caratula'), extraerCaratula: dlg.Control('btn_extraer_caratula'), eliminarTodo: dlg.Control('btn_eliminar_todo'), guardar: dlg.Control('btn_guardar') }; // Si ningún archivo seleccionado: Desactivar todos los botones if (!tieneSeleccion) { for (var key in btn) { btn[key].enabled = false; } return; } // Determinar si hay carátula en el archivo seleccionado var hayCaratula = (typeof cover_archivo === "string" && cover_archivo !== ""); // Desactivar estos botones, pues no hay carátulas btn.desdeArchivo.enabled = true; btn.anadirCaratula.enabled = true; btn.eliminarTodo.enabled = true; btn.guardar.enabled = true; // Activar estos botones, pues hay carátulas btn.eliminarCaratula.enabled = hayCaratula; btn.extraerCaratula.enabled = hayCaratula; } // ========================================================================================================================================= // BUCLE PRINCIPAL DEL DIÁLOGO: PROCESAMIENTO DE EVENTOS // ========================================================================================================================================= while (true) { msg = dlg.GetMsg(); if (!msg.result) break; // ARASTRAR Y SOLTAR ARCHIVOS SOBRE EL LISTADO ========================================================================================= if (msg.event === "drop" && msg.control === "listview1") { var archivosArrastrados = msg.object; anadirArchivos(dlg, archivosListado, archivosArrastrados); // Llamar a la función 'anadirArchivos' continue; } // TECLA DELETE ======================================================================================================================== // Control de foco if (msg.event === "focus" && msg.focus) { focoActual = msg.control; if (focoActual === LISTVIEW) { // Si foco en el listado... if (!hotkeyOn) { try { dlg.AddHotkey("limpiar", "delete"); hotkeyOn = true; } catch (e) {} // Activar la tecla Delete } } else { // Si no... if (hotkeyOn) { try { dlg.DelHotkey("limpiar"); hotkeyOn = false; } catch (e) {} // Desactivar la tecla Delete } } } // Acción de la tecla cuando activada if (msg.event === "hotkey" && msg.name === "limpiar") { limpiarLista(dlg); // Llamar a la función limpiarLista(dlg) } // DETECTAR CAMBIO DE SELECCIÓN EN LA LISTA ============================================================================================ if (msg.event === "selchange" && msg.control === "listview1") { var lista = dlg.Control("listview1"); var seleccionados = []; for (var i = 0; i < lista.count; i++) { var fila = lista.GetItemAt(i); if (fila.selected) { var ruta = fila.subitems(11); // Columna 11 = ruta real var archivo = archivosListado[ruta]; if (archivo) seleccionados.push(archivo); } } if (seleccionados.length === 0) { limpiarPanelDerecho(); item = null; } else if (seleccionados.length === 1) { // Si un único archivo seleccionado... var archivo = seleccionados[0]; cargarMetadatos(archivo); // Llamar a 'cargarMetadatos' para mostrar valores en el panel derecho item = archivo; } else { // Sino... item = null; compararControles(seleccionados, dlg); // Llamar a 'compararControles' para comparar controles y en los diferentes, mostrar < ignorar > } totalArchivos(dlg); // Llamar a 'totalArchivos' para mostrar el total de archivos en la barra del listado caratulaDuo(dlg, seleccionados); // Llamar a 'caratulaDuo' para mostrar informaciones de carátulas en el panel derecho activarBotones(dlg); // Llamar a 'activarBotones' para activar/desactivar botones según selección } // =========================================================== // RECONOCER CLICS EN BOTONES // =========================================================== else if (msg.event == 'click') { switch (msg.control) { // MOSTRAR VALORES DIFERENTES EN CAMPOS < IGNORAR > =========================================================================== case 'titulo': case 'artista': case 'album': case 'anio': case 'pista': case 'comentario': case 'artista_album': case 'compositor': case 'derechos': case 'numero_disco': case 'genero': break; // BOTÓN AÑADIR ARCHIVOS ======================================================================================================= case "btn_anadir_archivo": anadirArchivos(dlg, archivosListado); break; // LIMPIAR LISTA CON BOTÓN LIMPIAR LISTA ======================================================================================= case "btn_eliminar_archivo": limpiarLista(dlg); // Llama a la función limpiarLista totalArchivos(dlg) //Llama a la función totalArchivos para que actualice su estado (valores) break; // SELECCIONAR TODO ============================================================================================================ case "btn_seleccionar_todo": var lista = dlg.Control("listview1"); for (var i = 0; i < lista.count; i++) { lista.GetItemAt(i).selected = true; } break; // DESELECCIONAR =============================================================================================================== case "btn_deseleccionar": var lista = dlg.Control("listview1"); for (var i = 0; i < lista.count; i++) { lista.GetItemAt(i).selected = false; } limpiarPanelDerecho; // Llamar a esta función para limpiar panel derecho break; // AÑADIR CARÁTULA============================================================================================================== case "btn_anadir_caratula": new_coverart_usuario = dlg.Open('Covert'); try { if (new_coverart_usuario.result && new_coverart_usuario.metadata == 'image') { dlg.Control('caratula').label = Script.LoadImage(new_coverart_usuario); cover_usuario = new_coverart_usuario; dlg.Control('info_caratula').label = new_coverart_usuario.metadata.image.picwidth + ' x ' + new_coverart_usuario.metadata.image.picheight + ' x ' + new_coverart_usuario.metadata.image.picdepth + ' (' + new_coverart_usuario.size.fmt + ')'; cover_usuario = new_coverart_usuario + ''; dlg.Control('btn_eliminar_caratula').enabled = true; // Activar el botón Limpiar carátula dlg.Control('btn_extraer_caratula').enabled = true; // Activar el botón Extraer carátula } } catch (err) { DOpus.Output('Error leyendo la nueva carátula : ' + err); } break; // ELIMINAR CARÁTULA =========================================================================================================== case "btn_eliminar_caratula": dlg.Control("caratula").label = ""; dlg.Control("info_caratula").label = ""; cover_usuario = "eliminar"; cover_archivo = ""; dlg.Control('btn_eliminar_caratula').enabled = false; // Desactivar el botón Limpiar carátula dlg.Control('btn_extraer_caratula').enabled = false; // Desactivar el botón Extraer carátula break; // EXTRAER CARÁTULA ============================================================================================================ case "btn_extraer_caratula": var dlg_guardar = DOpus.Dlg; dlg_guardar.window = dlg; dlg_guardar.title = "Guardar carátulas"; var carpetaDescargas = DOpus.FSUtil.Resolve("%USERPROFILE%\\Downloads"); var rutaDestinoBase = dlg_guardar.Save(null, carpetaDescargas); if (!rutaDestinoBase || rutaDestinoBase == "") return; // Cancelado var fso = new ActiveXObject("Scripting.FileSystemObject"); var carpeta = fso.GetParentFolderName(rutaDestinoBase); var nombreBase = fso.GetBaseName(rutaDestinoBase); var extension = fso.GetExtensionName(rutaDestinoBase); if (extension === "") extension = "jpg"; if (!nombreBase || nombreBase == "") return; // Nombre inválido var lista = dlg.Control("listview1"); var seleccion = lista.value; if (!seleccion || seleccion.count === 0) return; var cmd = DOpus.Create.Command(); cmd.Clear(); var rutaFinalUnica = ""; var caratulasExtraidas = {}; // Evitar duplicados var contador = 1; for (var i = 0; i < seleccion.count; i++) { var fila = lista.GetItemAt(seleccion(i)); var rutaArchivo = fila.subitems(11); var file = archivosListado[rutaArchivo]; if (!file || !file.metadata || !file.metadata.audio || !file.metadata.audio.coverart) continue; var covers = file.metadata.audio.coverart; if (covers.count === 0) continue; for (var j = 0; j < covers.count; j++) { var cover = covers(j); if (!cover || !cover.data) continue; var tipo = cover.type || "caratula"; // Identificador de carátula (saber si la misma carátula existe en varios archivos, para no guardarla varias veces) var idCover = DOpus.FSUtil.Hash(cover.data, "md5"); if (caratulasExtraidas[idCover]) continue; caratulasExtraidas[idCover] = true; var tmp = DOpus.FSUtil().GetTempFile(); tmp.Write(cover.data); tmp.Close(); var coverPath = tmp.path + ""; // Nombre del archivo de salida var nombreFinal; if (seleccion.count === 1 && covers.count === 1 && contador === 1) { // Solo una carátula en un archivo = nombre simple nombreFinal = nombreBase + "." + extension; rutaFinalUnica = carpeta + "\\" + nombreFinal; } else { // Varias carátulas o varios archivos = numeración + tipo var numStr = ("0" + contador).slice(-2); nombreFinal = nombreBase + " (" + numStr + ") [" + tipo + "]." + extension; } contador++; cmd.AddLine('Copy FILE="' + coverPath + '" TO="' + carpeta + '" AS="' + nombreFinal + '"'); } } cmd.Run(); var dlg_exito = DOpus.Dlg; dlg_exito.window = dlg; dlg_exito.icon = "info"; var cantidadCaratulas = contador - 1; if (Script.config["idioma"] === 2) { dlg_exito.title = "Extraction"; dlg_exito.message = (cantidadCaratulas === 1) ? "Cover extracted to:\n" + rutaFinalUnica : "Covers extracted to:\n" + carpeta; dlg_exito.buttons = "Ok"; } else { dlg_exito.title = "Extracción"; dlg_exito.message = (cantidadCaratulas === 1) ? "Carátula extraída en:\n" + rutaFinalUnica : "Carátulas extraídas en:\n" + carpeta; dlg_exito.buttons = "Aceptar"; } dlg_exito.Show(); break; // ELIMINAR TODO =============================================================================================================== case 'btn_eliminar_todo': limpiarPanelDerecho(true); // Llama a la función limpiarPanelDerecho activarBotones(dlg); break; // DESDE ARCHIVO =============================================================================================================== case "btn_desde_archivo": var lista = dlg.Control("listview1"); var rutasSeleccionadas = []; // OBTENER RUTAS DE ARCHIVOS SELECCIONADOS for (var i = 0; i < lista.count; i++) { var fila = lista.GetItemAt(i); if (fila.selected) { var ruta = fila.subitems(11); if (ruta) rutasSeleccionadas.push(ruta); } } // EXTRAER DATOS DESDE NOMBRES DE ARCHIVOS for (var i = 0; i < rutasSeleccionadas.length; i++) { var ruta = rutasSeleccionadas[i]; var file = archivosListado[ruta]; if (!file) continue; // Obtener nombre de carpeta superior var carpeta_superior = file.path.filepart; // Obtener nombre de archivo sin extensión var nombreArchivo = file.name; // Nombre de archivo con extensión var punto = nombreArchivo.lastIndexOf("."); if (punto > 0) nombreArchivo = nombreArchivo.substr(0, punto); // Nombre de archivo sin extensión // Inicializar variables var pista = ""; var titulo = ""; var artista = ""; // Obtener número de pista en el inicio del nombre (ignorar separadores) var separadores = [". ", "_ ", "- ", " "]; for (var j = 0; j < separadores.length; j++) { var sep = separadores[j]; var sepIndex = nombreArchivo.indexOf(sep); if (sepIndex > 0 && /^\d+$/.test(nombreArchivo.substring(0, sepIndex))) { pista = nombreArchivo.substring(0, sepIndex); // Número de pista nombreArchivo = nombreArchivo.substring(sepIndex + sep.length); // Nombre sin pista break; } } // Obtener título y artista (dividiendo nombre sin pista, a nivel del guión " - " var partes = nombreArchivo.split(" - "); titulo = (partes.length > 0) ? ("" + partes[0]).replace(/^\s+|\s+$/g, "") : ""; artista = (partes.length > 1) ? ("" + partes[1]).replace(/^\s+|\s+$/g, "") : ""; // Guardar los datos extraídos en un objeto vinculado a la ruta datosExtraidos[ruta] = { pista: pista, titulo: titulo, artista: artista, album: carpeta_superior }; } // MOSTRAR DATOS EN EL PANEL var mostrarIndicador = (rutasSeleccionadas.length > 1); // Si varios archivos seleccionados var textoIndicador = idioma === 2 ? "< from filename >" : "< desde nombre archivo >"; // Texto indicador (texto a mostrar en los campos cuando varios archivos seleccionados) // Limpiar y establecer valor en un ComboBox function setCombo(ctrlName, valor) { var ctrl = dlg.Control(ctrlName); ctrl.RemoveItem(-1); // Elimina todos los ítems anteriores ctrl.AddItem(valor); // Añade el nuevo valor o texto ctrl.value = 0; // Selecciona el primer y único ítem } if (mostrarIndicador) { // Si varios archivos seleccionados: Mostrar texto indicador en los campos... setCombo("pista", textoIndicador); setCombo("titulo", textoIndicador); setCombo("artista", textoIndicador); setCombo("album", textoIndicador); } else if (rutasSeleccionadas.length === 1) { // Si un único archivo seleccionado: Mostrar valores reales en los campos... var datos = datosExtraidos[rutasSeleccionadas[0]]; setCombo("pista", datos.pista); setCombo("titulo", datos.titulo); setCombo("artista", datos.artista); setCombo("album", datos.album); } break; // AYUDA ======================================================================================================================= case "btn_ayuda": var dlg2 = DOpus.Dlg; dlg2.window = dlg; dlg2.icon = "info"; var idioma = Script.config["idioma"]; if (idioma === 2) { dlg2.title = "Help"; dlg2.message = "The option \"Track, title, artist and album from file name\" allows you to set the values of these fields based on the file name.\n\n" + "EXAMPLE\n\n" + "Baladas favoritas (folder)\n" + " 01. Words - Bee Gees.mp3\n\n" + "RESULT\n\n" + "Track: 01\n" + "Title: Words\n" + "Artist: Bee Gees\n" + "Album: Baladas favoritas\n\n" + "Note: Even if the file name doesn't exactly match the \"##. Title - Artist\" pattern, this option will still apply whatever values it can extract."; dlg2.buttons = "OK"; } else { dlg2.title = "Ayuda"; dlg2.message = "La opción \"Pista, título, artista y álbum desde archivo\" permite establecer los valores de estos campos desde el nombre del archivo.\n\n" + "EJEMPLO\n\n" + "Baladas favoritas (carpeta)\n" + " 01. Words - Bee Gees.mp3\n\n" + "RESULTADO\n\n" + "Pista: 01\n" + "Título: Words\n" + "Artista: Bee Gees\n" + "Álbum: Baladas favoritas\n\n" + "Nota: Aunque el nombre del archivo no coincida exactamente con el formato \"##. Título - Artista\", esta opción funcionará, aplicándose solo las informaciones coincidentes."; dlg2.buttons = "Aceptar"; } dlg2.Show(); break; // GUARDAR ===================================================================================================================== case "btn_guardar": var cmd = DOpus.Create().Command(); cmd.SetSourceTab(scriptCmdData.func.sourcetab); // Obtener valor de cada campo function obtenerCampo(id) { var val = dlg.Control(id).label; // Campos a ignorar del guardado if ((idioma === 2 && val === "< ignore >") || (idioma !== 2 && val === "< ignorar >")) return null; // Campos desde nombre de archivo if ((idioma === 2 && val === "< from filename >") || (idioma !== 2 && val === "< desde nombre archivo >")) return "< desde nombre archivo >"; return val; } var campos = { title: obtenerCampo("titulo"), artist: obtenerCampo("artista"), album: obtenerCampo("album"), year: obtenerCampo("anio"), comment: obtenerCampo("comentario"), albumartist: obtenerCampo("artista_album"), composer: obtenerCampo("compositor"), copyright: obtenerCampo("derechos"), track: obtenerCampo("pista"), disc: obtenerCampo("numero_disco"), genre: obtenerCampo("genero") }; var archivosGuardados = 0; // Obtener archivos seleccionados en el listview var lista = dlg.Control("listview1"); var seleccion = lista.value; var rutasSeleccionadas = []; if (typeof seleccion === "number" && seleccion >= 0) { var fila = lista.GetItemAt(seleccion); var ruta = fila.subitems(11); if (ruta) rutasSeleccionadas.push(ruta); } else if (seleccion && seleccion.count > 0) { for (var i = 0; i < seleccion.count; i++) { var fila = lista.GetItemAt(seleccion(i)); var ruta = fila.subitems(11); if (ruta) rutasSeleccionadas.push(ruta); } } // Verificar si debe guardarse la carátula var guardarCaratula = true; if (rutasSeleccionadas.length > 1) { var primera = cover_identificador[rutasSeleccionadas[0]]; var caratulasDistintas = false; for (var i = 1; i < rutasSeleccionadas.length; i++) { var actual = cover_identificador[rutasSeleccionadas[i]]; if (actual !== primera) { caratulasDistintas = true; break; } } if (!cover_usuario || (caratulasDistintas && cover_usuario === primera)) { guardarCaratula = false; // No se cambió la carátula o es igual a las que ya tenían: no guardar } } // Aplicar cambios a cada archivo seleccionado for (var i = 0; i < rutasSeleccionadas.length; i++) { var ruta = rutasSeleccionadas[i]; if (!ruta) continue; var file = archivosListado[ruta]; var metaCommand = 'SetAttr FILE="' + ruta + '" META '; // Campos de texto for (var campo in campos) { var valor = campos[campo]; if (valor === "< desde nombre archivo >") { var d = datosExtraidos[ruta]; if (d) { switch (campo) { case "title": valor = d.titulo; break; case "artist": valor = d.artista; break; case "album": valor = d.album; break; case "track": valor = d.pista; break; default: valor = null; break; } } else { valor = null; } } if (valor !== null && valor !== undefined) { metaCommand += campo + ':"' + valor + '" '; } } // Guardar campos de texto if (metaCommand.indexOf("META ") != metaCommand.length - 5) { cmd.RunCommand(metaCommand); archivosGuardados++; } // Guardar o eliminar carátula if (guardarCaratula) { if (cover_usuario === 'eliminar') { cmd.RunCommand('SetAttr FILE="' + ruta + '" META "coverart:-front"'); } else if (cover_usuario && cover_usuario != cover_archivo) { cmd.RunCommand('SetAttr FILE="' + ruta + '" META "coverart:front:' + cover_usuario + '"'); } } } // Cerrar si hubo cambios if (archivosGuardados > 0 || (guardarCaratula && cover_usuario)) { dlg.EndDlg(1); } else { DOpus.Output("No se aplicaron cambios."); } break; // CANCELAR ==================================================================================================================== case 'btn_cancelar': // FUNCION LOCAL: Evaluar si hay cambios function estadoFinal() { if (!item || !estadoInicial) return false; function valor(ctrl) { return dlg.Control(ctrl).label; } var campos = [ "titulo", "artista", "album", "anio", "pista", "comentario", "artista_album", "compositor", "derechos", "numero_disco", "genero" ]; for (var i = 0; i < campos.length; i++) { var campo = campos[i]; var valorFinal = valor(campo); var valorInicial = estadoInicial[campo]; if ((valorFinal || "") != (valorInicial || "")) { //DOpus.Output("Se han detectado cambios en el campo: " + campo); //DOpus.Output(" Valor inicial: " + valorInicial); //DOpus.Output(" Valor final: " + valorFinal); return true; } } if (cover_usuario && cover_usuario !== '' && cover_usuario !== cover_archivo) { //DOpus.Output("Se han detectado cambios en la carátula:"); //DOpus.Output(" Inicial: " + cover_archivo); //DOpus.Output(" Final: " + cover_usuario); return true; } return false; } // Evaluar si hubo cambios if (estadoFinal()) { var confirmar = DOpus.Dlg; confirmar.window = dlg; confirmar.icon = "question"; if (Script.config["idioma"] === 2) { confirmar.message = "Changes have been detected.\nDo you want to save them before exiting?"; confirmar.buttons = "Save|Don't save|Cancel"; confirmar.title = "Changes detected"; } else { confirmar.message = "Se han detectado cambios.\n¿Desea guardarlos antes de salir?"; confirmar.buttons = "Guardar|No guardar|Cancelar"; confirmar.title = "Cambios detectados"; } var resp = confirmar.Show(); if (resp == 1) { // Si se presiona el botón 1 (Guardar)... dlg.Control('btn_guardar').Click(); // Simular botón guardar dlg.EndDlg(0); // Cerrar el diálogo } else if (resp == 2) { // Si se presiona el botón 2 (Salir)... dlg.EndDlg(0); // Cerrar el diálogo (sin guardar) } // Si se presiona el botón 3 (Cancelar), no se hace nada, se cierra la ventana de notificación y se regresa al diálogo principal } else { dlg.EndDlg(0); // Si no se detectan cambios, salir directamente } break; // AUTOCOMPLETADO DE GÉNERO ============================================================================================================ } } else if (msg.event === 'editchange' && msg.name === 'genero') { dlg.SetTimer(150, 'genero_timer'); } else if (msg.event === 'timer' && msg.name === 'genero_timer') { dlg.KillTimer('genero_timer'); updateGenero(dlg.Control('genero').value.name); } // ===================================================================================================================================== // FINAL DEL BUCLE DE EVENTOS (while) // ===================================================================================================================================== } } ==SCRIPT RESOURCES