Skip to content

Instantly share code, notes, and snippets.

@miguelrgonzalez
Created April 13, 2015 09:10
Show Gist options
  • Save miguelrgonzalez/d8daf7e3840f20b8dcee to your computer and use it in GitHub Desktop.
Save miguelrgonzalez/d8daf7e3840f20b8dcee to your computer and use it in GitHub Desktop.
xquery version "1.0-ml";
module namespace exif = "http://marklogic.com/exif";
declare variable $FIELDS :=
map:new((
map:entry("010D", "DocumentName"),
map:entry("011A", "XResolution"),
map:entry("011B", "YResolution"),
map:entry("0128", "ResolutionUnit"),
map:entry("0213", "YCbCrPositioning"),
map:entry("8769", "ExifOffset"),
map:entry("9000", "ExifVersion"),
map:entry("9101", "ComponentsConfiguration"),
map:entry("A000", "FlashpixVersion"),
map:entry("A001", "ColorSpace"),
map:entry("A420", "ImageUniqueID"),
map:entry("A431", "BodySerialNumber")
));
declare variable $TYPES :=
<types>
<type id="1" size="1">Byte</type>
<type id="2" size="1" decode="true">ASCII</type>
<type id="3" size="2">Short</type>
<type id="4" size="4">Long</type>
<type id="5" size="8">Rational</type>
<type id="6" size="1">SBYTE</type>
<type id="7" size="1" decode="true">Undefinned</type>
<type id="8" size="2">SShort</type>
<type id="9" size="4">SLong</type>
<type id="10" size="8">SRational</type>
<type id="11" size="4">Float</type>
<type id="12" size="8">Double</type>
</types>;
declare variable $FIELD-SIZE := 12;
declare function exif:endianness($binary as binary(), $byte-order as xs:string) as binary()
{
if ($byte-order eq 'LI')
then $binary
else
(: BI :)
let $size := xdmp:binary-size($binary)
(: See bug #33099. Whenever the bug is fixed, this will fail :)
let $binary := binary { concat(string($binary), "00")}
let $result :=
for $pos in (0 to $size -1)
return string(xdmp:subbinary($binary, $size - $pos, 1))
return binary {xs:hexBinary(string-join($result, ''))}
};
declare function exif:process-field($field as binary(),
$byte-order as xs:string,
$image as binary(),
$tiff-offset as xs:integer) as element()?
{
let $tag-id := xdmp:subbinary($field, 1, 2)
let $type-id := xdmp:hex-to-integer(string(xs:hexBinary(xs:string(xdmp:subbinary($field, 3, 2)))))
let $count := xdmp:hex-to-integer(string(xs:hexBinary(xdmp:subbinary($field, 5, 4))))
let $offset-to-value := xdmp:subbinary($field, 9, 4)
return
element field {
attribute original { xs:string($field) },
attribute count { $count },
attribute name { map:get($FIELDS, xs:string($tag-id)) },
attribute tag-id { xs:string($tag-id) },
attribute type { $TYPES/type[@id eq $type-id]/text() },
attribute offset { xs:hexBinary($offset-to-value) },
exif:fetch-value($image,
$byte-order,
$tiff-offset,
$offset-to-value,
$type-id,
$count)
}
};
declare function exif:fetch-value($image as binary(),
$byte-order as xs:string,
$tiff-offset as xs:integer,
$offset as binary(),
$type as xs:integer,
$count as xs:integer) as item()?
{
if ($TYPES/type[@id eq $type] ne 'ExifOffset')
then
if ($count * $TYPES/type[@id eq $type]/@size > 4)
(: if the value is bigger than 4 bytes will be stored in the data section :)
then
let $binary := xdmp:subbinary($image,
$tiff-offset + xdmp:hex-to-integer(string(xs:hexBinary($offset))),
$count * $TYPES/type[@id eq $type]/@size)
return
if (xdmp:binary-size($binary) > 0)
then
if ($TYPES/type[@id eq $type and @decode eq 'true'])
then xdmp:binary-decode($binary, 'utf8')
else xs:string(data($binary))
else ''
else
if ($TYPES/type[@id eq $type and @decode eq 'true'])
then xdmp:binary-decode($offset, 'utf8')
else xs:string(data($offset))
else ''
};
declare function exif:process-fields($image as binary(),
$byte-order as xs:string,
$tiff-offset as xs:integer,
$offset as xs:integer) as element()*
{
let $field-count := xdmp:hex-to-integer(string(xs:hexBinary(xdmp:subbinary($image, $tiff-offset + $offset, 2))))
let $fields :=
for $field in 0 to $field-count - 1
return exif:process-field(binary { xdmp:subbinary($image, $tiff-offset + $offset + 2 + ($FIELD-SIZE* $field), $FIELD-SIZE) }, $byte-order, $image, $tiff-offset)
return
if ($fields[@name eq 'ExifOffset'])
(: There are more fields. Process them :)
then ( $fields, exif:process-fields($image, $byte-order, $tiff-offset, xdmp:hex-to-integer($fields[@name eq 'ExifOffset']/@offset)))
else $fields
};
declare function exif:get-exif-fields($image as binary()) as element()
{
let $magic-number := xs:hexBinary(xdmp:subbinary($image, 1, 2))
(: See http://www.cipa.jp/std/documents/e/DC-008-2012_E.pdf :)
return
<fields>
{
if(string($magic-number) eq 'FFD8')
then
(
(: it's a jpeg :)
let $app0-marker := string(xs:hexBinary(xdmp:subbinary($image, 3, 2)))
let $app0-size := xdmp:hex-to-integer(string(xs:hexBinary(xdmp:subbinary($image, 5, 2))))
(: app1 block contains Exif Attribute Information :)
let $app1-marker := string(xs:hexBinary(xdmp:subbinary($image, $app0-size + 5, 2)))
let $app1-block-start := $app0-size + 7
let $app1-size := xdmp:hex-to-integer(string(xs:hexBinary(xdmp:subbinary($image, $app1-block-start, 2))))
let $app1-block := string(xs:hexBinary(xdmp:subbinary($image, $app1-block-start, $app1-size)))
return
(: http://www.cipa.jp/std/documents/e/DC-008-2012_E.pdf Page 89
Checking for Exif ID code :)
if (matches($app1-block, '^....457869660000.*'))
then
(
let $tiff-header-start := $app1-block-start + 8
let $tiff-header := string(xs:hexBinary(xdmp:subbinary($image, $tiff-header-start, $app1-size)))
let $byte-order :=
if (matches($tiff-header, '^4D4D00.*'))
then
(: Big indian :)
(
'BI'
)
else
(: Assume 4949 Little indian :)
('LI')
(: see http://partners.adobe.com/public/developer/en/tiff/TIFF6.pdf :)
let $ifd0-offset := xdmp:hex-to-integer(string(xs:hexBinary(xdmp:subbinary($image, $tiff-header-start + 4, 4))))
let $field-count := xdmp:hex-to-integer(string(xs:hexBinary(xdmp:subbinary($image, $tiff-header-start + $ifd0-offset, 2))))
return
exif:process-fields($image, $byte-order, $tiff-header-start, $ifd0-offset)
)
(: App1 does not contain Exif information :)
else ()
)
(: Not a jpeg :)
else ()
}
</fields>
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment