Mp3 Tag File (Built)

@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="&lt;" type="button" width="30" x="236" y="6" />
			<control height="12" name="btn_siguiente" title="&gt;" 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>