The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
#------------------------------------------------------------------------------
# File:         Samsung.pm
#
# Description:  Samsung EXIF maker notes tags
#
# Revisions:    2010/03/01 - P. Harvey Created
#
# References:   1) Tae-Sun Park private communication
#               2) http://www.cybercom.net/~dcoffin/dcraw/
#               3) Pascal de Bruijn private communication (NX100)
#               4) Jaroslav Stepanek via rt.cpan.org
#               5) Niels Kristian Bech Jensen private communication
#               6) Nick Livchits private communication
#------------------------------------------------------------------------------

package Image::ExifTool::Samsung;

use strict;
use vars qw($VERSION %samsungLensTypes);
use Image::ExifTool qw(:DataAccess :Utils);
use Image::ExifTool::Exif;

$VERSION = '1.27';

sub WriteSTMN($$$);
sub ProcessINFO($$$);
sub ProcessSamsungIFD($$$);

# Samsung LensType lookup
%samsungLensTypes = (
    # (added "Samsung NX" in all of these lens names - ref 4)
    0 => 'Built-in or Manual Lens', #PH (EX1, WB2000)
    1 => 'Samsung NX 30mm F2 Pancake',
    2 => 'Samsung NX 18-55mm F3.5-5.6 OIS', # (also version II, ref 1)
    3 => 'Samsung NX 50-200mm F4-5.6 ED OIS',
    # what about the non-OIS version of the 18-55,
    # which was supposed to be available before the 20-50? - PH
    4 => 'Samsung NX 20-50mm F3.5-5.6 ED', #PH/4
    5 => 'Samsung NX 20mm F2.8 Pancake', #PH
    6 => 'Samsung NX 18-200mm F3.5-6.3 ED OIS', #4
    7 => 'Samsung NX 60mm F2.8 Macro ED OIS SSA', #1
    8 => 'Samsung NX 16mm F2.4 Pancake', #1/4
    9 => 'Samsung NX 85mm F1.4 ED SSA', #4
    10 => 'Samsung NX 45mm F1.8', #3
    11 => 'Samsung NX 45mm F1.8 2D/3D', #3
    12 => 'Samsung NX 12-24mm F4-5.6 ED', #4
    13 => 'Saumsun NX 16-50mm F2-2.8 S ED OIS', #forum3833
    14 => 'Samsung NX 10mm F3.5 Fisheye', #5
    15 => 'Samsung NX 16-50mm F3.5-5.6 Power Zoom ED OIS', #6
    20 => 'Samsung NX 50-150mm F2.8 S ED OIS', #PH
);

# range of values for Formats used in encrypted information
my %formatMinMax = (
    int16u => [ 0, 65535 ],
    int32u => [ 0, 4294967295 ],
    int16s => [ -32768, 32767 ],
    int32s => [ -2147483648, 2147483647 ],
);

# Samsung "STMN" maker notes (ref PH)
%Image::ExifTool::Samsung::Main = (
    PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
    WRITE_PROC => \&WriteSTMN,
    CHECK_PROC => \&Image::ExifTool::CheckBinaryData,
    WRITABLE => 1,
    FORMAT => 'int32u',
    FIRST_ENTRY => 0,
    IS_OFFSET => [ 2 ],   # tag 2 is 'IsOffset'
    IS_SUBDIR => [ 11 ],
    GROUPS => { 0 => 'MakerNotes', 2 => 'Image' },
    NOTES => q{
        Tags found in the binary "STMN" format maker notes written by a number of
        Samsung models.
    },
    0 => {
        Name => 'MakerNoteVersion',
        Format => 'undef[8]',
    },
    2 => {
        Name => 'PreviewImageStart',
        OffsetPair => 3,  # associated byte count tagID
        DataTag => 'PreviewImage',
        IsOffset => 3,
        Protected => 2,
    },
    3 => {
        Name => 'PreviewImageLength',
        OffsetPair => 2,   # point to associated offset
        DataTag => 'PreviewImage',
        Protected => 2,
    },
    11 => {
        Name => 'SamsungIFD',
        # Note: this is not always an IFD.  In many models the string
        # "Park Byeongchan" is found at this location
        Condition => '$$valPt =~ /^[^\0]\0\0\0/',
        Format => 'undef[$size - 44]',
        SubDirectory => { TagTable => 'Image::ExifTool::Samsung::IFD' },
    },
);

%Image::ExifTool::Samsung::IFD = (
    PROCESS_PROC => \&ProcessSamsungIFD,
    NOTES => q{
        This is a standard-format IFD found in the maker notes of some Samsung
        models, except that the entry count is a 4-byte integer and the offsets are
        relative to the end of the IFD.  Currently, no tags in this IFD are known,
        so the Unknown (-u) or Verbose (-v) option must be used to see this
        information.
    },
    GROUPS => { 0 => 'MakerNotes', 2 => 'Image' },
    # 0x0001 - undef[4000|4100]: starts with "MN_PRV" (or all zeros)
    # 0x0002 - undef[7000]     : starts with "Kim Miae"
    # 0x0003 - undef[5000]     : starts with "Lee BK"
    # 0x0004 - undef[500|2000] : starts with "IPCD"   (or all zeros)
    # 0x0006 - undef[100|200]  : starts with "MN_ADS" (or all zeros)
);

