@errante, it took a bit of work, but thanks to your invaluable help, I got it right, now the Gender field is filterable. I'm leaving the commented script here because maybe some newbie like me could learn something from it. I just want to thank you once again.
Mp3 Tag File 1.4.js.txt (23.8 KB)
// Mp3 Tag File
// (c) 2024 DASOTA
// Script para Directory Opus.
// Metadatos y configuraciones del script (parte de estas informaciones se muestra en el administrador de scripts)
function OnInit(initData) {
initData.name = 'Mp3 Tag File';
initData.version = '1.4';
initData.copyright = '(c) 2024 DASOTA';
initData.desc = 'Mp3 Tag File';
initData.default_enable = true;
initData.min_version = '13.16';
// Configuraciones del script (botón de engranaje del script en el administrador de scripts)
initData.config_desc = DOpus.Create.Map(); // Mapa de opciones (se especifica solo una vez para todas las posibles opciones de configuraciones)
initData.config_desc('genre') = 'Añadir/eliminar géneros musicales'; // Comentario a mostrarse cuando se seleccione la opción 'genre'
var gen_perso = DOpus.Create.Vector(); // Crea la variable 'gen_perso', como un objeto tipo vector (lista dinámica)
gen_perso.push_back('Romántico'); // Agrega el valor 'Romántico' al final de esa lista (gen_perso)
gen_perso.push_back('Balada');
gen_perso.push_back('Bolero');
gen_perso.push_back('Trova');
gen_perso.push_back('Patrio');
gen_perso.push_back('Son');
gen_perso.push_back('Salsa');
gen_perso.push_back('Guaracha');
gen_perso.push_back('Montuno');
gen_perso.push_back('Cumbia');
gen_perso.push_back('Merengue');
gen_perso.push_back('Tropical');
gen_perso.push_back('Ranchera');
gen_perso.push_back('Reggae');
gen_perso.push_back('Reggaeton');
gen_perso.push_back('Hip Hop');
gen_perso.push_back('Pop');
gen_perso.push_back('Rock');
gen_perso.push_back('Tecno');
gen_perso.push_back('Electrónico');
gen_perso.push_back('Jazz');
gen_perso.push_back('Infantil');
gen_perso.push_back('Religioso');
gen_perso.push_back('Instrumental');
gen_perso.push_back('Vocal');
gen_perso.push_back('Efecto sonoro');
gen_perso.push_back('Inglés');
gen_perso.push_back('Bossa Nova');
gen_perso.push_back('Tropicália');
gen_perso.push_back('Samba');
gen_perso.push_back('Pagode');
gen_perso.push_back('Lambada');
gen_perso.push_back('MPB');
gen_perso.push_back('Forró');
gen_perso.push_back('Sertanejo');
gen_perso.push_back('Sertanejo universitário');
gen_perso.push_back('Axé');
gen_perso.push_back('Funk');
gen_perso.push_back('Gospel');
gen_perso.push_back('Brega');
gen_perso.push_back('Choro');
gen_perso.push_back('Frevo');
gen_perso.push_back('Baião');
gen_perso.push_back('Piseiro');
gen_perso.push_back('Pisadinha');
initData.config['genre'] = gen_perso; // Le asigna, a la opción 'genre', el valor de 'gen_perso'
}
// Añadir comando (parte de estas informaciones se muestran en: Barras > Personalización > Comandos)
function OnAddCommands(addCmdData) {
var cmd = addCmdData.AddCommand();
cmd.name = 'Mp3TagFile'; // Función, utilidad
cmd.method = 'OnMp3TagFile'; // Método o función que se invocará (ejecutará) cuando se llame al comando
cmd.desc = ''; // Descripción
cmd.label = 'Mp3 Tag File'; // Etiqueta, nombre
cmd.template = 'Mp3TagFile'; //Nombre del objeto Diálogo (pestaña Recursos)
cmd.hide = false;
cmd.icon = 'script';
}
// Función principal del script
function OnMp3TagFile(scriptCmdData) {
var dlg = scriptCmdData.func.Dlg(); // Crear un nuevo diálogo (ventana) vacío
dlg.title = 'Mp3 Tag File - Directory Opus'; // Título de la ventana
dlg.template = 'Mp3TagFile'; //Nombre del objeto Diálogo (pestaña Recursos)
dlg.detach = true; // Ventana independiente, separada de Opus, permanece abierta y activa incluso si el script termina su ejecución
dlg.Create(); // Crea la ventana internamente (en segundo plano)
var tab = scriptCmdData.func.sourcetab;
if (!tab || tab.selected_files.count == 0) { // Si no hay archivos de audio seleccionados...
DOpus.Output('No hay archivos válidos para continuar'); // Mostrar este mensaje en la consola de Opus...
return; // y cancelar la ejecución del resto del código
}
var item;
var genres = Script.config['genre'];
var org_cover_usuario, cover_interna;
var str_tools = DOpus.Create().StringTools(); // Varieble para manipulación de strings (eliminar acentos)
var generos_map = DOpus.NewMap(); // Crea un mapa para almacenar los géneros y sus versiones normalizadas
var enum_gen; // Variable para enumerar los géneros más adelante
// Función que prepara el mapa de géneros
function setupGeneroMap() {
generos_map.Clear(); // Limpia el mapa si ya tenía datos
genres.unique(); // Elimina posibles géneros duplicados en la lista original
// Recorre la lista de géneros en orden inverso (por optimización)
for (var i = genres.length - 1; i >= 0; i--) {
generos_map.Set(genres(i), str_tools.RemoveDiacritics(genres(i).toLowerCase())); // Añade al mapa, valores sin acentos y en minúsculas
}
enum_gen = new Enumerator(generos_map); // Crea un enumerador para poder recorrer el mapa fácilmente
}
// Filtra la lista desplegable según lo que se escriba
function updateGenero(combo_str) {
if (combo_str) var str = str_tools.RemoveDiacritics(combo_str.toLowerCase()); // Si hay texto escrito, lo normaliza (elimina acentos y lo convierte en minúsculas)
dlg.Control('genero').redraw = false; // Desactiva el redibujado para evitar parpadeos visuales
dlg.Control('genero').RemoveItem(-1); // Limpia todos los items actuales de la lista (-1 = todos)
enum_gen.moveFirst(); // Prepara para recorrer el mapa desde el primer elemento
var item; // Variable temporal para cada género
// Recorre todos los elementos del mapa
for (; !enum_gen.atEnd(); enum_gen.moveNext()) {
item = enum_gen.item(); // Obtiene el nombre original del género
// Si no hay texto escrito, o si el género normalizado contiene el texto escrito
if (!combo_str || generos_map(item).indexOf(str) !== -1)
dlg.Control('genero').AddItem(item); // Añade el género a la lista
}
dlg.Control('genero').redraw = true; // Reactiva el redibujado del control
if (!combo_str) return; // Si no había texto escrito, termina aquí
dlg.Control('genero').label = combo_str; // Restaura el texto que había escrito el usuario (no el filtrado)
dlg.Control('genero').SelectRange(combo_str.length); // Coloca el cursor al final del texto
}
function loadMP3Data(audio_item) {
if (!audio_item || audio_item.ext != '.mp3' || audio_item.metadata != 'audio') { // Si no es un archivo de audio, o es de audio pero no tiene metadatos de audio
DOpus.Output('Se requiere de un archivo de audio con metadatos editables'); // Mostrar este mensaje
return false; // Detiene la ejecución de la función y devuelve el valor booleano 'false'
}
try {
// Asignación de valores a los controles
//DOpus.Output('Leyendo ' + audio_item); // Mostrar mensaje de lectura de archivo
dlg.Control('archivo').value = audio_item.name_stem;
dlg.Control('titulo').value = audio_item.metadata.audio.mp3title;
dlg.Control('artista').value = audio_item.metadata.audio.mp3artist;
dlg.Control('album').value = audio_item.metadata.audio.mp3album;
dlg.Control('anio').value = audio_item.metadata.audio.mp3year;
dlg.Control('pista').value = audio_item.metadata.audio.mp3track;
dlg.Control('comentario').value = audio_item.metadata.audio.mp3comment;
dlg.Control('artista_album').value = audio_item.metadata.audio.mp3albumartist;
dlg.Control('compositor').value = audio_item.metadata.audio.composers;
dlg.Control('derechos').value = audio_item.metadata.audio.copyright;
dlg.Control('numero_disco').value = audio_item.metadata.audio.mp3disc;
cover_interna = '';
// Buscar carátulas dentro del archivo
if (audio_item.metadata.audio.coverart > 0) { // Si el archivo tiene una o más carátulas...
var reso;
// ...las recorre todas buscando la principal (front)
for (var i = 0; i < audio_item.metadata.audio.coverart.count; i++) {
reso = audio_item.metadata.audio.coverart(i);
if (reso + '' == 'front') {
cover_interna = reso + '';
break;
}
}
//... si se encuntra alguna carátula, se guarda en un archivo temporal y se muestra, además de su resolución y tamaño
if (cover_interna) {
var tmpFile = DOpus.FSUtil().GetTempFile();
tmpFile.Write(reso.data);
tmpFile.Close();
org_cover_usuario = tmpFile.path + '';
dlg.Control('caratula').label = Script.LoadImage(tmpFile);
dlg.Control('resolucion').label = reso.width + ' x ' + reso.height + ' x ' + reso.depth + ' (' + reso.size.fmt + ')';
} else DOpus.Output('Ninguna carátula encontrada');
}
//... si no se encuentra ninguna carátula, se limpian los campos relacionados a ella
if (!cover_interna) {
cover_interna = 'front';
org_cover_usuario = '';
dlg.Control('caratula').label = '';
dlg.Control('resolucion').label = '';
}
setupGeneroMap();
updateGenero();
dlg.Control('genero').InsertItemAt(0, audio_item.metadata.audio.mp3genre);
dlg.Control('genero').value = 0;
item = audio_item;
return true;
} catch (err) {
DOpus.Output('Error leyendo el archivo ' + audio_item + ':' + err);
return false;
}
}
// Colores del control carátula
first_item = item + '';
dlg.Control('caratula').bg = '#FFFFFF'; // Fondo de la carátula (blanco)
dlg.Control('resolucion').fg = '#000000'; // Texto de la etiqueta resolución (negro)
dlg.Control('resolucion').bg = '#FFFFFF'; // Fondo de la etiqueta resolución (blanco)
// Activar los botones añadir, eliminar, eliminar todo y guadar, solo si existe seleccionado algún archivo válido (audio)
dlg.Control('btn_guardar').enabled = dlg.Control('btn_eliminar_todo').enabled =
dlg.Control('btn_anadir_caratula').enabled = dlg.Control('btn_eliminar_caratula').enabled = loadMP3Data(tab.selected_files(0));
// Preparar el objeto cmd para acciones futuras (modificar etiquetas, guardar, etc.)
var cmd = scriptCmdData.func.command;
cmd.ClearFiles();
// Estado inicial y observación de la pestaña (guarda estado inicial de la carátula del usuario (por si se cambia luego), y "vigila" la pestaña por si se agregan o eliminan archivos, sabiendo siempre cuántos archivos hay)
var new_coverart_usuario;
var cover_usuario = org_cover_usuario;
dlg.WatchTab(tab, 'add,delete');
var item_count = tab.stats.items;
dlg.Show(); // Se muestra la ventana al usuario, con todos sus campos y valores
var count; // Variable para uso posterior (ej. número de ítems procesados)
while (true) { // Inicia un bucle infinito, que solo se detendrá cuando ocurra una condición específica (en este caso, cuando el usuario cierre la ventana o presione un botón que finalice el diálogo)
msg = dlg.GetMsg(); // Espera y captura eventos del usuario, como clic en botones (Aceptar, Cancelar, etc.), cambios en campos de texto, selecciones en listas, acciones del teclado o ratón
if (!msg.result) break; // Si no hay más eventos por procesar (si presionado el botón cancelar o cerrar) se sale del bucle (se cierra la ventana)
if (msg.event == 'tab') {
DOpus.Output('value:' + msg.value + '; data:' + msg.data);
switch (msg.value) {
case 'close':
dlg.Control('btn_anterior').enabled = false;
dlg.Control('btn_siguiente').enabled = false;
break;
case 'filechange':
if (msg.data & 1) {
DOpus.Output('Un archivo fue añadido');
item_count++;
}
if (msg.data & 2) {
DOpus.Output('Un archivo fue eliminado');
item_count--;
}
break;
}
} else if (msg.event == 'click') {
switch (msg.control) {
case 'btn_anterior':
case 'btn_siguiente':
count = 0;
do {
if (cmd.RunCommand('Select ' + (msg.control == 'btn_anterior' ? 'PREV' : 'NEXT'))) {
DOpus.Delay(10);
item = tab.GetFocusItem();
} else break;
count++;
} while (count <= item_count && (item == null || item.ext != '.mp3' || item + '' == first_item));
dlg.Control('btn_guardar').enabled = dlg.Control('btn_eliminar_todo').enabled =
dlg.Control('btn_anadir_caratula').enabled = dlg.Control('btn_eliminar_caratula').enabled = loadMP3Data(item);
break;
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);
dlg.Control('resolucion').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 + '';
}
} catch (err) {
DOpus.Output('Error leyendo la nueva carátula : ' + err);
}
break;
case 'btn_eliminar_caratula':
dlg.Control('caratula').label = '';
dlg.Control('resolucion').label = '';
cover_usuario = '';
break;
case 'btn_eliminar_todo':
dlg.Control('titulo').value = '';
dlg.Control('artista').value = '';
dlg.Control('album').value = '';
dlg.Control('anio').value = '';
dlg.Control('pista').value = '';
dlg.Control('genero').label = '';
dlg.Control('comentario').value = '';
dlg.Control('artista_album').value = '';
dlg.Control('compositor').value = '';
dlg.Control('derechos').value = '';
dlg.Control('numero_disco').value = '';
dlg.Control('caratula').label = '';
dlg.Control('resolucion').label = '';
cover_usuario = '';
break;
case "btn_desde_archivo":
var name = dlg.Control("archivo").value;
var pista_name = name.replace(/(^[0-9]*)(.*)/, "$1");
var match = name.match(/(^[0-9\.\s]*)(.*)(\s-\s)(.*)/);
if (match) {
var title_name = name.replace(/(^[0-9\.\s]*)(.*)(\s-\s)(.*)/, "$2");
var artist_name = name.replace(/(^[0-9\.\s]*)(.*)(\s-\s)(.*)/, "$4");
} else {
var title_name = name.replace(/(^[0-9\.\s]*)(.*)/, "$2");
var artist_name = "";
}
var carpeta_superior = item.path.filepart;
dlg.Control("pista").value = pista_name;
dlg.Control("titulo").value = title_name;
dlg.Control("artista").value = artist_name;
dlg.Control("album").value = carpeta_superior;
break;
case "btn_ayuda":
var dlg2 = DOpus.Dlg;
dlg2.window = dlg;
dlg2.title = "Help";
dlg2.icon = "info";
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\nEJEMPLO\n\nBaladas favoritas (carpeta)\n 01. Words - Bee Gees.mp3\n\nRESULTADO\n\nPista: 01\nTítulo: Words\nArtista: Bee Gees\nÁlbum: Baladas favoritas\n\nNota: 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;
case 'btn_guardar':
cmd.RunCommand('SetAttr' + ' FILE="' + item + '" META "title:' + dlg.Control("titulo").value + '"');
cmd.RunCommand('SetAttr' + ' FILE="' + item + '" META "artist:' + dlg.Control("artista").value + '"');
cmd.RunCommand('SetAttr' + ' FILE="' + item + '" META "album:' + dlg.Control("album").value + '"');
cmd.RunCommand('SetAttr' + ' FILE="' + item + '" META "year:' + dlg.Control("anio").value + '"');
cmd.RunCommand('SetAttr' + ' FILE="' + item + '" META "track:' + dlg.Control("pista").value + '"');
cmd.RunCommand('SetAttr' + ' FILE="' + item + '" META "genre:' + dlg.Control("genero").value.name + '"');
cmd.RunCommand('SetAttr' + ' FILE="' + item + '" META "comment:' + dlg.Control("comentario").value + '"');
cmd.RunCommand('SetAttr' + ' FILE="' + item + '" META "albumartist:' + dlg.Control("artista_album").value + '"');
cmd.RunCommand('SetAttr' + ' FILE="' + item + '" META "composers:' + dlg.Control("compositor").value + '"');
cmd.RunCommand('SetAttr' + ' FILE="' + item + '" META "copyright:' + dlg.Control("derechos").value + '"');
cmd.RunCommand('SetAttr' + ' FILE="' + item + '" META "discnumber:' + dlg.Control("numero_disco").value + '"');
if (org_cover_usuario != cover_usuario)
cmd.RunCommand('SetAttr FILE="' + item + '" META' + ' "coverart:' + (cover_usuario ? (cover_interna + ':' + cover_usuario) : ('-' + cover_interna)) + '"');
break;
case 'btn_cancelar':
dlg.EndDlg(0);
break;
}
} 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);
}
}
//DOpus.Output('Concluido!');
}
==SCRIPT RESOURCES
<resources>
<resource name="Mp3TagFile" type="dialog">
<dialog fontsize="9" height="193" lang="esm" title="Mp3TagFile" width="332">
<control halign="left" height="8" name="archivo_t" title="Archivo:" type="static" valign="top" width="48" x="8" y="8" />
<control halign="left" height="12" name="archivo" readonly="yes" type="edit" width="148" x="58" y="6" />
<control halign="left" height="8" name="titulo_t" title="Título:" type="static" valign="top" width="48" x="8" y="23" />
<control halign="left" height="12" name="titulo" type="edit" width="148" x="58" y="21" />
<control halign="left" height="8" name="artista_t" title="Artista:" type="static" valign="top" width="48" x="8" y="38" />
<control halign="left" height="12" name="artista" type="edit" width="148" x="58" y="36" />
<control halign="left" height="8" name="album_t" title="Álbum:" type="static" valign="top" width="48" x="8" y="53" />
<control halign="left" height="12" name="album" type="edit" width="148" x="58" y="51" />
<control halign="left" height="8" name="anio_t" title="Año:" type="static" valign="top" width="48" x="8" y="68" />
<control halign="left" height="12" name="anio" number="yes" type="edit" updown="yes" val_min="1" width="50" x="58" y="66" />
<control halign="left" height="8" name="pista_t" title="Pista:" type="static" valign="top" width="18" x="136" y="68" />
<control halign="left" height="12" name="pista" number="yes" type="edit" updown="yes" val_min="1" width="30" x="157" y="66" />
<control halign="left" height="8" name="genero_t" title="Género:" type="static" valign="top" width="48" x="8" y="84" />
<control edit="yes" height="40" name="genero" sort="yes" type="combo" width="148" x="58" y="82" />
<control halign="left" height="8" name="comentario_t" title="Comentario:" type="static" valign="top" width="48" x="8" y="99" />
<control halign="left" height="12" name="comentario" type="edit" width="148" x="58" y="97" />
<control halign="left" height="8" name="artista_album_t" title="Artista álbum:" type="static" valign="top" width="48" x="9" y="113" />
<control halign="left" height="12" name="artista_album" type="edit" width="148" x="59" y="112" />
<control halign="left" height="8" name="compositor_t" title="Compositor:" type="static" valign="top" width="48" x="8" y="129" />
<control halign="left" height="12" name="compositor" type="edit" width="148" x="58" y="127" />
<control halign="left" height="8" name="derechos_t" title="Copyright:" type="static" valign="top" width="48" x="8" y="144" />
<control halign="left" height="12" name="derechos" type="edit" updown="yes" val_min="1" width="148" x="58" y="142" />
<control halign="left" height="8" name="numero_disco_t" title="Número disco:" type="static" valign="top" width="48" x="8" y="159" />
<control halign="left" height="12" name="numero_disco" type="edit" updown="yes" val_min="1" width="148" x="58" y="157" />
<control height="14" name="btn_desde_archivo" title="Pista, título, artista y álbum desde nombre de archivo" type="button" width="181" x="8" y="174" />
<control height="14" name="btn_ayuda" title="?" type="button" width="14" x="192" y="174" />
<control height="12" name="btn_anterior" title="<" type="button" width="30" x="236" y="6" />
<control height="12" name="btn_siguiente" title=">" type="button" width="30" x="273" y="6" />
<control height="122" name="caratula_t" title="Carátula" type="group" width="113" x="212" y="21" />
<control halign="center" height="8" name="resolucion" type="static" valign="top" width="100" x="218" y="112" />
<control halign="center" height="86" image="yes" name="caratula" type="static" valign="center" width="100" x="218" y="32" />
<control height="14" name="btn_anadir_caratula" title="Añadir..." type="button" width="46" x="220" y="124" />
<control height="14" name="btn_eliminar_caratula" title="Limpiar" type="button" width="46" x="270" y="124" />
<control height="14" name="btn_eliminar_todo" title="Limpiar todos los campos" type="button" width="97" x="220" y="147" />
<control default="yes" height="14" name="btn_guardar" title="Guardar" type="button" width="46" x="220" y="174" />
<control height="14" name="btn_cancelar" title="Salir" type="button" width="46" x="270" y="174" />
</dialog>
</resource>
</resources>