;#
;# ds7_lib.pm version 1.2.4 1998/08/01
;#                          Copyright (C) 1997,1998 Mamoru Ohno
;#                               E-mail: mamo@yk.rim.or.jp 
;#
;# 富士フイルム デジタルカメラ DS-7 インタフェースライブラリ Version 1.2.4
;#
;# linux(2.0.35) と perl(5.004) で動作を確認
;# 
;# --------------------------------------
;# 1.2.4 変更点
;# 109コマ目以降の画像取り込みに失敗するバグを修正
;# (井口剛様 patch をありがとうございました)
;#
;# --------------------------------------
;# 1.2.3 変更点
;# ライブラリへの変更は無し
;#
;# --------------------------------------
;# 1.2.2β 変更点
;# 撮影時間取得方法を変更(DS-20,DS-10で発生した不具合ため)
;# サムネイル取得機能を追加
;# 
;# --------------------------------------
;# 1.2.1 変更点
;# setoflag を使って回線速度を設定していた部分を setospeed に変更
;# (内川 喜章様 patch をありがとうございました)
;# 
;# --------------------------------------
;# 1.2 変更点
;# 端末パラメータ 制御モードを変更
;#    &B9600 | &CS8  | &CREAD | &PARENB | &CRTSCTS
;#                     ↓
;#    &B9600 | &CS8 | &PARENB | &CLOCAL | &CREAD
;# ioctl を使用していた部分を POSIX の setcflag に変更
;#    これにより機種ごとに端末パラメータを変更する必要が無くなった
;# version返却関数(&ds7lib_version)を追加
;#    
;# --------------------------------------
;# 1.1 変更点
;# フジフィルム → 富士フイルム
;# チェックサム → BCC(Block Check Character)
;# バグ修正
;#          read_ds7 ヘッダ部分に10 が含まれていた場合の処理異常

package ds7_lib;
use	POSIX;
my($version) = 1.24;

sub BEGIN
{
    require Exporter;
    @ISA = qw(Exporter);
    @EXPORT = qw(open_ds7 close_ds7 get_image read_ds7 set_speed image_name finish_ds7 image_date ds7lib_version image_thumb get_thumb);
}

;#
;# 端末パラメータ値 (linux用)
;#
eval 'sub B57600 {0010001;}';

;#
;# シリアル読み込みのタイミング微調整用
;#
#$Wait = 0.2 ;

;#
;# 制御キャラクター定義
;#
$ETX = 0x03;			# テキスト終了
$ACK = 0x06;			# 肯定応答
$NAK = 0x15;			# 否定応答
$ETB = 0x17;			# 伝送ブロック終了

;# ds-7 をオープンし、収録枚数を返却する。
;# Usage:
;#           &open_ds7($handle,$serial_line)
;#                          $handle:          ファイルハンドル
;#                          $serial_line:     DS-7 の接続されたデバイス名
;#                                            COM1 の場合は "/dev/cua0"
;#                          RETURN:           写真収録枚数
sub open_ds7
{
    $SIG{'INT'} = "finish_ds7";
    my($handle,$serial_line) = @_ ;
    my($buf,$dummy) = '';
    my($new_cflag) = (&CS8 | &PARENB | &CLOCAL | &CREAD);
    #
    # 通信速度 9600 でオープン
    #
    &open_serial($handle,$serial_line,$new_cflag, &B9600) || 
	die "Can't open $serial_line: $!\n";
    #----------------------------------------
    # DS-7 との通信
    #----------------------------------------
    &write_serial($handle,"05");	# 接続開始(問い合わせ)
    &ACK($handle) || die " Can't talk to DS-7.\n"; # 応答受信
    #----------------------------------------
    &write_serial($handle,make_msg("00090000")); # カメラ情報の取得
    &ACK($handle) || die " Error.\n" ;
    ($buf,$dummy) = &read_ds7($handle); # 情報取得1
    #printf (" OK! This is %s .\n",substr($buf,5,4));	# 00.00DS-7 …
    #----------------------------------------
    &OK($handle);		# 肯定応答
    &write_serial($handle,make_msg("000b0000")); # 記録情報の取得
    &ACK($handle) || die " Error.\n";
    ($buf,$dummy) = &read_ds7($handle);		# 情報取得2
    &OK($handle);		# 肯定応答
    my($number) = unpack("c*",substr($buf,0,1)) ;  # 記録枚数
    $number ;
}

