Skip to content

Commit

Permalink
Merge pull request #324 from StudioMaX/nikon-nctg-tags
Browse files Browse the repository at this point in the history
Improved support of Nikon-specific atoms in QuickTime videos
  • Loading branch information
JamesHeinrich committed May 26, 2021
2 parents ee705f2 + 8cb0970 commit 4466028
Show file tree
Hide file tree
Showing 2 changed files with 1,486 additions and 193 deletions.
231 changes: 38 additions & 193 deletions getid3/module.audio-video.quicktime.php
Original file line number Diff line number Diff line change
Expand Up @@ -1623,33 +1623,61 @@ public function QuicktimeParseAtom($atomname, $atomsize, $atom_data, $baseoffset
break;

case 'NCDT':
// http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Nikon.html
// https://exiftool.org/TagNames/Nikon.html
// Nikon-specific QuickTime tags found in the NCDT atom of MOV videos from some Nikon cameras such as the Coolpix S8000 and D5100
$atom_structure['subatoms'] = $this->QuicktimeParseContainerAtom($atom_data, $baseoffset + 4, $atomHierarchy, $ParseAllPossibleAtoms);
break;
case 'NCTH': // Nikon Camera THumbnail image
case 'NCVW': // Nikon Camera preVieW image
// http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Nikon.html
case 'NCM1': // Nikon Camera preview iMage 1
case 'NCM2': // Nikon Camera preview iMage 2
// https://exiftool.org/TagNames/Nikon.html
if (preg_match('/^\xFF\xD8\xFF/', $atom_data)) {
$descriptions = array(
'NCTH' => 'Nikon Camera Thumbnail Image',
'NCVW' => 'Nikon Camera Preview Image',
'NCM1' => 'Nikon Camera Preview Image 1',
'NCM2' => 'Nikon Camera Preview Image 2',
);
$atom_structure['data'] = $atom_data;
$atom_structure['image_mime'] = 'image/jpeg';
$atom_structure['description'] = (($atomname == 'NCTH') ? 'Nikon Camera Thumbnail Image' : (($atomname == 'NCVW') ? 'Nikon Camera Preview Image' : 'Nikon preview image'));
$info['quicktime']['comments']['picture'][] = array('image_mime'=>$atom_structure['image_mime'], 'data'=>$atom_data, 'description'=>$atom_structure['description']);
$atom_structure['description'] = isset($descriptions[$atomname]) ? $descriptions[$atomname] : 'Nikon preview image';
$info['quicktime']['comments']['picture'][] = array(
'image_mime' => $atom_structure['image_mime'],
'data' => $atom_data,
'description' => $atom_structure['description']
);
}
break;
case 'NCTG': // Nikon - http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Nikon.html#NCTG
$atom_structure['data'] = $this->QuicktimeParseNikonNCTG($atom_data);
case 'NCTG': // Nikon - https://exiftool.org/TagNames/Nikon.html#NCTG
getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.tag.nikon-nctg.php', __FILE__, true);
$nikonNCTG = new getid3_tag_nikon_nctg($this->getid3);

$atom_structure['data'] = $nikonNCTG->parse($atom_data);
break;
case 'NCHD': // Nikon:MakerNoteVersion - https://exiftool.org/TagNames/Nikon.html
$makerNoteVersion = '';
for ($i = 0, $iMax = strlen($atom_data); $i < $iMax; ++$i) {
if (ord($atom_data[$i]) >= 0x00 && ord($atom_data[$i]) <= 0x1F) {
$makerNoteVersion .= ' '.ord($atom_data[$i]);
} else {
$makerNoteVersion .= $atom_data[$i];
}
}
$makerNoteVersion = rtrim($makerNoteVersion, "\x00");
$atom_structure['data'] = array(
'MakerNoteVersion' => $makerNoteVersion
);
break;
case 'NCHD': // Nikon:MakerNoteVersion - http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Nikon.html
case 'NCDB': // Nikon - http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Nikon.html
case 'CNCV': // Canon:CompressorVersion - http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Canon.html
case 'NCDB': // Nikon - https://exiftool.org/TagNames/Nikon.html
case 'CNCV': // Canon:CompressorVersion - https://exiftool.org/TagNames/Canon.html
$atom_structure['data'] = $atom_data;
break;

case "\x00\x00\x00\x00":
// some kind of metacontainer, may contain a big data dump such as:
// mdta keys \005 mdtacom.apple.quicktime.make (mdtacom.apple.quicktime.creationdate ,mdtacom.apple.quicktime.location.ISO6709 $mdtacom.apple.quicktime.software !mdtacom.apple.quicktime.model ilst \01D \001 \015data \001DE\010Apple 0 \002 (data \001DE\0102011-05-11T17:54:04+0200 2 \003 *data \001DE\010+52.4936+013.3897+040.247/ \01D \004 \015data \001DE\0104.3.1 \005 \018data \001DE\010iPhone 4
// http://www.geocities.com/xhelmboyx/quicktime/formats/qti-layout.txt
// https://xhelmboyx.tripod.com/formats/qti-layout.txt

$atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1));
$atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3));
Expand Down Expand Up @@ -2632,189 +2660,6 @@ public function QuicktimeStoreFrontCodeLookup($sfid) {
return (isset($QuicktimeStoreFrontCodeLookup[$sfid]) ? $QuicktimeStoreFrontCodeLookup[$sfid] : 'invalid');
}

