2013/11/23
Exif 情報から GPS の緯度/経度情報を削除する
例によって こちら のブログをネタに
他人の褌で相撲を取る記事です。
元記事では Exif 情報を操作する時に、
バイナリを直接操作するのではなく
od (1) を利用してバイナリをテキスト型式に変換し、
テキスト操作した後で再度バイナリ型式に戻すという
正統的な手法でのアプローチをしています。
しかし dd (1) を利用すると
シェルスクリプトからバイナリを操作できるので、
バイナリ型式の画像を直接操作してしまうというアプローチを取ってみます。
画像ファイルに格納されている Exif 情報を簡単に解析して
GPS 情報を格納している領域 (GPSInfoIFD) から
緯度/経度情報だけをゼロに置換する処理となっています。
流石にシェル芸では無理なのでシェルスクリプトです。
1#!/bin/sh 2 3# 4# Endian を考慮して 4 バイト数字を取得する 5# 一度 od(1) で 16 進数に変換しバイト並びを修正した後に 10 進数に変換する 6# 7# $1: ファイル 8# $2: オフセット 9# 10get4Byte() 11{ 12 13 if [ ${endian} = "MM" ] 14 then 15 printf "%d" `dd if="${1}" bs=1 skip=${2} count=4 2> /dev/null | od -x | 16 sed -n "s/^0000000 *\(..\)\(..\)[ ]*\(..\)\(..\)/0x\2\1\4\3/p"` 17 else 18 printf "%d" `dd if="${1}" bs=1 skip=${2} count=4 2> /dev/null | od -x | 19 sed -n "s/^0000000 *\(..\)\(..\)[ ]*\(..\)\(..\)/0x\1\2\3\4/p"` 20 fi 21 22 23} 24 25# 26# Endian を考慮して 2 バイト数字を取得する 27# 一度 od(1) で 16 進数に変換しバイト並びを修正した後に 10 進数に変換する 28# 29# $1: ファイル 30# $2: オフセット 31# 32get2Byte() 33{ 34 35 if [ ${endian} = "MM" ] 36 then 37 printf "%d" `dd if="${1}" bs=1 skip=${2} count=2 2> /dev/null | od -x | 38 sed -n "s/^0000000 *\(..\)\(..\)/0x\2\1/p"` 39 else 40 printf "%d" `dd if="${1}" bs=1 skip=${2} count=2 2> /dev/null | od -x | 41 sed -n "s/^0000000 *\(..\)\(..\)/0x\1\2/p"` 42 fi 43 44} 45 46# 47# 任意の長さの文字列を取得する 48# 49# $1: ファイル 50# $2: オフセット 51# $3: 文字列長 52# 53getString() 54{ 55 56 dd if="${1}" bs=1 skip=${2} count=${3} 2> /dev/null 57 58} 59 60# 61# GPSInfoIFD の解析を行い緯度/経度情報をクリアする 62# 63# $1: ファイル 64# $2: GPSInfoIFD のタグ数 65# $3: GPSInfoIFD のオフセット位置 66# 67gps() 68{ 69 70 local i j offset tag val 71 72 i=0 73 74 while [ ${i} -lt ${2} ] 75 do 76 # GPSInfoIFD のタグ情報 77 offset=`expr ${3} + 14 + \( ${i} \* 12 \)` 78 tag=`get2Byte "${1}" ${offset}` 79 # タグ種類 80 # 2: 緯度 81 # 4: 経度 82 if [ ${tag} -eq 2 -o ${tag} -eq 4 ] 83 then 84 # データの数とデータのオフセットを取得 85 # 緯度/経度情報は値のタイプに RATIONAL(8バイト長) が指定されている 86 num=`get4Byte "${1}" \`expr ${offset} + 4\`` 87 val=`get4Byte "${1}" \`expr ${offset} + 8\`` 88 89 # 情報を削除するためにオフセットから 8 * データの数をゼロクリア 90 dd if=/dev/zero of="${1}" bs=1 conv=notrunc \ 91 seek=`expr ${val} + 12` count=`expr ${num} \* 8` 2> /dev/null 92 fi 93 i=`expr $i + 1` 94 done 95 96} 97 98# 99# 0th IFD データの解析処理 100# 101# $1: ファイル 102# $2: 0th IFD のタグ数 103# 104ifd() 105{ 106 107 local i offset count position 108 109 i=0 110 111 while [ ${i} -lt ${2} ] 112 do 113 # 0th IFD のタグ情報 114 offset=`expr 22 + \( ${i} \* 12 \)` 115 # GPSInfoIFDPointer の場合はオフセットとタグ数を取得 116 if [ `get2Byte "${1}" ${offset}` -eq 34853 ] 117 then 118 position=`get4Byte "${1}" \`expr ${offset} + 8\`` 119 count=`get2Byte "${1}" \`expr ${position} + 12\`` 120 121 gps "${1}" ${count} ${position} 122 fi 123 i=`expr $i + 1` 124 done 125 126} 127 128# 処理開始 129if [ "`getString "${1}" 6 4`" = "Exif" ] 130then 131 endian=`getString "${1}" 12 2` 132 133 ifd "${1}" `get2Byte "${1}" 20` 134fi
GPS 情報が削除されており緯度/経度がそれぞれゼロになっています。
2013/11/11
grep -o
少々前の話ではあるが、某ブログ にて
grep -o について言及する記事が出ていた。
非常に便利そうな機能ではあるのだが、
POSIX (IEEE Std 1003.1, 2013 Edition) 準拠ではなく独自拡張の機能の様なので
POSIX の範囲での実現方法を考えてみた。
例えば
$ grep -o 'href="[^"]*"'
は
$ sed -n 's/.*\(href="[^"]*"\).*/\1/p'
で代用可能なので、
$ cat << EOF | sed -n 's/.*\(href="[^"]*"\).*/\1/p' <!DOCTYPE html> <html> <body> <a href="http://aho.aho" target="_blank">あほ</a> <img src="aho.jpg" alt="aho">エロい画像</a> </body> </html> EOF href="http://aho.aho"という結果が得られる。
素晴らしい! POSIX 準拠なコマンドで POSIX 非準拠をぶち破ったぞ!
論破! 論破! 論破!
ただし grep (1) の -E オプションは POSIX に準拠しているが、 sed (1) の -E オプションは POSIX に定義されていないので、
$ grep -o -E '(src|href)="[^"]*"'
には対応できない。FreeBSD の sed (1) など -E オプションが利用できれば
$ sed -n -E 's/.*((src|href)="[^"]*").*/\1/p'
とする事で対応できるのだが、
$ cat << EOF | sed -n -E 's/.*((src|href)="[^"]*").*/\1/p' <!DOCTYPE html> <html> <body> <a href="http://aho.aho" target="_blank">あほ</a> <img src="aho.jpg" alt="aho">エロい画像</a> </body> </html> EOF href="http://aho.aho" src="aho.jpg"POSIX の範疇を逸脱した時点で grep -o を使えば良いとなってしまう。
残念っ!
2013/11/23 追記
頑張る必要は全くありません(笑)普段は GNU の便利なオプションも使っていますし、 POSIX 準拠していないコマンドやオプションも使ってます。
純粋に楽しいからやってるだけです。