;# ラインをopenする
;# Usage:
;#           &open_serial($handle,$dev,$cflag)
;#                          $handle:          ファイルハンドル
;#                          $dev:             シリアルデバイスファイル名
;#                          $cflag:           端末制御フラグ
sub open_serial
{
    my($handle,$dev,$cflag,$sspeed) = @_;
    return 0 unless defined $dev;
    return 0 if defined fileno($handle);
    #printf("%s\n",$dev);
    open($handle, "+< $dev") || die "Can't open $dev:$!";
    # 通信条件の設定
    &set_ioctl($handle,$cflag,$sspeed);
}

;# 通信条件を設定する
;# Usage:
;#           &set_ioctl($handle,$cflag)
;#                          $handle:          ファイルハンドル
;#                          $cflag:           端末制御フラグ
sub set_ioctl
{
    my($handle,$cflag,$sspeed) = @_;
    select((select($handle), $| = 1)[0]);
    # 通信条件の設定
#    &show_param($handle);
    my($termios) = POSIX::Termios->new;
    $termios->getattr(fileno($handle));
    $termios->setcflag( $cflag ); # 制御モード
    $termios->setiflag( 0 );	# 入力モード
    $termios->setoflag( 0 );	# 出力モード
    $termios->setlflag( 0 );	# ローカルモード
    for(my($i)=0;$i<&NCCS;++$i){
        $termios->setcc($i,0);
    }
    $termios->setispeed($sspeed); # 入力速度
    $termios->setospeed($sspeed); # 出力速度
    $termios->setattr(fileno($handle), &POSIX::TCSANOW ); # モード設定
#    &show_param($handle);
}

;#
;# 端末パラメータ表示
;# Usage:
;#          &show_param($handle)
;#                          $handle:          ファイルハンドル
sub show_param
{
    my($handle) = @_;
    my($imes,$omes,$cmes,$lmes,$ismes,$osmes,$sizemes) = '';
    my(%idb) = (&IGNBRK => 'IGNBRK',
    		&BRKINT => 'BRKINT',
		&IGNPAR => 'IGNPAR',
		&PARMRK => 'PARMRK',
		&INPCK => 'INPCK',
		&ISTRIP => 'ISTRIP',
		&INLCR => 'INLCR',
		&IGNCR => 'IGNCR',
		&ICRNL => 'ICRNL',
		&IXON => 'IXON',
		&IXOFF => 'IXOFF'
		);
    my(%odb) = (&OPOST => 'OPOST');
    my(%sdb) = (&B0 => 'B0',
		&B50 => 'B50',
		&B75 => 'B75',
		&B110 => 'B110',
		&B134 => 'B134',
		&B150 => 'B150',
		&B200 => 'B200',
		&B300 => 'B300',
		&B600 => 'B600',
		&B1200 => 'B1200',
		&B1800 => 'B1800',
		&B2400 => 'B2400',
		&B4800 => 'B4800',
		&B9600 => 'B9600',
		&B19200 => 'B19200',
		&B38400 => 'B38400'
		);
    my(%bdb) = (&CS5 => 'CS5',
		&CS6 => 'CS6',
		&CS7 => 'CS7',
		&CS8 => 'CS8'
		);
    my(%cdb) = (&CSTOPB => 'CSTOPB',
		&CREAD => 'CREAD',
		&PARENB => 'PARENB',
		&PARODD => 'PARODD',
		&HUPCL => 'HUPCL',
		&CLOCAL => 'CLOCAL'
		);
    my(%ldb) = (&ISIG => 'ISIG',
		&ICANON => 'ICANON',
		&ECHO => 'ECHO',
		&ECHOE => 'ECHOE',
		&ECHOK => 'ECHOK',
		&ECHONL => 'ECHONL',
		&NOFLSH => 'NOFLSH',
		&TOSTOP => 'TOSTOP',
		&IEXTEN => 'IEXTEN'
		);
    my($termios) = POSIX::Termios->new;
    $termios->getattr(fileno($handle));
    my($iflag) = $termios->getiflag;
    my($oflag) = $termios->getoflag;
    my($cflag) = $termios->getcflag;
    my($lflag) = $termios->getlflag;
    my($ispeed) = $termios->getispeed;
    my($ospeed) = $termios->getospeed;
    my($csize) = $cflag & &CSIZE ;
    foreach $key (sort keys %idb)
    {
	if($iflag & $key)
	{
	    $imes .= $idb{$key}." ";
	}
    }
    foreach $key (sort keys %odb)
    {
	if($oflag & $key)
	{
	    $omes .= $odb{$key}." ";
	}
    }
    foreach $key (sort keys %cdb)
    {
	if($cflag & $key)
	{
	    $cmes .= $cdb{$key}." ";
	}
    }
    foreach $key (sort keys %ldb)
    {
	if($lflag & $key)
	{
	    $lmes .= $ldb{$key}." ";
	}
    }
    $ismes = $sdb{$ispeed};
    $osmes = $sdb{$ospeed};
    $sizemes = $bdb{$csize};
    printf("%s\n%s\n%s%s %s %s\n%s\n",
	   $imes,$omes,$cmes,$ismes,$osmes,$sizemes,$lmes);
    for(my($i)=0;$i<&NCCS;++$i){
        $c_cc[$i] = $termios->getcc($i);
        printf("%d:",$c_cc[$i]);
    }
    printf;
}

