写真に設定されているジオタグや撮影条件などのExifデータをAndroidで扱うために,Metadata ExtractorとSanselanAndroidを使う例です
Androidから写真のExifデータを扱う
写真に設定されているジオタグや撮影条件などのExifデータを扱うためには,
Androidの標準で ExifInterfaceクラス があります.
しかしこれにはいくつか不満があります.
- 扱える属性が少ない.API Level 11になればかなり揃いますが,それ以前だとかなり物足りないです
- 書き込みをすると,対応していない属性の値が破壊されてしまう(ように見える)
- Android 2.0以上でないと使えない
それで他の方法がないか調べたところ,Androidで使えそうなライブラリが見つかりました.
これらの使い方について,簡単に書きます.
- | 使用したバージョン |
Metadata Extractor | metadata-extractor-2.3.1-src.jar |
SanselanAndroid | 2012/11/12時点のトランクソースをsvnで取得したもの |
Androidのバージョンは,2.3.4です.
実装例
Exifを読み出す
Metadata Extractorの例
import
com.drew.imaging.jpeg.JpegMetadataReader;
import
com.drew.lang.Rational;
import
com.drew.metadata.Directory;
import
com.drew.metadata.Metadata;
import
com.drew.metadata.exif.ExifDirectory;
import
com.drew.metadata.exif.GpsDirectory;
public
static
void
extractExifLatLonDrew(String imagePath) {
try
{
Metadata metadata = JpegMetadataReader.readMetadata(new
File(imagePath));
Directory directory = metadata.getDirectory(GpsDirectory.class
);
if
(directory instanceof
GpsDirectory) {
final
GpsDirectory gps = (GpsDirectory) directory;
Rational[] lat = gps.getRationalArray(GpsDirectory.TAG_GPS_LATITUDE);
float
latitude = lat[0].floatValue() + lat[1].floatValue() / 60 + lat[2].floatValue() / 3600;
Rational[] lng = gps.getRationalArray(GpsDirectory.TAG_GPS_LONGITUDE);
float
longitude = lng[0].floatValue() + lng[1].floatValue() / 60 + lng[2].floatValue() / 3600;
if
("S"
.equals(gps.getString(GpsDirectory.TAG_GPS_LATITUDE_REF))) {
// 緯度
latitude = -latitude;
}
if
("W"
.equals(gps.getString(GpsDirectory.TAG_GPS_LONGITUDE_REF))) {
// 経度
longitude = -longitude;
}
}
Directory exifDirectory = metadata.getDirectory(ExifDirectory.class
);
int
tag;
tag = ExifDirectory.TAG_ISO_EQUIVALENT;
if
(exifDirectory.containsTag(tag)) {
// ISO感度
int
v = exifDirectory.getInt(tag);
}
tag = ExifDirectory.TAG_EXPOSURE_TIME;
if
(exifDirectory.containsTag(tag)) {
// 露出時間(= シャッタースピードの逆数)
double
v = exifDirectory.getDouble(tag);
}
tag = ExifDirectory.TAG_FNUMBER;
if
(exifDirectory.containsTag(tag)) {
// 絞りのF値
double
v = exifDirectory.getDouble(tag);
}
tag = ExifDirectory.TAG_FOCAL_LENGTH;
if
(exifDirectory.containsTag(tag)) {
// 焦点距離
double
v = exifDirectory.getDouble(tag);
}
} catch
(Exception e) {
//
}
}
SanselanAndroidの例
import
org.apache.sanselan.Sanselan;
import
org.apache.sanselan.common.IImageMetadata;
import
org.apache.sanselan.formats.jpeg.JpegImageMetadata;
import
org.apache.sanselan.formats.tiff.TiffField;
import
org.apache.sanselan.formats.tiff.TiffImageMetadata;
import
org.apache.sanselan.formats.tiff.constants.ExifTagConstants;
public
static
void
extractExifLatLonSans(String imagePath) {
try
{
IImageMetadata metadata = Sanselan.getMetadata(new
File(imagePath));
if
(metadata instanceof
JpegImageMetadata) {
JpegImageMetadata jpegMetadata = (JpegImageMetadata) metadata;
TiffImageMetadata exifMetadata = jpegMetadata.getExif();
if
(exifMetadata != null
) {
TiffImageMetadata.GPSInfo gpsInfo = exifMetadata.getGPS();
if
(gpsInfo != null
) {
// 緯度
double
latitude = gpsInfo.getLatitudeAsDegreesNorth();
// 経度
double
longitude = gpsInfo.getLongitudeAsDegreesEast();
}
}
TiffField field;
field = jpegMetadata.findEXIFValue(ExifTagConstants.EXIF_TAG_ISO);
if
(field != null
) {
// ISO感度
int
v = field.getIntValue();
}
field = jpegMetadata.findEXIFValue(ExifTagConstants.EXIF_TAG_EXPOSURE_TIME);
if
(field != null
) {
// 露出時間(= シャッタースピードの逆数)
double
v = field.getDoubleValue();
}
field = jpegMetadata.findEXIFValue(ExifTagConstants.EXIF_TAG_FNUMBER);
if
(field != null
) {
// 絞りのF値
double
v = field.getDoubleValue();
}
field = jpegMetadata.findEXIFValue(ExifTagConstants.EXIF_TAG_FOCAL_LENGTH);
if
(field != null
) {
// 焦点距離
double
v = field.getDoubleValue();
}
}
} catch
(Exception e) {
//
}
}
なお,後述の書き込みでSanselanAndroidを使うとすると読み込みもSanselanAndroidに統一すれば良さそうな気がしますが,
SanselanAndroidだと,ExifInterfaceで書き込んでしまったことによってデータが失われたExifファイルを読み込もうとしたときに
java.io.IOException: Not a Valid TIFF File
という例外を出して読めませんでした.
それに対してMetadata Extractorの場合は,消えている値が多いものの読むことはできたため,使う意味があると思います.
Exifを更新する
SanselanAndroid
import
org.apache.sanselan.Sanselan;
import
org.apache.sanselan.formats.jpeg.JpegImageMetadata;
import
org.apache.sanselan.formats.jpeg.exifRewrite.ExifRewriter;
import
org.apache.sanselan.formats.tiff.TiffImageMetadata;
import
org.apache.sanselan.formats.tiff.write.TiffOutputSet;
public
static
void
saveLatLong(String imagePath, float
lat, float
lon, String newFilePath) {
File originalFile = new
File(imagePath);
try
{
JpegImageMetadata jpegMetadata = (JpegImageMetadata) Sanselan.getMetadata(originalFile);
TiffImageMetadata exif;
if
(jpegMetadata != null
) {
exif = jpegMetadata.getExif();
} else
{
exif = null
;
}
TiffOutputSet outputSet = null
;
if
(exif != null
) {
outputSet = exif.getOutputSet();
}
if
(outputSet == null
) {
outputSet = new
TiffOutputSet();
}
outputSet.setGPSInDegrees(lon, lat);
File newFile = new
File(newFilePath);
OutputStream os = new
FileOutputStream(newFile);
os = new
BufferedOutputStream(os);
try
{
new
ExifRewriter().updateExifMetadataLossless(originalFile, os, outputSet);
} finally
{
os.close();
}
} catch
(Exception ex) {
//
}
}
既存のExifから読み出したTiffOutputSetに更新後の値を上書きして,ExifRewriter#updateExifMetadataLossless()を実行します.
元のファイル(originalFile)と別のファイルnewFileを作成していますので,置換するならoriginalFileを削除してnewFileをリネームなどすれば良いと思います.
Exifを新規作成する
SanselanAndroid
import
org.apache.sanselan.ImageWriteException;
import
org.apache.sanselan.Sanselan;
import
org.apache.sanselan.common.BinaryConstants;
import
org.apache.sanselan.formats.jpeg.exifRewrite.ExifRewriter;
import
org.apache.sanselan.formats.tiff.constants.ExifTagConstants;
import
org.apache.sanselan.formats.tiff.constants.TagInfo;
import
org.apache.sanselan.formats.tiff.write.TiffOutputDirectory;
import
org.apache.sanselan.formats.tiff.write.TiffOutputField;
import
org.apache.sanselan.formats.tiff.write.TiffOutputSet;
public
static
void
saveLatLong(String imagePath, float
lat, float
lon, Date originalDate, String newFilePath) {
File originalFile = new
File(imagePath);
try
{
TiffOutputSet outputSet = new
TiffOutputSet(BinaryConstants.BYTE_ORDER_BIG_ENDIAN);
TiffOutputDirectory exifDirectory = outputSet.getOrCreateExifDirectory();
addTag(exifDirectory, ExifTagConstants.EXIF_TAG_DATE_TIME_ORIGINAL,
new
SimpleDateFormat("yyyy:MM:dd HH:mm:ss"
).format(originalDate);
outputSet.setGPSInDegrees(lon, lat);
File newFile = new
File(newFilePath);
OutputStream os = new
FileOutputStream(newFile);
os = new
BufferedOutputStream(os);
try
{
new
ExifRewriter().updateExifMetadataLossless(originalFile, os, outputSet);
} finally
{
os.close();
}
} catch
(Exception ex) {
//
}
}
private
static
void
addTag(TiffOutputDirectory outDir, TagInfo tagInfo, Number v) throws
ImageWriteException {
if
(v != null
) {
outDir.add(TiffOutputField.create(tagInfo, BinaryConstants.BYTE_ORDER_BIG_ENDIAN, v));
}
}
private
static
void
addTag(TiffOutputDirectory outDir, TagInfo tagInfo, String v) throws
ImageWriteException {
if
(v != null
) {
outDir.add(TiffOutputField.create(tagInfo, BinaryConstants.BYTE_ORDER_BIG_ENDIAN, v));
}
}
更新との違いは,TiffOutputSetを既存から読み出すのではなく,新規に作成するところです.
指定しているエンディアン(BYTE_ORDER_BIG_ENDIAN)は全体で揃えるように注意してください.
privateメソッドのaddTag()は,null値のスキップとエンディアン指定を組み込んだ省力化メソッドです.
補足
ここで紹介したバージョンのSanselanAndroidには,Canonのデジカメで撮影したExifのメーカーノート部が壊れるという問題があります.
以下のパッチを入れれば,対応できます.
参考