# Samsung maker notes (ref PH)
%Image::ExifTool::Samsung::Type2 = (
    WRITE_PROC => \&Image::ExifTool::Exif::WriteExif,
    CHECK_PROC => \&Image::ExifTool::Exif::CheckExif,
    WRITABLE => 1,
    GROUPS => { 0 => 'MakerNotes', 2 => 'Image' },
    NOTES => 'Tags found in the EXIF-format maker notes of newer Samsung models.',
    0x0001 => {
        Name => 'MakerNoteVersion',
        Writable => 'undef',
        Count => 4,
    },
    0x0021 => { #1
        Name => 'PictureWizard',
        Writable => 'int16u',
        SubDirectory => { TagTable => 'Image::ExifTool::Samsung::PictureWizard' },
    },
    # 0x0023 - string: "0123456789" (PH)
    0x0030 => { #1 (NX100 with GPS)
        Name => 'LocalLocationName',
        Writable => 'string',
        Format => 'undef',
        # this contains 2 place names (in Korean if in Korea), separated by a null+space
        # - terminate at double-null and replace nulls with newlines
        ValueConv => '$val=~s/\0\0.*//; $val=~s/\0 */\n/g; $val',
        ValueConvInv => '$val=~s/(\x0d\x0a|\x0d|\x0a)/\0 /g; $val . "\0\0"'
    },
    0x0031 => { #1 (NX100 with GPS)
        Name => 'LocationName',
        Writable => 'string',
    },
    0x0035 => {
        Name => 'PreviewIFD',
        Condition => '$$self{TIFF_TYPE} eq "SRW"', # (not an IFD in JPEG images)
        Groups => { 1 => 'PreviewIFD' },
        Flags => 'SubIFD',
        SubDirectory => {
            TagTable => 'Image::ExifTool::Nikon::PreviewIFD',
            ByteOrder => 'Unknown',
            Start => '$val',
        },
    },
    0x0043 => { #1 (NC)
        Name => 'CameraTemperature',
        Groups => { 2 => 'Camera' },
        Writable => 'rational64s',
        # (DPreview samples all 0.2 C --> pre-production model)
        PrintConv => '"$val C"',
        PrintConvInv => '$val=~s/ ?C//; $val',
    },
    # 0x00a0 - undef[8192]: white balance information (ref 1):
    #   At byte 5788, the WBAdjust: "Adjust\0\X\0\Y\0\Z\xee\xea\xce\xab", where
    #   Y = BA adjust (0=Blue7, 7=0, 14=Amber7), Z = MG (0=Magenta7, 7=0, 14=Green7)
#
# the following tags found only in SRW images
#
    # 0xa000 - rational64u: 1 or 1.1 (ref PH)
    0xa001 => { #1
        Name => 'FirmwareName',
        Groups => { 2 => 'Camera' },
        Writable => 'string',
    },
    # 0xa002 - string[30]: '0' or 'DY049P000000' (ref PH)
    0xa003 => { #1 (SRW images only)
        Name => 'LensType',
        Groups => { 2 => 'Camera' },
        Writable => 'int16u',
        Count => -1,
        PrintConv => [ \%samsungLensTypes ],
    },
    0xa004 => { #1
        Name => 'LensFirmware',
        Groups => { 2 => 'Camera' },
        Writable => 'string',
    },
    # 0xa005 - string[30]: constant for a given lens? Not the printed serial number (ref 1)
    0xa010 => { #1
        Name => 'SensorAreas',
        Groups => { 2 => 'Camera' },
        Notes => 'full and valid sensor areas',
        Writable => 'int32u',
        Count => 8,
    },
    0xa011 => { #1
        Name => 'ColorSpace',
        Writable => 'int16u',
        PrintConv => {
            0 => 'sRGB',
            1 => 'Adobe RGB',
        },
    },
    0xa012 => { #1
        Name => 'SmartRange',
        Writable => 'int16u',
        PrintConv => { 0 => 'Off', 1 => 'On' },
    },
    0xa013 => { #1
        Name => 'ExposureCompensation',
        Writable => 'rational64s',
    },
    0xa014 => { #1
        Name => 'ISO',
        Writable => 'int32u',
    },
    0xa018 => { #1
        Name => 'ExposureTime',
        Writable => 'rational64u',
        PrintConv => 'Image::ExifTool::Exif::PrintExposureTime($val)',
        PrintConvInv => 'Image::ExifTool::Exif::ConvertFraction($val)',
    },
    0xa019 => { #1
        Name => 'FNumber',
        Priority => 0,
        Writable => 'rational64u',
        PrintConv => 'sprintf("%.1f",$val)',
        PrintConvInv => '$val',
    },
    0xa01a => { #1
        Name => 'FocalLengthIn35mmFormat',
        Groups => { 2 => 'Camera' },
        Priority => 0,
        Format => 'int32u',
        ValueConv => '$val / 10',
        ValueConvInv => '$val * 10',
        PrintConv => '"$val mm"',
        PrintConvInv => '$val=~s/\s*mm$//;$val',
    },
    0xa020 => { #1
        Name => 'EncryptionKey',
        Writable => 'int32u',
        Count => 11,
        Protected => 1,
        DataMember => 'EncryptionKey',
        RawConv => '$$self{EncryptionKey} = [ split(" ",$val) ]; $val',
        Notes => 'key used to decrypt the tags below',
        # value is "305 72 737 456 282 307 519 724 13 505 193"
    },
    0xa021 => { #1
        Name => 'WB_RGGBLevelsUncorrected',
        Writable => 'int32u',
        Count => 4,
        Notes => 'these tags not corrected for WB_RGGBLevelsBlack',
        RawConv    => 'Image::ExifTool::Samsung::Crypt($self,$val,$tagInfo,"-0")',
        RawConvInv => 'Image::ExifTool::Samsung::Crypt($self,$val,$tagInfo,0)',
    },
    0xa022 => { #1
        Name => 'WB_RGGBLevelsAuto',
        Writable => 'int32u',
        Count => 4,
        RawConv    => 'Image::ExifTool::Samsung::Crypt($self,$val,$tagInfo,-4)',
        RawConvInv => 'Image::ExifTool::Samsung::Crypt($self,$val,$tagInfo,4)',
    },
    0xa023 => { #1
        Name => 'WB_RGGBLevelsIlluminator1',
        Writable => 'int32u',
        Count => 4,
        RawConv    => 'Image::ExifTool::Samsung::Crypt($self,$val,$tagInfo,-8)',
        RawConvInv => 'Image::ExifTool::Samsung::Crypt($self,$val,$tagInfo,8)',
    },
    0xa024 => { #1
        Name => 'WB_RGGBLevelsIlluminator2',
        Writable => 'int32u',
        Count => 4,
        RawConv    => 'Image::ExifTool::Samsung::Crypt($self,$val,$tagInfo,-1)',
        RawConvInv => 'Image::ExifTool::Samsung::Crypt($self,$val,$tagInfo,1)',
    },
    #this doesn't seem correct
    #0xa025 => { #PH/1
    #    Name => 'ColorTemperatureAuto',
    #    Writable => 'int32u',
    #    RawConv    => 'Image::ExifTool::Samsung::Crypt($self,$val,$tagInfo,6)',
    #    RawConvInv => 'Image::ExifTool::Samsung::Crypt($self,$val,$tagInfo,-6)',
    #},
    0xa028 => { #2/PH
        Name => 'WB_RGGBLevelsBlack',
        Writable => 'int32s',
        Count => 4,
        RawConv    => 'Image::ExifTool::Samsung::Crypt($self,$val,$tagInfo,"-0")',
        RawConvInv => 'Image::ExifTool::Samsung::Crypt($self,$val,$tagInfo,0)',
    },
    0xa030 => { #1
        Name => 'ColorMatrix',
        Writable =>  'int32s',
        Count => 9,
        RawConv    => 'Image::ExifTool::Samsung::Crypt($self,$val,$tagInfo,0)',
        RawConvInv => 'Image::ExifTool::Samsung::Crypt($self,$val,$tagInfo,"-0")',
    },
    0xa031 => { #1
        Name => 'ColorMatrixSRGB',
        Writable =>  'int32s',
        Count => 9,
        RawConv    => 'Image::ExifTool::Samsung::Crypt($self,$val,$tagInfo,0)',
        RawConvInv => 'Image::ExifTool::Samsung::Crypt($self,$val,$tagInfo,"-0")',
    },
    0xa032 => { #1
        Name => 'ColorMatrixAdobeRGB',
        Writable =>  'int32s',
        Count => 9,
        RawConv    => 'Image::ExifTool::Samsung::Crypt($self,$val,$tagInfo,0)',
        RawConvInv => 'Image::ExifTool::Samsung::Crypt($self,$val,$tagInfo,"-0")',
    },
    0xa033 => { #1
        Name => 'Samsung_Type2_0xa033',
        Unknown => 1,
        Hidden => 1,
        Writable => 'int32s',
        Count => 4,
        RawConv    => 'Image::ExifTool::Samsung::Crypt($self,$val,$tagInfo,0)',
        RawConvInv => 'Image::ExifTool::Samsung::Crypt($self,$val,$tagInfo,"-0")',
    },
    0xa034 => { #1
        Name => 'Samsung_Type2_0xa034',
        Unknown => 1,
        Hidden => 1,
        Writable => 'int32s',
        Count => 4,
        RawConv    => 'Image::ExifTool::Samsung::Crypt($self,$val,$tagInfo,4)',
        RawConvInv => 'Image::ExifTool::Samsung::Crypt($self,$val,$tagInfo,-4)',
    },
    0xa035 => { #1
        Name => 'Samsung_Type2_0xa035',
        Unknown => 1,
        Hidden => 1,
        Writable => 'int32u',
        Count => 2,
        RawConv    => 'Image::ExifTool::Samsung::Crypt($self,$val,$tagInfo,"-0")',
        RawConvInv => 'Image::ExifTool::Samsung::Crypt($self,$val,$tagInfo,0)',
    },
    0xa036 => { #1
        Name => 'Samsung_Type2_0xa036',
        Unknown => 1,
        Hidden => 1,
        Writable => 'int32u',
        Count => 2,
        RawConv    => 'Image::ExifTool::Samsung::Crypt($self,$val,$tagInfo,-2)',
        RawConvInv => 'Image::ExifTool::Samsung::Crypt($self,$val,$tagInfo,2)',
    },
    0xa040 => { #1
        Name => 'ToneCurve1',
        Writable =>  'int32u',
        Count => 23,
        Notes => q{
            first value gives the number of tone curve entries.  This is followed by an
            array of X coordinates then an array of Y coordinates
        },
        RawConv    => 'Image::ExifTool::Samsung::Crypt($self,$val,$tagInfo,0,"-0")',
        RawConvInv => 'Image::ExifTool::Samsung::Crypt($self,$val,$tagInfo,"-0",0)',
    },
    0xa041 => { #1
        Name => 'ToneCurve2',
        Writable =>  'int32u',
        Count => 23,
        RawConv    => 'Image::ExifTool::Samsung::Crypt($self,$val,$tagInfo,0,"-0")',
        RawConvInv => 'Image::ExifTool::Samsung::Crypt($self,$val,$tagInfo,"-0",0)',
    },
    0xa042 => { #1
        Name => 'ToneCurve3',
        Writable =>  'int32u',
        Count => 23,
        RawConv    => 'Image::ExifTool::Samsung::Crypt($self,$val,$tagInfo,0,"-0")',
        RawConvInv => 'Image::ExifTool::Samsung::Crypt($self,$val,$tagInfo,"-0",0)',
    },
    0xa043 => { #1
        Name => 'ToneCurve4',
        Writable =>  'int32u',
        Count => 23,
        RawConv    => 'Image::ExifTool::Samsung::Crypt($self,$val,$tagInfo,0,"-0")',
        RawConvInv => 'Image::ExifTool::Samsung::Crypt($self,$val,$tagInfo,"-0",0)',
    },
    0xa048 => { #1
        Name => 'Samsung_Type2_0xa048',
        Unknown => 1,
        Hidden => 1,
        Writable => 'int32s',
        Count => 12,
        RawConv    => 'Image::ExifTool::Samsung::Crypt($self,$val,$tagInfo,0)',
        RawConvInv => 'Image::ExifTool::Samsung::Crypt($self,$val,$tagInfo,"-0")',
    },
    0xa050 => { #1 (vignette curve?)
        Name => 'Samsung_Type2_0xa050',
        Unknown => 1,
        Hidden => 1,
        Writable => 'int32s',
        Count => 8,
        RawConv    => 'Image::ExifTool::Samsung::Crypt($self,$val,$tagInfo,0)',
        RawConvInv => 'Image::ExifTool::Samsung::Crypt($self,$val,$tagInfo,"-0")',
    },
    0xa051 => { #1
        Name => 'Samsung_Type2_0xa051',
        Unknown => 1,
        Hidden => 1,
        Writable => 'int16u',
        Count => 22,
        RawConv    => 'Image::ExifTool::Samsung::Crypt($self,$val,$tagInfo,"-0",-7,-3)',
        RawConvInv => 'Image::ExifTool::Samsung::Crypt($self,$val,$tagInfo,0,7,3)',
    },
    0xa052 => { #1 (vignette curve?)
        Name => 'Samsung_Type2_0xa052',
        Unknown => 1,
        Hidden => 1,
        Writable => 'int16u',
        Count => 15,
        RawConv    => 'Image::ExifTool::Samsung::Crypt($self,$val,$tagInfo,0,"-0")',
        RawConvInv => 'Image::ExifTool::Samsung::Crypt($self,$val,$tagInfo,"-0",0)',
    },
    0xa053 => { #1
        Name => 'Samsung_Type2_0xa053',
        Unknown => 1,
        Hidden => 1,
        Writable => 'int16u',
        Count => 15,
        RawConv    => 'Image::ExifTool::Samsung::Crypt($self,$val,$tagInfo,0,"-0")',
        RawConvInv => 'Image::ExifTool::Samsung::Crypt($self,$val,$tagInfo,"-0",0)',
    },
    0xa054 => { #1
        Name => 'Samsung_Type2_0xa054',
        Unknown => 1,
        Hidden => 1,
        Writable => 'int16u',
        Count => 15,
        RawConv    => 'Image::ExifTool::Samsung::Crypt($self,$val,$tagInfo,0,"-0")',
        RawConvInv => 'Image::ExifTool::Samsung::Crypt($self,$val,$tagInfo,"-0",0)',
    },
    0xa055 => { #1
        Name => 'Samsung_Type2_0xa055',
        Unknown => 1,
        Hidden => 1,
        Writable => 'int32s',
        Count => 8,
        RawConv    => 'Image::ExifTool::Samsung::Crypt($self,$val,$tagInfo,8)',
        RawConvInv => 'Image::ExifTool::Samsung::Crypt($self,$val,$tagInfo,-8)',
    },
    0xa056 => { #1
        Name => 'Samsung_Type2_0xa056',
        Unknown => 1,
        Hidden => 1,
        Writable => 'int32s',
        Count => 8,
        RawConv    => 'Image::ExifTool::Samsung::Crypt($self,$val,$tagInfo,5)',
        RawConvInv => 'Image::ExifTool::Samsung::Crypt($self,$val,$tagInfo,-5)',
    },
    0xa057 => { #1
        Name => 'Samsung_Type2_0xa057',
        Unknown => 1,
        Hidden => 1,
        Writable => 'int32s',
        Count => 8,
        RawConv    => 'Image::ExifTool::Samsung::Crypt($self,$val,$tagInfo,2)',
        RawConvInv => 'Image::ExifTool::Samsung::Crypt($self,$val,$tagInfo,-2)',
    },
);