;# ds-7 をクローズする。
;# Usage:
;#           &close_ds7($handle)
;#                          $handle:          ファイルハンドル
sub close_ds7
{
    my($handle) = @_;
    return 0 unless defined fileno($handle);
    &OK($handle);		# 通信終了通知
    &write_serial($handle,"04");
    close($handle);
}

;# 1枚分のイメージを取得する
;# Usage:
;#           &get_image($handle,$num)
;#                          $handle:          ファイルハンドル
;#                          $num:             イメージ番号
;#                          RETURN:           イメージデータ
sub get_image
{
    my($handle,$num) = @_;
    my($buf,$image,$bcc) = '';
    &write_serial($handle,make_msg("00020200".sprintf("%02x",$num)."00"));
    &ACK($handle) || die " get_image error :$!\n"; # 応答受信
    $| = 1;
    do {
	($buf,$bcc) = &read_ds7($handle);
	$image .= $buf;
	if ($bcc == $ETB) {	# ETB(0x17) の場合はデータが継続
	    print STDERR "+";
	    &OK($handle);		# 肯定応答
	    select(undef,undef,undef,$Wait) if defined $Wait;
	}
	last if ($end);
    } until ($bcc == $ETX);	# ETX(0x03) の場合はデータが完了
    print STDERR "\n";
    &OK($handle);
    $image;
}
;# 1枚分のサムネイルを取得する
;# Usage:
;#           &get_thumb($handle,$num)
;#                          $handle:          ファイルハンドル
;#                          $num:             イメージ番号
;#                          RETURN:           サムネイルを含むデータ
sub get_thumb
{
    my($handle,$num) = @_;
    my($buf,$thumb,$bcc) = '';
    &write_serial($handle,make_msg("00000200".sprintf("%02x",$num)."00"));
    &ACK($handle) || die " get_thumb error :$!\n"; # 応答受信
    $| = 1;
    do {
	($buf,$bcc) = &read_ds7($handle);
	$thumb .= $buf;
	if ($bcc == $ETB) {	# ETB(0x17) の場合はデータが継続
	    print STDERR "+";
	    &OK($handle);		# 肯定応答
	    select(undef,undef,undef,$Wait) if defined $Wait;
	}
	last if ($end);
    } until ($bcc == $ETX);	# ETX(0x03) の場合はデータが完了
    print STDERR "\n";
    &OK($handle);
    $thumb;
}

