{"id":989,"date":"2025-02-10T14:16:28","date_gmt":"2025-02-10T19:16:28","guid":{"rendered":"https:\/\/80bits.blog\/?p=989"},"modified":"2025-02-10T14:16:29","modified_gmt":"2025-02-10T19:16:29","slug":"golang-modificar-archivos-en-modo-binario","status":"publish","type":"post","link":"https:\/\/80bits.blog\/index.php\/2025\/02\/10\/golang-modificar-archivos-en-modo-binario\/","title":{"rendered":"Golang &#8211; Modificar archivos en modo binario"},"content":{"rendered":"\n<p>Estos d\u00edas sal\u00ed a tomar algunas fotograf\u00edas con mi c\u00e1mara <a href=\"https:\/\/en.wikipedia.org\/wiki\/Sony_%CE%B16000\" target=\"_blank\" rel=\"noopener\" title=\"\">Sony a6000<\/a>, que requiere que ingrese la fecha y la hora manualmente, ya que no se sincroniza con el tel\u00e9fono. Comet\u00ed un error al ingresar 2024 en lugar de 2025, lo que provoc\u00f3 que todas las fotos fueran tomadas con el a\u00f1o incorrecto. Esto hizo que toda la informaci\u00f3n EXIF quedara con el a\u00f1o anterior.<\/p>\n\n\n\n<p>Busqu\u00e9 algunas herramientas y descubr\u00ed que algunos plugins de Lightroom permiten editar de manera masiva la fecha de varias fotos. Esto result\u00f3 muy \u00fatil, ya que tom\u00e9 m\u00e1s de 300 fotos y hacerlo manualmente habr\u00eda llevado demasiado tiempo.<\/p>\n\n\n\n<p>Pero como programador, decid\u00ed crear algo sencillo y aprovechar para practicar mis habilidades en Go editando directamente los bytes de los archivos RAW.<\/p>\n\n\n\n<p>No quise complicarme tratando de entender c\u00f3mo est\u00e1n estructurados los datos EXIF, as\u00ed que opt\u00e9 por buscar las fechas en texto plano con un programa que me permitiera ver los archivos a nivel hexadecimal. En mi caso, us\u00e9 <a href=\"https:\/\/flathub.org\/apps\/org.gnome.GHex\" target=\"_blank\" rel=\"noopener\" title=\"\">GHex<\/a> en Linux.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" width=\"1920\" height=\"1036\" src=\"https:\/\/80bits.blog\/wp-content\/uploads\/2025\/02\/ghexScreenshoot.jpg\" alt=\"GHex\" class=\"wp-image-994\" srcset=\"https:\/\/80bits.blog\/wp-content\/uploads\/2025\/02\/ghexScreenshoot.jpg 1920w, https:\/\/80bits.blog\/wp-content\/uploads\/2025\/02\/ghexScreenshoot-1536x829.jpg 1536w\" sizes=\"(max-width: 1920px) 100vw, 1920px\" \/><\/figure>\n\n\n\n<p>Como se puede ver en la captura, la posici\u00f3n donde aparece el n\u00famero &#8220;2&#8221;, que corresponde al inicio de la fecha &#8220;2025:02:03 15:05:49&#8221; (ya editada), se encuentra en el offset 0x15A. Este n\u00famero hexadecimal, al convertirse a decimal, resulta ser 346, que es la posici\u00f3n en el archivo donde debo comenzar a editar. Tengo que editar un total de 19 posiciones, lo que equivale a algo como este vector: [2, 0, 2, 5, :, 0, 2, :, 0, 3, , 1, 5, :, 0, 5, :4, 9].<\/p>\n\n\n\n<p>En Go, es bastante sencillo abrir un archivo en modo stream y escribir en posiciones espec\u00edficas. Esto se hace de la siguiente manera:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>file, err := os.OpenFile(imagePath, os.O_RDWR, 0666)<\/code><\/pre>\n\n\n\n<p>os.O_RDWR este par\u00e1metro indica que el archivo se abrir\u00e1 en modo escritura. El siguiente par\u00e1metro corresponde a los permisos del archivo en formato *unix.<\/p>\n\n\n\n<p>Luego, convierto la fecha, que est\u00e1 en formato string, a un vector de bytes. En Go, un string es realmente un vector de bytes, por lo que la conversi\u00f3n es bastante sencilla, casi como un cast.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>date := \"2025:02:03 15:05:49\"\narrayBytes := &#91;]byte(date)<\/code><\/pre>\n\n\n\n<p>El array <code>arrayBytes<\/code> es lo que voy a escribir directamente en el archivo para reemplazar la fecha y hora. No quise complicarme editando la hora o tratando de conservar la hora original, ya que me dio flojera escribir m\u00e1s c\u00f3digo, pero no es dif\u00edcil cambiar la l\u00f3gica para si fuera necesario.<\/p>\n\n\n\n<p>Para poder editar exactamente donde quiero, una vez que hice el an\u00e1lisis del archivo con GHex y ubiqu\u00e9 las posiciones donde hab\u00eda fechas (que son 4 lugares), lo \u00fanico que debo hacer es posicionar el puntero en la posici\u00f3n correcta dentro del archivo.<\/p>\n\n\n\n<p>En los archivos de la Sony a6000 con firmware 3.21, las fechas comienzan en las posiciones 346, 1038, 2602 y 38592.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>offset := 346\n_, err := file.Seek(offset, io.SeekStart)<\/code><\/pre>\n\n\n\n<p>Para posicionarme en el offset indicado, uso el m\u00e9todo <code>Seek<\/code> de la estructura <code>File<\/code>, donde <code>file<\/code> es el archivo que se &#8220;abri\u00f3&#8221;. Este m\u00e9todo recibe la posici\u00f3n y desde d\u00f3nde comenzar, en este caso <code>io.SeekStart<\/code>, lo que indica que debe comenzar desde el inicio del archivo hasta el offset que se le indique.<\/p>\n\n\n\n<p>Para escribir los datos, utilizo el m\u00e9todo <code>Write<\/code> de <code>File<\/code>. Este m\u00e9todo escribe desde la posici\u00f3n previamente establecida con <code>Seek<\/code> hasta el tama\u00f1o del vector de bytes. En el caso de la fecha que convert\u00ed, son 19 posiciones.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>_, err = file. Write(arrayBytes)<\/code><\/pre>\n\n\n\n<p>Y listo as\u00ed se reemplaza la fecha.<\/p>\n\n\n\n<p>El c\u00f3digo original en el repositorio no tiene las mismas variables y contiene algunas impresiones a consola para depuraci\u00f3n. Adem\u00e1s, recibe algunos parametros entre ellos la ruta del archivo, tambien tiene algunas validaciones simples. Esto lo hice para poder reutilizarlo en el futuro, ya que una vez compilado el archivo, deber\u00eda ejecutarse de la siguiente manera:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>.\/changedate.exe -w y -d 2025:02:03\/15:05:49 -f \/path\/file<\/code><\/pre>\n\n\n\n<ul class=\"wp-block-list\">\n<li>-w:  y\/n b\u00e1sicamente escribe o no la fecha, en caso de no escribir la fecha solo imprime lo que encontr\u00f3 en el archivo, me sirve en modo depuraci\u00f3n nada mas<\/li>\n\n\n\n<li>-d: recibe la fecha en este formato 2025:02:03\/15:05:49 en lugar de un espacio agregue \/ ya que en consola los espacios se comportan como otro par\u00e1metro.<\/li>\n\n\n\n<li>-f: la ruta al archivo.<\/li>\n<\/ul>\n\n\n\n<p>Escrib\u00ed el script de esta manera para que funcione con un solo archivo por ejecuci\u00f3n, ya que la l\u00f3gica de procesar todos los archivos la iba a desarrollar con un simple script bash de Linux. Este es el script:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>for file in *.ARW; do\n    echo \"Procesando $file...\"\n\n    .\/changedate.exe -w y -d 2025:02:03\/15:05:49 -f $file\n    \n    # Verifica si hubo errores\n    if &#91; $? -ne 0 ]; then\n        echo \"Error al modificar $file\"\n    fi\ndone\n<\/code><\/pre>\n\n\n\n<p>Este script obtiene todos los archivos con extensi\u00f3n <code>.ARW<\/code> dentro de la misma carpeta donde se ejecuta. Luego, para cada archivo, ejecuta el binario compilado de Go, pas\u00e1ndole los par\u00e1metros necesarios para cambiar la fecha y el nombre del archivo, lo que es m\u00e1s pr\u00e1ctico para m\u00ed que tener que escribir c\u00f3digo en Go para obtener todos los archivos de una carpeta con esa extensi\u00f3n. La idea es centrarme en modificar un solo archivo a la vez.<\/p>\n\n\n\n<p><a href=\"https:\/\/github.com\/genitalico\/gexiftoolsonya6000\" target=\"_blank\" rel=\"noopener\" title=\"\">Codigo en Github.<\/a><\/p>\n\n\n\n<p><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Estos d\u00edas sal\u00ed a tomar algunas fotograf\u00edas con mi c\u00e1mara Sony a6000, que requiere que ingrese la fecha y la hora manualmente, ya que no se sincroniza con el tel\u00e9fono. Comet\u00ed un error al ingresar 2024 en lugar de 2025, lo que provoc\u00f3 que todas las fotos fueran tomadas con el a\u00f1o incorrecto. Esto hizo [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":975,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_uf_show_specific_survey":0,"_uf_disable_surveys":false,"jetpack_post_was_ever_published":false,"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_memberships_contains_paid_content":false,"footnotes":""},"categories":[60,80,20,66,49,65,67],"tags":[118,16,81,73,85,51,18,117,84],"class_list":["post-989","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-comandos","category-golang","category-lenguajes-de-programacion","category-linux","category-macos","category-sistemas-operativos","category-windows","tag-bash","tag-development","tag-go","tag-linux","tag-mac","tag-macos","tag-programacion","tag-script","tag-windows"],"jetpack_featured_media_url":"https:\/\/80bits.blog\/wp-content\/uploads\/2024\/12\/A-cartoon-of-a-hamster-running-a-marathon.webp","jetpack-related-posts":[],"jetpack_sharing_enabled":true,"jetpack_likes_enabled":true,"_links":{"self":[{"href":"https:\/\/80bits.blog\/index.php\/wp-json\/wp\/v2\/posts\/989","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/80bits.blog\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/80bits.blog\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/80bits.blog\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/80bits.blog\/index.php\/wp-json\/wp\/v2\/comments?post=989"}],"version-history":[{"count":28,"href":"https:\/\/80bits.blog\/index.php\/wp-json\/wp\/v2\/posts\/989\/revisions"}],"predecessor-version":[{"id":1019,"href":"https:\/\/80bits.blog\/index.php\/wp-json\/wp\/v2\/posts\/989\/revisions\/1019"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/80bits.blog\/index.php\/wp-json\/wp\/v2\/media\/975"}],"wp:attachment":[{"href":"https:\/\/80bits.blog\/index.php\/wp-json\/wp\/v2\/media?parent=989"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/80bits.blog\/index.php\/wp-json\/wp\/v2\/categories?post=989"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/80bits.blog\/index.php\/wp-json\/wp\/v2\/tags?post=989"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}