# Picture Wizard information (ref 1)
%Image::ExifTool::Samsung::PictureWizard = (
    PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
    WRITE_PROC => \&Image::ExifTool::WriteBinaryData,
    CHECK_PROC => \&Image::ExifTool::CheckBinaryData,
    WRITABLE => 1,
    FORMAT => 'int16u',
    FIRST_ENTRY => 0,
    GROUPS => { 0 => 'MakerNotes', 2 => 'Image' },
    0 => {
        Name => 'PictureWizardMode',
        PrintConvColumns => 3,
        PrintConv => { #3
             0 => 'Standard',
             1 => 'Vivid',
             2 => 'Portrait',
             3 => 'Landscape',
             4 => 'Forest',
             5 => 'Retro',
             6 => 'Cool',
             7 => 'Calm',
             8 => 'Classic',
             9 => 'Custom1',
            10 => 'Custom2',
            11 => 'Custom3',
           255 => 'n/a', #PH
        },
    },
    1 => 'PictureWizardColor',
    2 => {
        Name => 'PictureWizardSaturation',
        ValueConv => '$val - 4',
        ValueConvInv => '$val + 4',
    },
    3 => {
        Name => 'PictureWizardSharpness',
        ValueConv => '$val - 4',
        ValueConvInv => '$val + 4',
    },
    4 => {
        Name => 'PictureWizardContrast',
        ValueConv => '$val - 4',
        ValueConvInv => '$val + 4',
    },
);

