instant-0chan/lib/getid3/module.tag.nikon-nctg.php

1449 lines
58 KiB
PHP

<?php
/////////////////////////////////////////////////////////////////
/// getID3() by James Heinrich <info@getid3.org> //
// available at https://github.com/JamesHeinrich/getID3 //
// or https://www.getid3.org //
// or http://getid3.sourceforge.net //
// see readme.txt for more details //
/////////////////////////////////////////////////////////////////
// //
// module.tag.nikon-nctg.php //
// //
/////////////////////////////////////////////////////////////////
/**
* Module for analyzing Nikon NCTG metadata in MOV files
*
* @author Pavel Starosek <starosekpd@gmail.com>
* @author Phil Harvey <philharvey66@gmail.com>
*
* @link https://exiftool.org/TagNames/Nikon.html#NCTG
* @link https://github.com/exiftool/exiftool/blob/master/lib/Image/ExifTool/Nikon.pm
* @link https://leo-van-stee.github.io/
*/
class getid3_tag_nikon_nctg
{
const EXIF_TYPE_UINT8 = 0x0001;
const EXIF_TYPE_CHAR = 0x0002;
const EXIF_TYPE_UINT16 = 0x0003;
const EXIF_TYPE_UINT32 = 0x0004;
const EXIF_TYPE_URATIONAL = 0x0005;
const EXIF_TYPE_INT8 = 0x0006;
const EXIF_TYPE_RAW = 0x0007;
const EXIF_TYPE_INT16 = 0x0008;
const EXIF_TYPE_INT32 = 0x0009;
const EXIF_TYPE_RATIONAL = 0x000A;
protected static $exifTypeSizes = array(
self::EXIF_TYPE_UINT8 => 1,
self::EXIF_TYPE_CHAR => 1,
self::EXIF_TYPE_UINT16 => 2,
self::EXIF_TYPE_UINT32 => 4,
self::EXIF_TYPE_URATIONAL => 8,
self::EXIF_TYPE_INT8 => 1,
self::EXIF_TYPE_RAW => 1,
self::EXIF_TYPE_INT16 => 2,
self::EXIF_TYPE_INT32 => 4,
self::EXIF_TYPE_RATIONAL => 8,
);
protected static $exposurePrograms = array(
0 => 'Not Defined',
1 => 'Manual',
2 => 'Program AE',
3 => 'Aperture-priority AE',
4 => 'Shutter speed priority AE',
5 => 'Creative (Slow speed)',
6 => 'Action (High speed)',
7 => 'Portrait',
8 => 'Landscape'
);
protected static $meteringModes = array(
0 => 'Unknown',
1 => 'Average',
2 => 'Center-weighted average',
3 => 'Spot',
4 => 'Multi-spot',
5 => 'Multi-segment',
6 => 'Partial',
255 => 'Other'
);
protected static $cropHiSpeeds = array(
0 => 'Off',
1 => '1.3x Crop',
2 => 'DX Crop',
3 => '5:4 Crop',
4 => '3:2 Crop',
6 => '16:9 Crop',
8 => '2.7x Crop',
9 => 'DX Movie Crop',
10 => '1.3x Movie Crop',
11 => 'FX Uncropped',
12 => 'DX Uncropped',
15 => '1.5x Movie Crop',
17 => '1:1 Crop'
);
protected static $colorSpaces = array(
1 => 'sRGB',
2 => 'Adobe RGB'
);
protected static $vibrationReductions = array(
1 => 'On',
2 => 'Off'
);
protected static $VRModes = array(
0 => 'Normal',
1 => 'On (1)',
2 => 'Active',
3 => 'Sport'
);
protected static $activeDLightnings = array(
0 => 'Off',
1 => 'Low',
3 => 'Normal',
5 => 'High',
7 => 'Extra High',
8 => 'Extra High 1',
9 => 'Extra High 2',
10 => 'Extra High 3',
11 => 'Extra High 4',
65535 => 'Auto'
);
protected static $pictureControlDataAdjusts = array(
0 => 'default',
1 => 'quick',
2 => 'full'
);
protected static $pictureControlDataFilterEffects = array(
0x80 => 'off',
0x81 => 'yellow',
0x82 => 'orange',
0x83 => 'red',
0x84 => 'green',
0xff => 'n/a'
);
protected static $pictureControlDataToningEffects = array(
0x80 => 'b&w',
0x81 => 'sepia',
0x82 => 'cyanotype',
0x83 => 'red',
0x84 => 'yellow',
0x85 => 'green',
0x86 => 'blue-green',
0x87 => 'blue',
0x88 => 'purple-blue',
0x89 => 'red-purple',
0xff => 'n/a'
);
protected static $isoInfoExpansions = array(
0x0000 => 'Off',
0x0101 => 'Hi 0.3',
0x0102 => 'Hi 0.5',
0x0103 => 'Hi 0.7',
0x0104 => 'Hi 1.0',
0x0105 => 'Hi 1.3',
0x0106 => 'Hi 1.5',
0x0107 => 'Hi 1.7',
0x0108 => 'Hi 2.0',
0x0109 => 'Hi 2.3',
0x010a => 'Hi 2.5',
0x010b => 'Hi 2.7',
0x010c => 'Hi 3.0',
0x010d => 'Hi 3.3',
0x010e => 'Hi 3.5',
0x010f => 'Hi 3.7',
0x0110 => 'Hi 4.0',
0x0111 => 'Hi 4.3',
0x0112 => 'Hi 4.5',
0x0113 => 'Hi 4.7',
0x0114 => 'Hi 5.0',
0x0201 => 'Lo 0.3',
0x0202 => 'Lo 0.5',
0x0203 => 'Lo 0.7',
0x0204 => 'Lo 1.0',
);
protected static $isoInfoExpansions2 = array(
0x0000 => 'Off',
0x0101 => 'Hi 0.3',
0x0102 => 'Hi 0.5',
0x0103 => 'Hi 0.7',
0x0104 => 'Hi 1.0',
0x0105 => 'Hi 1.3',
0x0106 => 'Hi 1.5',
0x0107 => 'Hi 1.7',
0x0108 => 'Hi 2.0',
0x0201 => 'Lo 0.3',
0x0202 => 'Lo 0.5',
0x0203 => 'Lo 0.7',
0x0204 => 'Lo 1.0',
);
protected static $vignetteControls = array(
0 => 'Off',
1 => 'Low',
3 => 'Normal',
5 => 'High'
);
protected static $flashModes = array(
0 => 'Did Not Fire',
1 => 'Fired, Manual',
3 => 'Not Ready',
7 => 'Fired, External',
8 => 'Fired, Commander Mode',
9 => 'Fired, TTL Mode',
18 => 'LED Light'
);
protected static $flashInfoSources = array(
0 => 'None',
1 => 'External',
2 => 'Internal'
);
protected static $flashInfoExternalFlashFirmwares = array(
'0 0' => 'n/a',
'1 1' => '1.01 (SB-800 or Metz 58 AF-1)',
'1 3' => '1.03 (SB-800)',
'2 1' => '2.01 (SB-800)',
'2 4' => '2.04 (SB-600)',
'2 5' => '2.05 (SB-600)',
'3 1' => '3.01 (SU-800 Remote Commander)',
'4 1' => '4.01 (SB-400)',
'4 2' => '4.02 (SB-400)',
'4 4' => '4.04 (SB-400)',
'5 1' => '5.01 (SB-900)',
'5 2' => '5.02 (SB-900)',
'6 1' => '6.01 (SB-700)',
'7 1' => '7.01 (SB-910)',
);
protected static $flashInfoExternalFlashFlags = array(
0 => 'Fired',
2 => 'Bounce Flash',
4 => 'Wide Flash Adapter',
5 => 'Dome Diffuser',
);
protected static $flashInfoExternalFlashStatuses = array(
0 => 'Flash Not Attached',
1 => 'Flash Attached',
);
protected static $flashInfoExternalFlashReadyStates = array(
0 => 'n/a',
1 => 'Ready',
6 => 'Not Ready',
);
protected static $flashInfoGNDistances = array(
0 => 0, 19 => '2.8 m',
1 => '0.1 m', 20 => '3.2 m',
2 => '0.2 m', 21 => '3.6 m',
3 => '0.3 m', 22 => '4.0 m',
4 => '0.4 m', 23 => '4.5 m',
5 => '0.5 m', 24 => '5.0 m',
6 => '0.6 m', 25 => '5.6 m',
7 => '0.7 m', 26 => '6.3 m',
8 => '0.8 m', 27 => '7.1 m',
9 => '0.9 m', 28 => '8.0 m',
10 => '1.0 m', 29 => '9.0 m',
11 => '1.1 m', 30 => '10.0 m',
12 => '1.3 m', 31 => '11.0 m',
13 => '1.4 m', 32 => '13.0 m',
14 => '1.6 m', 33 => '14.0 m',
15 => '1.8 m', 34 => '16.0 m',
16 => '2.0 m', 35 => '18.0 m',
17 => '2.2 m', 36 => '20.0 m',
18 => '2.5 m', 255 => 'n/a'
);
protected static $flashInfoControlModes = array(
0x00 => 'Off',
0x01 => 'iTTL-BL',
0x02 => 'iTTL',
0x03 => 'Auto Aperture',
0x04 => 'Automatic',
0x05 => 'GN (distance priority)',
0x06 => 'Manual',
0x07 => 'Repeating Flash',
);
protected static $flashInfoColorFilters = array(
0 => 'None',
1 => 'FL-GL1 or SZ-2FL Fluorescent',
2 => 'FL-GL2',
9 => 'TN-A1 or SZ-2TN Incandescent',
10 => 'TN-A2',
65 => 'Red',
66 => 'Blue',
67 => 'Yellow',
68 => 'Amber',
);
protected static $highISONoiseReductions = array(
0 => 'Off',
1 => 'Minimal',
2 => 'Low',
3 => 'Medium Low',
4 => 'Normal',
5 => 'Medium High',
6 => 'High'
);
protected static $AFInfo2ContrastDetectAFChoices = array(
0 => 'Off',
1 => 'On',
2 => 'On (2)'
);
protected static $AFInfo2AFAreaModesWithoutContrastDetectAF = array(
0 => 'Single Area',
1 => 'Dynamic Area',
2 => 'Dynamic Area (closest subject)',
3 => 'Group Dynamic',
4 => 'Dynamic Area (9 points)',
5 => 'Dynamic Area (21 points)',
6 => 'Dynamic Area (51 points)',
7 => 'Dynamic Area (51 points, 3D-tracking)',
8 => 'Auto-area',
9 => 'Dynamic Area (3D-tracking)',
10 => 'Single Area (wide)',
11 => 'Dynamic Area (wide)',
12 => 'Dynamic Area (wide, 3D-tracking)',
13 => 'Group Area',
14 => 'Dynamic Area (25 points)',
15 => 'Dynamic Area (72 points)',
16 => 'Group Area (HL)',
17 => 'Group Area (VL)',
18 => 'Dynamic Area (49 points)',
128 => 'Single',
129 => 'Auto (41 points)',
130 => 'Subject Tracking (41 points)',
131 => 'Face Priority (41 points)',
192 => 'Pinpoint',
193 => 'Single',
195 => 'Wide (S)',
196 => 'Wide (L)',
197 => 'Auto',
);
protected static $AFInfo2AFAreaModesWithContrastDetectAF = array(
0 => 'Contrast-detect',
1 => 'Contrast-detect (normal area)',
2 => 'Contrast-detect (wide area)',
3 => 'Contrast-detect (face priority)',
4 => 'Contrast-detect (subject tracking)',
128 => 'Single',
129 => 'Auto (41 points)',
130 => 'Subject Tracking (41 points)',
131 => 'Face Priority (41 points)',
192 => 'Pinpoint',
193 => 'Single',
194 => 'Dynamic',
195 => 'Wide (S)',
196 => 'Wide (L)',
197 => 'Auto',
198 => 'Auto (People)',
199 => 'Auto (Animal)',
200 => 'Normal-area AF',
201 => 'Wide-area AF',
202 => 'Face-priority AF',
203 => 'Subject-tracking AF',
);
protected static $AFInfo2PhaseDetectAFChoices = array(
0 => 'Off',
1 => 'On (51-point)',
2 => 'On (11-point)',
3 => 'On (39-point)',
4 => 'On (73-point)',
5 => 'On (5)',
6 => 'On (105-point)',
7 => 'On (153-point)',
8 => 'On (81-point)',
9 => 'On (105-point)',
);
protected static $NikkorZLensIDS = array(
1 => 'Nikkor Z 24-70mm f/4 S',
2 => 'Nikkor Z 14-30mm f/4 S',
4 => 'Nikkor Z 35mm f/1.8 S',
8 => 'Nikkor Z 58mm f/0.95 S Noct',
9 => 'Nikkor Z 50mm f/1.8 S',
11 => 'Nikkor Z DX 16-50mm f/3.5-6.3 VR',
12 => 'Nikkor Z DX 50-250mm f/4.5-6.3 VR',
13 => 'Nikkor Z 24-70mm f/2.8 S',
14 => 'Nikkor Z 85mm f/1.8 S',
15 => 'Nikkor Z 24mm f/1.8 S',
16 => 'Nikkor Z 70-200mm f/2.8 VR S',
17 => 'Nikkor Z 20mm f/1.8 S',
18 => 'Nikkor Z 24-200mm f/4-6.3 VR',
21 => 'Nikkor Z 50mm f/1.2 S',
22 => 'Nikkor Z 24-50mm f/4-6.3',
23 => 'Nikkor Z 14-24mm f/2.8 S',
);
protected static $nikonTextEncodings = array(
1 => 'UTF-8',
2 => 'UTF-16'
);
/**
* Ref 4
*
* @var int[][]
*/
protected static $decodeTables = array(
array(
0xc1,0xbf,0x6d,0x0d,0x59,0xc5,0x13,0x9d,0x83,0x61,0x6b,0x4f,0xc7,0x7f,0x3d,0x3d,
0x53,0x59,0xe3,0xc7,0xe9,0x2f,0x95,0xa7,0x95,0x1f,0xdf,0x7f,0x2b,0x29,0xc7,0x0d,
0xdf,0x07,0xef,0x71,0x89,0x3d,0x13,0x3d,0x3b,0x13,0xfb,0x0d,0x89,0xc1,0x65,0x1f,
0xb3,0x0d,0x6b,0x29,0xe3,0xfb,0xef,0xa3,0x6b,0x47,0x7f,0x95,0x35,0xa7,0x47,0x4f,
0xc7,0xf1,0x59,0x95,0x35,0x11,0x29,0x61,0xf1,0x3d,0xb3,0x2b,0x0d,0x43,0x89,0xc1,
0x9d,0x9d,0x89,0x65,0xf1,0xe9,0xdf,0xbf,0x3d,0x7f,0x53,0x97,0xe5,0xe9,0x95,0x17,
0x1d,0x3d,0x8b,0xfb,0xc7,0xe3,0x67,0xa7,0x07,0xf1,0x71,0xa7,0x53,0xb5,0x29,0x89,
0xe5,0x2b,0xa7,0x17,0x29,0xe9,0x4f,0xc5,0x65,0x6d,0x6b,0xef,0x0d,0x89,0x49,0x2f,
0xb3,0x43,0x53,0x65,0x1d,0x49,0xa3,0x13,0x89,0x59,0xef,0x6b,0xef,0x65,0x1d,0x0b,
0x59,0x13,0xe3,0x4f,0x9d,0xb3,0x29,0x43,0x2b,0x07,0x1d,0x95,0x59,0x59,0x47,0xfb,
0xe5,0xe9,0x61,0x47,0x2f,0x35,0x7f,0x17,0x7f,0xef,0x7f,0x95,0x95,0x71,0xd3,0xa3,
0x0b,0x71,0xa3,0xad,0x0b,0x3b,0xb5,0xfb,0xa3,0xbf,0x4f,0x83,0x1d,0xad,0xe9,0x2f,
0x71,0x65,0xa3,0xe5,0x07,0x35,0x3d,0x0d,0xb5,0xe9,0xe5,0x47,0x3b,0x9d,0xef,0x35,
0xa3,0xbf,0xb3,0xdf,0x53,0xd3,0x97,0x53,0x49,0x71,0x07,0x35,0x61,0x71,0x2f,0x43,
0x2f,0x11,0xdf,0x17,0x97,0xfb,0x95,0x3b,0x7f,0x6b,0xd3,0x25,0xbf,0xad,0xc7,0xc5,
0xc5,0xb5,0x8b,0xef,0x2f,0xd3,0x07,0x6b,0x25,0x49,0x95,0x25,0x49,0x6d,0x71,0xc7
),
array(
0xa7,0xbc,0xc9,0xad,0x91,0xdf,0x85,0xe5,0xd4,0x78,0xd5,0x17,0x46,0x7c,0x29,0x4c,
0x4d,0x03,0xe9,0x25,0x68,0x11,0x86,0xb3,0xbd,0xf7,0x6f,0x61,0x22,0xa2,0x26,0x34,
0x2a,0xbe,0x1e,0x46,0x14,0x68,0x9d,0x44,0x18,0xc2,0x40,0xf4,0x7e,0x5f,0x1b,0xad,
0x0b,0x94,0xb6,0x67,0xb4,0x0b,0xe1,0xea,0x95,0x9c,0x66,0xdc,0xe7,0x5d,0x6c,0x05,
0xda,0xd5,0xdf,0x7a,0xef,0xf6,0xdb,0x1f,0x82,0x4c,0xc0,0x68,0x47,0xa1,0xbd,0xee,
0x39,0x50,0x56,0x4a,0xdd,0xdf,0xa5,0xf8,0xc6,0xda,0xca,0x90,0xca,0x01,0x42,0x9d,
0x8b,0x0c,0x73,0x43,0x75,0x05,0x94,0xde,0x24,0xb3,0x80,0x34,0xe5,0x2c,0xdc,0x9b,
0x3f,0xca,0x33,0x45,0xd0,0xdb,0x5f,0xf5,0x52,0xc3,0x21,0xda,0xe2,0x22,0x72,0x6b,
0x3e,0xd0,0x5b,0xa8,0x87,0x8c,0x06,0x5d,0x0f,0xdd,0x09,0x19,0x93,0xd0,0xb9,0xfc,
0x8b,0x0f,0x84,0x60,0x33,0x1c,0x9b,0x45,0xf1,0xf0,0xa3,0x94,0x3a,0x12,0x77,0x33,
0x4d,0x44,0x78,0x28,0x3c,0x9e,0xfd,0x65,0x57,0x16,0x94,0x6b,0xfb,0x59,0xd0,0xc8,
0x22,0x36,0xdb,0xd2,0x63,0x98,0x43,0xa1,0x04,0x87,0x86,0xf7,0xa6,0x26,0xbb,0xd6,
0x59,0x4d,0xbf,0x6a,0x2e,0xaa,0x2b,0xef,0xe6,0x78,0xb6,0x4e,0xe0,0x2f,0xdc,0x7c,
0xbe,0x57,0x19,0x32,0x7e,0x2a,0xd0,0xb8,0xba,0x29,0x00,0x3c,0x52,0x7d,0xa8,0x49,
0x3b,0x2d,0xeb,0x25,0x49,0xfa,0xa3,0xaa,0x39,0xa7,0xc5,0xa7,0x50,0x11,0x36,0xfb,
0xc6,0x67,0x4a,0xf5,0xa5,0x12,0x65,0x7e,0xb0,0xdf,0xaf,0x4e,0xb3,0x61,0x7f,0x2f
)
);
/**
* @var getID3
*/
private $getid3;
public function __construct(getID3 $getid3)
{
$this->getid3 = $getid3;
}
/**
* Get a copy of all NCTG tags extracted from the video
*
* @param string $atomData
*
* @return array<string, mixed>
*/
public function parse($atomData) {
// Nikon-specific QuickTime tags found in the NCDT atom of MOV videos from some Nikon cameras such as the Coolpix S8000 and D5100
// Data is stored as records of:
// * 4 bytes record type
// * 2 bytes size of data field type:
// 0x0001 = flag / unsigned byte (size field *= 1-byte)
// 0x0002 = char / ascii strings (size field *= 1-byte)
// 0x0003 = DWORD+ / unsigned short (size field *= 2-byte), values are stored CDAB
// 0x0004 = QWORD+ / unsigned long (size field *= 4-byte), values are stored EFGHABCD
// 0x0005 = float / unsigned rational (size field *= 8-byte), values are stored aaaabbbb where value is aaaa/bbbb; possibly multiple sets of values appended together
// 0x0006 = signed byte (size field *= 1-byte)
// 0x0007 = raw bytes (size field *= 1-byte)
// 0x0008 = signed short (size field *= 2-byte), values are stored as CDAB
// 0x0009 = signed long (size field *= 4-byte), values are stored as EFGHABCD
// 0x000A = float / signed rational (size field *= 8-byte), values are stored aaaabbbb where value is aaaa/bbbb; possibly multiple sets of values appended together
// * 2 bytes data size field
// * ? bytes data (string data may be null-padded; datestamp fields are in the format "2011:05:25 20:24:15")
// all integers are stored BigEndian
$NCTGtagName = array(
0x00000001 => 'Make',
0x00000002 => 'Model',
0x00000003 => 'Software',
0x00000011 => 'CreateDate',
0x00000012 => 'DateTimeOriginal',
0x00000013 => 'FrameCount',
0x00000016 => 'FrameRate',
0x00000019 => 'TimeZone',
0x00000022 => 'FrameWidth',
0x00000023 => 'FrameHeight',
0x00000032 => 'AudioChannels',
0x00000033 => 'AudioBitsPerSample',
0x00000034 => 'AudioSampleRate',
0x00001002 => 'NikonDateTime',
0x00001013 => 'ElectronicVR',
0x0110829a => 'ExposureTime',
0x0110829d => 'FNumber',
0x01108822 => 'ExposureProgram',
0x01109204 => 'ExposureCompensation',
0x01109207 => 'MeteringMode',
0x0110920a => 'FocalLength', // mm
0x0110a431 => 'SerialNumber',
0x0110a432 => 'LensInfo',
0x0110a433 => 'LensMake',
0x0110a434 => 'LensModel',
0x0110a435 => 'LensSerialNumber',
0x01200000 => 'GPSVersionID',
0x01200001 => 'GPSLatitudeRef',
0x01200002 => 'GPSLatitude',
0x01200003 => 'GPSLongitudeRef',
0x01200004 => 'GPSLongitude',
0x01200005 => 'GPSAltitudeRef', // 0 = Above Sea Level, 1 = Below Sea Level
0x01200006 => 'GPSAltitude',
0x01200007 => 'GPSTimeStamp',
0x01200008 => 'GPSSatellites',
0x01200010 => 'GPSImgDirectionRef', // M = Magnetic North, T = True North
0x01200011 => 'GPSImgDirection',
0x01200012 => 'GPSMapDatum',
0x0120001d => 'GPSDateStamp',
0x02000001 => 'MakerNoteVersion',
0x02000005 => 'WhiteBalance',
0x02000007 => 'FocusMode',
0x0200000b => 'WhiteBalanceFineTune',
0x0200001b => 'CropHiSpeed',
0x0200001e => 'ColorSpace',
0x0200001f => 'VRInfo',
0x02000022 => 'ActiveDLighting',
0x02000023 => 'PictureControlData',
0x02000024 => 'WorldTime',
0x02000025 => 'ISOInfo',
0x0200002a => 'VignetteControl',
0x0200002c => 'UnknownInfo',
0x02000032 => 'UnknownInfo2',
0x02000039 => 'LocationInfo',
0x02000083 => 'LensType',
0x02000084 => 'Lens',
0x02000087 => 'FlashMode',
0x02000098 => 'LensData',
0x020000a7 => 'ShutterCount',
0x020000a8 => 'FlashInfo',
0x020000ab => 'VariProgram',
0x020000b1 => 'HighISONoiseReduction',
0x020000b7 => 'AFInfo2',
0x020000c3 => 'BarometerInfo',
);
$firstPassNeededTags = array(
0x00000002, // Model
0x0110a431, // SerialNumber
0x020000a7, // ShutterCount
);
$datalength = strlen($atomData);
$parsed = array();
$model = $serialNumber = $shutterCount = null;
for ($pass = 0; $pass < 2; ++$pass) {
$offset = 0;
$parsed = array();
$data = null;
while ($offset < $datalength) {
$record_type = getid3_lib::BigEndian2Int(substr($atomData, $offset, 4));
$offset += 4;
$data_size_type = getid3_lib::BigEndian2Int(substr($atomData, $offset, 2));
$data_size = static::$exifTypeSizes[$data_size_type];
$offset += 2;
$data_count = getid3_lib::BigEndian2Int(substr($atomData, $offset, 2));
$offset += 2;
$data = array();
if ($pass === 0 && !in_array($record_type, $firstPassNeededTags, true)) {
$offset += $data_count * $data_size;
continue;
}
switch ($data_size_type) {
case self::EXIF_TYPE_UINT8: // 0x0001 = flag / unsigned byte (size field *= 1-byte)
for ($i = 0; $i < $data_count; ++$i) {
$data[] = getid3_lib::BigEndian2Int(substr($atomData, $offset + ($i * $data_size), $data_size));
}
$offset += ($data_count * $data_size);
break;
case self::EXIF_TYPE_CHAR: // 0x0002 = char / ascii strings (size field *= 1-byte)
$data = substr($atomData, $offset, $data_count * $data_size);
$offset += ($data_count * $data_size);
$data = rtrim($data, "\x00");
break;
case self::EXIF_TYPE_UINT16: // 0x0003 = DWORD+ / unsigned short (size field *= 2-byte), values are stored CDAB
for ($i = 0; $i < $data_count; ++$i) {
$data[] = getid3_lib::BigEndian2Int(substr($atomData, $offset + ($i * $data_size), $data_size));
}
$offset += ($data_count * $data_size);
break;
case self::EXIF_TYPE_UINT32: // 0x0004 = QWORD+ / unsigned long (size field *= 4-byte), values are stored EFGHABCD
// нужно проверить FrameCount
for ($i = 0; $i < $data_count; ++$i) {
$data[] = getid3_lib::BigEndian2Int(substr($atomData, $offset + ($i * $data_size), $data_size));
}
$offset += ($data_count * $data_size);
break;
case self::EXIF_TYPE_URATIONAL: // 0x0005 = float / unsigned rational (size field *= 8-byte), values are stored aaaabbbb where value is aaaa/bbbb; possibly multiple sets of values appended together
for ($i = 0; $i < $data_count; ++$i) {
$numerator = getid3_lib::BigEndian2Int(substr($atomData, $offset + ($i * $data_size) + 0, 4));
$denomninator = getid3_lib::BigEndian2Int(substr($atomData, $offset + ($i * $data_size) + 4, 4));
if ($denomninator == 0) {
$data[] = false;
} else {
$data[] = (float)$numerator / $denomninator;
}
}
$offset += ($data_size * $data_count);
break;
case self::EXIF_TYPE_INT8: // 0x0006 = bytes / signed byte (size field *= 1-byte)
// NOT TESTED
for ($i = 0; $i < $data_count; ++$i) {
$data[] = getid3_lib::BigEndian2Int(substr($atomData, $offset + ($i * $data_size), $data_size), false, true);
}
$offset += ($data_count * $data_size);
break;
case self::EXIF_TYPE_RAW: // 0x0007 = raw bytes (size field *= 1-byte)
$data = substr($atomData, $offset, $data_count * $data_size);
$offset += ($data_count * $data_size);
break;
case self::EXIF_TYPE_INT16: // 0x0008 = signed short (size field *= 2-byte), values are stored as CDAB
for ($i = 0; $i < $data_count; ++$i) {
$value = getid3_lib::BigEndian2Int(substr($atomData, $offset + ($i * $data_size), $data_size));
if ($value >= 0x8000) {
$value -= 0x10000;
}
$data[] = $value;
}
$offset += ($data_count * $data_size);
break;
case self::EXIF_TYPE_INT32: // 0x0009 = signed long (size field *= 4-byte), values are stored as EFGHABCD
// NOT TESTED
for ($i = 0; $i < $data_count; ++$i) {
$data = getid3_lib::BigEndian2Int(substr($atomData, $offset + ($i * $data_size), $data_size), false, true);
}
$offset += ($data_count * $data_size);
break;
case self::EXIF_TYPE_RATIONAL: // 0x000A = float / signed rational (size field *= 8-byte), values are stored aaaabbbb where value is aaaa/bbbb; possibly multiple sets of values appended together
// NOT TESTED
for ($i = 0; $i < $data_count; ++$i) {
$numerator = getid3_lib::BigEndian2Int(substr($atomData, $offset + ($i * $data_size) + 0, 4), false, true);
$denomninator = getid3_lib::BigEndian2Int(substr($atomData, $offset + ($i * $data_size) + 4, 4), false, true);
if ($denomninator == 0) {
$data[] = false;
} else {
$data[] = (float)$numerator / $denomninator;
}
}
$offset += ($data_size * $data_count);
if (count($data) == 1) {
$data = $data[0];
}
break;
default:
$this->getid3->warning('QuicktimeParseNikonNCTG()::unknown $data_size_type: ' . $data_size_type);
break 2;
}
if (is_array($data) && count($data) === 1) {
$data = $data[0];
}
switch ($record_type) {
case 0x00000002:
$model = $data;
break;
case 0x00000013: // FrameCount
if (is_array($data) && count($data) === 2 && $data[1] == 0) {
$data = $data[0];
}
break;
case 0x00000011: // CreateDate
case 0x00000012: // DateTimeOriginal
case 0x00001002: // NikonDateTime
$data = strtotime($data);
break;
case 0x00001013: // ElectronicVR
$data = (bool) $data;
break;
case 0x0110829a: // ExposureTime
// Print exposure time as a fraction
/** @var float $data */
if ($data < 0.25001 && $data > 0) {
$data = sprintf("1/%d", intval(0.5 + 1 / $data));
}
break;
case 0x01109204: // ExposureCompensation
$data = $this->printFraction($data);
break;
case 0x01108822: // ExposureProgram
$data = isset(static::$exposurePrograms[$data]) ? static::$exposurePrograms[$data] : $data;
break;
case 0x01109207: // MeteringMode
$data = isset(static::$meteringModes[$data]) ? static::$meteringModes[$data] : $data;
break;
case 0x0110a431: // SerialNumber
$serialNumber = $this->serialKey($data, $model);
break;
case 0x01200000: // GPSVersionID
$parsed['GPS']['computed']['version'] = 'v'.implode('.', $data);
break;
case 0x01200002: // GPSLatitude
if (is_array($data)) {
$direction_multiplier = ((isset($parsed['GPSLatitudeRef']) && ($parsed['GPSLatitudeRef'] === 'S')) ? -1 : 1);
$parsed['GPS']['computed']['latitude'] = $direction_multiplier * ($data[0] + ($data[1] / 60) + ($data[2] / 3600));
}
break;
case 0x01200004: // GPSLongitude
if (is_array($data)) {
$direction_multiplier = ((isset($parsed['GPSLongitudeRef']) && ($parsed['GPSLongitudeRef'] === 'W')) ? -1 : 1);
$parsed['GPS']['computed']['longitude'] = $direction_multiplier * ($data[0] + ($data[1] / 60) + ($data[2] / 3600));
}
break;
case 0x01200006: // GPSAltitude
if (isset($parsed['GPSAltitudeRef'])) {
$direction_multiplier = (!empty($parsed['GPSAltitudeRef']) ? -1 : 1); // 0 = above sea level; 1 = below sea level
$parsed['GPS']['computed']['altitude'] = $direction_multiplier * $data;
}
break;
case 0x0120001d: // GPSDateStamp
if (isset($parsed['GPSTimeStamp']) && is_array($parsed['GPSTimeStamp']) && $data !== '') {
$explodedDate = explode(':', $data);
$parsed['GPS']['computed']['timestamp'] = gmmktime($parsed['GPSTimeStamp'][0], $parsed['GPSTimeStamp'][1], $parsed['GPSTimeStamp'][2], $explodedDate[1], $explodedDate[2], $explodedDate[0]);
}
break;
case 0x02000001: // MakerNoteVersion
$data = ltrim(substr($data, 0, 2) . '.' . substr($data, 2, 2), '0');
break;
case 0x0200001b: // CropHiSpeed
if (is_array($data) && count($data) === 7) {
$name = isset(static::$cropHiSpeeds[$data[0]]) ? static::$cropHiSpeeds[$data[0]] : sprintf('Unknown (%d)', $data[0]);
$data = array(
'Name' => $name,
'OriginalWidth' => $data[1],
'OriginalHeight' => $data[2],
'CroppedWidth' => $data[3],
'CroppedHeight' => $data[4],
'PixelXPosition' => $data[5],
'PixelYPosition' => $data[6],
);
}
break;
case 0x0200001e: // ColorSpace
$data = isset(static::$colorSpaces[$data]) ? static::$colorSpaces[$data] : $data;
break;
case 0x0200001f: // VRInfo
$data = array(
'VRInfoVersion' => substr($data, 0, 4),
'VibrationReduction' => isset(static::$vibrationReductions[ord(substr($data, 4, 1))])
? static::$vibrationReductions[ord(substr($data, 4, 1))]
: null,
'VRMode' => static::$VRModes[ord(substr($data, 6, 1))],
);
break;
case 0x02000022: // ActiveDLighting
$data = isset(static::$activeDLightnings[$data]) ? static::$activeDLightnings[$data] : $data;
break;
case 0x02000023: // PictureControlData
switch (substr($data, 0, 2)) {
case '01':
$data = array(
'PictureControlVersion' => substr($data, 0, 4),
'PictureControlName' => rtrim(substr($data, 4, 20), "\x00"),
'PictureControlBase' => rtrim(substr($data, 24, 20), "\x00"),
//'?' => substr($data, 44, 4),
'PictureControlAdjust' => static::$pictureControlDataAdjusts[ord(substr($data, 48, 1))],
'PictureControlQuickAdjust' => $this->printPC(ord(substr($data, 49, 1)) - 0x80),
'Sharpness' => $this->printPC(ord(substr($data, 50, 1)) - 0x80, 'No Sharpening', '%d'),
'Contrast' => $this->printPC(ord(substr($data, 51, 1)) - 0x80),
'Brightness' => $this->printPC(ord(substr($data, 52, 1)) - 0x80),
'Saturation' => $this->printPC(ord(substr($data, 53, 1)) - 0x80),
'HueAdjustment' => $this->printPC(ord(substr($data, 54, 1)) - 0x80, 'None'),
'FilterEffect' => static::$pictureControlDataFilterEffects[ord(substr($data, 55, 1))],
'ToningEffect' => static::$pictureControlDataToningEffects[ord(substr($data, 56, 1))],
'ToningSaturation' => $this->printPC(ord(substr($data, 57, 1)) - 0x80),
);
break;
case '02':
$data = array(
'PictureControlVersion' => substr($data, 0, 4),
'PictureControlName' => rtrim(substr($data, 4, 20), "\x00"),
'PictureControlBase' => rtrim(substr($data, 24, 20), "\x00"),
//'?' => substr($data, 44, 4),
'PictureControlAdjust' => static::$pictureControlDataAdjusts[ord(substr($data, 48, 1))],
'PictureControlQuickAdjust' => $this->printPC(ord(substr($data, 49, 1)) - 0x80),
'Sharpness' => $this->printPC(ord(substr($data, 51, 1)) - 0x80, 'None', '%.2f', 4),
'Clarity' => $this->printPC(ord(substr($data, 53, 1)) - 0x80, 'None', '%.2f', 4),
'Contrast' => $this->printPC(ord(substr($data, 55, 1)) - 0x80, 'None', '%.2f', 4),
'Brightness' => $this->printPC(ord(substr($data, 57, 1)) - 0x80, 'Normal', '%.2f', 4),
'Saturation' => $this->printPC(ord(substr($data, 59, 1)) - 0x80, 'None', '%.2f', 4),
'Hue' => $this->printPC(ord(substr($data, 61, 1)) - 0x80, 'None', '%.2f', 4),
'FilterEffect' => static::$pictureControlDataFilterEffects[ord(substr($data, 63, 1))],
'ToningEffect' => static::$pictureControlDataToningEffects[ord(substr($data, 64, 1))],
'ToningSaturation' => $this->printPC(ord(substr($data, 65, 1)) - 0x80, 'None', '%.2f', 4),
);
break;
case '03':
$data = array(
'PictureControlVersion' => substr($data, 0, 4),
'PictureControlName' => rtrim(substr($data, 8, 20), "\x00"),
'PictureControlBase' => rtrim(substr($data, 28, 20), "\x00"),
'PictureControlAdjust' => static::$pictureControlDataAdjusts[ord(substr($data, 54, 1))],
'PictureControlQuickAdjust' => $this->printPC(ord(substr($data, 55, 1)) - 0x80),
'Sharpness' => $this->printPC(ord(substr($data, 57, 1)) - 0x80, 'None', '%.2f', 4),
'MidRangeSharpness' => $this->printPC(ord(substr($data, 59, 1)) - 0x80, 'None', '%.2f', 4),
'Clarity' => $this->printPC(ord(substr($data, 61, 1)) - 0x80, 'None', '%.2f', 4),
'Contrast' => $this->printPC(ord(substr($data, 63, 1)) - 0x80, 'None', '%.2f', 4),
'Brightness' => $this->printPC(ord(substr($data, 65, 1)) - 0x80, 'Normal', '%.2f', 4),
'Saturation' => $this->printPC(ord(substr($data, 67, 1)) - 0x80, 'None', '%.2f', 4),
'Hue' => $this->printPC(ord(substr($data, 69, 1)) - 0x80, 'None', '%.2f', 4),
'FilterEffect' => static::$pictureControlDataFilterEffects[ord(substr($data, 71, 1))],
'ToningEffect' => static::$pictureControlDataToningEffects[ord(substr($data, 72, 1))],
'ToningSaturation' => $this->printPC(ord(substr($data, 73, 1)) - 0x80, 'None', '%.2f', 4),
);
break;
default:
$data = array(
'PictureControlVersion' => substr($data, 0, 4),
);
break;
}
break;
case 0x02000024: // WorldTime
// https://exiftool.org/TagNames/Nikon.html#WorldTime
// timezone is stored as offset from GMT in minutes
$timezone = getid3_lib::BigEndian2Int(substr($data, 0, 2));
if ($timezone & 0x8000) {
$timezone = 0 - (0x10000 - $timezone);
}
$hours = (int)abs($timezone / 60);
$minutes = abs($timezone) - $hours * 60;
$dst = (bool)getid3_lib::BigEndian2Int(substr($data, 2, 1));
switch (getid3_lib::BigEndian2Int(substr($data, 3, 1))) {
case 2:
$datedisplayformat = 'D/M/Y';
break;
case 1:
$datedisplayformat = 'M/D/Y';
break;
case 0:
default:
$datedisplayformat = 'Y/M/D';
break;
}
$data = array(
'timezone' => sprintf('%s%02d:%02d', $timezone >= 0 ? '+' : '-', $hours, $minutes),
'dst' => $dst,
'display' => $datedisplayformat
);
break;
case 0x02000025: // ISOInfo
$data = array(
'ISO' => (int)ceil(100 * pow(2, ord(substr($data, 0, 1)) / 12 - 5)),
'ISOExpansion' => static::$isoInfoExpansions[getid3_lib::BigEndian2Int(substr($data, 4, 2))],
'ISO2' => (int)ceil(100 * pow(2, ord(substr($data, 6, 1)) / 12 - 5)),
'ISOExpansion2' => static::$isoInfoExpansions2[getid3_lib::BigEndian2Int(substr($data, 10, 2))]
);
break;
case 0x0200002a: // VignetteControl
$data = isset(static::$vignetteControls[$data]) ? static::$vignetteControls[$data] : $data;
break;
case 0x0200002c: // UnknownInfo
$data = array(
'UnknownInfoVersion' => substr($data, 0, 4),
);
break;
case 0x02000032: // UnknownInfo2
$data = array(
'UnknownInfo2Version' => substr($data, 0, 4),
);
break;
case 0x02000039: // LocationInfo
$encoding = isset(static::$nikonTextEncodings[ord(substr($data, 4, 1))])
? static::$nikonTextEncodings[ord(substr($data, 4, 1))]
: null;
$data = array(
'LocationInfoVersion' => substr($data, 0, 4),
'TextEncoding' => $encoding,
'CountryCode' => trim(substr($data, 5, 3), "\x00"),
'POILevel' => ord(substr($data, 8, 1)),
'Location' => getid3_lib::iconv_fallback($encoding, $this->getid3->info['encoding'], substr($data, 9, 70)),
);
break;
case 0x02000083: // LensType
if ($data) {
$decodedBits = array(
'1' => (bool) (($data >> 4) & 1),
'MF' => (bool) (($data >> 0) & 1),
'D' => (bool) (($data >> 1) & 1),
'E' => (bool) (($data >> 6) & 1),
'G' => (bool) (($data >> 2) & 1),
'VR' => (bool) (($data >> 3) & 1),
'[7]' => (bool) (($data >> 7) & 1), // AF-P?
'[8]' => (bool) (($data >> 5) & 1) // FT-1?
);
if ($decodedBits['D'] === true && $decodedBits['G'] === true) {
$decodedBits['D'] = false;
}
} else {
$decodedBits = array('AF' => true);
}
$data = $decodedBits;
break;
case 0x0110a432: // LensInfo
case 0x02000084: // Lens
if (count($data) !== 4) {
break;
}
$value = $data[0];
if ($data[1] && $data[1] !== $data[0]) {
$value .= '-' . $data[1];
}
$value .= 'mm f/' . $data[2];
if ($data[3] && $data[3] !== $data[2]) {
$value .= '-' . $data[3];
}
$data = $value;
break;
case 0x02000087: // FlashMode
$data = isset(static::$flashModes[$data]) ? static::$flashModes[$data] : $data;
break;
case 0x02000098: // LensData
$version = substr($data, 0, 4);
switch ($version) {
case '0100':
$data = array(
'LensDataVersion' => $version,
'LensIDNumber' => ord(substr($data, 6, 1)),
'LensFStops' => ord(substr($data, 7, 1)) / 12,
'MinFocalLength' => 5 * pow(2, ord(substr($data, 8, 1)) / 24), // mm
'MaxFocalLength' => 5 * pow(2, ord(substr($data, 9, 1)) / 24), // mm
'MaxApertureAtMinFocal' => pow(2, ord(substr($data, 10, 1)) / 24),
'MaxApertureAtMaxFocal' => pow(2, ord(substr($data, 11, 1)) / 24),
'MCUVersion' => ord(substr($data, 12, 1)),
);
break;
case '0101':
case '0201':
case '0202':
case '0203':
$isEncrypted = $version !== '0101';
if ($isEncrypted) {
$data = $this->decryptLensInfo($data, $serialNumber, $shutterCount, 4);
}
$data = array(
'LensDataVersion' => $version,
'ExitPupilPosition' => ord(substr($data, 4, 1)) > 0 ? 2048 / ord(substr($data, 4, 1)) : 0, // mm
'AFAperture' => pow(2, ord(substr($data, 5, 1)) / 24),
'FocusPosition' => '0x' . str_pad(strtoupper(dechex(ord(substr($data, 8, 1)))), 2, '0', STR_PAD_LEFT),
'FocusDistance' => 0.01 * pow(10, ord(substr($data, 9, 1)) / 40), // m
'FocalLength' => 5 * pow(2, ord(substr($data, 10, 1)) / 24), // mm
'LensIDNumber' => ord(substr($data, 11, 1)),
'LensFStops' => ord(substr($data, 12, 1)) / 12,
'MinFocalLength' => 5 * pow(2, ord(substr($data, 13, 1)) / 24), // mm
'MaxFocalLength' => 5 * pow(2, ord(substr($data, 14, 1)) / 24), // mm
'MaxApertureAtMinFocal' => pow(2, ord(substr($data, 15, 1)) / 24),
'MaxApertureAtMaxFocal' => pow(2, ord(substr($data, 16, 1)) / 24),
'MCUVersion' => ord(substr($data, 17, 1)),
'EffectiveMaxAperture' => pow(2, ord(substr($data, 18, 1)) / 24),
);
break;
case '0204':
$data = $this->decryptLensInfo($data, $serialNumber, $shutterCount, 4);
$data = array(
'LensDataVersion' => $version,
'ExitPupilPosition' => ord(substr($data, 4, 1)) > 0 ? 2048 / ord(substr($data, 4, 1)) : 0, // mm
'AFAperture' => pow(2, ord(substr($data, 5, 1)) / 24),
'FocusPosition' => '0x' . str_pad(strtoupper(dechex(ord(substr($data, 8, 1)))), 2, '0', STR_PAD_LEFT),
'FocusDistance' => 0.01 * pow(10, ord(substr($data, 10, 1)) / 40), // m
'FocalLength' => 5 * pow(2, ord(substr($data, 11, 1)) / 24), // mm
'LensIDNumber' => ord(substr($data, 12, 1)),
'LensFStops' => ord(substr($data, 13, 1)) / 12,
'MinFocalLength' => 5 * pow(2, ord(substr($data, 14, 1)) / 24), // mm
'MaxFocalLength' => 5 * pow(2, ord(substr($data, 15, 1)) / 24), // mm
'MaxApertureAtMinFocal' => pow(2, ord(substr($data, 16, 1)) / 24),
'MaxApertureAtMaxFocal' => pow(2, ord(substr($data, 17, 1)) / 24),
'MCUVersion' => ord(substr($data, 18, 1)),
'EffectiveMaxAperture' => pow(2, ord(substr($data, 19, 1)) / 24),
);
break;
case '0400':
case '0401':
$data = $this->decryptLensInfo($data, $serialNumber, $shutterCount, 4);
$data = array(
'LensDataVersion' => $version,
'LensModel' => substr($data, 394, 64),
);
break;
case '0402':
$data = $this->decryptLensInfo($data, $serialNumber, $shutterCount, 4);
$data = array(
'LensDataVersion' => $version,
'LensModel' => substr($data, 395, 64),
);
break;
case '0403':
$data = $this->decryptLensInfo($data, $serialNumber, $shutterCount, 4);
$data = array(
'LensDataVersion' => $version,
'LensModel' => substr($data, 684, 64),
);
break;
case '0800':
case '0801':
$data = $this->decryptLensInfo($data, $serialNumber, $shutterCount, 4);
$newData = array(
'LensDataVersion' => $version,
);
if (!preg_match('#^.\0+#s', substr($data, 3, 17))) {
$newData['ExitPupilPosition'] = ord(substr($data, 4, 1)) > 0 ? 2048 / ord(substr($data, 4, 1)) : 0; // mm
$newData['AFAperture'] = pow(2, ord(substr($data, 5, 1)) / 24);
$newData['FocusPosition'] = '0x' . str_pad(strtoupper(dechex(ord(substr($data, 9, 1)))), 2, '0', STR_PAD_LEFT);
$newData['FocusDistance'] = 0.01 * pow(10, ord(substr($data, 11, 1)) / 40); // m
$newData['FocalLength'] = 5 * pow(2, ord(substr($data, 12, 1)) / 24); // mm
$newData['LensIDNumber'] = ord(substr($data, 13, 1));
$newData['LensFStops'] = ord(substr($data, 14, 1)) / 12;
$newData['MinFocalLength'] = 5 * pow(2, ord(substr($data, 15, 1)) / 24); // mm
$newData['MaxFocalLength'] = 5 * pow(2, ord(substr($data, 16, 1)) / 24); // mm
$newData['MaxApertureAtMinFocal'] = pow(2, ord(substr($data, 17, 1)) / 24);
$newData['MaxApertureAtMaxFocal'] = pow(2, ord(substr($data, 18, 1)) / 24);
$newData['MCUVersion'] = ord(substr($data, 19, 1));
$newData['EffectiveMaxAperture'] = pow(2, ord(substr($data, 20, 1)) / 24);
}
if (!preg_match('#^.\0+#s', substr($data, 47, 17))) {
$newData['LensID'] = static::$NikkorZLensIDS[getid3_lib::LittleEndian2Int(substr($data, 48, 2))];
$newData['MaxAperture'] = pow(2, (getid3_lib::LittleEndian2Int(substr($data, 54, 2)) / 384 - 1));
$newData['FNumber'] = pow(2, (getid3_lib::LittleEndian2Int(substr($data, 56, 2)) / 384 - 1));
$newData['FocalLength'] = getid3_lib::LittleEndian2Int(substr($data, 60, 2)); // mm
$newData['FocusDistance'] = 0.01 * pow(10, ord(substr($data, 79, 1)) / 40); // m
}
$data = $newData;
break;
default:
// $data = $this->decryptLensInfo($data, $serialNumber, $shutterCount, 4);
$data = array(
'LensDataVersion' => $version,
);
break;
}
break;
case 0x020000a7: // ShutterCount
$shutterCount = $data;
break;
case 0x020000a8: // FlashInfo
$version = substr($data, 0, 4);
switch ($version) {
case '0100':
case '0101':
$data = array(
'FlashInfoVersion' => substr($data, 0, 4),
'FlashSource' => static::$flashInfoSources[ord(substr($data, 4, 1))],
'ExternalFlashFirmware' => $this->flashFirmwareLookup(ord(substr($data, 6, 1)), ord(substr($data, 7, 1))),
'ExternalFlashFlags' => $this->externalFlashFlagsLookup(ord(substr($data, 8, 1))),
'FlashCommanderMode' => (bool)(ord(substr($data, 9, 1)) & 0x80),
'FlashControlMode' => static::$flashInfoControlModes[ord(substr($data, 9, 1)) & 0x7F],
'FlashOutput' => (ord(substr($data, 9, 1)) & 0x7F) >= 0x06 ? sprintf('%.0f%%', pow(2, (-ord(substr($data, 10, 1)) / 6) * 100)) : 0,
'FlashCompensation' => $this->printFraction(-getid3_lib::BigEndian2Int(substr($data, 10, 1), false, true) / 6),
'FlashFocalLength' => ord(substr($data, 11, 1)), // mm
'RepeatingFlashRate' => ord(substr($data, 12, 1)), // Hz
'RepeatingFlashCount' => ord(substr($data, 13, 1)),
'FlashGNDistance' => static::$flashInfoGNDistances[ord(substr($data, 14, 1))],
'FlashGroupAControlMode' => static::$flashInfoControlModes[ord(substr($data, 15, 1)) & 0x0F],
'FlashGroupBControlMode' => static::$flashInfoControlModes[ord(substr($data, 16, 1)) & 0x0F],
'FlashGroupAOutput' => (ord(substr($data, 15, 1)) & 0x0F) >= 0x06 ? sprintf('%.0f%%', pow(2, (-ord(substr($data, 17, 1)) / 6) * 100)) : 0,
'FlashGroupACompensation' => sprintf('%+.1f', -getid3_lib::BigEndian2Int(substr($data, 17, 1), false, true) / 6),
'FlashGroupBOutput' => (ord(substr($data, 16, 1)) & 0xF0) >= 0x06 ? sprintf('%.0f%%', pow(2, (-ord(substr($data, 18, 1)) / 6) * 100)) : 0,
'FlashGroupBCompensation' => sprintf('%+.1f', -getid3_lib::BigEndian2Int(substr($data, 18, 1), false, true) / 6),
);
break;
case '0102':
$data = array(
'FlashInfoVersion' => substr($data, 0, 4),
'FlashSource' => static::$flashInfoSources[ord(substr($data, 4, 1))],
'ExternalFlashFirmware' => $this->flashFirmwareLookup(ord(substr($data, 6, 1)), ord(substr($data, 7, 1))),
'ExternalFlashFlags' => $this->externalFlashFlagsLookup(ord(substr($data, 8, 1))),
'FlashCommanderMode' => (bool)(ord(substr($data, 9, 1)) & 0x80),
'FlashControlMode' => static::$flashInfoControlModes[ord(substr($data, 9, 1)) & 0x7F],
'FlashOutput' => (ord(substr($data, 9, 1)) & 0x7F) >= 0x06 ? sprintf('%.0f%%', pow(2, (-ord(substr($data, 10, 1)) / 6) * 100)) : 0,
'FlashCompensation' => $this->printFraction(-getid3_lib::BigEndian2Int(substr($data, 10, 1), false, true) / 6),
'FlashFocalLength' => ord(substr($data, 12, 1)), // mm
'RepeatingFlashRate' => ord(substr($data, 13, 1)), // Hz
'RepeatingFlashCount' => ord(substr($data, 14, 1)),
'FlashGNDistance' => static::$flashInfoGNDistances[ord(substr($data, 15, 1))],
'FlashGroupAControlMode' => static::$flashInfoControlModes[ord(substr($data, 16, 1)) & 0x0F],
'FlashGroupBControlMode' => static::$flashInfoControlModes[ord(substr($data, 17, 1)) & 0xF0],
'FlashGroupCControlMode' => static::$flashInfoControlModes[ord(substr($data, 17, 1)) & 0x0F],
'FlashGroupAOutput' => (ord(substr($data, 16, 1)) & 0x0F) >= 0x06 ? sprintf('%.0f%%', pow(2, (-ord(substr($data, 18, 1)) / 6) * 100)) : 0,
'FlashGroupACompensation' => sprintf('%+.1f', -getid3_lib::BigEndian2Int(substr($data, 18, 1), false, true) / 6),
'FlashGroupBOutput' => (ord(substr($data, 17, 1)) & 0xF0) >= 0x60 ? sprintf('%.0f%%', pow(2, (-ord(substr($data, 19, 1)) / 6) * 100)) : 0,
'FlashGroupBCompensation' => sprintf('%+.1f', -getid3_lib::BigEndian2Int(substr($data, 19, 1), false, true) / 6),
'FlashGroupCOutput' => (ord(substr($data, 17, 1)) & 0x0F) >= 0x06 ? sprintf('%.0f%%', pow(2, (-ord(substr($data, 20, 1)) / 6) * 100)) : 0,
'FlashGroupCCompensation' => sprintf('%+.1f', -getid3_lib::BigEndian2Int(substr($data, 20, 1), false, true) / 6),
);
break;
case '0103':
case '0104':
case '0105':
$data = array(
'FlashInfoVersion' => substr($data, 0, 4),
'FlashSource' => static::$flashInfoSources[ord(substr($data, 4, 1))],
'ExternalFlashFirmware' => $this->flashFirmwareLookup(ord(substr($data, 6, 1)), ord(substr($data, 7, 1))),
'ExternalFlashFlags' => $this->externalFlashFlagsLookup(ord(substr($data, 8, 1))),
'FlashCommanderMode' => (bool)(ord(substr($data, 9, 1)) & 0x80),
'FlashControlMode' => static::$flashInfoControlModes[ord(substr($data, 9, 1)) & 0x7F],
'FlashOutput' => (ord(substr($data, 9, 1)) & 0x7F) >= 0x06 ? sprintf('%.0f%%', pow(2, (-ord(substr($data, 10, 1)) / 6) * 100)) : 0,
'FlashCompensation' => $this->printFraction(-getid3_lib::BigEndian2Int(substr($data, 10, 1), false, true) / 6),
'FlashFocalLength' => ord(substr($data, 12, 1)), // mm
'RepeatingFlashRate' => ord(substr($data, 13, 1)), // Hz
'RepeatingFlashCount' => ord(substr($data, 14, 1)),
'FlashGNDistance' => static::$flashInfoGNDistances[ord(substr($data, 15, 1))],
'FlashColorFilter' => static::$flashInfoColorFilters[ord(substr($data, 16, 1))],
'FlashGroupAControlMode' => static::$flashInfoControlModes[ord(substr($data, 17, 1)) & 0x0F],
'FlashGroupBControlMode' => static::$flashInfoControlModes[ord(substr($data, 18, 1)) & 0xF0],
'FlashGroupCControlMode' => static::$flashInfoControlModes[ord(substr($data, 18, 1)) & 0x0F],
'FlashGroupAOutput' => (ord(substr($data, 17, 1)) & 0x0F) >= 0x06 ? sprintf('%.0f%%', pow(2, (-ord(substr($data, 19, 1)) / 6) * 100)) : 0,
'FlashGroupACompensation' => sprintf('%+.1f', -getid3_lib::BigEndian2Int(substr($data, 19, 1), false, true) / 6),
'FlashGroupBOutput' => (ord(substr($data, 18, 1)) & 0xF0) >= 0x60 ? sprintf('%.0f%%', pow(2, (-ord(substr($data, 20, 1)) / 6) * 100)) : 0,
'FlashGroupBCompensation' => sprintf('%+.1f', -getid3_lib::BigEndian2Int(substr($data, 20, 1), false, true) / 6),
'FlashGroupCOutput' => (ord(substr($data, 18, 1)) & 0x0F) >= 0x06 ? sprintf('%.0f%%', pow(2, (-ord(substr($data, 21, 1)) / 6) * 100)) : 0,
'FlashGroupCCompensation' => sprintf('%+.1f', -getid3_lib::BigEndian2Int(substr($data, 21, 1), false, true) / 6),
'ExternalFlashCompensation' => $this->printFraction(-getid3_lib::BigEndian2Int(substr($data, 27, 1), false, true) / 6),
'FlashExposureComp3' => $this->printFraction(-getid3_lib::BigEndian2Int(substr($data, 29, 1), false, true) / 6),
'FlashExposureComp4' => $this->printFraction(-getid3_lib::BigEndian2Int(substr($data, 39, 1), false, true) / 6),
);
break;
case '0106':
$data = array(
'FlashInfoVersion' => substr($data, 0, 4),
'FlashSource' => static::$flashInfoSources[ord(substr($data, 4, 1))],
'ExternalFlashFirmware' => $this->flashFirmwareLookup(ord(substr($data, 6, 1)), ord(substr($data, 7, 1))),
'ExternalFlashFlags' => $this->externalFlashFlagsLookup(ord(substr($data, 8, 1))),
'FlashCommanderMode' => (bool)(ord(substr($data, 9, 1)) & 0x80),
'FlashControlMode' => static::$flashInfoControlModes[ord(substr($data, 9, 1)) & 0x7F],
'FlashFocalLength' => ord(substr($data, 12, 1)), // mm
'RepeatingFlashRate' => ord(substr($data, 13, 1)), // Hz
'RepeatingFlashCount' => ord(substr($data, 14, 1)),
'FlashGNDistance' => self::$flashInfoGNDistances[ord(substr($data, 15, 1))],
'FlashColorFilter' => static::$flashInfoColorFilters[ord(substr($data, 16, 1))],
'FlashGroupAControlMode' => static::$flashInfoControlModes[ord(substr($data, 17, 1)) & 0x0F],
'FlashGroupBControlMode' => static::$flashInfoControlModes[ord(substr($data, 18, 1)) & 0xF0],
'FlashGroupCControlMode' => static::$flashInfoControlModes[ord(substr($data, 18, 1)) & 0x0F],
'FlashOutput' => (ord(substr($data, 9, 1)) & 0x7F) >= 0x06 ? sprintf('%.0f%%', pow(2, (-ord(substr($data, 39, 1)) / 6) * 100)) : 0,
'FlashCompensation' => $this->printFraction(-getid3_lib::BigEndian2Int(substr($data, 39, 1), false, true) / 6),
'FlashGroupAOutput' => (ord(substr($data, 17, 1)) & 0x0F) >= 0x06 ? sprintf('%.0f%%', pow(2, (-ord(substr($data, 40, 1)) / 6) * 100)) : 0,
'FlashGroupACompensation' => sprintf('%+.1f', -getid3_lib::BigEndian2Int(substr($data, 40, 1), false, true) / 6),
'FlashGroupBOutput' => (ord(substr($data, 18, 1)) & 0xF0) >= 0x60 ? sprintf('%.0f%%', pow(2, (-ord(substr($data, 41, 1)) / 6) * 100)) : 0,
'FlashGroupBCompensation' => sprintf('%+.1f', -getid3_lib::BigEndian2Int(substr($data, 41, 1), false, true) / 6),
'FlashGroupCOutput' => (ord(substr($data, 18, 1)) & 0x0F) >= 0x06 ? sprintf('%.0f%%', pow(2, (-ord(substr($data, 42, 1)) / 6) * 100)) : 0,
'FlashGroupCCompensation' => sprintf('%+.1f', -getid3_lib::BigEndian2Int(substr($data, 42, 1), false, true) / 6),
);
break;
case '0107':
case '0108':
$data = array(
'FlashInfoVersion' => substr($data, 0, 4),
'FlashSource' => static::$flashInfoSources[ord(substr($data, 4, 1))],
'ExternalFlashFirmware' => $this->flashFirmwareLookup(ord(substr($data, 6, 1)), ord(substr($data, 7, 1))),
'ExternalFlashZoomOverride' => (bool)(ord(substr($data, 8, 1)) & 0x80),
'ExternalFlashStatus' => static::$flashInfoExternalFlashStatuses[ord(substr($data, 8, 1)) & 0x01],
'ExternalFlashReadyState' => static::$flashInfoExternalFlashReadyStates[ord(substr($data, 9, 1)) & 0x07],
'FlashCompensation' => $this->printFraction(-getid3_lib::BigEndian2Int(substr($data, 10, 1), false, true) / 6),
'FlashFocalLength' => ord(substr($data, 12, 1)), // mm
'RepeatingFlashRate' => ord(substr($data, 13, 1)), // Hz
'RepeatingFlashCount' => ord(substr($data, 14, 1)),
'FlashGNDistance' => static::$flashInfoGNDistances[ord(substr($data, 15, 1))],
'FlashGroupAControlMode' => static::$flashInfoControlModes[ord(substr($data, 17, 1)) & 0x0F],
'FlashGroupBControlMode' => static::$flashInfoControlModes[ord(substr($data, 18, 1)) & 0xF0],
'FlashGroupCControlMode' => static::$flashInfoControlModes[ord(substr($data, 18, 1)) & 0x0F],
'FlashGroupAOutput' => (ord(substr($data, 17, 1)) & 0x0F) >= 0x06 ? sprintf('%.0f%%', pow(2, (-ord(substr($data, 40, 1)) / 6) * 100)) : 0,
'FlashGroupACompensation' => sprintf('%+.1f', -getid3_lib::BigEndian2Int(substr($data, 40, 1), false, true) / 6),
'FlashGroupBOutput' => (ord(substr($data, 18, 1)) & 0xF0) >= 0x60 ? sprintf('%.0f%%', pow(2, (-ord(substr($data, 41, 1)) / 6) * 100)) : 0,
'FlashGroupBCompensation' => sprintf('%+.1f', -getid3_lib::BigEndian2Int(substr($data, 41, 1), false, true) / 6),
'FlashGroupCOutput' => (ord(substr($data, 18, 1)) & 0x0F) >= 0x06 ? sprintf('%.0f%%', pow(2, (-ord(substr($data, 42, 1)) / 6) * 100)) : 0,
'FlashGroupCCompensation' => sprintf('%+.1f', -getid3_lib::BigEndian2Int(substr($data, 42, 1), false, true) / 6),
);
break;
case '0300':
$data = array(
'FlashInfoVersion' => substr($data, 0, 4),
'FlashSource' => static::$flashInfoSources[ord(substr($data, 4, 1))],
'ExternalFlashFirmware' => $this->flashFirmwareLookup(ord(substr($data, 6, 1)), ord(substr($data, 7, 1))),
'FlashCompensation' => $this->printFraction(-getid3_lib::BigEndian2Int(substr($data, 27, 1), false, true) / 6),
);
break;
default:
$data = array(
'FlashInfoVersion' => substr($data, 0, 4),
);
break;
}
break;
case 0x020000b1: // HighISONoiseReduction
$data = isset(static::$highISONoiseReductions[$data]) ? static::$highISONoiseReductions[$data] : $data;
break;
case 0x020000b7: // AFInfo2
$avInfo2Version = substr($data, 0, 4);
$contrastDetectAF = ord(substr($data, 4, 1));
$phaseDetectAF = ord(substr($data, 6, 1));
$rows = array(
'AFInfo2Version' => $avInfo2Version,
'ContrastDetectAF' => static::$AFInfo2ContrastDetectAFChoices[$contrastDetectAF],
'AFAreaMode' => $contrastDetectAF
? static::$AFInfo2AFAreaModesWithContrastDetectAF[ord(substr($data, 5, 1))]
: static::$AFInfo2AFAreaModesWithoutContrastDetectAF[ord(substr($data, 5, 1))],
'PhaseDetectAF' => static::$AFInfo2PhaseDetectAFChoices[$phaseDetectAF],
);
if ($avInfo2Version === '0100') {
$rows['AFImageWidth'] = getid3_lib::BigEndian2Int(substr($data, 16, 2));
$rows['AFImageHeight'] = getid3_lib::BigEndian2Int(substr($data, 18, 2));
$rows['AFAreaXPosition'] = getid3_lib::BigEndian2Int(substr($data, 20, 2));
$rows['AFAreaYPosition'] = getid3_lib::BigEndian2Int(substr($data, 22, 2));
$rows['AFAreaWidth'] = getid3_lib::BigEndian2Int(substr($data, 24, 2));
$rows['AFAreaHeight'] = getid3_lib::BigEndian2Int(substr($data, 26, 2));
$rows['ContrastDetectAFInFocus'] = (bool)ord(substr($data, 28, 1));
} elseif (strpos($avInfo2Version, '03') === 0) {
$rows['AFImageWidth'] = getid3_lib::BigEndian2Int(substr($data, 42, 2));
$rows['AFImageHeight'] = getid3_lib::BigEndian2Int(substr($data, 44, 2));
if ($contrastDetectAF === 2
|| ($contrastDetectAF === 1 && $avInfo2Version === '0301')
) {
$rows['AFAreaXPosition'] = getid3_lib::BigEndian2Int(substr($data, 46, 2));
$rows['AFAreaYPosition'] = getid3_lib::BigEndian2Int(substr($data, 48, 2));
}
$rows['AFAreaWidth'] = getid3_lib::BigEndian2Int(substr($data, 50, 2));
$rows['AFAreaHeight'] = getid3_lib::BigEndian2Int(substr($data, 52, 2));
} elseif ($contrastDetectAF === 1 && $avInfo2Version === '0101') {
$rows['AFImageWidth'] = getid3_lib::BigEndian2Int(substr($data, 70, 2));
$rows['AFImageHeight'] = getid3_lib::BigEndian2Int(substr($data, 72, 2));
$rows['AFAreaXPosition'] = getid3_lib::BigEndian2Int(substr($data, 74, 2));
$rows['AFAreaYPosition'] = getid3_lib::BigEndian2Int(substr($data, 76, 2));
$rows['AFAreaWidth'] = getid3_lib::BigEndian2Int(substr($data, 78, 2));
$rows['AFAreaHeight'] = getid3_lib::BigEndian2Int(substr($data, 80, 2));
$rows['ContrastDetectAFInFocus'] = (bool) ord(substr($data, 82, 1));
}
$data = $rows;
break;
case 0x020000c3: // BarometerInfo
$data = array(
'BarometerInfoVersion' => substr($data, 0, 4),
'Altitude' => getid3_lib::BigEndian2Int(substr($data, 6, 4), false, true), // m
);
break;
}
$tag_name = (isset($NCTGtagName[$record_type]) ? $NCTGtagName[$record_type] : '0x' . str_pad(dechex($record_type), 8, '0', STR_PAD_LEFT));
$parsed[$tag_name] = $data;
}
}
return $parsed;
}
/**
* @param int $value 0x80 subtracted
* @param string $normalName 'Normal' (0 value) string
* @param string|null $format format string for numbers (default '%+d'), 3) v2 divisor
* @param int|null $div
*
* @return string
*/
protected function printPC($value, $normalName = 'Normal', $format = '%+d', $div = 1) {
switch ($value) {
case 0:
return $normalName;
case 0x7f:
return 'n/a';
case -0x80:
return 'Auto';
case -0x7f:
return 'User';
}
return sprintf($format, $value / $div);
}
/**
* @param int|float $value
*
* @return string
*/
protected function printFraction($value) {
if (!$value) {
return '0';
} elseif ((int) $value /$value > 0.999) {
return sprintf("%+d", (int) $value);
} elseif ((int) ($value * 2) / ($value * 2) > 0.999) {
return sprintf("%+d/2", (int) ($value * 2));
} elseif ((int) ($value * 3) / ($value * 3) > 0.999) {
return sprintf("%+d/3", (int) ($value * 3));
}
return sprintf("%+.3g", $value);
}
/**
* @param int $firstByte
* @param int $secondByte
*
* @return string
*/
protected function flashFirmwareLookup($firstByte, $secondByte)
{
$indexKey = $firstByte.' '.$secondByte;
if (isset(static::$flashInfoExternalFlashFirmwares[$indexKey])) {
return static::$flashInfoExternalFlashFirmwares[$indexKey];
}
return sprintf('%d.%.2d (Unknown model)', $firstByte, $secondByte);
}
/**
* @param int $flags
*
* @return string[]|string
*/
protected function externalFlashFlagsLookup($flags)
{
$result = array();
foreach (static::$flashInfoExternalFlashFlags as $bit => $value) {
if (($flags >> $bit) & 1) {
$result[] = $value;
}
}
return $result;
}
/**
* @param string $data
* @param mixed|null $serialNumber
* @param mixed|null $shutterCount
* @param int $decryptStart
*
* @return false|string
*/
protected function decryptLensInfo(
$data,
$serialNumber = null,
$shutterCount = null,
$decryptStart = 0
) {
if (null === $serialNumber && null === $shutterCount) {
return false;
}
if (!is_int($serialNumber) || !is_int($shutterCount)) {
if (null !== $serialNumber && null !== $shutterCount) {
$this->getid3->warning('Invalid '.(!is_int($serialNumber) ? 'SerialNumber' : 'ShutterCount'));
} else {
$this->getid3->warning('Cannot decrypt Nikon tags because '.(null === $serialNumber ? 'SerialNumber' : 'ShutterCount').' key is not defined.');
}
return false;
}
$start = $decryptStart;
$length = strlen($data) - $start;
return $this->decrypt($data, $serialNumber, $shutterCount, $start, $length);
}
/**
* Decrypt Nikon data block
*
* @param string $data
* @param int $serialNumber
* @param int $count
* @param int $start
* @param int $length
*
* @return string
*/
protected function decrypt($data, $serialNumber, $count, $start = 0, $length = null)
{
$maxLen = strlen($data) - $start;
if (null === $length || $length > $maxLen) {
$length = $maxLen;
}
if ($length <= 0) {
return $data;
}
$key = 0;
for ($i = 0; $i < 4; ++$i) {
$key ^= ($count >> ($i * 8)) & 0xFF;
}
$ci = static::$decodeTables[0][$serialNumber & 0xff];
$cj = static::$decodeTables[1][$key];
$ck = 0x60;
$unpackedData = array();
for ($i = $start; $i < $length + $start; ++$i) {
$cj = ($cj + $ci * $ck) & 0xff;
$ck = ($ck + 1) & 0xff;
$unpackedData[] = ord($data[$i]) ^ $cj;
}
$end = $start + $length;
$pre = $start ? substr($data, 0, $start) : '';
$post = $end < strlen($data) ? substr($data, $end) : '';
return $pre . implode('', array_map('chr', $unpackedData)) . $post;
}
/**
* Get serial number for use as a decryption key
*
* @param string $serialNumber
* @param string|null $model
*
* @return int|null
*/
protected function serialKey($serialNumber, $model = null)
{
if (empty($serialNumber) || ctype_digit($serialNumber)) {
return (int) $serialNumber;
}
if (null !== $model && preg_match('#\bD50$#', $model)) {
return 0x22;
}
return 0x60;
}
}