Xwork's Blog

The lord is waiting to take your hand.

Vamos a programar #109 - KeyRemap M 1.0

No hay comentarios.

Hola de nuevo a todos, el día de hoy vamos a ver una aplicación que hice desde hace ya tiempo pero que por alguna razón no terminé hasta hace poco.


 Para comprender mejor el post, te recomiendo revisar los post referentes al tema:

Pero por si no quieres leer. KeyRemap es una aplicación que sirve para reasignar las algunas de las teclas del teclado. Es importante mencionar que las resignaciones pueden no funcionar en algunas aplicaciones o juegos, pero para la mayoría de las aplicaciones de windows esta aplicación podría servirnos.

El Código 

El Código de la aplicación es el siguiente:

using System;
using System.Windows.Forms;
using Microsoft.Win32;
using System.Collections.Generic;
using System.IO;
using System.Security;
using System.Security.AccessControl;

namespace KeyRemap
{
public partial class FrmMain : Form
{
private byte[] Buff = { };

public FrmMain()
{
InitializeComponent();
}

#region "Funciones y procedimientos"

/// <summary>
/// Escribe una secuencia de bytes en el registro de windows
/// </summary>
/// <param name="Data">Secuencia de bytes a escribir</param>
private void WriteHexRegKey(byte[] Data)
{

string user = Environment.UserDomainName + "\\" + Environment.UserName;

RegistrySecurity rs = new RegistrySecurity();

rs.AddAccessRule(new RegistryAccessRule(user, RegistryRights.ReadKey | RegistryRights.Delete, InheritanceFlags.None, PropagationFlags.None, AccessControlType.Allow));


RegistryKey RegKey = Registry.LocalMachine;
RegKey = RegKey.CreateSubKey(@"SYSTEM\CurrentControlSet\Control\Keyboard Layout",RegistryKeyPermissionCheck.Default, rs);
RegKey.SetValue("ScanCode Map", Data, RegistryValueKind.Binary);

RegKey.Close();
}

/// <summary>
/// Elimina la entrada "ScanCode Map" Del registro de windows
/// </summary>
private void DeleteRegKey()
{
try
{
RegistryKey RegKey = Registry.LocalMachine;
RegKey = RegKey.OpenSubKey(@"SYSTEM\CurrentControlSet\Control\Keyboard Layout", true);
RegKey.DeleteValue("ScanCode Map");
}
catch(Exception Err)
{
MessageBox.Show("Un error ocurrio, prueba iniciar la aplicacion en modo de administrador\n" + Err.StackTrace.ToString() + "","Error",MessageBoxButtons.OK,MessageBoxIcon.Error);
}
}

/// <summary>
/// Leee los datos de la llave del registro "ScanCode Map"
/// </summary>
/// <returns>Una secuencia de bytes con el contenido de la llave </returns>
private byte[] ReadRegKey()
{
byte[] Data = { };
try
{
RegistryKey RegKey = Registry.LocalMachine;
RegKey = RegKey.OpenSubKey(@"SYSTEM\CurrentControlSet\Control\Keyboard Layout");
byte[] KeyVal = (byte[])RegKey.GetValue("Scancode Map");
RegKey.Close();
Data = KeyVal;
return Data;
}
catch
{
return Data;
}
}

/// <summary>
/// Guarda las reasignaciones de las teclas en arhivo ".bat"
/// </summary>
/// <param name="Data">Los datos que se van a escribir</param>
private void SaveRegFile(byte[] Data)
{
using (SaveFileDialog SFD = new SaveFileDialog())
{
SFD.Filter = "Archivo de registro|*.reg";
SFD.AddExtension = true;
if (SFD.ShowDialog() == DialogResult.OK)
{
using (StreamWriter SW = new StreamWriter(SFD.OpenFile()))
{
SW.AutoFlush = true;
SW.WriteLine(@"Windows Registry Editor Version 5.00");
SW.WriteLine();
SW.WriteLine(@"[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Keyboard Layout]");
SW.Write(string.Concat("\"", "Scancode Map", "\"", "=hex:"));
for (int i = 0; i < Data.Length; i++)
{
if (i < Data.Length - 1)
SW.Write(string.Concat(Data[i].ToString("X2"), ","));
else
SW.Write(Data[i].ToString("X2"));
}
}
}

}
}

/// <summary>
/// Determina si un número es par
/// </summary>
/// <param name="Number">Número que se quiere saber si es par</param>
/// <returns>Regresa true si es par; false en caso contrario</returns>
private bool IsEven(int Number)
{
if ((Number % 2) == 0)
return true;
else
return false;
}

/// <summary>
/// Llena el ListView con los datos aplicando el formato correcto
/// </summary>
/// <param name="Data">DAtos a formatear</param>
private void FillListView(byte[] Data)
{
int Entries = Data[8] - 1;
if (Data.Length < 20 && (Data.Length % 4) != 0)
MessageBox.Show("La configuracion no parece estar correcta\nQuieres eliminarla?", "Error", MessageBoxButtons.YesNo, MessageBoxIcon.Exclamation);
else
{
//e020
//57376
//224
//32
for (int i = 0; i < Entries; i++)
{
int MainItem = Data[12 + (i * 4)] | (Data[13+ (i * 4)] << 8);
int SubItem = Data[14 + (i * 4)] | (Data[15 + (i * 4)] << 8);
ListViewItem LV = new ListViewItem(new string[] { MainItem.ToString(), SubItem.ToString() });
LvReassignedKeys.Items.Add(LV);
}
}
}

/// <summary>
/// Escribe o guarda los datos de las reasignaciones
/// </summary>
/// <param name="SaveFile">SI es true, genera un arhivo .bat para guardar. Si es false, escribe la llave en el registro</param>
private void BuildAndSaveData(bool SaveFile)
{
List<string> Items = new List<string>();
for (int i = 0; i < LvReassignedKeys.Items.Count; i++)
{
Items.AddRange(new string[] { LvReassignedKeys.Items[i].Text, LvReassignedKeys.Items[i].SubItems[1].Text });
}

if (SaveFile)
{
if (Items.Count > 0)
SaveRegFile(RebuildRegistryEntry(Items));
}
else
{
WriteHexRegKey(RebuildRegistryEntry(Items));
}

}

/// <summary>
/// Formatea el número para usarse en las reasignaciones
/// </summary>
/// <param name="Number">Número que se va a formatear</param>
/// <returns>Regresa el número con el formato adecuado</returns>
private byte GetBytes(int Number)
{
return (byte)(Number % 0xE0000);
}

/// <summary>
/// Lee los datos del registro y los agrega al ListView
/// </summary>
private void LoadData()
{

Buff = ReadRegKey();

try
{

if (!(Buff == null))
{
if ((Buff.Length % 4) != 0 || Buff.Length < 20)
{
throw new InvalidDataException();
}

TSlblCount.Text = Convert.ToString(Buff[8] - 1);
FillListView(Buff);
}

}
catch (InvalidDataException)
{
MessageBox.Show("Se encontraron datos no válidos en el registro", "Error" , MessageBoxButtons.OK , MessageBoxIcon.Error);

TSlblCount.Text = "0";

}
}

/// <summary>
/// Crea los datos para agregarse al registro con el formato adecuado
/// </summary>
/// <param name="Items">Lista del tipo string con los elementos</param>
/// <returns>Regresa un arreglo de bytes con los datos</returns>
private byte[] RebuildRegistryEntry(List<string> Items)
{

byte[] OutBuff = new byte[16 + ((Items.Count/2)*4)]; //Encabezado y flags

OutBuff[0] = 0; //Encabezado y flags
OutBuff[1] = 0; //Encabezado y flags
OutBuff[2] = 0; //Encabezado y flags
OutBuff[3] = 0; //Encabezado y flags
OutBuff[4] = 0; //Encabezado y flags
OutBuff[5] = 0; //Encabezado y flags
OutBuff[6] = 0; //Encabezado y flags
OutBuff[7] = 0; //Encabezado y flags
OutBuff[8] = (byte)((Items.Count / 2) + 1); //Numero de entradas
OutBuff[9] = 0; //Numero de entradas
OutBuff[10] = 0;//Numero de entradas
OutBuff[11] = 0;//Numero de entradas

for (int i = 0; i < Items.Count; i++)
{
if (Convert.ToInt32((Items[i])) > 255)
{
OutBuff[12 + (i * 2)] = GetBytes(Convert.ToInt32(Items[i]));
OutBuff[13 + (i * 2)] = 0xe0;
}
else
{
OutBuff[12 + (i * 2)] = Convert.ToByte( Items[i]);
OutBuff[13 + (i * 2)] = 0x00;
}
}

OutBuff[OutBuff.Length - 4] = 0;
OutBuff[OutBuff.Length - 3] = 0;
OutBuff[OutBuff.Length - 2] = 0;
OutBuff[OutBuff.Length - 1] = 0;

return OutBuff;
}

#endregion

private void FrmMain_Load(object sender, EventArgs e)
{
LoadData();
}

private void BtnAdd_Click(object sender, EventArgs e)
{
if (!string.IsNullOrWhiteSpace(TxtKeyIn.Text) && !string.IsNullOrWhiteSpace(TxtKeyOut.Text))
{
ListViewItem LV = new ListViewItem(TxtKeyOut.Text);
LV.SubItems.Add(TxtKeyIn.Text);
LvReassignedKeys.Items.Add(LV);
}
else
MessageBox.Show("Revisa los valores de las teclas", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
}

private void textBox1_KeyDown(object sender, KeyEventArgs e)
{
e.SuppressKeyPress = true;
}



private void TSMIExport_Click(object sender, EventArgs e)
{

BuildAndSaveData(SaveFile:true);
}

private void BtnApply_Click(object sender, EventArgs e)
{
if (MessageBox.Show("Realmente quires reasignar estas teclas?", "Eliminar", MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes)
BuildAndSaveData(false);
}

private void BtnDelete_Click(object sender, EventArgs e)
{
try
{
DeleteRegKey();
}
catch
{
MessageBox.Show( e.ToString());
}

if (LvReassignedKeys.SelectedItems.Count > 0)
LvReassignedKeys.Items.Remove(LvReassignedKeys.SelectedItems[0]);
}

private void TSMIUpdate_Click(object sender, EventArgs e)
{
LvReassignedKeys.Items.Clear();
LoadData();
}

private void salirToolStripMenuItem_Click(object sender, EventArgs e)
{
Application.Exit();
}

private void TSMIReset_Click(object sender, EventArgs e)
{
if (MessageBox.Show("Realmente quieres eliminar todo", "Eliminar", MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes)
DeleteRegKey();

LvReassignedKeys.Items.Clear();
LoadData();

}

private void BtnRemoveKey_Click(object sender, EventArgs e)
{
foreach (ListViewItem LV in LvReassignedKeys.CheckedItems)
{
LvReassignedKeys.Items.Remove(LV);
}
}
}
}

El procedimiento "WriteHexRegKey()". Este procedimiento recibe un arreglo del tipo "byte", en esta función es donde se escribe la entrada en el registro. Pero entes de hacerlo, primero se crea una directiva en las cual se nos permite escribir en el registro usando "RegistrySecurity()". Luego se crea la entrada en el registro.

El siguiente procedimiento es: "DeleteRegKey()". Cómo su nombre lo sugiere, este procedimiento se encarga de borrar la entrada del registro donde se guardan las resignaciones.

La siguiente función es: "ReadRegKey()". Está función  lee los datos de la clave en el registro y regresa un arreglo de bytes con los valores.

El siguiente procedimiento es: "SaveRegFile()".  Este procedimiento recibe un arreglo de bytes con los datos de las resignaciones y guarda los datos en archivo .reg (Entrada del registro).

La siguiente función es: "IsEven()". Esta función recibe un valor del tipo "int", si el número es par regresa true, en caso contrario regresa false.

El siguiente procedimiento es "FillListView()". Este recibe un arreglo del tipo "byte" con las resignaciones, luego las agrega a un "listview" en donde la primer columna es el valor asignado a la tecla y la segunda es el valor real de la tecla.

El siguiente procedimiento es "BuildAndSaveData()". Recibe un valor del tipo "bool", si el valor es "true", guarda el archivo ".reg", en caso contrario escribe los valores en el registro.

La siguiente función es: "GetBytes()", recibe un valor del tipo "int", le aplica la división con el valor "0xE0000" y regresa el valor.

El siguiente procedimiento es "LoadData()". Este Procedimiento lee los datos del registro y los agrega al "listview". Antes hace unas comprobaciones para que los datos desplegados sean validos.

La última funcion es "RebuidRegistryEntry()" esta función recibe una lista del tipo "string" con los pares de las asignaciones y les da el formato adecuado. Devuelve un arreglos bytes con las resignaciones.

¿Cómo se usa?


 

Para usarlo, simplemente debes de hacer clic en la caja de texto que dice "Tecla a reprogramar" y presionar la tecla que quieres reasignar en mi caso, da el valor de 57361 y en la segunda caja de texto debes escribir el valor de la tecla de función, en este caso quiero que la tecla haga la función de silenciar y su valor es de 57376. Cuando termines con la asignaciones basta con hacer clic en el botón aplicar y se guardaran los valores en el registro. Si no quieres hacer los cambios en el registro aun, puedes guardar los datos en un archivo REG para poder usarlos después, para eso debes de hacer clic en el menú archivo y despues seleccionar "Exportar REG" con eso se generará el archivo que se puede integrar después.


 

 Si en algun momento quieres restablecer las teclas, bastará con hacer clic en el menú archivo y después "Eliminar TODO". Recuerda que puedes revisar algunos valores de las teclas en esta página. También recuerda que  hay que reiniciar la computadora para que los cambios surtan efecto.

El código fuente los puedes descargar de mi DropBox cómo de costumbre.

Por el momento es todo. Los leo luego.

No hay comentarios. :

Publicar un comentario

Vamos a programar #108 - Excel y algunas macros.

1 comentario

Hola de nuevo a todos. El día de hoy vamos a continuar con mas programación.


 

En tiempos recientes he tenido que trabajar con hojas de cálculo, mas concretamente con Excel de Microsoft, las tareas son sencillas, simplemente llevar la cuenta de algunos elementos. Para llevar a cabo la cuenta, uno por uno se pasan por un escáner de códigos de barras y la tarea es simple: si no hay ningún elemento se anota el código que se leyó y se anota un uno en la celda de la siguiente columna.

Para hacerlo decidí agregar un formulario con una caja de texto, un botón y una marca de comprobación algo cómo la imagen siguiente:

Para que todo esto me funcione, necesito que el programa, simplemente lea el contenido de la caja, compruebe la longitud; en este caso que el contenido tenga una longitud de al menos ocho caracteres (que es lo que ocupo), pero cómo no todos los elementos necesariamente son iguales, simplemente usamos la casilla para saltarnos esa condición si es necesario.

Ya llegados a este punto, vamos a suponer que si estás leyendo esto, ya estás familiarizado con la macros y cómo usarlas. Para poder usar el código, debemos de agregar los siguientes controles:

  •  Texbox
    • (Name) "TextBox1"
  •  CommandButton
    • (Name) "cmdbtnAdd"
  •  CheckBox
    •  chkCheckSize
  •  Label
    • lblInfo.

Ahora con los controles, veamos el código que hace funcionar las cosas:

 

Option Explicit

Private Sub Count()

Application.ScreenUpdating = False

Dim TextoABuscar As String
Dim Rango As Range

TextoABuscar = TextBox1.Text

If TextoABuscar = vbNullString Then

lblInfo.Caption = "Info: Cadena vacia"
Exit Sub

End If

If Len(TextoABuscar) < 9 And chkCheckSize.Value = True Then

lblInfo.Caption = "Info: Cadena muy corta"
Exit Sub

End If



Dim lastrow As Long

lastrow = Cells(Rows.Count, "A").End(xlUp).Row
Set Rango = Range("A2:A" & lastrow).Find(TextoABuscar, LookIn:=xlValues, lookat:=xlWhole)

If Rango Is Nothing Then

Range("A" & lastrow + 1).Value = TextoABuscar
Range("A" & lastrow + 1).Offset(0, 1).Value = 1
lblInfo.Caption = "Info: Agregado en A" & lastrow + 1


Else

Rango.Offset(0, 1).Value = Rango.Offset(0, 1).Value + 1
lblInfo.Caption = "Info: Agregado"

End If

Application.ScreenUpdating = True
TextBox1.SetFocus

End Sub

Private Sub cmdbtnAdd_Click()

Count
ActiveWorkbook.Save

End Sub

Private Sub TextBox1_KeyDown(ByVal KeyCode As MSForms.ReturnInteger, ByVal Shift As Integer)

If KeyCode = vbKeyReturn Then
Count
TextBox1.Text = ""
End If

End Sub




Primero creamos la función  "Count()" en este procedimiento, primero revisamos si la cadena de texto está vacía, si es el caso, cambiamos el texto de "lblInfo" indicando que la cadena de texto está vacía y salimos del procedimiento, si no es el caso comprobamos la casilla de verificación este o no marcada, al mismo tiempo revisamos la longitud de la cadena de texto, si la comprobación de la casilla es verdadera y la longitud es menor que nueve, se cambia el texto de la etiqueta y se sale de la función. Luego declaramos una variable llamada "lastrow", luego le asignamos el valor de la ultima celda de las filas con el tango de la columna en la cual vamos a buscar, PARA MI CASO, es la columna A, si deseas hacer búsqueda en otra columna debes de cambiarla. Una vez hecho, procedemos a buscar el texto (que está almacenado en la variable TextoABuscar), para eso creamos la variable "Rango". Al ejecutar la función, vamos a comprobar si tiene algún dato, si no es el caso, asumimos que nuestro valor no se encontró por lo que procedemos a asignar el valor de nuestra cadena de texto a la última celda de la columna A, para agregar el uno (porque se supone que es la primera aparición del elemento), nos desplazaremos una celda desde la ultima que poseía un valor (lastrow + 1) y a la celda de la columna A le asignaremos la cadena de texto que buscamos y en la columna siguiente le asignaremos el valor de 1 (se supone que es la primera aparición de este elemento), para eso usaremos "Offset(0,1)" para desplazarnos cero filas y una columna, luego en la etiqueta de información mostraremos que se agrego el valor en cierta celda. Si se encuentra el valor, simplemente aumentaremos la cantidad por lo que simplemente nos desplazaremos una columna a partir de donde encontramos el valor y le sumamos uno al valor que esta posea.

Para que sea mas rápido el proceso, le devolvemos el foco al TextBox1 para que continuemos agregando valores.

En el evento "Click()" del botón, mandaremos a llamar el procedimiento "Count", pero además, guardaremos el documento.

En el evento "KeyDown" de TextBox1 comprobaremos si se presionó la tecla enter, en el escaner que uso, cuando termina de leer el código de barras, también envía una pulsación de la tecla enter, por eso comprobamos que si es igual a vbKeyReturn llamaremos a la función "count" y limpiaremos el texto de TextBox1

Con lo anterior, tendremos algo como lo que sigue. 


Dependiendo de cuantos controles uses, hay que establecer la propiedad "TabStop" de todos ellos salvo de la caja de texto principal, eso para que no pierda el foco y cuando termines de introducir el texto, de inmediato puedas agregar otro.

Y bien, por ahora es todo recuerda que si descargas un documento con macros revisa bien su fuente antes de ejecutarlo. Para probar este ejemplo, te recomiendo copiar y pegar el código.

Los leo luego. 

 

 

1 comentario :

Publicar un comentario

Vamos a programar #107 - Agregando imagenes a archivos FLAC usando C# (Parte 1).

No hay comentarios.

Hola de nuevo a todos. El día de hoy vamos a continuar con mas de los archivos FLAC. En post anteriores vimos cómo insertar la imagen usando solo un editor hexadecimal, pero hoy, vamos a usar C# para crear un programa que lo haga por nosotros.


 Antes de continuar te recomiendo que leas los post relacionados:

 Una vez leidos los post anteriores, vamos a ver el código que hace funcionar el programa. Algo importante que debo aclarar, es que por primera vez en la historia del blog, voy a usar visual studio 2022 y .Net 8.0, la mayor parte del código es retro-compatible, pero en c# 7.0 se agregaron algunas caracteristicas que hacen las cosas mas sencillas, por lo que si quieres probar el código deberías de instalar al menos esa version de visual studio.Además el código es mas que nada una prueba de concepto por lo que se puede optimizar, pero además, todo se hace para que sea lo mas similar al proceso que se hizo a mano (Vamos a programar #102) por lo que si quieres optimizarlo, puedes hacerlo sin ningun problema.

 

Una vez dicho eso,  el código es el siguiente:

using System.Diagnostics.Eventing.Reader;
using System.Runtime.InteropServices;
using System.Text;
using System.Drawing.Imaging;
using System.Net.Http.Headers;
using System.ComponentModel;

namespace FlacPictureWriter
{
public partial class Form1 : Form

{

///Picture type:
/// $00 Other
/// $01 32x32 pixels 'file icon' (PNG only)
/// $02 Other file icon
/// $03 Cover (front)
/// $04 Cover (back)
/// $05 Leaflet page
/// $06 Media (e.g. lable side of CD)
/// $07 Lead artist/lead performer/soloist
/// $08 Artist/performer
/// $09 Conductor
/// $0A Band/Orchestra
/// $0B Composer
/// $0C Lyricist/text writer
/// $0D Recording Location
/// $0E During recording
/// $0F During performance
/// $10 Movie/video screen capture
/// $11 A bright coloured fish
/// $12 Illustration
/// $13 Band/artist logotype
/// $14 Publisher/Studio logotype
private enum APICType
{
/// <summary>
/// Imagen génerica
/// </summary>
APIC_TYPE_Other = 0,
/// <summary>
/// ícono (solo PNG)
/// </summary>
APIC_TYPE_FileIcon,
/// <summary>
/// Otro ícono
/// </summary>
APIC_TYPE_OtherFile_Icon,
/// <summary>
/// Cubierta frontal del disco
/// </summary>
APIC_TYPE_CoverFront,
/// <summary>
/// Cubierta trasera del disco
/// </summary>
APIC_TYPE_CoverBack,
/// <summary>
/// Folleto
/// </summary>
APIC_TYPE_LeafletPage,
/// <summary>
/// Otra ímagen
/// </summary>
APIC_TYPE_Media,
/// <summary>
/// Artista principal
/// </summary>
APIC_TYPE_LeadArtist,
/// <summary>
/// Artista
/// </summary>
APIC_TYPE_Artist,
/// <summary>
/// Conductor
/// </summary>
APIC_TYPE_Conductor,
/// <summary>
/// Banda
/// </summary>
APIC_TYPE_Band,
/// <summary>
/// Compositor
/// </summary>
APIC_TYPE_Composer,
/// <summary>
/// Letrista
/// </summary>
APIC_TYPE_Lyricist,
/// <summary>
/// Lugar de la grabacion
/// </summary>
APIC_TYPE_RecordingLocation,
/// <summary>
/// Captura durante la grabacion
/// </summary>
APIC_TYPE_DuringRecording,
/// <summary>
/// Captura durante concierto
/// </summary>
APIC_TYPE_DuringPerformance,
/// <summary>
/// Captura de videclip
/// </summary>
APIC_TYPE_VideoScreenCapture,
/// <summary>
/// ???
/// </summary>
APIC_TYPE_ABrightColouredFish,
/// <summary>
/// Ilustracion
/// </summary>
APIC_TYPE_Illustration,
/// <summary>
/// Logotipo de la banda/artista
/// </summary>
APIC_TYPE_ArtistLogotype,
/// <summary>
/// Logotipo de la discografica
/// </summary>
APIC_TYPE_PublisherLogotype,
APIC_TYPE_NoValid

}


// 0 : STREAMINFO 0000000
// 1 : PADDING 0000001
// 2 : APPLICATION 0000010
// 3 : SEEKTABLE 0000011
// 4 : VORBIS_COMMENT 0000100
// 5 : CUESHEET 0000101
// 6 : PICTURE 0000110
// 7-126 : reserved 1111000 - Invalid
// 127 : invalid, to avoid confusion with a frame sync code
/// <summary>
/// Enumeración con los posibles bloques contenidos en un archivo FLAC
/// </summary>
private enum BlocksTypes
{
/// <summary>
/// Bloque mandatorio con la información del Stream
/// </summary>
Block_Type_StreamInfo = 0,
/// <summary>
/// Bloque de Padding
/// </summary>
Block_Type_Padding = 1,
/// <summary>
/// Bloque con la informacion de la aplicación
/// </summary>
Block_Type_Application = 2,
/// <summary>
/// Bloque Seektable
/// </summary>
Block_Type_SeekTable = 3,
/// <summary>
/// Bloque con metadatos
/// </summary>
Block_Type_VorbisComment = 4,
/// <summary>
/// Bloque con cuesheet
/// </summary>
Block_Type_CueSheet = 5,
/// <summary>
/// Bloque con imagen
/// </summary>
Block_Type_Picture = 6,
/// <summary>
/// Bloque no válido
/// </summary>
Block_Type_NoValid = 7

}
public Form1()
{
InitializeComponent();
}


List<UInt32> Direcciones;
bool TieneImagen = false;
UInt32 ImageSize = 0;
UInt32 LastBLockPosition = 0;
UInt32 VorbisCommentPosition = 0;
UInt32 ImagePosition = 0;

private byte[] NormalizeToFourBytes(UInt32 Value, bool Reverse = false)
{
byte[] buff = BitConverter.GetBytes(Value);

if (Reverse)
Array.Reverse(buff);

return buff;
}
/// <summary>
/// Obtiene el "Mime Type" basado en la extension de un archivo
/// </summary>
/// <param name="FilePath">Ruta de la cual se va a extraer la información</param>
/// <returns></returns>
private string GetMIMEType(string FilePath)
{

FileInfo FI = new(FilePath);
if (string.Compare(FI.Extension, ".jpg", StringComparison.OrdinalIgnoreCase) == 0)
{
return @"image/jpeg";
}
else if (string.Compare(FI.Extension, ".png", StringComparison.OrdinalIgnoreCase) == 0)
{
return @"image/PNG";
}
else
{
return "No valido";
}
}


/// <summary>
/// Crea una imagen a partir de una secuencia de bytes
/// </summary>
/// <param name="bytesArr">Matriz de bytes que contiene los datos de la imagen</param>
/// <returns>Regresa una imagen</returns>
public Image ByteArrayToImage(byte[] bytesArr)
{
using (MemoryStream memstr = new(bytesArr))
{
Image img = Image.FromStream(memstr);
return img;
}
}

// 4 4 n*4 4 n*4 4 4 4 4 !4 !n*4
//<32>,<32>,<n*8>,<32>,<n*8>,<32>,<32>,<32>,<32>,<32>,<n*8>
// 1 2 3 4 5 6 7 8 9 10 11
/// <summary>
/// Lee todo los datos del bloque PICTURE y extrae la imagen contenida
/// </summary>
/// <param name="TheData">Arreglos bytes que contiene todo el bloque PICTURE</param>
/// <returns>Regresa una imagen</returns>
private Image ReadPictureData(byte[] TheData)
{

uint CurrentPos = 4;//1
uint CurrentSize = GetBlockSize(TheData, (int)CurrentPos, 4);
CurrentPos += 4;//2
string MimeType = Encoding.ASCII.GetString(TheData, (int)CurrentPos, (int)CurrentSize);

CurrentPos += CurrentSize;//3

CurrentSize = GetBlockSize(TheData, (int)CurrentPos, 4);
CurrentPos += (CurrentSize + (4 * 5));//4-9

CurrentSize = GetBlockSize(TheData, (int)CurrentPos, 4);
CurrentPos += 4;

byte[] TempImage = new byte[CurrentSize];
Array.Copy(TheData, CurrentPos, TempImage, 0, CurrentSize);
return ByteArrayToImage(TempImage);


}

/// <summary>
/// Actualiza la imagen de un archivo FLAC.
/// </summary>
/// <param name="ImagePath">Ruta de la imagen JPG</param>
private byte[] BuildAPICFrame(string ImagePath, int PictureType, string MimeType, UInt32 ImageSize)
{

List<byte> Data = new List<byte>();
byte[] Buff = new byte[ImageSize];

using (FileStream FS = new FileStream(ImagePath, FileMode.Open))
{
using (BinaryReader BR = new(FS))
{
Buff = BR.ReadBytes((int)ImageSize);
}
}

//Tipo de Imagen APIC
Data.AddRange(NormalizeToFourBytes((UInt32)PictureType, true));
//Tamaño del tipo de contenido
Data.AddRange(NormalizeToFourBytes((UInt32)MimeType.Length, true));
//Tipo de contenido
Data.AddRange(Encoding.ASCII.GetBytes(MimeType));
//Descripcion del tipo de contenido
Data.AddRange(NormalizeToFourBytes((UInt32)MimeType.Length, true));
//Tamaño de la descripcion del contenido
Data.AddRange(Encoding.ASCII.GetBytes(MimeType));
//Ancho de la imagen
Data.AddRange(NormalizeToFourBytes(0, true));
//Alto de la imagen
Data.AddRange(NormalizeToFourBytes(0, true));
//Profundidad del color
Data.AddRange(NormalizeToFourBytes(0, true));
//Indice de la paleta de colores
Data.AddRange(NormalizeToFourBytes(0, true));
//Tamaño de la imagen en bytes
Data.AddRange(NormalizeToFourBytes(ImageSize, true));
//Datos de la Imagen
Data.AddRange(Buff);



return Data.ToArray();
}
/// <summary>
/// Construye el bloque de imagen
/// </summary>
/// <param name="APICData"></param>
/// <returns></returns>
private byte[] BuildPictureBlock(byte[] APICData)
{
byte[] Buff = new byte[APICData.Length + 4];
byte[] PICSize = new byte[4];

PICSize = NormalizeToFourBytes((UInt32)APICData.Length, true);
PICSize.CopyTo(Buff, 0);


//siempre pondremos la imagen al final de los metadatos
//Establecemos el marcador de ultimo bloque y el tipo de bloque en 6 (Block_Type_Picture)
//Quedando 10000110b o 134
Buff[0] = 134;
APICData.CopyTo(Buff, 4);

return Buff;
}


private List<UInt32> GetOffsets(string FileName)
{
byte[] DataBuff;
List<UInt32> offsets = new List<UInt32>();
bool LastFrame = false;


using (FileStream FS = new(FileName, FileMode.Open, FileAccess.Read, FileShare.Read))
{
offsets.Add((UInt32)FS.Position);
using (BinaryReader BR = new(FS, Encoding.ASCII))
{
DataBuff = BR.ReadBytes(4);
if (string.Equals(Encoding.ASCII.GetString(DataBuff), "fLaC", StringComparison.Ordinal))
{
UInt32 CurrentBlockSize;
UInt32 CurrentPosition = (UInt32)FS.Position;
BlocksTypes CurrentBlockType = NumberToBlockType(GetBlockType(DataBuff[0]));


while (!LastFrame)
{
//primer bloque siempre ser� Block_Type_StreamInfo


offsets.Add((UInt32)FS.Position);
DataBuff = BR.ReadBytes(4);
LastFrame = IsLastFrame(DataBuff[0]);
CurrentBlockType = NumberToBlockType(GetBlockType(DataBuff[0]));
CurrentBlockSize = GetBlockSize(DataBuff);
FS.Seek(FS.Position + CurrentBlockSize, SeekOrigin.Begin);


if (CurrentBlockType == BlocksTypes.Block_Type_VorbisComment)
{
VorbisCommentPosition = offsets[offsets.Count - 1];
}


if (CurrentBlockType == BlocksTypes.Block_Type_Picture)
{
TieneImagen = true;
ImagePosition = offsets[offsets.Count - 1];
}


if (LastFrame)
LastBLockPosition = offsets[offsets.Count - 1];
}
}
else
{
MessageBox.Show("Archivo Flac no v�lido");
}


}
}


return offsets;
}


/// <summary>
/// Construye el encabezado de un bloque de informacion
/// </summary>
/// <param name="Number">Valor que indica la longitud del bloque en bytes</param>
/// <param name="BlockType">Valor que indica el tipo de bloque de acuerdo a <c>BlockTypes</c></param>
/// <param name="IsLastFrame">Indica si es el ultimo bloque de informacion</param>
/// <returns>Regresa un arreglo de cuatro bytes que representan el encabezado del bloque</returns>
private byte[] BuildHeader(uint Number, BlocksTypes BlockType, bool IsLastFrame)
{
byte[] HeaderBuff = new byte[4];

HeaderBuff = BitConverter.GetBytes(Number);



HeaderBuff[3] = (byte)BlockType;
if (IsLastFrame)
{
HeaderBuff[3] |= 128;
}

Array.Reverse(HeaderBuff);

return HeaderBuff;
}
/// <summary>
/// Convierte un número en su valor equivalente al tipo de bloque
/// </summary>
/// <param name="Data">
/// Entero sin signo con la representacion del bloque</param>
/// <returns>Regresa un valor de la enumeración <typeparamref name="BlockTypes"/>BlockTypes</returns>
private BlocksTypes NumberToBlockType(uint Data)
{
switch (Data)
{
case 0:
return BlocksTypes.Block_Type_StreamInfo;
case 1:
return BlocksTypes.Block_Type_Padding;
case 2:
return BlocksTypes.Block_Type_Application;
case 3:
return BlocksTypes.Block_Type_SeekTable;
case 4:
return BlocksTypes.Block_Type_VorbisComment;
case 5:
return BlocksTypes.Block_Type_CueSheet;
case 6:
return BlocksTypes.Block_Type_Picture;
default:
return BlocksTypes.Block_Type_NoValid;
}
}

/// <summary>
/// Lee el bloque con la informacion estructurada
/// </summary>
/// <param name="TheData">Arreglo de bytes del cual se extraera la información</param>
/// <returns>Regresa un arreglo del tipo string con todos los campos que se encontraron</returns>
private string[] ReadVorbisData(byte[] TheData)
{

uint NumberOfFields = 0;
uint CurrentField = 1;
uint CurrentPosition = 0;
//Vendor
uint CurrentSize = BitConverter.ToUInt32(TheData, (int)CurrentPosition);
CurrentPosition += 4;
byte[] CurrentChunk = new byte[CurrentSize];
Array.Copy(TheData, CurrentPosition, CurrentChunk, 0, CurrentSize);
string Currenttext = Encoding.UTF8.GetString(CurrentChunk);

CurrentPosition += CurrentSize;
NumberOfFields = BitConverter.ToUInt32(TheData, (int)CurrentPosition);
CurrentPosition += 4;

string[] Fields = new string[NumberOfFields];
//CommentField
while (CurrentField <= NumberOfFields)
{
CurrentSize = BitConverter.ToUInt32(TheData, (int)CurrentPosition);
CurrentPosition += 4;
byte[] CurrenUserCommentList = new byte[CurrentSize];
Array.Copy(TheData, CurrentPosition, CurrenUserCommentList, 0, CurrentSize);
Currenttext = Encoding.UTF8.GetString(CurrenUserCommentList);
CurrentPosition += CurrentSize;
Fields[CurrentField - 1] = Currenttext;
CurrentField += 1;
}

return Fields;
}

/// <summary>
/// Obtiene el valor del bloque desde un byte
/// </summary>
/// <param name="Data">byte del cual se va a obtener la información</param>
/// <returns>Regresa un valor que es equivalente al tipo de bloque</returns>
private uint GetBlockType(byte Data)
{
Data <<= 3;
Data >>= 3;

return Data;
}

/// <summary>
/// Obtiene si el bloque es el último de la serie
/// </summary>
/// <param name="Data">byte del cual se obtendra la información</param>
/// <returns>true si el bloque es el último, false en caso contrario</returns>
private bool IsLastFrame(byte Data)
{
Data >>= 7;

if (Data == 1)
return true;
else
return false;
}

/// <summary>
/// Obtiene el tamaño de un bloque
/// </summary>
/// <param name="Data">Arreglo de bytes que contiene el tamaño del bloque</param>
/// <returns>Regresa el tamaño del bloque actual</returns>
private uint GetBlockSize(byte[] Data)
{

byte[] CurrentData = Data;
//Ponemos el primer byte en 0 porque se usa para identificar el bloque
//a la hora de convertir a UInt32 se esperan 4 bytes, pero este siempre será 0 u otro valor no relevante
//para el tamaño del bloque
CurrentData[0] = 0;
Array.Reverse(CurrentData);

return BitConverter.ToUInt32(CurrentData, 0);
}

/// <summary>
/// Obtiene el tamaño de un bloque
/// </summary>
/// <param name="Data">Arreglo de bytes que contiene el tamaño del bloque</param>
/// <param name="StartIndex">Indica la posicion en donde se empezará a leer</param>
/// <param name="Size">Indica el tamaño en bytes que se van a leer</param>
/// <returns>Regresa el tamaño del bloque actual</returns>

private uint GetBlockSize(byte[] Data, int StartIndex, int Size)
{
byte[] CurrentData = new byte[4];
Array.Copy(Data, StartIndex, CurrentData, 0, Size);
Array.Reverse(CurrentData);

return BitConverter.ToUInt32(CurrentData, 0);
}

/// <summary>
/// Obtiene una imagen desde un archivo
/// </summary>
private void GetImageFromFile()
{

using OpenFileDialog OpDiag = new()
{
Filter = "Imagenes|*.jpg;*.png|Archivos JPG|*.jpg|Imagenes PNG|*.png",
Multiselect = false
};

if (OpDiag.ShowDialog() == DialogResult.OK)
{
TxtInputPic.Text = OpDiag.FileName;
PicInput.ImageLocation = TxtInputPic.Text;
PicInput.SizeMode = PictureBoxSizeMode.StretchImage;
FileInfo FileSize = new FileInfo(TxtInputPic.Text);
ImageSize = (UInt32)FileSize.Length;
LblPicInfo.Text = "Tamaño de la imagen en bytes " + ImageSize.ToString() + " bytes";
}
}

/// <summary>
/// Obtiene un bloque de datos de un archivo a partir de una posicion definida
/// </summary>
/// <param name="FileName">Archivo del cual se va a extraer el bloque</param>
/// <param name="StartPosition">Posicion des la cual se va a obtener el bloque relativo al inicio del archivo</param>
/// <returns>Regresa un arreglo de bytes con el bloque completo</returns>
private byte[] GetBlockData(string FileName, int StartPosition)
{
byte[] DataBuff;
using (FileStream FS = new(FileName, FileMode.Open, FileAccess.Read, FileShare.Read))
{
using (BinaryReader BR = new(FS, Encoding.ASCII))
{
UInt32 CurrentBlockSize;
UInt32 CurrentPosition = (UInt32)FS.Position;


FS.Seek(StartPosition, SeekOrigin.Begin);
DataBuff = BR.ReadBytes(4);

CurrentBlockSize = GetBlockSize(DataBuff);
FS.Seek(StartPosition, SeekOrigin.Begin);

return (BR.ReadBytes((int)(CurrentBlockSize + 4)));
}
}
}

/// <summary>
/// Obtiene los datos de un bloque
/// </summary>
/// <param name="FileName">Nombre del archivo del cual se obtendra</param>
/// <param name="TypeOfBlock">Tipo de bloque que se va a buscar</param>
private void GetBlock(string FileName, BlocksTypes TypeOfBlock, int StartPosition)
{
byte[] DataBuff;
using (FileStream FS = new(FileName, FileMode.Open, FileAccess.Read, FileShare.Read))
{
using (BinaryReader BR = new(FS, Encoding.ASCII))
{
UInt32 CurrentBlockSize;
UInt32 CurrentPosition = (UInt32)FS.Position;
BlocksTypes CurrentBlockType = BlocksTypes.Block_Type_NoValid;


FS.Seek(StartPosition, SeekOrigin.Begin);
DataBuff = BR.ReadBytes(4);


CurrentBlockType = NumberToBlockType(GetBlockType(DataBuff[0]));


if (CurrentBlockType == TypeOfBlock)
{
if (TypeOfBlock == BlocksTypes.Block_Type_VorbisComment)
{
string[] Fields;
CurrentBlockSize = GetBlockSize(DataBuff);
Fields = ReadVorbisData(BR.ReadBytes((int)CurrentBlockSize));
TxtInfo.Clear();


for (int i = 0; i < Fields.Length; i++)
{
TxtInfo.Text += Fields[i] + " | ";
}
}
else if (TypeOfBlock == BlocksTypes.Block_Type_Picture)
{


CurrentBlockSize = GetBlockSize(DataBuff);
PicFlac.Image = ReadPictureData(BR.ReadBytes((int)CurrentBlockSize));
}
}
else
{
return;
}
}
}
}


private void BtnOpen_Click(object sender, EventArgs e)
{
TieneImagen = false;
VorbisCommentPosition = 0;
ImagePosition = 0;

OpenFileDialog OpDiag = new()
{
Filter = "Archivos Flac|*.flac|Todos los arvhivos|*.*",
Multiselect = false
};

if (OpDiag.ShowDialog() == DialogResult.OK)
{
txtInputFlac.Text = OpDiag.FileName;
PicFlac.SizeMode = PictureBoxSizeMode.StretchImage;
Direcciones = GetOffsets(txtInputFlac.Text);

GetBlock(txtInputFlac.Text, BlocksTypes.Block_Type_VorbisComment, (int)VorbisCommentPosition);

if (TieneImagen)
GetBlock(txtInputFlac.Text, BlocksTypes.Block_Type_Picture, (int)ImagePosition);
}
}

private void BtnOpenPic_Click(object sender, EventArgs e)
{
ImageSize = 0;
GetImageFromFile();
}

private void BtnUpdateImage_Click(object sender, EventArgs e)
{
if (string.IsNullOrEmpty(txtInputFlac.Text))
{
MessageBox.Show("No hay arhivo de entrada", "Flac Image Writer", MessageBoxButtons.OK, MessageBoxIcon.Error);
return;
}else if (string.IsNullOrEmpty(TxtInputPic.Text))
{
MessageBox.Show("No hay imagen de entrada", "Flac Image Writer", MessageBoxButtons.OK, MessageBoxIcon.Error);
return;
}

string SaveFile;
bool HasImage = false;
List<UInt32> Direcciones = GetOffsets(txtInputFlac.Text);
int CurrentSize = (int)Direcciones[Direcciones.Count - 1];
int bufferSize = 1024 * 1024;

using (SaveFileDialog SavDiag = new SaveFileDialog())
{
SavDiag.Filter = "Archivos FLAC|*.flac";
SavDiag.AddExtension = true;
SavDiag.OverwritePrompt = true;
if (SavDiag.ShowDialog() == DialogResult.OK)
SaveFile = SavDiag.FileName;
else
return;
}

using (FileStream FSWrite = new FileStream(SaveFile, FileMode.Create, FileAccess.Write, FileShare.ReadWrite))
{
using (BinaryWriter BW = new(FSWrite))
{

BW.Write(Encoding.ASCII.GetBytes("fLaC"));

for (int x = 1; x < Direcciones.Count; x++)
{
byte[] FileBuff = GetBlockData(txtInputFlac.Text, (int)Direcciones[x]);

BlocksTypes CurrentBlockType = BlocksTypes.Block_Type_NoValid;
CurrentBlockType = NumberToBlockType(GetBlockType(FileBuff[0]));

if (CurrentBlockType == BlocksTypes.Block_Type_Picture)
HasImage = true;


if (x == Direcciones.Count - 1)
{
if (!HasImage)
{
CurrentSize += FileBuff.Length;
BW.Write(BuildHeader((uint)(FileBuff.Length - 4), CurrentBlockType, false));
}
else
{
CurrentSize += FileBuff.Length;
BW.Write(BuildHeader((uint)(FileBuff.Length - 4), CurrentBlockType, true));
}

}
else
{

BW.Write(BuildHeader((uint)(FileBuff.Length - 4), CurrentBlockType, false));
}

BW.Write(FileBuff, 4, FileBuff.Length - 4);

}

if (HasImage == false)
BW.Write(BuildPictureBlock(BuildAPICFrame(TxtInputPic.Text, CboPictureType.SelectedIndex, GetMIMEType(TxtInputPic.Text), ImageSize)));



FileStream fs = new FileStream(txtInputFlac.Text, FileMode.Open, FileAccess.ReadWrite);

fs.Position = CurrentSize;
int bytesRead = -1;
byte[] bytes = new byte[bufferSize];

while ((bytesRead = fs.Read(bytes, 0, bufferSize)) > 0)
{
BW.Write(bytes, 0, bytesRead);
}

fs.Close();

}
}
}

private void Form1_Load(object sender, EventArgs e)
{
CboPictureType.SelectedIndex = 3;
}

private void PicFlac_DoubleClick(object sender, EventArgs e)
{
try
{


if (TieneImagen)
{
using (SaveFileDialog SavDiag = new SaveFileDialog())
{
SavDiag.Filter = "Arhivo Jpeg|*.jpg";

if (SavDiag.ShowDialog() == DialogResult.OK)
PicFlac.Image.Save(SavDiag.FileName);
}
//PicFlac.Image.Save
}
}
catch
{
MessageBox.Show("Error al guardar la imagen","Error",MessageBoxButtons.OK,MessageBoxIcon.Error);
}

}


private void button1_Click(object sender, EventArgs e)
{
}
}
}

 

Ahora veamos las funciones. 

Primero empezamos con la enumeración "APICType", en ella se definen los posibles valores para el tipo de imagen que se va incrustarde acuerdo a lo establecido en la documentacion. Esto solo se hace para poder identificar las cosas de la forma mas humana posible, no es del todo necesario, pero ayuda bastante.

La primera funcion es  "NormalizeToFourBytes()". Esta función recibe dos parámetros, el primero, es un valor del tipo UInt32 llamado "Value" y el otro es un valor del tipo bool llamado "Reverse". Esta función se encarga de convertir un numero en su equivalente en una secuencia de bytes, pero además tiene la opción de invertir el orden de la secuencia de bytes, dependiendo de la maquina en la que se trabaje o del idioma, los números se pueden representar de forma distinta, por ejemplo, si tenemos el número hexadecimal 0x3259 , puede representarse cómo 0x32 0x59 o 0x59 0x32. La función devuelve un arreglo de bytes con la representación del número.

La siguiente función es "GetMIMEType()". Esta función recibe un valor del tipo string con una cadena de texto. Se espera solo la extensión de las imágenes  compatibles, es decir solo .jpg o .png. Regresa el Mime Type basado en la cadena de texto.

La siguiente funcion es "ByteArrayToImage()" Esta funcion recibe un arreglo de bytes, se espera que la secuencia de bytes en si conformen una imagen. Regresa un valor del tipo Image que contiene la imagen de la secuencia de bytes.

Y bien, por ahora es todo, las siguientes funciones merecen una explicacion mas detallada para poder entender mejor cada una. el código del formulario principal lo puedes copiar y pegar para probarlo, pero al final cuando todo este listo lo publicaré en mi dropbox para que lo puedas descargar y probar.

Los leo luego 

 

No hay comentarios. :

Publicar un comentario

Cumplimos 10 Años!!!

No hay comentarios.

Un día cómo hoy pero de hace diez años surgió el blog de XWork. Ha pasado tanto tiempo desde que todo esto empezó, si bien ha venido en declive la cantidad de entradas que se crean al mes (eso por decirlo de alguna manera ya que el blog ha estado totalmente muerto en los últimos meses). creo que es importante seguir.

Es un camino complicado, ya que actualmente mucha gente ya no recurre a lugares como este para poder encontrar informacion, eso debido a que la inteligencia artificial ha ayudado a resolver muchos de los dilemas de los programadores, no por eso digo que sea malo usarla, cómo cualquier herramienta, es buena mientras no se vuelva la encargada de hacer todo por uno.

Confío en que los post seguiran fluyendo y la gennte venga a leer un poco.

Cómo de costumbre, los sigo leyendo a todos y sé que sitios cómo este siempre perduraran. 

No hay comentarios. :

Publicar un comentario

Vamos a platicar #13 - It´s no over (yet)

No hay comentarios.

Hola de nuevo a todos. Ha pasado el tiempo desde la última vez que algo se publico por acá. Los tiempos han cambiado y cada vez es menos común que la gente recurra a este tipo de sitios para buscar información. 


En los próximos días este sitio cumplirá diez años; si bien es cierto que desde hace un par, todo ha venido en declive, es importante mencionar que trataré de continuar con el, mas que nada por todo lo que representa para mi.

Por ahora es todo y cómo de costumbre le agradezco a todos los que se toman el tiempo de pasar a leer.

Los sigo leyendo. 

No hay comentarios. :

Publicar un comentario