# INFO tags in Samsung MP4 videos (ref PH)
%Image::ExifTool::Samsung::INFO = (
    PROCESS_PROC => \&ProcessINFO,
    GROUPS => { 0 => 'MakerNotes', 2 => 'Video' },
    NOTES => q{
        This information is found in MP4 videos from Samsung models such as the
        SMX-C20N.
    },
    EFCT => 'Effect', # (guess)
    QLTY => 'Quality',
    # MDEL - value: 0
    # ASPT - value: 1, 2
);

# Samsung MP4 TAGS information (PH - from WP10 sample)
# --> very similar to Sanyo MP4 information
%Image::ExifTool::Samsung::MP4 = (
    PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
    GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' },
    NOTES => q{
        This information is found in Samsung MP4 videos from models such as the
        WP10.
    },
    0x00 => {
        Name => 'Make',
        Format => 'string[24]',
        PrintConv => 'ucfirst(lc($val))',
    },
    0x18 => {
        Name => 'Model',
        Description => 'Camera Model Name',
        Format => 'string[16]',
    },
    0x2e => { # (NC)
        Name => 'ExposureTime',
        Format => 'int32u',
        ValueConv => '$val ? 10 / $val : 0',
        PrintConv => 'Image::ExifTool::Exif::PrintExposureTime($val)',
    },
    0x32 => {
        Name => 'FNumber',
        Format => 'rational64u',
        PrintConv => 'sprintf("%.1f",$val)',
    },
    0x3a => { # (NC)
        Name => 'ExposureCompensation',
        Format => 'rational64s',
        PrintConv => '$val ? sprintf("%+.1f", $val) : 0',
    },
    0x6a => {
        Name => 'ISO',
        Format => 'int32u',
    },
    0x7d => {
        Name => 'Software',
        Format => 'string[32]',
        # (these tags are not at a constant offset for Olympus/Sanyo videos,
        #  so just to be safe use this to validate subsequent tags)
        RawConv => q{
            $val =~ /^SAMSUNG/ or return undef;
            $$self{SamsungMP4} = 1;
            return $val;
        },
    },
    0xf4 => {
        Name => 'Thumbnail',
        Condition => '$$self{SamsungMP4}',
        SubDirectory => {
            TagTable => 'Image::ExifTool::Samsung::Thumbnail',
            Base => '$start',
        },
    },
);