/**
* @param string $atom_data
*
* @return array
*/
public function QuicktimeParseNikonNCTG($atom_data) {
// http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Nikon.html#NCTG
// 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 (size field *= 1-byte)
// 0x0002 = char (size field *= 1-byte)
// 0x0003 = DWORD+ (size field *= 2-byte), values are stored CDAB
// 0x0004 = QWORD+ (size field *= 4-byte), values are stored EFGHABCD
// 0x0005 = float (size field *= 8-byte), values are stored aaaabbbb where value is aaaa/bbbb; possibly multiple sets of values appended together
// 0x0007 = bytes (size field *= 1-byte), values are stored as ??????
// 0x0008 = ????? (size field *= 2-byte), values are stored as ??????
// * 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',
0x00000022 => 'FrameWidth',
0x00000023 => 'FrameHeight',
0x00000032 => 'AudioChannels',
0x00000033 => 'AudioBitsPerSample',
0x00000034 => 'AudioSampleRate',
0x02000001 => 'MakerNoteVersion',
0x02000005 => 'WhiteBalance',
0x0200000b => 'WhiteBalanceFineTune',
0x0200001e => 'ColorSpace',
0x02000023 => 'PictureControlData',
0x02000024 => 'WorldTime',
0x02000032 => 'UnknownInfo',
0x02000083 => 'LensType',
0x02000084 => 'Lens',
);

$offset = 0;
$data = null;
$datalength = strlen($atom_data);
$parsed = array();
while ($offset < $datalength) {
$record_type = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 4)); $offset += 4;
$data_size_type = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 2)); $offset += 2;
$data_size = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 2)); $offset += 2;
switch ($data_size_type) {
case 0x0001: // 0x0001 = flag (size field *= 1-byte)
$data = getid3_lib::BigEndian2Int(substr($atom_data, $offset, $data_size * 1));
$offset += ($data_size * 1);
break;
case 0x0002: // 0x0002 = char (size field *= 1-byte)
$data = substr($atom_data, $offset, $data_size * 1);
$offset += ($data_size * 1);
$data = rtrim($data, "\x00");
break;
case 0x0003: // 0x0003 = DWORD+ (size field *= 2-byte), values are stored CDAB
$data = '';
for ($i = $data_size - 1; $i >= 0; $i--) {
$data .= substr($atom_data, $offset + ($i * 2), 2);
}
$data = getid3_lib::BigEndian2Int($data);
$offset += ($data_size * 2);
break;
case 0x0004: // 0x0004 = QWORD+ (size field *= 4-byte), values are stored EFGHABCD
$data = '';
for ($i = $data_size - 1; $i >= 0; $i--) {
$data .= substr($atom_data, $offset + ($i * 4), 4);
}
$data = getid3_lib::BigEndian2Int($data);
$offset += ($data_size * 4);
break;
case 0x0005: // 0x0005 = float (size field *= 8-byte), values are stored aaaabbbb where value is aaaa/bbbb; possibly multiple sets of values appended together
$data = array();
for ($i = 0; $i < $data_size; $i++) {
$numerator = getid3_lib::BigEndian2Int(substr($atom_data, $offset + ($i * 8) + 0, 4));
$denomninator = getid3_lib::BigEndian2Int(substr($atom_data, $offset + ($i * 8) + 4, 4));
if ($denomninator == 0) {
$data[$i] = false;
} else {
$data[$i] = (double) $numerator / $denomninator;
}
}
$offset += (8 * $data_size);
if (count($data) == 1) {
$data = $data[0];
}
break;
case 0x0007: // 0x0007 = bytes (size field *= 1-byte), values are stored as ??????
$data = substr($atom_data, $offset, $data_size * 1);
$offset += ($data_size * 1);
break;
case 0x0008: // 0x0008 = ????? (size field *= 2-byte), values are stored as ??????
$data = substr($atom_data, $offset, $data_size * 2);
$offset += ($data_size * 2);
break;
default:
echo 'QuicktimeParseNikonNCTG()::unknown $data_size_type: '.$data_size_type.'<br>';
break 2;
}

switch ($record_type) {
case 0x00000011: // CreateDate
case 0x00000012: // DateTimeOriginal
$data = strtotime($data);
break;
case 0x0200001e: // ColorSpace
switch ($data) {
case 1:
$data = 'sRGB';
break;
case 2:
$data = 'Adobe RGB';
break;
}
break;
case 0x02000023: // PictureControlData
$PictureControlAdjust = array(0=>'default', 1=>'quick', 2=>'full');
$FilterEffect = array(0x80=>'off', 0x81=>'yellow', 0x82=>'orange', 0x83=>'red', 0x84=>'green', 0xff=>'n/a');
$ToningEffect = 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');
$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' => $PictureControlAdjust[ord(substr($data, 48, 1))],
'PictureControlQuickAdjust' => ord(substr($data, 49, 1)),
'Sharpness' => ord(substr($data, 50, 1)),
'Contrast' => ord(substr($data, 51, 1)),
'Brightness' => ord(substr($data, 52, 1)),
'Saturation' => ord(substr($data, 53, 1)),
'HueAdjustment' => ord(substr($data, 54, 1)),
'FilterEffect' => $FilterEffect[ord(substr($data, 55, 1))],
'ToningEffect' => $ToningEffect[ord(substr($data, 56, 1))],
'ToningSaturation' => ord(substr($data, 57, 1)),
);
break;
case 0x02000024: // WorldTime
// http://www.sno.phy.queensu.ca/~phil/exiftool/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);
}
$timezone /= 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'=>floatval($timezone), 'dst'=>$dst, 'display'=>$datedisplayformat);
break;
case 0x02000083: // LensType
$data = array(
//'_' => $data,
'mf' => (bool) ($data & 0x01),
'd' => (bool) ($data & 0x02),
'g' => (bool) ($data & 0x04),
'vr' => (bool) ($data & 0x08),
);
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 string $keyname
* @param string|array $data
Expand Down
Loading

0 comments on commit 4466028

Please sign in to comment.