;# ds-7 からデータをリードする
;# Usage:
;#           &read_ds7($handle)
;#                          $handle:          ファイルハンドル
;#                          RETURN:           取得データと制御キャラクタ
;#                                            ($buf,$ctl)
sub read_ds7
{
    my($handle) = @_;
    my($ctl,$buf,@F) = '';
    for (;;) {
	&read_serial($handle,2);	# ヘッダ読み込み(DLE+STX)

	# Block Check Character 用
	my($xor) = 0;

	# リード準備完了待ち
	$fds = '';
	vec($fds, fileno($handle), 1) = 1;
	$ret = select($fds, undef, undef, 1);
	last if ($end || $ret < 1);

	$buf = '';
	do {
	    # データ、フッタ部分読み込み
	    1 while(sysread($handle,$buf,1024,length($buf)) == 1024);

	    # データ部分の 1010 は実際には 10 を意味する
	    # 制御キャラクターの 10 と区別するために、1010 が出現した部分で
	    # 配列に分割する
	    @F = split(/\x10{2}/,$buf);	

	    # 終了の割り込みチェック
	    last if ($end);
	} until (unpack("c*",substr($F[$#F],-3,1)) == 0x10);
	# 0x10 があるまで繰り返す。
	# 0x10 の後に 0x17 か 0x03 が あり、最後に1バイトのチェックサムがくる。

	$ctl = unpack("c*",substr($F[$#F],-2,1)); # 制御キャラクタ
	
	$bcc = unpack("c*",substr($F[$#F],-1,1)); # BCC(Block Check Character)

	substr($F[$#F],-3,3) = ""; # 0x10 以降(フッタ)を削除

	$buf = join("\x10",@F);	# 1010 で分割してできた配列を10で連結

	# データが正しく取得できたかチェックする
	@G = unpack("c*",$buf);	# データを配列に分割
	$xor ^= $ctl;		# 制御キャラクタをチェック
	while (@G) {		# データの配列をチェック
	    $xor ^= shift(@G);
	}
	$xor ^= $bcc;		# BCCをチェック
	last if ($xor == 0);	# チェック結果が正常の場合は、ループを抜ける
	print STDERR ":$xor Retry"; # 異常(データが正しく取得できていない)
	# 再送依頼をする。(否定応答)
	&write_serial($handle,sprintf("%02x",$NAK));
    } 
    $buf = substr($buf, 4) ;	# データに関係ない部分をカット
    @ret = ($buf,$ctl);
}

;# データを出力する
;# Usage:
;#           &write_serial($handle,$buf)
;#                          $handle:          ファイルハンドル
;#                          $buf:             出力データ
;#                          RETURN:           出力されたデータのバイト数
sub write_serial
{
    my($handle,$buf) = @_;
    my($bare) = '';
    my($count,$ret) = (0);
    while(($len=length($buf))>0){
	$fds = '';
	vec($fds, fileno($handle), 1) = 1;
	$ret = select(undef, $fds, undef, 8);
	last if ($end || $ret < 1);
	$bare = substr($buf,0,2);
	while(($ret = syswrite($handle,pack("H*",$bare),1)) != 1) {
	    die "System write error: $!\n" unless defined $ret;
	}
	if ($count++ % 128 == 0) {
	    select(undef, undef, undef, 0.1);
	}
	$buf = substr($buf,2,$len-2);
	last if ($end) ;
    }
    $count;
}

;# データを指定バイト数リードする
;# Usage:
;#           &read_serial($handle,$len)
;#                          $handle:          ファイルハンドル
;#                          $len:             リードバイト数
;#                          RETURN:           リードしたデータ
sub read_serial
{
    my($handle,$len) = @_;
    my($buf) = '';
    my($fds,$ret,$c);
    while($len-- > 0) {
	($fds,$ret) = '';
	vec($fds, fileno($handle), 1) = 1;
	$ret = select($fds, undef, undef, 2);
	last if ($end || $ret < 1);
	undef($ret);
	$ret = sysread($handle,$c,1); 
	die "Error in sysread: $!\n" unless defined $ret;
	$buf .= $c;
    }
    $buf;
}

;# データに制御キャラクタ(ヘッダ、フッタ)とBCCを追加する
;# Usage:
;#           &make_msg($buf)
;#                          $buf:             元データ
;#                          RETURN:           編集後データ
sub make_msg
{
    my($buf) = @_;
    my($str) = '';
    my($head) = "1002" ;
    my($foot) = "1003" ;
    my($bcc) = 0x03 ;
    my($c) = '';

    while (length($buf)>0) {
        $c = substr($buf,0,2);
        $bcc ^= hex($c) ;
        $str .= $c;
        if( $c eq "10" ) {
            $str = sprintf( "%s10",$str);
        }
        substr($buf,0,2) = '';
    }
    $buf = sprintf("%s%s%s%02x",$head,$str,$foot,$bcc);
    $buf;
}

;# データを1バイト受信し、ACKを受け取れば真を返す
;# Usage:
;#           &ACK($handle)
;#                          $handle:          ファイルハンドル
;#                          RETURN:           真偽
sub ACK
{
    my($handle) = @_;
    my($buf) = '';
    $buf = &read_serial(cua1,1);
    select(undef,undef,undef,$Wait) if defined $Wait;
    if (unpack("c*",$buf ) != $ACK) {
	return 0;
    }
    return 1;
}

;# ACKを1バイト送信する
;# Usage:
;#           &OK($handle)
;#                          $handle:          ファイルハンドル
sub OK
{
    my($handle) = @_;
    &write_serial($handle,"06");
}

;# イメージの名前を取得する
;# Usage:
;#           &image_name($handle,$num)
;#                          $handle:          ファイルハンドル
;#                          $num:             イメージ番号
;#                          RETURN:           イメージの名前
sub image_name
{
    my($handle,$num) = @_;
    &write_serial($handle,
		  make_msg("000a0200".sprintf("%02x",$num)."00"));
				# ファイル名取得
    &ACK($handle) || die "Can't get filename : $!\n";
    my($buf,$dummy) = &read_ds7($handle);		# 情報取得
    &OK($handle);		# 肯定応答
    $filename = substr($buf,0,12);
    $filename;
}
;# 通信速度を設定する
;# Usage:
;#           &set_speed($handle,$speed)
;#                          $handle:          ファイルハンドル
;#                          $speed:           通信速度
;#                          RETURN:           通信可否
#----------------------------------------
# 通信速度設定 57600,38400,19200,9600
# 57600 "0107010007"
# 38400 "0107010006"
# 19200 "0107010004"
# 9600  "0107010000"
sub set_speed
{
    %speed_msg = (
		  "57600","0107010007",
		  "38400","0107010006",
		  "19200","0107010004",
		  "9600","0107010000",
		  );
    %speed_boud = (
		   "57600",&B57600,
		   "38400",&B38400,
		   "19200",&B19200,
		   "9600",&B9600,
		   );

    my($handle,$speed) = @_;
    return 0 unless defined $speed_msg{$speed};
    &write_serial($handle,make_msg($speed_msg{$speed})); # 通信速度指定
    &ACK($handle) || die "Can't change speed : $!\n";
    ($buf,$dummy) = &read_ds7($handle);		# 情報取得
    &OK($handle);
    #----------------------------------------
    &write_serial($handle,"04"); # 通信一時終了
    #----------------------------------------
    my($new_cflag) = (&CS8 | &PARENB | &CLOCAL | &CREAD);
    &set_ioctl($handle,$new_cflag,$speed_boud{$speed});
    &write_serial($handle,"05"); # 接続開始(問い合わせ)
    &ACK($handle) || return 0;
    &OK($handle);
    return 1;
}
;# イメージの撮影時間を取得する
;# Usage:
;#           &image_date($buf)
;#                          $buf:             イメージデータ
;#                          RETURN:           撮影時間
;#                                            "1997:04:29 22:45:50"の形式
sub image_date
{
    my($buf) = @_;
    my($exif) = &get_exif($buf); # イメージからexifデータを取り出す
    my($A) = unpack("V",substr($exif,4,4)); # 最初のIFDへのオフセット
    my($dummy1,$dummy2,$Value) = &tag_value($exif,$A,34665);
				# DS-7独自データ部分へのオフセット
    $A = unpack("V",$Value);
    ($dummy1,$dummy2,$Value) = &tag_value($exif,$A,36867);
				# DS-7独自データ中の日付部分へのオフセット
    my($date) = unpack("A*",$Value);
    $date;
}
;# イメージデータからサムネイルを取得する
;# Usage:
;#           &image_thumb($buf)
;#                          $buf:             イメージデータ
;#                          RETURN:           サムネイルのtiff形式データ
sub image_thumb
{
    my($buf) = @_;
    my($entry_t) = 'vvVV';	# エントリーの型
    my(@size)=(0,1,1,2,4,8);	# Typeとサイズの対応表
				# 0,BYTE,ASCII.SHORT,LONG,RATIONAL
    my($i,$Tag,$Type,$Count,$Value,$dummy1,$dummy2,$StripOffsets,$StripByteCounts);
    my($exif) = &get_exif($buf); # イメージからexifデータを取り出す
    #
    # 元データの2番目のIFDに格納されているサムネイルデータを、
    # 最初のIFDに格納し、サムネイルのtiff形式データとして返却する。
    #
    my($A) = &next_ifd($exif,unpack("V",substr($exif,4,4)));
    die "Next IFD is empty!" if ($A == 0);
				# サムネイルの格納されているIFDに移動
    my($entry) = unpack("v",substr($exif,$A,2));
				# エントリ個数を取得
    # tiffデータ初期化
    my($tiff) = pack("A*","II"); # Intel byte order
    $tiff .= pack('vVv',(42,8,$entry));	# 42,IFDへのオフセット,エントリ個数
    my($data_p) = 8+6+$entry*12; # tiffファイルデータ部分へのオフセット 
    my($initial_data_p) = $data_p;
    # ここまでが初期化(固定データ)
    # サムネイルイメージ取得
    ($dummy1,$dummy2,$StripOffsets) = &tag_value($exif,$A,273);
				# サムネイルイメージ部分へのオフセット
    ($dummy1,$dummy2,$StripByteCounts) = &tag_value($exif,$A,279);
				# サムネイルイメージサイズ
    my($data) = substr($exif,unpack("V",$StripOffsets),unpack("v",$StripByteCounts));
				# サムネイルイメージを格納
    $data_p += unpack('v',$StripByteCounts);
    # イメージ情報取得
    for($i = 0; $i<$entry;++$i) {
	($Tag,$Type,$Count,$Value) = &get_value($exif,$A,$i);
	if($Tag == 273) {		# サムネイルイメージ部分へのオフセット
	    $tiff .= pack($entry_t,($Tag,$Type,$Count,$initial_data_p));
	    next;
	}
	if($size[$Type]*$Count < 5){ # 値が4バイト以内の場合
	    # 次の4バイトが値
	    $tiff .= pack($entry_t,($Tag,$Type,$Count,unpack("V",$Value)));
	} else {            # 値が4バイト以上の場合
	    $tiff .= pack($entry_t,($Tag,$Type,$Count,$data_p));
	    $data .= $Value;
	    $data_p += length($Value); # オフセットを更新
	}
    }
    $tiff .= pack("V",0);	# 次のIFDなし
    $tiff .= $data;		# データ部分を結合
    $tiff;
}
;# イメージからexifデータを取り出す
;# Usage:
;#           &get_exif($buf)
;#                          $buf:             イメージデータ
;#                          RETURN:           exifデータ
sub get_exif
{
    my($buf) = @_;
				# Exifデータの取り出し
    my($len) = unpack("n", substr($buf,4,2));
    my($exif) = substr($buf,12,$len-8);
				# 先頭から6バイト目からAPP1データが始まり
				# その後6バイトがExifヘッダ
				# データ長-2(サイズデータ)-6(Exifヘッダ)
    $exif;
}
;# 次のIFDのオフセットを返す
;# Usage:
;#           &next_ifd($exif,$A)
;#                          $exif:            exifデータ
;#                          $A:               現在のIFDのオフセット
;#                          RETURN:           次のIFDのオフセット
;#           最初のIFDのオフセットは、$A = unpack("V",substr($exif,4,4));
;#           として取得する。
sub next_ifd
{
    my($exif,$A) = @_;
    my($B) = unpack("v",substr($exif,$A,2));
				# IFDに含まれるエントリの個数
    my($next) = unpack("V",substr($exif,$A+2+$B*12,4));
				# 次のIFDへのオフセット
    $next;
}
;# IFDからTag番号指定でマッチした情報を返す
;# Usage:
;#           &tag_value($exif,$A,$tag)
;#                          $exif:            exifデータ
;#                          $A:               現在のIFDのオフセット
;#                          $tag:             Tag番号
;#                          RETURN:           Type,Count,指定したTagの値
sub tag_value
{
    my($exif,$A,$tag) = @_;
    my($i,$DirEnt,$Tag,$Type,$Count,$ValueOffset,$Value);
    my($entry_t) = 'vvVV';	# エントリーの型
    my(@size)=(0,1,1,2,4,8);	# Typeとサイズの対応表
				# 0,BYTE,ASCII.SHORT,LONG,RATIONAL
    my($B) = unpack("v",substr($exif,$A,2));
    for($i=0;$i<$B;++$i) {
	($Tag,$Type,$Count,$Value) = unpack($entry_t,substr($exif,$A+2+$i*12,12));
				# IFDに含まれるエントリの個数
	last if($Tag == $tag);
    }
    die "Tag don't match!" unless ($i < $B);
    if($size[$Type]*$Count > 4){ # 値が4バイト以上の場合
	$ValueOffset = $Value;
	# 次の4バイトは、値へのオフセット
	$Value = substr($exif,$ValueOffset,$size[$Type]*$Count);
    } else {
	$Value = pack("V",$Value);
    }
    ($Type,$Count,$Value);
}
;# 指定した番号のディレクトリエントリの情報を返す
;# Usage:
;#           &get_value($exif,$A,$entry)
;#                          $exif:            exifデータ
;#                          $A:               現在のIFDのオフセット
;#                          $entry:           entry番号
;#                          RETURN:           Tag,Type,Count,指定したTagの値
sub get_value
{
    my($exif,$A,$entry) = @_;
    my($DirEnt,$Tag,$Type,$Count,$ValueOffset,$Value);
    my($entry_t) = 'vvVV';	# エントリーの型
    my(@size)=(0,1,1,2,4,8);	# Typeとサイズの対応表
				# 0,BYTE,ASCII.SHORT,LONG,RATIONAL
    my($B) = unpack("v",substr($exif,$A,2));
				# IFDに含まれるエントリの個数
    die "Parameter ENTRY is too large!" unless ($entry < $B);
    ($Tag,$Type,$Count,$Value) = unpack($entry_t,substr($exif,$A+2+$entry*12,12));
				# IFDに含まれるエントリの個数
    if($size[$Type]*$Count > 4){ # 値が4バイト以上の場合
	$ValueOffset = $Value;
	# 次の4バイトは、値へのオフセット
	$Value = substr($exif,$ValueOffset,$size[$Type]*$Count);
    } else {
	$Value = pack("V",$Value);
    }
    ($Tag,$Type,$Count,$Value);
}
;#----------------------------------------
;# 処理の最中に止められるようにシグナルハンドラを設定
;#         キーボードからの割り込みを受信したら、$end を設定する
$end = 0;
sub finish_ds7 {
    $end = 1;
}
;# version情報を返却する
;# Usage:
;#           &ds7lib_version()
;#                          RETURN:           version情報
sub ds7lib_version
{
    $version;
}
1;