# thumbnail image information found in MP4 videos (similar in Olympus,Samsung,Sanyo)
%Image::ExifTool::Samsung::Thumbnail = (
    PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
    GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' },
    FIRST_ENTRY => 0,
    FORMAT => 'int32u',
    1 => 'ThumbnailWidth',
    2 => 'ThumbnailHeight',
    3 => 'ThumbnailLength',
    4 => { Name => 'ThumbnailOffset', IsOffset => 1 },
);

# Samsung MP4 @sec information (PH - from WB30F sample)
%Image::ExifTool::Samsung::sec = (
    PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
    GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' },
    NOTES => q{
        This information is found in the @sec atom of Samsung MP4 videos from models
        such as the WB30F.
    },
    0x00 => {
        Name => 'Make',
        Format => 'string[32]',
        PrintConv => 'ucfirst(lc($val))',
    },
    0x20 => {
        Name => 'Model',
        Description => 'Camera Model Name',
        Format => 'string[32]',
    },
    0x200 => { Name => 'ThumbnailWidth',  Format => 'int32u' },
    0x204 => { Name => 'ThumbnailHeight', Format => 'int32u' },
    0x208 => { Name => 'ThumbnailLength', Format => 'int32u' }, # (2 bytes too long in my sample)
    0x20c => {
        Name => 'ThumbnailImage',
        Format => 'undef[$val{0x208}]',
        Notes => 'the THM image, embedded metadata is extracted as the first sub-document',
        SetBase => 1,
        RawConv => q{
            my $pt = $self->ValidateImage(\$val, $tag);
            if ($pt) {
                $$self{BASE} += 0x20c;
                $$self{DOC_NUM} = ++$$self{DOC_COUNT};
                $self->ExtractInfo($pt, { ReEntry => 1 });
                $$self{DOC_NUM} = 0;
            }
            return $pt;
        },
    },
);

# Samsung MP4 smta information (PH - from SM-C101 sample)
%Image::ExifTool::Samsung::smta = (
    GROUPS => { 0 => 'MakerNotes', 2 => 'Video' },
    NOTES => q{
        This information is found in the smta atom of Samsung MP4 videos from models
        such as the Galaxy S4.
    },
    svss => {
        Name => 'SamsungSvss',
        SubDirectory => { TagTable => 'Image::ExifTool::Samsung::svss' },
    },
    # swtr - 4 bytes, all zero
    # scid - 8 bytes, all zero
    # saut - 4 bytes, all zero
);

# Samsung MP4 svss information (PH - from SM-C101 sample)
%Image::ExifTool::Samsung::svss = (
    GROUPS => { 0 => 'MakerNotes', 2 => 'Video' },
    NOTES => q{
        This information is found in the svss atom of Samsung MP4 videos from models
        such as the Galaxy S4.
    },
    # junk - 10240 bytes, all zero
);

# thumbnail image information found in some MP4 videos
%Image::ExifTool::Samsung::Thumbnail2 = (
    PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
    GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' },
    FIRST_ENTRY => 0,
    FORMAT => 'int32u',
    1 => 'ThumbnailWidth',
    2 => 'ThumbnailHeight',
    3 => 'ThumbnailLength',
    4 => { Name => 'ThumbnailOffset', IsOffset => 1 },
);

# information extracted from Samsung trailer (ie. Samsung SM-T805 "Sound & Shot" JPEG)
%Image::ExifTool::Samsung::Trailer = (
    GROUPS => { 0 => 'MakerNotes', 2 => 'Other' },
    VARS => { NO_ID => 1 },
    NOTES => q{
        Tags extracted from the trailer of JPEG images written when using certain
        features (such as "Sound & Shot" or "Shot & More") from Samsung models such
        as the Galaxy S4 and Tab S.
    },
    # stuff written with "Shot & More" feature
    '0x0001' => { Name => 'EmbeddedImage', Binary => 1 },
    '0x0001-name' => 'EmbeddedImageName',
    # 0x0830 - unknown (164004 bytes, name like "1165724808.pre")

    # stuff written with "Sound & Shot" feature
    '0x0100' => { Name => 'EmbeddedAudioFile', Binary => 1 },
    '0x0100-name' => 'EmbeddedAudioFileName',
    # 0x0800 - SoundShot_Meta_Info (contains only already-extracted sound shot name)
);

# Samsung composite tags
%Image::ExifTool::Samsung::Composite = (
    GROUPS => { 2 => 'Image' },
    WB_RGGBLevels => {
        Require => {
            0 => 'WB_RGGBLevelsUncorrected',
            1 => 'WB_RGGBLevelsBlack',
        },
        ValueConv => q{
            my @a = split ' ', $val[0];
            my @b = split ' ', $val[1];
            $a[$_] -= $b[$_] foreach 0..$#a;
            return "@a";
        },
    },
);

# add our composite tags
Image::ExifTool::AddCompositeTags('Image::ExifTool::Samsung');

#------------------------------------------------------------------------------
# Encrypt/Decrypt NX10 information
# Inputs: 0) ExifTool ref, 1) value as a string of integers,
#         2) tagInfo hash ref, 3-N) encryption salt values
# Returns: encrypted/decrypted value
# Notes:
# 1) The encryption salt starts with '-' to reverse the encryption algorithm
# 2) Additional salt values are provided when tag stores multiple arrays
#    (in which case the first value of the tag gives the array length)
sub Crypt($$$@)
{
    my ($et, $val, $tagInfo, @salt) = @_;
    my $key = $$et{EncryptionKey} or return undef;
    my $format = $$tagInfo{Writable} || $$tagInfo{Format} or return undef;
    return undef unless $formatMinMax{$format};
    my ($min, $max) = @{$formatMinMax{$format}};
    my @a = split ' ', $val;
    my $newSalt = (@salt > 1) ? 1 : 0;  # skip length entry if this is an array
    my ($i, $sign, $salt, $start);
    for ($i=$newSalt; $i<@a; ++$i) {
        if ($i == $newSalt) {
            $start = $i;
            $salt = shift @salt;
            $sign = ($salt =~ s/^-//) ? -1 : 1;
            $newSalt += $a[0] if @salt;
        }
        $a[$i] += $sign * $$key[($salt+$i-$start) % scalar(@$key)];
        # handle integer wrap-around
        if ($sign > 0) {
            $a[$i] -= $max - $min + 1 if $a[$i] > $max;
        } else {
            $a[$i] += $max - $min + 1 if $a[$i] < $min;
        }
    }
    return "@a";
}

#------------------------------------------------------------------------------
# Process Samsung MP4 INFO data
# Inputs: 0) ExifTool ref, 1) dirInfo ref, 2) tag table ref
# Returns: 1 on success
sub ProcessINFO($$$)
{
    my ($et, $dirInfo, $tagTablePtr) = @_;
    my $dataPt = $$dirInfo{DataPt};
    my $pos = $$dirInfo{DirStart};
    my $len = $$dirInfo{DirLen};
    my $end = $pos + $len;
    $et->VerboseDir('INFO', undef, $len);
    while ($pos + 8 <= $end) {
        my $tag = substr($$dataPt, $pos, 4);
        my $val = Get32u($dataPt, $pos + 4);
        unless ($$tagTablePtr{$tag}) {
            my $name = "Samsung_INFO_$tag";
            $name =~ tr/-_0-9a-zA-Z//dc;
            AddTagToTable($tagTablePtr, $tag, { Name => $name }) if $name;
        }
        $et->HandleTag($tagTablePtr, $tag, $val);
        $pos += 8;
    }
    return 1;
}

#------------------------------------------------------------------------------
# Inputs: 0) ExifTool object ref, 1) source dirInfo ref, 2) tag table ref
# Returns: true on success
sub ProcessSamsungIFD($$$)
{
    my ($et, $dirInfo, $tagTablePtr) = @_;
    my $len = $$dirInfo{DataLen};
    my $pos = $$dirInfo{DirStart};
    return 0 unless $pos + 4 < $len;
    my $dataPt = $$dirInfo{DataPt};
    my $buff = substr($$dataPt, $pos, 4);
    # this is not an IFD for many models
    # (the string "Park Byeongchan" is often found here)
    return 0 unless $buff =~ s/^([^\0])\0\0\0/$1\0$1\0/s;
    my $numEntries = ord $1;
    if ($$et{HTML_DUMP}) {
        my $pt = $$dirInfo{DirStart} + $$dirInfo{DataPos} + $$dirInfo{Base};
        $et->HDump($pt-44, 44, "MakerNotes header", 'Samsung');
        $et->HDump($pt, 4, "MakerNotes entries", "Format: int32u\nEntry count: $numEntries");
        $$dirInfo{NoDumpEntryCount} = 1;
    }
    substr($$dataPt, $pos, 4) = $buff;      # insert bogus 2-byte entry count
    # offset base is at end of IFD
    my $shift = $$dirInfo{DirStart} + 4 + $numEntries * 12 + 4;
    $$dirInfo{Base} += $shift;
    $$dirInfo{DataPos} -= $shift;
    $$dirInfo{DirStart} += 2;       # start at bogus entry count
    $$dirInfo{ZeroOffsetOK} = 1;    # disable check for zero offset
    delete $$et{NO_UNKNOWN};  # (set for BinaryData, but not for EXIF IFD's)
    my $rtn = Image::ExifTool::Exif::ProcessExif($et, $dirInfo, $tagTablePtr);
    substr($$dataPt, $pos + 2, 1) = "\0";   # remove bogus count
    return $rtn;
}

#------------------------------------------------------------------------------
# Read/write Samsung trailer (ie. "Sound & Shot" written by Galaxy Tab S (SM-T805))
# Inputs: 0) ExifTool object reference, 1) dirInfo reference
# Returns: 1 on success, 0 not valid Samsung trailer, or -1 error writing
# - updates DataPos to point to start of Samsung trailer
# - updates DirLen to existing trailer length
sub ProcessSamsung($$$)
{
    my ($et, $dirInfo) = @_;
    my $raf = $$dirInfo{RAF};
    my $offset = $$dirInfo{Offset} || 0;
    my $outfile = $$dirInfo{OutFile};
    my $verbose = $et->Options('Verbose');
    my $unknown = $et->Options('Unknown');
    my ($buff, $buf2, $index, $offsetPos, $audioNOff, $audioSize);

    return 0 unless $raf->Seek(-6-$offset, 2) and $raf->Read($buff, 6) == 6 and
                    ($buff eq 'QDIOBS' or $buff eq "\0\0SEFT");
    my $endPos = $raf->Tell();
    $raf->Seek(-2, 1) or return 0 if $buff eq 'QDIOBS'; # rewind to before 'BS'
    my $blockEnd = $raf->Tell();
    SetByteOrder('II');

    # read blocks backward until we find the SEFH/SEFT block
    # (the only other block I have seen is QDIO/QDIO)
SamBlock:
    for (;;) {
        last unless $raf->Seek($blockEnd-8, 0) and $raf->Read($buff, 8) == 8;
        my $type = substr($buff, 4);
        last unless $type =~ /^\w+$/;
        my $len = Get32u(\$buff, 0);
        last unless $len < 0x10000 and $len >= 4 and $len + 8 < $blockEnd;
        last unless $raf->Seek(-8-$len, 1) and $raf->Read($buff, $len) == $len;
        $blockEnd -= $len + 8;
        unless ($type eq 'SEFT') {  # look for directory block (ends with "SEFT")
            next unless $outfile and $type eq 'QDIO';
            # QDIO block format:
            #   0 - 'QDIO'
            #   4 - int32u: 101 (version)
            #   8 - int32u: 1
            #  12 - int32u: absolute offset of audio file start (augh!!)
            #  16 - int32u: absolute offset of audio file end (augh!!)
            #  20 - int32u: 20 (QDIO block length minus 8)
            #  24 - 'QDIO'
            if ($len == 20) {
                # save position of audio file offset in QDIO block
                $offsetPos = $endPos - $raf->Tell() + $len - 12;
            } else {
                $et->Error('Unsupported Samsung trailer QDIO block', 1);
            }
            next;
        }
        last unless $buff =~ /^SEFH/ and $len >= 12;   # validate SEFH header
        my $dirPos = $raf->Tell() - $len;
        # my $ver = Get32u(\$buff, 0x04);  # version (=101)
        my $count = Get32u(\$buff, 0x08);
        last if 12 + 12 * $count > $len;
        my $tagTablePtr = GetTagTable('Image::ExifTool::Samsung::Trailer');

        # scan ahead quickly to look for the block where the data comes first
        # (have only seen this to be the first in the directory, but just in case)
        my $firstBlock = 0;
        for ($index=0; $index<$count; ++$index) {
            my $entry = 12 + 12 * $index;
            my $noff = Get32u(\$buff, $entry + 4);  # negative offset
            $firstBlock = $noff if $firstBlock < $noff;
        }
        # save trailer position and length
        my $dataPos = $$dirInfo{DataPos} = $dirPos - $firstBlock;
        my $dirLen = $$dirInfo{DirLen} = $endPos - $dataPos;
        if (($verbose or $$et{HTML_DUMP}) and not $outfile) {
            $et->DumpTrailer($dirInfo);
            return 1 if $$et{HTML_DUMP};
        }
        # read through the SEFH/SEFT directory entries
        for ($index=0; $index<$count; ++$index) {
            my $entry = 12 + 12 * $index;
            # first 2 bytes always 0 (may be part of block type)
            my $type = Get16u(\$buff, $entry + 2);  # block type
            my $noff = Get32u(\$buff, $entry + 4);  # negative offset
            my $size = Get32u(\$buff, $entry + 8);  # block size
            last SamBlock if $noff > $dirPos or $size > $noff or $size < 8;
            $firstBlock = $noff if $firstBlock < $noff;
            if ($outfile) {
                next unless $type == 0x0100 and not $audioNOff;
                # save offset and length of first audio file for QDIO block
                last unless $raf->Seek($dirPos-$noff, 0) and $raf->Read($buf2, 8) == 8;
                $len = Get32u(\$buf2, 4);
                $audioNOff = $noff - 8 - $len;   # negative offset to start of audio data
                $audioSize = $size - 8 - $len;
                next;
            }
            # add unknown tags if necessary
            my $tag = sprintf("0x%.4x", $type);
            unless ($$tagTablePtr{$tag}) {
                next unless $unknown or $verbose;
                my %tagInfo = (
                    Name        => "SamsungTrailer_$tag",
                    Description => "Samsung Trailer $tag",
                    Unknown     => 1,
                    Binary      => 1,
                );
                AddTagToTable($tagTablePtr, $tag, \%tagInfo);
                my %tagInfo2 = (
                    Name        => "SamsungTrailer_${tag}Name",
                    Description => "Samsung Trailer $tag Name",
                    Unknown     => 1,
                );
                AddTagToTable($tagTablePtr, "$tag-name", \%tagInfo2);
            }
            last unless $raf->Seek($dirPos-$noff, 0) and $raf->Read($buf2, $size) == $size;
            # (could validate the first 4 bytes of the block because they
            # are the same as the first 4 bytes of the directory entry)
            $len = Get32u(\$buf2, 4);
            last if $len + 8 > $size;
            # extract tag name and value
            $et->HandleTag($tagTablePtr, "$tag-name", undef,
                DataPt  => \$buf2,
                DataPos => $dirPos - $noff,
                Start   => 8,
                Size    => $len,
            );
            $et->HandleTag($tagTablePtr, $tag, undef,
                DataPt  => \$buf2,
                DataPos => $dirPos - $noff,
                Start   => 8 + $len,
                Size    => $size - (8 + $len),
            );
        }
        if ($outfile) {
            last unless $raf->Seek($dataPos, 0) and $raf->Read($buff, $dirLen) == $dirLen;
            # adjust the absolute offset in the QDIO block if necessary
            if ($offsetPos and $audioNOff) {
                # initialize the audio file start/end position in the QDIO block
                my $newPos = Tell($outfile) + $dirPos - $audioNOff - $dataPos;
                Set32u($newPos, \$buff, length($buff) - $offsetPos);
                Set32u($newPos + $audioSize, \$buff, length($buff) - $offsetPos + 4);
                # add a fixup so the calling routine can apply further shifts if necessary
                require Image::ExifTool::Fixup;
                my $fixup = $$dirInfo{Fixup};
                $fixup or $fixup = $$dirInfo{Fixup} = new Image::ExifTool::Fixup;
                $fixup->AddFixup(length($buff) - $offsetPos);
                $fixup->AddFixup(length($buff) - $offsetPos + 4);
            }
            $et->VPrint(0, "Writing Samsung trailer ($dirLen bytes)\n") if $verbose;
            Write($$dirInfo{OutFile}, $buff) or return -1;
            return 1;
        }
        return 1;
    }
    $et->Warn('Error processing Samsung trailer',1);
    return 0;
}

#------------------------------------------------------------------------------
# Write Samsung STMN maker notes
# Inputs: 0) ExifTool object ref, 1) source dirInfo ref, 2) tag table ref
# Returns: Binary data block or undefined on error
sub WriteSTMN($$$)
{
    my ($et, $dirInfo, $tagTablePtr) = @_;
    # create a Fixup for the PreviewImage
    $$dirInfo{Fixup} = new Image::ExifTool::Fixup;
    my $val = Image::ExifTool::WriteBinaryData($et, $dirInfo, $tagTablePtr);
    # force PreviewImage into the trailer even if it fits in EXIF segment
    $$et{PREVIEW_INFO}{IsTrailer} = 1 if $$et{PREVIEW_INFO};
    return $val;
}

1;  # end

__END__

=head1 NAME

Image::ExifTool::Samsung - Samsung EXIF maker notes tags

=head1 SYNOPSIS

This module is loaded automatically by Image::ExifTool when required.

=head1 DESCRIPTION

This module contains definitions required by Image::ExifTool to interpret
Samsung maker notes in EXIF information.

=head1 AUTHOR

Copyright 2003-2014, Phil Harvey (phil at owl.phy.queensu.ca)

This library is free software; you can redistribute it and/or modify it
under the same terms as Perl itself.

=head1 REFERENCES

=over 4

=item L<http://www.cybercom.net/~dcoffin/dcraw/>

=back

=head1 ACKNOWLEDGEMENTS

Thanks to Tae-Sun Park for decoding a number of tags, and Pascal de Bruijn
for the PictureWizard values.

=head1 SEE ALSO

L<Image::ExifTool::TagNames/Samsung Tags>,
L<Image::ExifTool(3pm)|Image::ExifTool>

=cut